codehere 0.7.1 → 1.0.1

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.
Files changed (139) hide show
  1. package/README.md +22 -13
  2. package/dist/application/services/verdict-service.js +154 -0
  3. package/dist/application/use-cases/assess-cross-vendor.js +126 -0
  4. package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
  5. package/dist/application/use-cases/export-aat.js +107 -0
  6. package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
  7. package/dist/application/use-cases/generate-final-report.js +140 -15
  8. package/dist/application/use-cases/generate-narration.js +5 -3
  9. package/dist/application/use-cases/generate-thread-title.js +5 -2
  10. package/dist/application/use-cases/narrator-prompt-template.js +1 -1
  11. package/dist/application/use-cases/plan-auto-review.js +57 -0
  12. package/dist/application/use-cases/reconcile-audit.js +100 -0
  13. package/dist/application/use-cases/rerun-tests.js +226 -0
  14. package/dist/application/use-cases/search-sessions.js +70 -0
  15. package/dist/application/use-cases/should-auto-review.js +71 -0
  16. package/dist/application/use-cases/thread-export.js +90 -0
  17. package/dist/application/use-cases/verify-claim.js +187 -5
  18. package/dist/domain/audit.js +11 -0
  19. package/dist/domain/services/codebase-detector.js +0 -6
  20. package/dist/domain/task.js +91 -0
  21. package/dist/error-handler.js +3 -115
  22. package/dist/index.js +279 -31
  23. package/dist/infrastructure/completion/bash-completion.js +7 -3
  24. package/dist/infrastructure/completion/fish-completion.js +7 -12
  25. package/dist/infrastructure/completion/zsh-completion.js +7 -12
  26. package/dist/infrastructure/config/config-manager.js +4 -3
  27. package/dist/infrastructure/config/product-context.js +1 -19
  28. package/dist/infrastructure/context/adapters/figma-source.js +37 -2
  29. package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
  30. package/dist/infrastructure/context/github-api.js +110 -0
  31. package/dist/infrastructure/context/github-device-flow.js +65 -0
  32. package/dist/infrastructure/context/oauth-store.js +5 -2
  33. package/dist/infrastructure/context/registry-bootstrap.js +0 -4
  34. package/dist/infrastructure/cost/budget-config.js +4 -3
  35. package/dist/infrastructure/cost/cost-router.js +16 -23
  36. package/dist/infrastructure/cost/spend-aggregator.js +91 -16
  37. package/dist/infrastructure/cost/team-ledger.js +355 -0
  38. package/dist/infrastructure/discovery/discovery-log.js +85 -0
  39. package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
  40. package/dist/infrastructure/hooks/claude-sdk-hook.js +139 -3
  41. package/dist/infrastructure/hooks/hook-mode.js +20 -4
  42. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
  43. package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
  44. package/dist/infrastructure/observability/execution-tracer.js +1 -1
  45. package/dist/infrastructure/orchestration/agents-config-seed.js +23 -11
  46. package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
  47. package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
  48. package/dist/infrastructure/orchestration/cassette-recorder.js +1 -1
  49. package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
  50. package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
  51. package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
  52. package/dist/infrastructure/orchestration/context-handoff.js +32 -14
  53. package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
  54. package/dist/infrastructure/orchestration/failover-config.js +1 -11
  55. package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
  56. package/dist/infrastructure/orchestration/generic-cli-adapter.js +134 -3
  57. package/dist/infrastructure/orchestration/graphify-context.js +14 -59
  58. package/dist/infrastructure/orchestration/memory-injector.js +4 -1
  59. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +167 -9
  60. package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
  61. package/dist/infrastructure/orchestration/progress-aggregator.js +275 -23
  62. package/dist/infrastructure/orchestration/session-manager.js +593 -94
  63. package/dist/infrastructure/orchestration/session-persistence.js +177 -41
  64. package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
  65. package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
  66. package/dist/infrastructure/protocols/cloud-sync.js +2 -2
  67. package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
  68. package/dist/infrastructure/protocols/mcp-server-impl.js +1498 -354
  69. package/dist/infrastructure/protocols/mcp-tools.js +401 -0
  70. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
  71. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
  72. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
  73. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
  74. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
  75. package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
  76. package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
  77. package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
  78. package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
  79. package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
  80. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
  81. package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
  82. package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
  83. package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
  84. package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
  85. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
  86. package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
  87. package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
  88. package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
  89. package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
  90. package/dist/infrastructure/protocols/ui/styles.js +2147 -0
  91. package/dist/infrastructure/protocols/ui/tokens.js +96 -0
  92. package/dist/infrastructure/protocols/ui-page-v2.js +116 -4126
  93. package/dist/infrastructure/protocols/verify-page.js +884 -148
  94. package/dist/infrastructure/security/fix-code.js +144 -0
  95. package/dist/infrastructure/security/fix-secret.js +89 -0
  96. package/dist/infrastructure/security/permissions-config.js +3 -2
  97. package/dist/infrastructure/security/scan-types.js +10 -0
  98. package/dist/infrastructure/security/ship-scan.js +195 -0
  99. package/dist/infrastructure/security/trust-scorer.js +70 -3
  100. package/dist/infrastructure/security/vibe-checks.js +99 -0
  101. package/dist/infrastructure/storage/atomic-write.js +86 -0
  102. package/dist/infrastructure/storage/project-thread-mirror.js +151 -4
  103. package/dist/infrastructure/storage/project-thread-reader.js +262 -25
  104. package/dist/infrastructure/storage/project-thread-title-backfill.js +1 -1
  105. package/dist/monitoring.js +2 -2
  106. package/dist/presentation/cli/commands/audit-command.js +79 -4
  107. package/dist/presentation/cli/commands/budget-command.js +2 -2
  108. package/dist/presentation/cli/commands/check-command.js +96 -0
  109. package/dist/presentation/cli/commands/context-source-command.js +20 -1
  110. package/dist/presentation/cli/commands/help-command.js +5 -5
  111. package/dist/presentation/cli/commands/hook-command.js +5 -5
  112. package/dist/presentation/cli/commands/init-command.js +89 -66
  113. package/dist/presentation/cli/commands/memory-command.js +28 -28
  114. package/dist/presentation/cli/commands/permission-command.js +1 -12
  115. package/dist/presentation/cli/commands/protocols-builder.js +2 -2
  116. package/dist/presentation/cli/commands/protocols-command.js +4 -4
  117. package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
  118. package/dist/presentation/cli/commands/report-command.js +48 -32
  119. package/dist/presentation/cli/commands/scan-command.js +52 -0
  120. package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
  121. package/dist/presentation/cli/commands/search-command.js +51 -0
  122. package/dist/presentation/cli/commands/setup-command.js +31 -8
  123. package/dist/presentation/cli/commands/status-command.js +39 -86
  124. package/dist/presentation/cli/commands/team-command.js +256 -0
  125. package/dist/presentation/cli/commands/thread-command.js +87 -5
  126. package/dist/presentation/cli/commands/trust-command.js +53 -31
  127. package/dist/presentation/cli/commands/verify-command.js +74 -9
  128. package/dist/presentation/cli/error-display.js +0 -8
  129. package/dist/presentation/cli/repl.js +15 -7
  130. package/dist/presentation/cli/task-deliverable.js +36 -10
  131. package/dist/testing/replay-adapter.js +0 -19
  132. package/dist/ui.js +51 -239
  133. package/dist/utils/version.js +0 -6
  134. package/package.json +30 -10
  135. package/dist/domain/entities/task.js +0 -61
  136. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  137. package/dist/infrastructure/security/input-validator.js +0 -152
  138. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  139. package/dist/infrastructure/storage/task-repository.js +0 -206
@@ -17,6 +17,36 @@ function shortPath(p) {
17
17
  function truncate(s, max = MAX_LABEL) {
18
18
  return s.length > max ? s.slice(0, max - 1) + '…' : s;
19
19
  }
20
+ /** A successful tool_result sometimes extracts to no text (structured payloads),
21
+ * leaving the step row bare. For tools whose SUCCESS carries a computable claim
22
+ * (Edit succeeded = the replacement applied), derive the outcome from the input
23
+ * we captured at tool_use time. Only states what the success guarantees. */
24
+ function synthesizeOutcome(tool, input) {
25
+ if (!tool || !input)
26
+ return '';
27
+ if (tool === 'Edit' || tool === 'MultiEdit') {
28
+ // MultiEdit carries {edits:[{old_string,new_string,replace_all?}]}; Edit is flat.
29
+ const edits = Array.isArray(input.edits) ? input.edits : [input];
30
+ let d = 0;
31
+ for (const e of edits) {
32
+ // replace_all applies per-occurrence — a computed delta would understate
33
+ // the real change, so claim only what success guarantees.
34
+ if (e.replace_all)
35
+ return 'change applied';
36
+ const o = String(e.old_string ?? '');
37
+ const n = String(e.new_string ?? '');
38
+ if (!o && !n)
39
+ continue;
40
+ d += n.split('\n').length - o.split('\n').length;
41
+ }
42
+ return d === 0 ? 'change applied' : `${d > 0 ? '+' + d : d} lines · change applied`;
43
+ }
44
+ if (tool === 'Write') {
45
+ const c = String(input.content ?? '');
46
+ return c ? `${c.split('\n').length} lines written` : 'file written';
47
+ }
48
+ return '';
49
+ }
20
50
  function labelForClaudeToolUse(name, input) {
21
51
  const i = (input && typeof input === 'object' ? input : {});
22
52
  switch (name) {
@@ -99,6 +129,26 @@ function truncateOutput(s) {
99
129
  return stripped;
100
130
  return '…' + stripped.slice(-(max - 1));
101
131
  }
132
+ /** Parse a Claude AskUserQuestion tool_use input into the step.question shape.
133
+ * Defensive — SDK shape is { questions: [{ question, header?, multiSelect?,
134
+ * options: [{ label, description? }] }] }; missing pieces degrade gracefully. */
135
+ function parseAskUserQuestion(toolUseId, input) {
136
+ const raw = Array.isArray(input?.questions) ? input.questions : [];
137
+ const questions = raw.map((qq) => {
138
+ const q = (qq && typeof qq === 'object' ? qq : {});
139
+ const opts = Array.isArray(q.options) ? q.options : [];
140
+ return {
141
+ question: String(q.question ?? ''),
142
+ ...(q.header ? { header: String(q.header) } : {}),
143
+ ...(q.multiSelect ? { multiSelect: true } : {}),
144
+ options: opts.map((oo) => {
145
+ const o = (oo && typeof oo === 'object' ? oo : {});
146
+ return { label: String(o.label ?? ''), ...(o.description ? { description: String(o.description) } : {}) };
147
+ }),
148
+ };
149
+ });
150
+ return { toolUseId, questions };
151
+ }
102
152
  function unwrapShellWrapper(command) {
103
153
  // Codex wraps its shell calls in `/bin/zsh -lc "…"` or `sh -c "…"`. Strip that
104
154
  // so the step label reads as the actual command instead of the wrapper.
@@ -161,6 +211,44 @@ export class ProgressAggregator {
161
211
  return this.ingestAider(type, data);
162
212
  if (this.agentId === 'opencode')
163
213
  return this.ingestOpencode(type, data);
214
+ // Generic-config agents (cursor, etc.): the generic-cli-adapter normalizes
215
+ // their stream-json to flat tool_use + stdout events. Build steps from those
216
+ // so "watch it work" isn't empty (real dogfood 2026-06-23: cursor runs edited
217
+ // files + verified green but rendered 0 steps).
218
+ return this.ingestGeneric(type, data);
219
+ }
220
+ ingestGeneric(type, data) {
221
+ if (type === 'tool_use') {
222
+ const name = String(data.name ?? data.tool_name ?? 'tool');
223
+ const input = (data.input ?? data.parameters ?? {});
224
+ const arg = (typeof input.path === 'string' && input.path) ||
225
+ (typeof input.file_path === 'string' && input.file_path) ||
226
+ (typeof input.command === 'string' && input.command) ||
227
+ (typeof input.query === 'string' && input.query) ||
228
+ '';
229
+ const short = arg ? arg.split('/').pop() || arg : '';
230
+ const verb = name ? name.charAt(0).toUpperCase() + name.slice(1) : 'Tool';
231
+ this._steps.push({
232
+ label: truncate(short ? `${verb} ${short}` : verb),
233
+ state: 'done',
234
+ startedAt: Date.now(),
235
+ completedAt: Date.now(),
236
+ });
237
+ return true;
238
+ }
239
+ if (type === 'stdout') {
240
+ const line = String(data.line ?? '').trim();
241
+ if (!line)
242
+ return false;
243
+ this._steps.push({
244
+ kind: 'say',
245
+ label: truncate(line),
246
+ state: 'done',
247
+ startedAt: Date.now(),
248
+ completedAt: Date.now(),
249
+ });
250
+ return true;
251
+ }
164
252
  return false;
165
253
  }
166
254
  // OpenCode · `run --format json` emits tool_use with shape
@@ -169,6 +257,14 @@ export class ProgressAggregator {
169
257
  // real gap surfaced by the mock harness). Build a step on the terminal
170
258
  // (status==='completed') event so opencode is a first-class agent.
171
259
  ingestOpencode(type, data) {
260
+ // OpenCode's narrative text rides `text` events with part.text (the same
261
+ // shape the adapter's summary extraction reads) — cross-vendor say capture
262
+ // per the v1 spec, so opencode's Final Report stops degrading to the
263
+ // deterministic fallback.
264
+ if (type === 'text') {
265
+ const part = (data.part ?? {});
266
+ return this.pushSay(String(part.text ?? ''));
267
+ }
172
268
  if (type !== 'tool_use')
173
269
  return false;
174
270
  const part = (data.part ?? {});
@@ -198,8 +294,10 @@ export class ProgressAggregator {
198
294
  }
199
295
  /** Flush: mark every still-active step as failed (called on abnormal exit). */
200
296
  finalize(status) {
201
- // Surface any trailing aider reasoning that had no following action.
297
+ // Surface any trailing reasoning that had no following action.
202
298
  let changed = this.flushAiderSay();
299
+ if (this.flushGeminiSay())
300
+ changed = true;
203
301
  for (const s of this._steps) {
204
302
  if (s.state === 'active' || s.state === 'pending') {
205
303
  s.state = status === 'completed' ? 'done' : 'failed';
@@ -213,6 +311,61 @@ export class ProgressAggregator {
213
311
  const idx = this._steps.push({ label, state, startedAt: Date.now() }) - 1;
214
312
  return idx;
215
313
  }
314
+ /**
315
+ * Git-diff fallback (Phase 1 — aider parity). Aider has no structured event
316
+ * stream; its file-edit steps are parsed from stdout lines ("Applied edit to
317
+ * X" / "Creating empty file X") matched against aider's `base_coder.py`
318
+ * wording. A version bump that re-words those lines would silently yield ZERO
319
+ * file-action steps — a run that really changed files would then render an
320
+ * empty timeline and a "no files changed" report (the exact "did nothing"
321
+ * lie user-#1 hit on a real aider run).
322
+ *
323
+ * This is the format-independent safety net. Given the git-diff file list the
324
+ * artifacts-extractor already computed (which the adapter passes on exit), if
325
+ * NO file-action step exists, synthesize one per changed file from the diff —
326
+ * git is more authoritative than stdout parsing, not less, so these aren't
327
+ * guesses. No-op when the stdout parse already produced file-action steps (so
328
+ * we never double-count). Returns true iff a synthetic step was added.
329
+ */
330
+ applyFileFallback(files) {
331
+ if (!files || files.length === 0)
332
+ return false;
333
+ const hasFileAction = this._steps.some((s) => /^(Edit|Write|Delete) /.test(s.label));
334
+ if (hasFileAction)
335
+ return false;
336
+ let added = false;
337
+ for (const f of files) {
338
+ const path = String(f?.path ?? '').trim();
339
+ if (!path)
340
+ continue;
341
+ this._steps.push({
342
+ label: 'Edit ' + shortPath(path),
343
+ state: 'done',
344
+ startedAt: Date.now(),
345
+ completedAt: Date.now(),
346
+ });
347
+ added = true;
348
+ }
349
+ return added;
350
+ }
351
+ /** The agent's own narration as a kind:'say' step — the "watch it work"
352
+ * signal AND what the Final Report engine reuses as the summary. Cross-
353
+ * vendor by decision of the v1 spec (the room thread is "tool steps woven
354
+ * with the agent's plain-English narration" — for every agent, not just
355
+ * claude/aider). Same 500-char cap as the original claude capture. */
356
+ pushSay(text) {
357
+ const said = String(text ?? '').trim();
358
+ if (!said)
359
+ return false;
360
+ this._steps.push({
361
+ kind: 'say',
362
+ label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
363
+ state: 'done',
364
+ startedAt: Date.now(),
365
+ completedAt: Date.now(),
366
+ });
367
+ return true;
368
+ }
216
369
  ingestClaude(type, data) {
217
370
  if (type === 'assistant') {
218
371
  const msg = (data.message ?? {});
@@ -228,19 +381,56 @@ export class ProgressAggregator {
228
381
  const said = b.text.trim();
229
382
  this._steps.push({
230
383
  kind: 'say',
231
- label: said.length > 500 ? said.slice(0, 500) + '\\u2026' : said,
384
+ label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
232
385
  state: 'done',
233
386
  startedAt: Date.now(),
234
387
  completedAt: Date.now(),
235
388
  });
236
389
  changed = true;
237
390
  }
391
+ else if (b.type === 'thinking') {
392
+ // Claude's interleaved reasoning block — was 100% dropped. Surface it
393
+ // as a muted 'think' step so the user can watch the agent reason.
394
+ const think = typeof b.thinking === 'string' ? b.thinking.trim() : '';
395
+ if (think) {
396
+ this._steps.push({
397
+ kind: 'think',
398
+ label: think.length > 20000 ? think.slice(0, 20000) + '…' : think,
399
+ state: 'done',
400
+ startedAt: Date.now(),
401
+ completedAt: Date.now(),
402
+ });
403
+ changed = true;
404
+ }
405
+ }
238
406
  else if (b.type === 'tool_use') {
239
407
  const id = String(b.id ?? '');
240
- const label = labelForClaudeToolUse(String(b.name ?? ''), b.input);
241
- const idx = this.addStep(label, 'active');
242
- if (id)
243
- this.toolUseIndex.set(id, idx);
408
+ const name = String(b.name ?? '');
409
+ const input = b.input && typeof b.input === 'object' ? b.input : undefined;
410
+ if (name === 'AskUserQuestion') {
411
+ // The agent is asking the user. Capture the questions+options (was
412
+ // discarded) and mark it 'ask' so the UI renders a question card —
413
+ // not a generic failed tool step.
414
+ const q = parseAskUserQuestion(id, input);
415
+ const n = q.questions.length;
416
+ const idx = this.addStep(`Asking you ${n} question${n === 1 ? '' : 's'}`, 'active');
417
+ const step = this._steps[idx];
418
+ step.kind = 'ask';
419
+ step.toolName = name;
420
+ step.question = q;
421
+ if (id)
422
+ this.toolUseIndex.set(id, idx);
423
+ }
424
+ else {
425
+ const label = labelForClaudeToolUse(name, input);
426
+ const idx = this.addStep(label, 'active');
427
+ const step = this._steps[idx];
428
+ step.toolName = name;
429
+ if (input)
430
+ step.toolInput = input;
431
+ if (id)
432
+ this.toolUseIndex.set(id, idx);
433
+ }
244
434
  changed = true;
245
435
  }
246
436
  }
@@ -258,17 +448,29 @@ export class ProgressAggregator {
258
448
  const idx = id ? this.toolUseIndex.get(id) : undefined;
259
449
  if (idx !== undefined) {
260
450
  const step = this._steps[idx];
261
- step.state = b.is_error ? 'failed' : 'done';
451
+ // An un-answered AskUserQuestion returns is_error headless, but it's
452
+ // a PENDING question, not a failure — don't flip it to 'failed'.
453
+ if (step.kind !== 'ask')
454
+ step.state = b.is_error ? 'failed' : 'done';
262
455
  step.completedAt = Date.now();
263
- // P22 / Track B #8 — capture tool-result text for the
264
- // dashboard's bash-output preview. Claude SDK shape:
265
- // content: string (most cases)
266
- // content: Array<{type:'text', text:string}> (multi-block)
267
- // Truncate to last ~400 chars so the SSE payload + render
268
- // cost stay bounded.
456
+ // Capture BOTH: fullOutput (untruncated, ANSI preserved — for the
457
+ // expandable view) and output (bounded, ANSI-stripped preview so the
458
+ // per-step SSE snapshot stays small). On failure, keep the error body.
269
459
  const out = extractClaudeToolResultText(b);
270
- if (out)
460
+ if (out) {
461
+ step.fullOutput = out;
271
462
  step.output = truncateOutput(out);
463
+ if (b.is_error)
464
+ step.errorText = out;
465
+ }
466
+ else if (!b.is_error && step.kind !== 'ask') {
467
+ // Structured results extract to '' and the row would read as a bare
468
+ // "Edit x · 0.5s". For tools whose success has a computable meaning,
469
+ // synthesize a truthful outcome from the input we already hold.
470
+ const synth = synthesizeOutcome(step.toolName, step.toolInput);
471
+ if (synth)
472
+ step.output = synth;
473
+ }
272
474
  changed = true;
273
475
  }
274
476
  }
@@ -290,6 +492,9 @@ export class ProgressAggregator {
290
492
  */
291
493
  ingestGemini(type, data) {
292
494
  if (type === 'tool_use') {
495
+ // Narration precedes the action in gemini's stream — flush any buffered
496
+ // assistant text first so the timeline reads "explains → acts".
497
+ this.flushGeminiSay();
293
498
  const id = String(data.tool_id ?? '');
294
499
  const name = String(data.tool_name ?? 'tool');
295
500
  const label = labelForClaudeToolUse(name, data.parameters);
@@ -322,8 +527,43 @@ export class ProgressAggregator {
322
527
  return true;
323
528
  }
324
529
  }
530
+ // Gemini's narrative text — THE REAL STREAM SHAPE (verified against
531
+ // @google/gemini-cli StreamJsonFormatter, mirrored in gemini-adapter.ts):
532
+ // `message` events `{ role:'assistant', content, delta?: bool }`, where
533
+ // content arrives as delta CHUNKS. The old `content`-type branch below
534
+ // never matched a real run — so real gemini sessions produced ZERO say
535
+ // steps and every Final Report degraded to the deterministic "didn't
536
+ // leave a written summary" fallback (R10 dogfood gap c). Buffer deltas;
537
+ // flush as ONE say step at a message boundary (non-delta), before the
538
+ // next action (tool_use above), or at stream end (result/error/finalize).
539
+ if (type === 'message') {
540
+ if (data.role === 'assistant' && typeof data.content === 'string' && data.content) {
541
+ this.geminiSayBuffer.push(data.content);
542
+ if (data.delta !== true)
543
+ return this.flushGeminiSay();
544
+ return false; // chunk buffered; the flush is the visible step
545
+ }
546
+ return false;
547
+ }
548
+ if (type === 'result' || type === 'error') {
549
+ return this.flushGeminiSay();
550
+ }
551
+ // Legacy shape kept for old cassettes: `content` events with a text field.
552
+ if (type === 'content' && typeof data.text === 'string') {
553
+ return this.pushSay(data.text);
554
+ }
325
555
  return false;
326
556
  }
557
+ /** Buffered gemini assistant delta chunks awaiting a boundary flush. */
558
+ geminiSayBuffer = [];
559
+ /** Flush buffered gemini narration as one kind:'say' step. */
560
+ flushGeminiSay() {
561
+ if (this.geminiSayBuffer.length === 0)
562
+ return false;
563
+ const text = this.geminiSayBuffer.join('');
564
+ this.geminiSayBuffer = [];
565
+ return this.pushSay(text);
566
+ }
327
567
  /**
328
568
  * Aider observability parity (v0.5 Path B per docs/design/f3_memory_spec.md
329
569
  * + ~/.claude/.../memory/learning_aider.md).
@@ -407,7 +647,12 @@ export class ProgressAggregator {
407
647
  * "must contain a space + length >= 4" guard in collectAiderSay already
408
648
  * drops bare filenames, fence langs and spinner rules; this catches the
409
649
  * noisy lines that DO contain spaces. */
410
- static AIDER_NONPROSE_RE = /^(=======\s*$|Tokens:|Cost:|Commit [0-9a-f]|Applied edit to |Running |Creating empty file |Did not |Added \S+ to the chat|Removed \S+ from the chat|No changes made|Warning: |Scanning repo|Initial repo scan|Repo-map:|Main model:|Weak model:|Model:|Git repo:|Use \/|aider |Committing )/;
650
+ static AIDER_NONPROSE_RE = /^(=======\s*$|Tokens:|Cost:|Commit [0-9a-f]|Applied edit to |Running |Creating empty file |Did not |Added \S+ to the chat|Removed \S+ from the chat|No changes made|Warning: |Scanning repo|Initial repo scan|Repo-map:|Repo-map can|Main model:|Weak model:|Model:|Git repo:|Cur working dir|Git working dir|Use \/|aider |Aider v|Committing )/;
651
+ /** Aider CLI startup/warning boilerplate that arrives as prose-shaped lines
652
+ * (often prefixed "Note: ..."), so it slips past the line-start regex above.
653
+ * Matched ANYWHERE in the line by its distinctive phrases — this is the noise
654
+ * that leaked into "the agent's words" and made the room read like stderr. */
655
+ static AIDER_BOILERPLATE_RE = /in-chat filenames|working dir|repo-map can'?t|deleted from the file system|but not from git\?/i;
411
656
  aiderFlushOutput() {
412
657
  if (this.aiderRunningStepIdx === null)
413
658
  return;
@@ -434,7 +679,7 @@ export class ProgressAggregator {
434
679
  return false;
435
680
  this._steps.push({
436
681
  kind: 'say',
437
- label: said.length > 500 ? said.slice(0, 500) + '…' : said,
682
+ label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
438
683
  state: 'done',
439
684
  startedAt: Date.now(),
440
685
  completedAt: Date.now(),
@@ -462,6 +707,8 @@ export class ProgressAggregator {
462
707
  return;
463
708
  if (ProgressAggregator.AIDER_NONPROSE_RE.test(line))
464
709
  return;
710
+ if (ProgressAggregator.AIDER_BOILERPLATE_RE.test(line))
711
+ return; // CLI startup noise, anywhere in the line
465
712
  // Require a space + minimum length so filenames/code tokens don't leak in.
466
713
  if (line.length < 4 || !line.includes(' '))
467
714
  return;
@@ -633,7 +880,9 @@ export class ProgressAggregator {
633
880
  return true;
634
881
  }
635
882
  if (itemType === 'web_search') {
636
- const q = String(item.query ?? '').trim().slice(0, 80);
883
+ const q = String(item.query ?? '')
884
+ .trim()
885
+ .slice(0, 80);
637
886
  this._steps.push({
638
887
  label: q ? 'Web search · ' + q : 'Web search',
639
888
  state: 'done',
@@ -642,12 +891,15 @@ export class ProgressAggregator {
642
891
  });
643
892
  return true;
644
893
  }
645
- // NOTE (mock-currency 2026-06-02): codex `agent_message` is intentionally NOT
646
- // captured here — a prior decision (pm-layers.test.ts "just chatter, not a
647
- // step") drops it. This is INCONSISTENT with ingestClaude/ingestAider, which
648
- // DO capture agent narration as kind:'say' (per #45 + A1 "watch it work").
649
- // Resolving that inconsistency is a product taste call → flagged for Ega in
650
- // mock-currency-2026-06-02.md, not changed unilaterally by the autonomous loop.
894
+ // agent_message = codex's own narration. Captured as kind:'say' since the
895
+ // v1 spec settled the taste call that used to be flagged here (pm-layers
896
+ // "just chatter" decision): the room thread is "tool steps woven with the
897
+ // agent's plain-English narration" for EVERY vendor, and the Final Report
898
+ // engine reuses 'say' steps as the summary — without this, codex always
899
+ // degraded to the deterministic fallback.
900
+ if (itemType === 'agent_message') {
901
+ return this.pushSay(String(item.text ?? ''));
902
+ }
651
903
  return false;
652
904
  }
653
905
  }