codehere 0.3.4 → 0.5.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,10 +4,10 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/codehere)](https://www.npmjs.com/package/codehere)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow)](../LICENSE)
7
- [![Tests](https://img.shields.io/badge/tests-362%2F362-brightgreen)]()
7
+ [![Tests](https://img.shields.io/badge/tests-443%2F443-brightgreen)]()
8
8
  [![Vulnerabilities](https://img.shields.io/badge/vulnerabilities-0-brightgreen)]()
9
9
 
10
- codehere is **a PM for your AI coding agents.** Delegate a task. codehere picks the right specialist (Claude Code, Codex, Aider, or OpenCode), spawns it, streams its work back to you, and hands you a plain-English Final Report with a **trust score on every file changed**. Today, Claude Code gets full pre-execution safety-hook enforcement on every tool call; other adapters are session-traced and reported, with per-tool hook parity planned for v0.4.
10
+ codehere is **a PM for your AI coding agents.** Delegate a task. codehere picks the right specialist (Claude Code, Codex, Aider, OpenCode, or Gemini), spawns it, streams its work back to you, and hands you a plain-English Final Report with a **trust score on every file changed**. Today, Claude Code gets full pre-execution safety-hook enforcement on every tool call; other adapters are session-traced and reported, with per-tool hook parity planned for v0.4.
11
11
 
12
12
  One manager, many specialists, every action recorded.
13
13
 
@@ -25,9 +25,10 @@ One manager, many specialists, every action recorded.
25
25
  | 1 | **Install** | `npm install -g codehere` (Node 20+) |
26
26
  | 2 | **Detect environment** | `codehere setup` — checks API key, finds your repo |
27
27
  | 3 | **Install the safety hook** | `codehere hook install` — every Claude Code tool call now audited |
28
- | 4 | **Install at least one specialist** | Claude Code: `npm i -g @anthropic-ai/claude-code` (Codex / Aider / OpenCode also supported) |
29
- | 5 | **Scaffold this repo** | `cd /your/repo && codehere init` — creates `.codehere/CONTEXT.md`, `HANDOVER.md`, `PROMPTS.md` |
30
- | 6 | **Fill in CONTEXT.md** | 5 minutes. The agent will follow what's written there. |
28
+ | 4 | **Install at least one specialist** | Claude Code: `npm i -g @anthropic-ai/claude-code` (Codex / Aider / OpenCode / Gemini also supported · see [docs/integrations/](../docs/integrations/) for Cursor MCP setup) |
29
+ | 5 | **Scaffold this repo** | `cd /your/repo && codehere init` — creates `.codehere/` with the F3 memory tree (working / episodic / semantic / personal / candidates) plus `AGENTS.md` (project briefing) and `skills/` (prompt templates) |
30
+ | 6 | **Fill in AGENTS.md** | 5 minutes. The agent will follow what's written there. |
31
+ | 7 | **Build the symbol map (optional)** | `codehere memory repo-map` — JSON map of every top-level export in your repo, ranked by reference count. Spawned agents read it on entry instead of cold-grepping. |
31
32
 
32
33
  You're done. `codehere start` opens the web UI at `:3773`; you delegate from there.
33
34
 
@@ -43,11 +44,20 @@ Context + End State + Constraints
43
44
 
44
45
  ### Folder convention (what `codehere init` writes)
45
46
 
46
- | File | Purpose | Committed? |
47
+ The v0.5 layout — F3 project memory:
48
+
49
+ | Path | Purpose | Committed? |
47
50
  |---|---|---|
48
- | `.codehere/CONTEXT.md` | What this repo is. Conventions. Style. Constraints. | yes |
49
- | `.codehere/HANDOVER.md` | Cross-session continuity (until F3 memory ships in v0.5). | **no** |
50
- | `.codehere/PROMPTS.md` | Your library of recurring task prompts. | yes |
51
+ | `.codehere/AGENTS.md` *(or legacy `CONTEXT.md`)* | What this repo is. Conventions. Style. Constraints. | yes |
52
+ | `.codehere/memory/working/session.md` *(or legacy `HANDOVER.md`)* | What's in flight in the current session. | **no** |
53
+ | `.codehere/memory/episodic/AGENT_LEARNINGS.jsonl` | Symlink to your full session history at `~/.codehere/sessions.jsonl`. | (symlink) |
54
+ | `.codehere/memory/episodic/repo_map.json` | Ranked symbol map. Built by `codehere memory repo-map`. | yes |
55
+ | `.codehere/memory/semantic/lessons.jsonl` | Project-specific lessons promoted from episodic patterns. | yes |
56
+ | `.codehere/memory/personal/preferences.json` | Per-developer prefs. | **no** |
57
+ | `.codehere/skills/<name>/SKILL.md` *(or legacy flat `PROMPTS.md`)* | Prompt templates — one folder per skill. | yes |
58
+ | `.codehere/protocols/permissions.md` + `hook_patterns.json` | Safety policy for the PreToolUse hook. | yes |
59
+
60
+ Upgrading from v0.4.1? Run `codehere memory init` once — it migrates `CONTEXT/HANDOVER/PROMPTS` into the new layout and leaves symlinks at the old paths so back-compat holds. `--dry-run` shows what would move first.
51
61
 
52
62
  ---
53
63
 
@@ -57,7 +67,7 @@ Context + End State + Constraints
57
67
 
58
68
  1. Spawns the agent with the right headless flags
59
69
  2. Streams the steps back as readable progress · *"Read src/auth.ts · Edit src/auth.ts · Run npm test"*
60
- 3. For Claude Code tasks: routes every tool call through the codehere safety hook — you see *"🛡 5 safe · 0 blocked"* grow live. For Codex / Aider / OpenCode tasks: emits the agent's native event stream (no per-tool blocking yet)
70
+ 3. For Claude Code tasks: routes every tool call through the codehere safety hook — you see *"🛡 5 safe · 0 blocked"* grow live. For Codex / Aider / OpenCode / Gemini tasks: emits the agent's native event stream (no per-tool blocking yet)
61
71
  4. On completion, hands you a **Final report**: files changed with `+/-` lines, trust score per file, test pass/fail, token usage
62
72
 
63
73
  Claude Code tool calls are logged to `~/.codehere/audit/hook-events.jsonl`; spawned-task lifecycle (any adapter) is logged to `~/.codehere/sessions.jsonl`. Open-source MIT. Node 20+.
@@ -75,6 +85,52 @@ codehere status # quick dashboard · sessions, blocks, trust trends
75
85
 
76
86
  The hook intercepts every `Bash`, `Edit`, `Write`, `MultiEdit`, and `NotebookEdit` call from any Claude Code session running on your machine, runs it through the trust-scorer pattern bank in under 5 ms, blocks anything matching the destructive / credential / injection patterns, and appends every decision to an append-only JSONL audit log at `~/.codehere/audit/`. (Hook coverage today is Claude Code-only — Codex / Aider / OpenCode adapters spawn through their native CLIs and emit to `~/.codehere/sessions.jsonl`; full hook parity across all adapters is v0.4 work.)
77
87
 
88
+ ## Memory · F3 (v0.5)
89
+
90
+ Once you've run `codehere init` in a project, every spawned agent — Claude Code, Codex, Aider, OpenCode, or Gemini — automatically reads the project's memory on entry. The agent starts warm with the briefing, recent lessons, and the most-referenced symbols in your codebase, instead of cold-grepping every session.
91
+
92
+ **What gets injected, in priority order:**
93
+ 1. **`.codehere/AGENTS.md`** — your project briefing (conventions, style, constraints)
94
+ 2. **`.codehere/memory/working/session.md`** — what's in flight right now (per-machine, gitignored)
95
+ 3. **`.codehere/memory/semantic/lessons.jsonl`** — graduated project-wide lessons
96
+ 4. **`.codehere/memory/episodic/repo_map.json`** — top-N most-referenced symbols (run `codehere memory repo-map` to build / refresh)
97
+
98
+ The injection is token-budget-aware (default ~3000 tokens, drops lower-priority sections first to fit; AGENTS.md is never dropped). Pass `--no-memory-prefix` if you want the bare prompt for a one-off task.
99
+
100
+ **Inspect what would inject** before spawning anything:
101
+ ```bash
102
+ codehere memory show injection
103
+ ```
104
+
105
+ **Add a lesson** so future spawns learn from it:
106
+ ```bash
107
+ codehere memory graduate --add "auth tests live alongside source files"
108
+ ```
109
+
110
+ **See everything codehere has accumulated:**
111
+ ```bash
112
+ codehere memory show # one-line summary per layer
113
+ codehere memory show semantic # numbered lesson list
114
+ codehere memory show episodic # session count + top symbols
115
+ codehere memory show protocols # safety patterns by category
116
+ ```
117
+
118
+ **Other memory commands:**
119
+ - `codehere memory init` — migrate the v0.4.1 layout (`CONTEXT.md` / `HANDOVER.md` / `PROMPTS.md`) into the F3 layout (one shot, leaves back-compat symlinks; `--dry-run` previews)
120
+ - `codehere memory repo-map [--rebuild]` — build the symbol map; v0.5 ships a TS-native MVP, v0.6 swaps in tree-sitter
121
+ - `codehere protocols export [--rebuild]` — write the trust-scorer pattern bank to `.codehere/protocols/hook_patterns.json` for inspection / cross-tool ingestion
122
+
123
+ ## Going deeper
124
+
125
+ For technical readers — the living documents that describe codehere as a system, not just a CLI:
126
+
127
+ | Doc | What it is |
128
+ |---|---|
129
+ | [`docs/decisions/`](../docs/decisions/) | ADR registry — every locked architectural decision (audit-first moat, supervisor-agent positioning, local-first, F3 memory layout, generic-CLI adapter, ...) numbered, dated, with rejected alternatives + rollback triggers |
130
+ | [`docs/THREAT_MODEL.md`](../docs/THREAT_MODEL.md) | Every attack surface, current mitigation, residual risk, planned hardening — npm supply chain, hook bypass, symlink redirect, audit log tampering, MCP server, prompt injection |
131
+ | [`docs/CAPABILITIES.md`](../docs/CAPABILITIES.md) | Honest matrix of what each adapter can and cannot do today — Claude Code, Codex, Aider, OpenCode, Gemini, Generic CLI — across spawn, stream, per-tool block, audit, F3 memory, auth |
132
+ | [`agent/REPO_MAP.md`](./REPO_MAP.md) | One-line-per-file index of the 54 source files with their top-level exports — generated, regenerated via `npm run repomap` after renames |
133
+
78
134
  ## Commands
79
135
 
80
136
  ```
@@ -129,6 +185,7 @@ For depth beyond this README:
129
185
  - **[CODEHERE.md](../CODEHERE.md)** · product spec · positioning, six-ring adoption funnel, F0–F6 foundations, defensibility, GTM, revenue model
130
186
  - **[EXPERIENCE.md](../EXPERIENCE.md)** · the human story · how engineering teams, CISOs, and regulated-industry buyers adopt codehere
131
187
  - **[AUDIT.md](../AUDIT.md)** · pre-publish audit report · methodology, findings, remediation
188
+ - **[docs/SECURITY_AUDIT_2026-05-01.md](../docs/SECURITY_AUDIT_2026-05-01.md)** · first-party security audit · OWASP Top 10 + STRIDE · 0 critical / 0 high · 2 medium fixed inline · methodology disclosed · reproducible
132
189
  - **[CONTRIBUTING.md](../CONTRIBUTING.md)** · how to contribute · setup, commit discipline, testing philosophy
133
190
  - **[SECURITY.md](../SECURITY.md)** · responsible disclosure · `security@codehere.uk`
134
191
  - **[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)** · community standards
@@ -138,7 +195,7 @@ For depth beyond this README:
138
195
 
139
196
  - **Node.js 20+** (pinned in `.nvmrc` and `engines.node`)
140
197
  - **macOS or Linux** · Windows via WSL works for development; native Windows is on the v0.4 roadmap
141
- - **Optional but recommended:** at least one supported agent (Claude Code, Codex, Aider, or OpenCode) installed before running `codehere start`
198
+ - **Optional but recommended:** at least one supported agent (Claude Code, Codex, Aider, OpenCode, or Gemini) installed before running `codehere start`
142
199
 
143
200
  ## License
144
201
 
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ import { join, dirname } from 'path';
16
16
  import { fileURLToPath } from 'url';
17
17
  import { executeSetupCommand } from './presentation/cli/commands/setup-command.js';
18
18
  import { executeInitCommand } from './presentation/cli/commands/init-command.js';
19
+ import { executeMemoryCommand } from './presentation/cli/commands/memory-command.js';
20
+ import { executeProtocolsCommand } from './presentation/cli/commands/protocols-command.js';
19
21
  import { executeConfigCommand } from './presentation/cli/commands/config-command.js';
20
22
  import { executeHelpCommand } from './presentation/cli/commands/help-command.js';
21
23
  import { executeStatusCommand } from './presentation/cli/commands/status-command.js';
@@ -103,6 +105,47 @@ program
103
105
  .action((options) => {
104
106
  executeInitCommand({ force: options.force, noGitignore: !options.gitignore });
105
107
  });
108
+ // ── memory ───────────────────────────────────────────────────────────────────
109
+ // F3 v0.5: project memory operations. `memory init` migrates v0.4.1 layout
110
+ // (CONTEXT/HANDOVER/PROMPTS) into the four-layer F3 layout (AGENTS.md +
111
+ // memory/ + skills/). `memory repo-map` builds a JSON symbol map. Future:
112
+ // memory show, memory graduate.
113
+ program
114
+ .command('memory')
115
+ .description('F3 project memory · init / repo-map / show / graduate / help')
116
+ .argument('[action]', 'Action: init, repo-map, show, graduate, help')
117
+ .argument('[layer]', 'Layer for `memory show` (working / episodic / semantic / personal / skills / protocols / injection)')
118
+ .option('-f, --force', 'Overwrite existing target files (memory init)')
119
+ .option('--no-symlinks', 'Delete old paths instead of leaving back-compat symlinks (memory init)')
120
+ .option('--dry-run', 'Print planned moves without touching disk (memory init)')
121
+ .option('--rebuild', 'Force a fresh build (memory repo-map)')
122
+ .option('--add <text>', 'Add a free-text lesson directly (memory graduate)')
123
+ .option('--accept <id>', 'Graduate a candidate by id (memory graduate)')
124
+ .option('--reject <id>', 'Reject a candidate by id without graduating (memory graduate)')
125
+ .action((action, layer, options) => {
126
+ executeMemoryCommand(action, {
127
+ force: options.force,
128
+ noSymlinks: !options.symlinks,
129
+ dryRun: options.dryRun,
130
+ rebuild: options.rebuild,
131
+ layer,
132
+ add: options.add,
133
+ accept: options.accept,
134
+ reject: options.reject,
135
+ });
136
+ });
137
+ // ── protocols ────────────────────────────────────────────────────────────────
138
+ // F3 v0.5: protocols layer operations. `protocols export` writes the
139
+ // trust-scorer pattern bank to .codehere/protocols/hook_patterns.json so
140
+ // auditors / cross-tool consumers can read codehere's safety surface.
141
+ program
142
+ .command('protocols')
143
+ .description('F3 protocols layer · export (write hook_patterns.json from trust-scorer bank)')
144
+ .argument('[action]', 'Action: export, help')
145
+ .option('--rebuild', 'Refresh built-in patterns (project patterns are preserved)')
146
+ .action((action, options) => {
147
+ executeProtocolsCommand(action, { rebuild: options.rebuild });
148
+ });
106
149
  // ── config ───────────────────────────────────────────────────────────────────
107
150
  program
108
151
  .command('config')
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * Docs: https://docs.anthropic.com/en/docs/claude-code/hooks
15
15
  */
16
- import { appendFileSync, existsSync, mkdirSync } from 'fs';
16
+ import { appendFileSync, chmodSync, existsSync, mkdirSync } from 'fs';
17
17
  import { join } from 'path';
18
18
  import { homedir } from 'os';
19
19
  const PATTERNS = [
@@ -392,9 +392,24 @@ export function appendAuditLog(entry) {
392
392
  try {
393
393
  const dir = AUDIT_FILE.substring(0, AUDIT_FILE.lastIndexOf('/'));
394
394
  if (dir && !existsSync(dir)) {
395
- mkdirSync(dir, { recursive: true });
395
+ // mode 0o700 — owner-only directory. The audit log records every tool call
396
+ // including bash commands and file paths; on multi-user systems a default
397
+ // 0o755 dir would let other users walk in and read the file. Surfaced by
398
+ // the May 1 2026 /cso security audit (Finding #1, MEDIUM).
399
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
400
+ }
401
+ // mode 0o600 — owner-only file. Same reasoning as the dir above. Only takes
402
+ // effect on first write (file creation); existing files are chmod'd below
403
+ // for backwards compatibility with audit logs created before this fix.
404
+ appendFileSync(AUDIT_FILE, JSON.stringify(entry) + '\n', { mode: 0o600 });
405
+ // Idempotent: if the file existed pre-fix with 0o644, tighten it now. The
406
+ // chmod is cheap and silent on already-0o600 files.
407
+ try {
408
+ chmodSync(AUDIT_FILE, 0o600);
409
+ }
410
+ catch {
411
+ /* fall through — chmod failure shouldn't block the audit append */
396
412
  }
397
- appendFileSync(AUDIT_FILE, JSON.stringify(entry) + '\n');
398
413
  }
399
414
  catch {
400
415
  // Audit log failure must not block tool execution
@@ -16,6 +16,7 @@ import { spawn, execFileSync } from 'child_process';
16
16
  import { EventEmitter } from 'events';
17
17
  import { randomUUID } from 'crypto';
18
18
  import { ProgressAggregator } from './progress-aggregator.js';
19
+ import { applyMemoryPrefix } from './memory-injector.js';
19
20
  import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
20
21
  const HEADLESS_FLAGS = [
21
22
  '--yes-always', // auto-accept all file-edit confirmations
@@ -70,8 +71,16 @@ export function spawnAider(prompt, options = {}) {
70
71
  let statusValue = 'running';
71
72
  let killRequested = false;
72
73
  const cwd = options.cwd ?? process.cwd();
74
+ // F3 Task 8 — memory injection (same shape as every other adapter).
75
+ const injected = applyMemoryPrefix(prompt, cwd, options);
76
+ const finalPrompt = injected.prompt;
73
77
  const snapshot = snapshotCwd(cwd);
74
- const aggregator = new ProgressAggregator('aider'); // no-op aggregator; emits no steps
78
+ // ProgressAggregator parses aider's stdout for `Running ...`, `Applied edit
79
+ // to ...`, and `Creating empty file ...` lines and emits Run/Edit/Write
80
+ // ProgressSteps. Observability parity (Path B) — see ingestAider() in
81
+ // progress-aggregator.ts. Blocking parity (Path A) needs the upstream
82
+ // `--pre-action-hook` PR.
83
+ const aggregator = new ProgressAggregator('aider');
75
84
  let stepsSnapshot = [];
76
85
  let artifactsSnapshot;
77
86
  let textAccum = '';
@@ -139,7 +148,7 @@ export function spawnAider(prompt, options = {}) {
139
148
  const spawnEnv = { ...process.env, ...(options.env ?? {}) };
140
149
  let child;
141
150
  try {
142
- child = spawn('aider', ['--message', prompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
151
+ child = spawn('aider', ['--message', finalPrompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
143
152
  cwd,
144
153
  env: spawnEnv,
145
154
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -225,6 +234,7 @@ export function spawnAider(prompt, options = {}) {
225
234
  startedAt,
226
235
  prompt,
227
236
  agent: 'aider',
237
+ memoryInjection: injected.record,
228
238
  get status() {
229
239
  return statusValue;
230
240
  },
@@ -19,6 +19,7 @@ import { EventEmitter } from 'events';
19
19
  import { randomUUID } from 'crypto';
20
20
  import { ProgressAggregator } from './progress-aggregator.js';
21
21
  import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
22
+ import { applyMemoryPrefix } from './memory-injector.js';
22
23
  // Runtime-error fingerprints → structured codehere:error events with recovery hints.
23
24
  // Matched against any stderr line or assistant-text content we see on the stream.
24
25
  const ERROR_PATTERNS = [
@@ -78,6 +79,12 @@ export function spawnClaudeCode(prompt, options = {}) {
78
79
  let killRequested = false;
79
80
  // PM UX Layer 2+3 state. Shared helper modules; per-session instances.
80
81
  const cwd = options.cwd ?? process.cwd();
82
+ // F3 Task 8 — inject project memory (AGENTS.md + lessons + repo_map slice)
83
+ // into the prompt prefix unless the caller opted out. Pure, side-effect-
84
+ // free; returns unchanged prompt + record=undefined when there's no
85
+ // .codehere/ at cwd.
86
+ const injected = applyMemoryPrefix(prompt, cwd, options);
87
+ const finalPrompt = injected.prompt;
81
88
  const snapshot = snapshotCwd(cwd);
82
89
  const aggregator = new ProgressAggregator('claude-code');
83
90
  let stepsSnapshot = [];
@@ -176,7 +183,7 @@ export function spawnClaudeCode(prompt, options = {}) {
176
183
  }
177
184
  let child;
178
185
  try {
179
- child = spawn('claude', ['-p', prompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
186
+ child = spawn('claude', ['-p', finalPrompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
180
187
  cwd,
181
188
  env: spawnEnv,
182
189
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -277,6 +284,7 @@ export function spawnClaudeCode(prompt, options = {}) {
277
284
  startedAt,
278
285
  prompt,
279
286
  agent: 'claude-code',
287
+ memoryInjection: injected.record,
280
288
  get status() {
281
289
  return statusValue;
282
290
  },
@@ -18,6 +18,7 @@ import { EventEmitter } from 'events';
18
18
  import { randomUUID } from 'crypto';
19
19
  import { ProgressAggregator } from './progress-aggregator.js';
20
20
  import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
21
+ import { applyMemoryPrefix } from './memory-injector.js';
21
22
  const HEADLESS_FLAGS = [
22
23
  'exec',
23
24
  '--json',
@@ -73,6 +74,9 @@ export function spawnCodex(prompt, options = {}) {
73
74
  // PM UX Layer 2+3 state — same shape as claude-code-adapter. Kept in-file (not
74
75
  // extracted to a base class) because the two CLIs diverge in more than they share.
75
76
  const cwd = options.cwd ?? process.cwd();
77
+ // F3 Task 8 — memory injection (same shape as every other adapter).
78
+ const injected = applyMemoryPrefix(prompt, cwd, options);
79
+ const finalPrompt = injected.prompt;
76
80
  const snapshot = snapshotCwd(cwd);
77
81
  const aggregator = new ProgressAggregator('codex');
78
82
  let stepsSnapshot = [];
@@ -151,7 +155,7 @@ export function spawnCodex(prompt, options = {}) {
151
155
  env: spawnEnv,
152
156
  stdio: ['pipe', 'pipe', 'pipe'],
153
157
  });
154
- child.stdin?.write(prompt);
158
+ child.stdin?.write(finalPrompt);
155
159
  child.stdin?.end();
156
160
  }
157
161
  catch (err) {
@@ -241,6 +245,7 @@ export function spawnCodex(prompt, options = {}) {
241
245
  startedAt,
242
246
  prompt,
243
247
  agent: 'codex',
248
+ memoryInjection: injected.record,
244
249
  get status() {
245
250
  return statusValue;
246
251
  },
@@ -284,8 +284,17 @@ function spawnConductor(prompt, options, lookup) {
284
284
  let artifactsSnapshot;
285
285
  specialistSession.subscribe((evt) => {
286
286
  buffer.push(evt);
287
- emitter.emit('event', evt);
288
- // Mirror progress + artifacts so PM-UX layers see them on the Conductor session, too.
287
+ // Mirror progress + artifacts BEFORE re-emitting so any subscriber that
288
+ // reads `session.status` / `session.steps` / `session.artifacts` from
289
+ // inside its event handler sees the up-to-date conductor state.
290
+ //
291
+ // BUG 2 (zombie RUNNING sessions, May 1 2026, 138/155 records affected):
292
+ // pre-fix this block emitted FIRST and updated status SECOND. session-
293
+ // manager.ts subscribes to the conductor session and persists `session.status`
294
+ // synchronously inside its on-exit handler. With emit-before-update,
295
+ // every conductor session was persisted with status='running' even though
296
+ // the underlying specialist had already finished. The fix reorders so the
297
+ // emit() at the end sees the freshly-mirrored statusValue.
289
298
  if (specialistSession.steps)
290
299
  stepsSnapshot = specialistSession.steps;
291
300
  if (specialistSession.artifacts)
@@ -298,6 +307,7 @@ function spawnConductor(prompt, options, lookup) {
298
307
  if (evt.type === 'exit' || evt.type === 'codehere:complete' || evt.type === 'codehere:error') {
299
308
  statusValue = specialistSession.status;
300
309
  }
310
+ emitter.emit('event', evt);
301
311
  });
302
312
  return {
303
313
  sessionId,