@gajae-code/coding-agent 0.2.5 → 0.3.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 (234) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/types/async/job-manager.d.ts +91 -2
  3. package/dist/types/cli/args.d.ts +1 -1
  4. package/dist/types/commands/deep-interview.d.ts +3 -0
  5. package/dist/types/commands/harness.d.ts +37 -0
  6. package/dist/types/config/keybindings.d.ts +5 -0
  7. package/dist/types/config/settings-schema.d.ts +10 -4
  8. package/dist/types/config/settings.d.ts +2 -0
  9. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  10. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  11. package/dist/types/deep-interview/render-middleware.d.ts +6 -0
  12. package/dist/types/eval/py/executor.d.ts +2 -0
  13. package/dist/types/eval/py/kernel.d.ts +2 -0
  14. package/dist/types/exec/bash-executor.d.ts +10 -0
  15. package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
  16. package/dist/types/extensibility/extensions/types.d.ts +6 -0
  17. package/dist/types/extensibility/shared-events.d.ts +1 -0
  18. package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
  19. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
  20. package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
  21. package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
  22. package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
  23. package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
  24. package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
  25. package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
  26. package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
  27. package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
  28. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
  29. package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
  30. package/dist/types/harness-control-plane/classifier.d.ts +13 -0
  31. package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
  32. package/dist/types/harness-control-plane/finalize.d.ts +47 -0
  33. package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
  34. package/dist/types/harness-control-plane/operate.d.ts +35 -0
  35. package/dist/types/harness-control-plane/owner.d.ts +46 -0
  36. package/dist/types/harness-control-plane/preserve.d.ts +19 -0
  37. package/dist/types/harness-control-plane/receipts.d.ts +88 -0
  38. package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
  39. package/dist/types/harness-control-plane/seams.d.ts +21 -0
  40. package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
  41. package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
  42. package/dist/types/harness-control-plane/storage.d.ts +53 -0
  43. package/dist/types/harness-control-plane/types.d.ts +162 -0
  44. package/dist/types/hooks/skill-keywords.d.ts +2 -1
  45. package/dist/types/hooks/skill-state.d.ts +23 -29
  46. package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
  47. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  48. package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
  49. package/dist/types/internal-urls/types.d.ts +4 -0
  50. package/dist/types/lsp/index.d.ts +10 -10
  51. package/dist/types/modes/bridge/auth.d.ts +12 -0
  52. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  53. package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
  54. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  55. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  56. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  57. package/dist/types/modes/components/hook-selector.d.ts +1 -0
  58. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  59. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  60. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  61. package/dist/types/modes/components/status-line.d.ts +2 -0
  62. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  63. package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
  64. package/dist/types/modes/index.d.ts +1 -0
  65. package/dist/types/modes/interactive-mode.d.ts +2 -0
  66. package/dist/types/modes/jobs-observer.d.ts +57 -0
  67. package/dist/types/modes/rpc/host-tools.d.ts +1 -16
  68. package/dist/types/modes/rpc/host-uris.d.ts +1 -38
  69. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
  70. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  71. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
  72. package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
  73. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  74. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  75. package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
  76. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  77. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  78. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  79. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  80. package/dist/types/modes/types.d.ts +2 -0
  81. package/dist/types/sdk.d.ts +4 -0
  82. package/dist/types/session/agent-session.d.ts +19 -1
  83. package/dist/types/skill-state/active-state.d.ts +2 -0
  84. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
  85. package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
  86. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  87. package/dist/types/task/executor.d.ts +3 -0
  88. package/dist/types/task/id.d.ts +7 -0
  89. package/dist/types/task/index.d.ts +5 -0
  90. package/dist/types/task/receipt.d.ts +85 -0
  91. package/dist/types/task/spawn-gate.d.ts +38 -0
  92. package/dist/types/task/types.d.ts +198 -14
  93. package/dist/types/tools/cron.d.ts +6 -0
  94. package/dist/types/tools/index.d.ts +2 -0
  95. package/dist/types/tools/path-utils.d.ts +1 -0
  96. package/dist/types/tools/subagent.d.ts +26 -1
  97. package/package.json +7 -7
  98. package/scripts/build-binary.ts +7 -0
  99. package/src/async/job-manager.ts +334 -6
  100. package/src/cli/args.ts +9 -2
  101. package/src/cli/auth-broker-cli.ts +1 -0
  102. package/src/cli/config-cli.ts +10 -2
  103. package/src/cli.ts +2 -0
  104. package/src/commands/deep-interview.ts +1 -0
  105. package/src/commands/harness.ts +862 -0
  106. package/src/commands/launch.ts +2 -2
  107. package/src/commands/state.ts +2 -1
  108. package/src/commands/team.ts +54 -39
  109. package/src/config/keybindings.ts +6 -0
  110. package/src/config/settings-schema.ts +13 -3
  111. package/src/config/settings.ts +5 -0
  112. package/src/dap/client.ts +17 -3
  113. package/src/debug/crash-diagnostics.ts +223 -0
  114. package/src/debug/runtime-gauges.ts +20 -0
  115. package/src/deep-interview/render-middleware.ts +372 -0
  116. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  117. package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
  118. package/src/defaults/gjc/skills/team/SKILL.md +47 -21
  119. package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
  120. package/src/eval/py/executor.ts +21 -1
  121. package/src/eval/py/kernel.ts +15 -0
  122. package/src/exec/bash-executor.ts +41 -0
  123. package/src/extensibility/custom-tools/types.ts +1 -0
  124. package/src/extensibility/extensions/types.ts +6 -0
  125. package/src/extensibility/shared-events.ts +1 -0
  126. package/src/gjc-runtime/cli-write-receipt.ts +31 -0
  127. package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
  128. package/src/gjc-runtime/goal-mode-request.ts +11 -3
  129. package/src/gjc-runtime/ralplan-runtime.ts +235 -43
  130. package/src/gjc-runtime/state-graph.ts +86 -0
  131. package/src/gjc-runtime/state-migrations.ts +179 -0
  132. package/src/gjc-runtime/state-renderer.ts +345 -0
  133. package/src/gjc-runtime/state-runtime.ts +1155 -46
  134. package/src/gjc-runtime/state-schema.ts +192 -0
  135. package/src/gjc-runtime/state-validation.ts +49 -0
  136. package/src/gjc-runtime/state-writer.ts +749 -0
  137. package/src/gjc-runtime/team-runtime.ts +1255 -189
  138. package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
  139. package/src/gjc-runtime/workflow-command-ref.ts +239 -0
  140. package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
  141. package/src/gjc-runtime/workflow-manifest.ts +427 -0
  142. package/src/harness-control-plane/classifier.ts +128 -0
  143. package/src/harness-control-plane/control-endpoint.ts +148 -0
  144. package/src/harness-control-plane/finalize.ts +222 -0
  145. package/src/harness-control-plane/frame-mapper.ts +286 -0
  146. package/src/harness-control-plane/operate.ts +225 -0
  147. package/src/harness-control-plane/owner.ts +600 -0
  148. package/src/harness-control-plane/preserve.ts +102 -0
  149. package/src/harness-control-plane/receipts.ts +216 -0
  150. package/src/harness-control-plane/rpc-adapter.ts +276 -0
  151. package/src/harness-control-plane/seams.ts +39 -0
  152. package/src/harness-control-plane/session-lease.ts +388 -0
  153. package/src/harness-control-plane/state-machine.ts +98 -0
  154. package/src/harness-control-plane/storage.ts +257 -0
  155. package/src/harness-control-plane/types.ts +214 -0
  156. package/src/hooks/skill-keywords.ts +4 -2
  157. package/src/hooks/skill-state.ts +197 -64
  158. package/src/internal-urls/agent-protocol.ts +68 -21
  159. package/src/internal-urls/artifact-protocol.ts +12 -17
  160. package/src/internal-urls/docs-index.generated.ts +3 -2
  161. package/src/internal-urls/registry-helpers.ts +19 -16
  162. package/src/internal-urls/types.ts +4 -0
  163. package/src/lsp/client.ts +18 -2
  164. package/src/main.ts +21 -5
  165. package/src/modes/bridge/auth.ts +41 -0
  166. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  167. package/src/modes/bridge/bridge-mode.ts +520 -0
  168. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  169. package/src/modes/bridge/event-stream.ts +70 -0
  170. package/src/modes/components/assistant-message.ts +5 -1
  171. package/src/modes/components/custom-editor.ts +101 -0
  172. package/src/modes/components/hook-selector.ts +133 -20
  173. package/src/modes/components/jobs-overlay-model.ts +109 -0
  174. package/src/modes/components/jobs-overlay.ts +172 -0
  175. package/src/modes/components/status-line/presets.ts +7 -5
  176. package/src/modes/components/status-line/segments.ts +25 -0
  177. package/src/modes/components/status-line/types.ts +2 -0
  178. package/src/modes/components/status-line.ts +9 -1
  179. package/src/modes/controllers/event-controller.ts +71 -6
  180. package/src/modes/controllers/extension-ui-controller.ts +43 -1
  181. package/src/modes/controllers/input-controller.ts +105 -9
  182. package/src/modes/controllers/selector-controller.ts +31 -1
  183. package/src/modes/index.ts +1 -0
  184. package/src/modes/interactive-mode.ts +28 -0
  185. package/src/modes/jobs-observer.ts +204 -0
  186. package/src/modes/rpc/host-tools.ts +1 -186
  187. package/src/modes/rpc/host-uris.ts +1 -235
  188. package/src/modes/rpc/rpc-client.ts +25 -10
  189. package/src/modes/rpc/rpc-mode.ts +12 -381
  190. package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
  191. package/src/modes/shared/agent-wire/command-validation.ts +131 -0
  192. package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
  193. package/src/modes/shared/agent-wire/handshake.ts +117 -0
  194. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  195. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  196. package/src/modes/shared/agent-wire/protocol.ts +96 -0
  197. package/src/modes/shared/agent-wire/responses.ts +17 -0
  198. package/src/modes/shared/agent-wire/scopes.ts +89 -0
  199. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  200. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  201. package/src/modes/types.ts +2 -0
  202. package/src/prompts/agents/executor.md +13 -0
  203. package/src/prompts/tools/subagent.md +39 -4
  204. package/src/prompts/tools/task-summary.md +3 -9
  205. package/src/prompts/tools/task.md +5 -1
  206. package/src/sdk.ts +8 -0
  207. package/src/session/agent-session.ts +445 -71
  208. package/src/session/session-manager.ts +13 -1
  209. package/src/skill-state/active-state.ts +58 -65
  210. package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
  211. package/src/skill-state/initial-phase.ts +2 -0
  212. package/src/skill-state/workflow-state-contract.ts +33 -4
  213. package/src/skill-state/workflow-state-version.ts +3 -0
  214. package/src/slash-commands/builtin-registry.ts +8 -0
  215. package/src/task/executor.ts +79 -13
  216. package/src/task/id.ts +33 -0
  217. package/src/task/index.ts +376 -74
  218. package/src/task/output-manager.ts +5 -4
  219. package/src/task/receipt.ts +297 -0
  220. package/src/task/render.ts +54 -134
  221. package/src/task/spawn-gate.ts +132 -0
  222. package/src/task/types.ts +104 -10
  223. package/src/tools/ask.ts +88 -27
  224. package/src/tools/ast-edit.ts +1 -0
  225. package/src/tools/ast-grep.ts +1 -0
  226. package/src/tools/bash.ts +1 -1
  227. package/src/tools/cron.ts +48 -0
  228. package/src/tools/find.ts +4 -1
  229. package/src/tools/index.ts +2 -0
  230. package/src/tools/path-utils.ts +3 -2
  231. package/src/tools/read.ts +1 -0
  232. package/src/tools/search.ts +1 -0
  233. package/src/tools/skill.ts +6 -1
  234. package/src/tools/subagent.ts +423 -79
@@ -50,12 +50,38 @@ Use `goal({"op":"get"})` snapshots inside Ultragoal for ledger reconciliation. T
50
50
 
51
51
  ## Create goals
52
52
 
53
- 1. Run one of:
53
+ 1. Decide on the brief. To produce **multiple** stories, separate them with a reserved `@goal:` delimiter line; the title follows on the same line and the objective is everything beneath it until the next delimiter:
54
+
55
+ ```text
56
+ Shared brief constraints / context go here (optional preamble).
57
+
58
+ @goal: Parse the intake CSVs
59
+ Ingest reviewer CSVs from the watch dir, validate headers, and reject
60
+ malformed rows with a per-row reason. Objectives can span multiple lines
61
+ and contain `code`, "quotes", or commands — no escaping needed.
62
+
63
+ @goal: Normalize records
64
+ Map raw rows onto the canonical schema and dedupe by record id.
65
+
66
+ @goal: Export the audit report
67
+ Emit an audit-ready report covering every accepted and rejected row.
68
+ ```
69
+
70
+ Delimiter contract:
71
+ - A `@goal` line is a story boundary **only** when it starts at column 0 (no leading whitespace) and the character right after `@goal` is `:`, whitespace (space or tab), or end-of-line. So `@goal: Title`, `@goal Title`, and a bare `@goal` line all open a story.
72
+ - `@goalish`, `@goals:`, `@goal-foo`, `@goal.foo`, `@goal/foo`, and any indented or mid-line `@goal` are ordinary objective text, not delimiters. To keep a literal `@goal` line inside an objective, indent it.
73
+ - A title-only block (no body) uses the title as its objective. An empty title borrows the first body line as the title. A block with **neither** title nor body is rejected — `create-goals` errors instead of writing a placeholder goal.
74
+ - **Preamble** (any text before the first `@goal` delimiter) is global context/constraints only; it is retained in the brief but is **not** turned into a goal. Every executable story needs its own `@goal` block.
75
+ - With **no** `@goal` delimiter anywhere, the whole brief becomes a single goal `G001` (unchanged legacy behavior).
76
+
77
+ Stories become `G001`, `G002`, … in order.
78
+
79
+ 2. Run one of:
54
80
  - `gjc ultragoal create-goals --brief "<brief>"`
55
81
  - `gjc ultragoal create-goals --brief-file <path>`
56
82
  - `cat <brief> | gjc ultragoal create-goals --from-stdin`
57
83
  - `gjc ultragoal create-goals --gjc-goal-mode per-story --brief "<brief>"` only when one GJC goal context per story is explicitly preferred
58
- 2. Inspect `.gjc/ultragoal/goals.json` and refine if needed.
84
+ 3. Inspect `.gjc/ultragoal/goals.json` and refine if needed.
59
85
 
60
86
  ## Complete goals
61
87
 
@@ -137,26 +163,29 @@ Workers do not own ultragoal goal state, do not create worker ultragoal ledgers,
137
163
 
138
164
  ## Mandatory completion cleanup and review gate
139
165
 
140
- An ultragoal story cannot be checkpointed `complete` until the active agent has run the quality gate:
166
+ An ultragoal story cannot be checkpointed `complete` until the active agent has run the quality gate. The gate is plan-first, contract-driven, and surface-based:
141
167
 
142
- 1. Run targeted verification for the story.
168
+ 1. Run targeted implementation verification for the story.
143
169
  2. Run a cleanup/refactor review pass on changed files only; if there are no relevant edits, the cleaner still runs and records a passed/no-op report.
144
170
  3. Rerun verification after the cleaner pass.
145
- 4. Run a final code review pass and fold it into the strict quality gate. Clean means `architectReview.architectureStatus`, `architectReview.productStatus`, and `architectReview.codeStatus` are all `"CLEAR"`, `architectReview.recommendation` is `"APPROVE"`, executor QA statuses are `"passed"`, iteration is `"passed"` with `fullRerun: true`, every evidence field is non-empty, and every blockers array is empty. `COMMENT`, `WATCH`, `REQUEST CHANGES`, `BLOCK`, missing evidence, or non-empty blockers are non-clean.
146
- 5. If review is non-clean, do **not** call `goal({"op":"complete"})`. Record durable blocker work instead:
147
-
148
- 1. Run targeted implementation verification for the story.
149
- 2. Delegate an `architect` review covering all three lanes:
171
+ 4. Delegate an `architect` review covering all three lanes:
150
172
  - architecture-side: system boundaries, layering, data/control flow, operational risks.
151
173
  - product-side: user-visible behavior, acceptance criteria, edge cases, regressions.
152
174
  - code-side: maintainability, tests, integration points, and unsafe shortcuts.
153
- 3. Delegate an `executor` QA/red-team lane to build and run the e2e/read-teaming QA suite appropriate for the story. This lane must try to break the change, not just confirm the happy path.
154
- 4. If any lane finds an issue, do **not** checkpoint `complete` and do **not** call `goal({"op":"complete"})`. Record durable blocker work instead:
175
+ 5. Delegate an `executor` QA/red-team lane to build and run the e2e/read-teaming QA suite appropriate for the story. This lane must try to break the change, not just confirm the happy path. It must start from the approved plan/spec/acceptance criteria, then user-facing contracts, and only then implementation code as supporting evidence. Plan/code mismatches are blockers, not items to paper over with implementation intent.
176
+ 6. The executor QA/red-team lane must prove evidence by the real surface under test:
177
+ - GUI/web surfaces require browser automation plus a screenshot or image verdict.
178
+ - CLI surfaces require logs or terminal transcripts from real invocation.
179
+ - API/package surfaces require external consumer or black-box tests through the public interface.
180
+ - Algorithm/math surfaces require boundary, property, adversarial, and failure-mode cases.
181
+ 7. The executor QA/red-team lane must report a matrix using `executorQa.contractCoverage`, `executorQa.surfaceEvidence`, `executorQa.adversarialCases`, and `executorQa.artifactRefs`. Not-applicable rows are allowed only in `contractCoverage` and `surfaceEvidence`; each `status: "not_applicable"` row requires `contractRef` plus `reason`. `adversarialCases` rows cannot be not-applicable.
182
+ 8. Run a final code review pass and fold it into the strict quality gate. Clean means `architectReview.architectureStatus`, `architectReview.productStatus`, and `architectReview.codeStatus` are all `"CLEAR"`, `architectReview.recommendation` is `"APPROVE"`, executor QA statuses are `"passed"`, iteration is `"passed"` with `fullRerun: true`, every evidence field is non-empty, every required matrix row is present, and every blockers array is empty. `COMMENT`, `WATCH`, `REQUEST CHANGES`, `BLOCK`, missing evidence, missing or shallow matrix rows, plan/code mismatches, or non-empty blockers are non-clean.
183
+ 9. If any lane finds an issue, do **not** checkpoint `complete` and do **not** call `goal({"op":"complete"})`. Record durable blocker work instead:
155
184
  ```sh
156
185
  gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve verification blockers" --objective "<blocker-resolution objective>" --evidence "<architect/executor findings>" --gjc-goal-json <active-goal-get-json-or-path>
157
186
  ```
158
- 5. Complete or steer through the blocker story, then rerun the full blocking verification loop. Repeat until all verifier lanes are clean.
159
- 6. Only after the loop is clean, checkpoint the story as complete with a structured quality gate and a fresh active `goal({"op":"get"})` snapshot. The checkpoint creates a receipt; `goals.json.status` alone is not proof. In aggregate mode, the final aggregate receipt must exist before `goal({"op":"complete"})` is allowed.
187
+ 10. Complete or steer through the blocker story, then rerun the full blocking verification loop. Repeat until all verifier lanes are clean.
188
+ 11. Only after the loop is clean, checkpoint the story as complete with a structured quality gate and a fresh active `goal({"op":"get"})` snapshot. The checkpoint creates a receipt; `goals.json.status` alone is not proof. In aggregate mode, the final aggregate receipt must exist before `goal({"op":"complete"})` is allowed.
160
189
 
161
190
  The native `checkpoint --status complete` command rejects missing or shallow gates. `--quality-gate-json` must include:
162
191
 
@@ -178,6 +207,70 @@ The native `checkpoint --status complete` command rejects missing or shallow gat
178
207
  "evidence": "executor-built e2e and red-team QA commands/results",
179
208
  "e2eCommands": ["bun test:e2e"],
180
209
  "redTeamCommands": ["bun test:red-team"],
210
+ "artifactRefs": [
211
+ {
212
+ "id": "browser-run",
213
+ "kind": "browser-automation",
214
+ "path": "artifacts/browser-run.json",
215
+ "description": "browser automation transcript invoking the approved user-facing flow"
216
+ },
217
+ {
218
+ "id": "gui-screenshot",
219
+ "kind": "screenshot",
220
+ "path": "artifacts/gui-screenshot.png",
221
+ "description": "screenshot or image-verdict evidence for the GUI/web result"
222
+ },
223
+ {
224
+ "id": "adversarial-report",
225
+ "kind": "failure-mode-test",
226
+ "path": "artifacts/adversarial-report.txt",
227
+ "description": "boundary, property, adversarial, or failure-mode result"
228
+ }
229
+ ],
230
+ "contractCoverage": [
231
+ {
232
+ "id": "contract-goal",
233
+ "contractRef": "approved plan/spec/acceptance criterion or user-facing contract id",
234
+ "obligation": "required behavior from the approved contract",
235
+ "status": "covered",
236
+ "surfaceEvidenceRefs": ["surface-gui"],
237
+ "adversarialCaseRefs": ["case-invalid-input"]
238
+ },
239
+ {
240
+ "id": "contract-out-of-scope",
241
+ "contractRef": "contract intentionally outside this story",
242
+ "obligation": "explicitly omitted approved-contract surface",
243
+ "status": "not_applicable",
244
+ "reason": "why this contract does not apply to the current story"
245
+ }
246
+ ],
247
+ "surfaceEvidence": [
248
+ {
249
+ "id": "surface-gui",
250
+ "contractRef": "user-facing surface or public interface under test",
251
+ "surface": "gui|web|cli|api|package|algorithm|math",
252
+ "invocation": "real browser action, CLI command, API/package consumer call, or algorithm/property check",
253
+ "verdict": "passed",
254
+ "artifactRefs": ["browser-run", "gui-screenshot"]
255
+ },
256
+ {
257
+ "id": "surface-out-of-scope",
258
+ "contractRef": "surface intentionally outside this story",
259
+ "surface": "gui|web|cli|api|package|algorithm|math",
260
+ "status": "not_applicable",
261
+ "reason": "why this surface does not apply to the current story"
262
+ }
263
+ ],
264
+ "adversarialCases": [
265
+ {
266
+ "id": "case-invalid-input",
267
+ "contractRef": "approved plan/spec/acceptance criterion or user-facing contract id",
268
+ "scenario": "boundary/property/adversarial/failure-mode input or user action",
269
+ "expectedBehavior": "contract-required rejection, handling, or invariant preservation",
270
+ "verdict": "passed",
271
+ "artifactRefs": ["adversarial-report"]
272
+ }
273
+ ],
181
274
  "blockers": []
182
275
  },
183
276
  "iteration": {
@@ -1,5 +1,6 @@
1
1
  import { getProjectDir, logger } from "@gajae-code/utils";
2
2
  import { Settings } from "../../config/settings";
3
+ import { formatCrashDiagnosticNotice, writeCrashReport } from "../../debug/crash-diagnostics";
3
4
  import { OutputSink } from "../../session/streaming-output";
4
5
  import type { ToolSession } from "../../tools";
5
6
  import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../../tools/output-meta";
@@ -62,6 +63,8 @@ export interface PythonExecutorOptions {
62
63
 
63
64
  export interface PythonKernelExecutor {
64
65
  execute: (code: string, options?: KernelExecuteOptions) => Promise<KernelExecuteResult>;
66
+ getExitCode?: () => number | null;
67
+ peekStderr?: () => string;
65
68
  }
66
69
 
67
70
  export interface PythonResult {
@@ -446,12 +449,29 @@ async function executeWithKernel(
446
449
  const annotation = result.timedOut
447
450
  ? formatKernelTimeoutAnnotation(executionTimeoutMs, result.kernelKilled ?? false)
448
451
  : undefined;
452
+ let crashNotice: string | null = null;
453
+ if (result.kernelKilled) {
454
+ crashNotice = formatCrashDiagnosticNotice(
455
+ await writeCrashReport(
456
+ {
457
+ kind: "python",
458
+ exitCode: kernel.getExitCode?.(),
459
+ cancelled: false,
460
+ timedOut: result.timedOut,
461
+ stderr: kernel.peekStderr?.(),
462
+ protocol: "eval.py.kernel",
463
+ },
464
+ { cwd: options?.cwd },
465
+ ),
466
+ );
467
+ }
468
+ const notice = [annotation, crashNotice].filter(text => text).join("; ") || undefined;
449
469
  return {
450
470
  exitCode: undefined,
451
471
  cancelled: true,
452
472
  displayOutputs,
453
473
  stdinRequested: result.stdinRequested,
454
- ...(await sink.dump(annotation)),
474
+ ...(await sink.dump(notice)),
455
475
  };
456
476
  }
457
477
 
@@ -183,6 +183,8 @@ export class PythonKernel {
183
183
  #disposed = false;
184
184
  #shutdownConfirmed = false;
185
185
  #exitedPromise: Promise<number> | null = null;
186
+ #exitCode: number | null = null;
187
+ #stderrTail = "";
186
188
  #pending = new Map<string, PendingExecution>();
187
189
  #readBuffer = "";
188
190
 
@@ -230,6 +232,7 @@ export class PythonKernel {
230
232
  kernel.#stdin = proc.stdin;
231
233
  kernel.#exitedPromise = proc.exited;
232
234
  void kernel.#exitedPromise.then(code => {
235
+ kernel.#exitCode = code;
233
236
  kernel.#alive = false;
234
237
  kernel.#abortPendingExecutions(`Python kernel exited with code ${code}`, { kernelKilled: true });
235
238
  });
@@ -255,6 +258,14 @@ export class PythonKernel {
255
258
  return this.#alive && !this.#disposed;
256
259
  }
257
260
 
261
+ getExitCode(): number | null {
262
+ return this.#exitCode;
263
+ }
264
+
265
+ peekStderr(): string {
266
+ return this.#stderrTail;
267
+ }
268
+
258
269
  async execute(code: string, options?: KernelExecuteOptions): Promise<KernelExecuteResult> {
259
270
  if (!this.isAlive()) {
260
271
  throw new Error("Python kernel is not running");
@@ -493,6 +504,10 @@ export class PythonKernel {
493
504
  const { done, value } = await reader.read();
494
505
  if (done) break;
495
506
  const text = decoder.decode(value);
507
+ this.#stderrTail += text;
508
+ if (this.#stderrTail.length > 4096) {
509
+ this.#stderrTail = this.#stderrTail.slice(-4096);
510
+ }
496
511
  if (text.trim()) {
497
512
  logger.warn("Python runner stderr", { text });
498
513
  }
@@ -5,7 +5,9 @@
5
5
  */
6
6
  import * as fs from "node:fs/promises";
7
7
  import { executeShell, type MinimizerOptions, Shell } from "@gajae-code/natives";
8
+ import { postmortem } from "@gajae-code/utils";
8
9
  import { Settings, type ShellMinimizerSettings } from "../config/settings";
10
+ import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
9
11
  import { OutputSink } from "../session/streaming-output";
10
12
  import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
11
13
  import { getOrCreateSnapshot } from "../utils/shell-snapshot";
@@ -58,6 +60,30 @@ export interface BashResult {
58
60
  const shellSessions = new Map<string, Shell>();
59
61
  const brokenShellSessions = new Set<string>();
60
62
 
63
+ /** Number of persistent shell sessions currently retained (owner gauge). */
64
+ export function getShellSessionCount(): number {
65
+ return shellSessions.size;
66
+ }
67
+
68
+ /**
69
+ * Dispose all persistent shell sessions: abort in-flight work and drop the
70
+ * strong references so the native shells can be finalized. Healthy persistent
71
+ * sessions are otherwise retained for the whole process lifetime (MEM-7). This
72
+ * is registered as a postmortem cleanup so shutdown/signals release native
73
+ * shell resources, and is also callable directly (e.g. on owner teardown).
74
+ */
75
+ export async function disposeAllShellSessions(): Promise<void> {
76
+ // Snapshot and drop strong references up front so concurrent callers cannot
77
+ // reuse a session that is being torn down, then await every native abort so
78
+ // shutdown/signal cleanup does not return before resources are released.
79
+ const sessions = [...shellSessions.values()];
80
+ shellSessions.clear();
81
+ brokenShellSessions.clear();
82
+ await Promise.allSettled(sessions.map(session => session.abort()));
83
+ }
84
+
85
+ postmortem.register("bash-executor:shell-sessions", () => disposeAllShellSessions());
86
+
61
87
  async function resolveShellCwd(cwd: string | undefined): Promise<string | undefined> {
62
88
  if (!cwd) return undefined;
63
89
 
@@ -280,6 +306,21 @@ export async function executeBash(command: string, options?: BashExecutorOptions
280
306
  }
281
307
  }
282
308
 
309
+ const crashReport = await writeCrashReport(
310
+ {
311
+ kind: "bash",
312
+ command: [shell, "-lc", finalCommand],
313
+ exitCode: winner.result.exitCode,
314
+ stderr: undefined,
315
+ },
316
+ { cwd: commandCwd },
317
+ );
318
+ const crashNotice = formatCrashDiagnosticNotice(crashReport);
319
+ if (crashNotice) {
320
+ const separator = "\n";
321
+ sink.push(`${separator}${crashNotice}\n`);
322
+ }
323
+
283
324
  // Normal completion
284
325
  return {
285
326
  exitCode: winner.result.exitCode,
@@ -109,6 +109,7 @@ export type CustomToolSessionEvent =
109
109
  maxAttempts: number;
110
110
  delayMs: number;
111
111
  errorMessage: string;
112
+ unbounded?: boolean;
112
113
  }
113
114
  | {
114
115
  reason: "auto_retry_end";
@@ -116,6 +116,12 @@ export interface ExtensionUIDialogOptions {
116
116
  * hint; non-TUI bridges (RPC, ACP) drop it and do not serialize it.
117
117
  */
118
118
  wrapFocused?: boolean;
119
+ /**
120
+ * For interactive TUI select dialogs, cap the title/prompt area to this
121
+ * many rows and let PageUp/PageDown scroll that prompt locally. This is a
122
+ * select-only rendering hint; non-TUI bridges drop it and do not serialize it.
123
+ */
124
+ scrollTitleRows?: number;
119
125
  }
120
126
 
121
127
  /** Raw terminal input listener for extensions. */
@@ -226,6 +226,7 @@ export interface AutoRetryStartEvent {
226
226
  maxAttempts: number;
227
227
  delayMs: number;
228
228
  errorMessage: string;
229
+ unbounded?: boolean;
229
230
  }
230
231
 
231
232
  /** Fired when auto-retry ends */
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CLI write/mutation receipt shaping (Workstream B, v4).
3
+ *
4
+ * `CliWriteReceipt` is the compact **stdout presentation** returned by a GJC
5
+ * mutation command. It carries only routing/audit fields a caller needs; it
6
+ * NEVER echoes the persisted body (full `state` envelope, ultragoal `plan`,
7
+ * team task body, ralplan `task`, etc.) — echoing those back is a redundant
8
+ * token leak because the caller already has the content it just wrote.
9
+ *
10
+ * This is intentionally a *separate* concept from the persisted
11
+ * `WorkflowStateReceipt` (the on-disk envelope `receipt` integrity field).
12
+ * Do NOT use `CliWriteReceipt` as a persistence schema or validate persisted
13
+ * envelopes against it.
14
+ */
15
+ export interface CliWriteReceipt {
16
+ ok: boolean;
17
+ [field: string]: unknown;
18
+ }
19
+
20
+ /**
21
+ * Serialize a write/mutation receipt to compact stdout JSON.
22
+ * `undefined` fields are dropped so optional routing fields stay absent
23
+ * rather than serialized as `null`.
24
+ */
25
+ export function renderCliWriteReceipt(receipt: Record<string, unknown>): string {
26
+ const out: Record<string, unknown> = {};
27
+ for (const key of Object.keys(receipt)) {
28
+ if (receipt[key] !== undefined) out[key] = receipt[key];
29
+ }
30
+ return `${JSON.stringify(out)}\n`;
31
+ }
@@ -4,8 +4,10 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { syncSkillActiveState } from "../skill-state/active-state";
6
6
  import { buildDeepInterviewHudSummary } from "../skill-state/workflow-hud";
7
+ import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
7
8
  import { runNativeRalplanCommand } from "./ralplan-runtime";
8
9
  import { runNativeStateCommand } from "./state-runtime";
10
+ import { appendJsonl, readExistingStateForMutation, writeArtifact, writeWorkflowEnvelopeAtomic } from "./state-writer";
9
11
 
10
12
  /**
11
13
  * Native implementation of `gjc deep-interview`.
@@ -94,23 +96,6 @@ function deepInterviewStatePath(cwd: string, sessionId: string | undefined): str
94
96
  return path.join(stateDirFor(cwd, sessionId), "deep-interview-state.json");
95
97
  }
96
98
 
97
- async function readJsonObject(filePath: string): Promise<Record<string, unknown>> {
98
- try {
99
- const parsed = JSON.parse(await fs.readFile(filePath, "utf-8"));
100
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed as Record<string, unknown>;
101
- } catch {
102
- // Missing/corrupt state should not prevent the sanctioned persistence CLI from writing a receipt.
103
- }
104
- return {};
105
- }
106
-
107
- async function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {
108
- await fs.mkdir(path.dirname(filePath), { recursive: true });
109
- const tmp = `${filePath}.tmp-${randomBytes(6).toString("hex")}`;
110
- await fs.writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`);
111
- await fs.rename(tmp, filePath);
112
- }
113
-
114
99
  async function resolveSpecContent(rawSpec: string, cwd: string): Promise<string> {
115
100
  const candidate = path.isAbsolute(rawSpec) ? rawSpec : path.resolve(cwd, rawSpec);
116
101
  try {
@@ -150,6 +135,7 @@ export interface ResolvedDeepInterviewSpecWriteArgs {
150
135
  json: boolean;
151
136
  deliberate: boolean;
152
137
  handoff?: "ralplan";
138
+ force: boolean;
153
139
  }
154
140
 
155
141
  export interface PersistedDeepInterviewSpec {
@@ -167,6 +153,8 @@ interface DeepInterviewSpecWriteSummary {
167
153
  slug: string;
168
154
  path: string;
169
155
  sha256: string;
156
+ spec_path: string;
157
+ sha: string;
170
158
  created_at: string;
171
159
  state_path: string;
172
160
  handoff?: {
@@ -202,9 +190,34 @@ async function readSettingsAmbiguityThreshold(
202
190
  return { threshold: candidate, source: settingsPath };
203
191
  }
204
192
 
193
+ function modernSettingsPath(): string {
194
+ const configDir = process.env.GJC_CODING_AGENT_DIR?.trim() || process.env.PI_CODING_AGENT_DIR?.trim();
195
+ if (configDir) return path.join(configDir, "config.yml");
196
+ const configRoot = process.env.GJC_CONFIG_DIR?.trim() || process.env.PI_CONFIG_DIR?.trim();
197
+ if (configRoot) return path.join(configRoot, "agent", "config.yml");
198
+ return path.join(os.homedir(), ".gjc", "agent", "config.yml");
199
+ }
200
+
201
+ async function readModernSettingsAmbiguityThreshold(): Promise<{ threshold: number; source: string } | undefined> {
202
+ const modernConfigPath = modernSettingsPath();
203
+ let parsed: unknown;
204
+ try {
205
+ parsed = (await import("bun")).YAML.parse(await fs.readFile(modernConfigPath, "utf-8"));
206
+ } catch {
207
+ return undefined;
208
+ }
209
+ const candidate = (parsed as { gjc?: { deepInterview?: { ambiguityThreshold?: unknown } } })?.gjc?.deepInterview
210
+ ?.ambiguityThreshold;
211
+ if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0 || candidate > 1)
212
+ return undefined;
213
+ return { threshold: candidate, source: modernConfigPath };
214
+ }
215
+
205
216
  async function resolveConfiguredAmbiguityThreshold(
206
217
  cwd: string,
207
218
  ): Promise<{ threshold: number; source: string } | undefined> {
219
+ const modernValue = await readModernSettingsAmbiguityThreshold();
220
+ if (modernValue) return modernValue;
208
221
  const projectSettings = path.join(cwd, ".gjc", "settings.json");
209
222
  const projectValue = await readSettingsAmbiguityThreshold(projectSettings);
210
223
  if (projectValue) return projectValue;
@@ -277,6 +290,7 @@ async function resolveSpecWriteArgs(args: readonly string[], cwd: string): Promi
277
290
  "--handoff",
278
291
  "--deliberate",
279
292
  "--json",
293
+ "--force",
280
294
  ]);
281
295
  let skipNext = false;
282
296
  for (const arg of args) {
@@ -300,6 +314,7 @@ async function resolveSpecWriteArgs(args: readonly string[], cwd: string): Promi
300
314
  sessionId,
301
315
  json: hasFlag(args, "--json"),
302
316
  deliberate: hasFlag(args, "--deliberate"),
317
+ force: hasFlag(args, "--force"),
303
318
  handoff: rawHandoff as "ralplan" | undefined,
304
319
  };
305
320
  }
@@ -373,27 +388,37 @@ export async function persistDeepInterviewSpec(
373
388
  cwd: string,
374
389
  resolved: ResolvedDeepInterviewSpecWriteArgs,
375
390
  ): Promise<PersistedDeepInterviewSpec> {
376
- const specsDir = path.join(cwd, ".gjc", "specs");
377
- await fs.mkdir(specsDir, { recursive: true });
378
- const specPath = path.join(specsDir, `deep-interview-${resolved.slug}.md`);
391
+ const statePath = deepInterviewStatePath(cwd, resolved.sessionId);
392
+ const existingRead = await readExistingStateForMutation(statePath);
393
+ if (existingRead.kind === "corrupt" && !resolved.force) {
394
+ throw new DeepInterviewCommandError(
395
+ 2,
396
+ `existing deep-interview state is corrupt or tampered (${existingRead.error}); use --force to overwrite ${statePath}`,
397
+ );
398
+ }
399
+ const existing = existingRead.kind === "valid" ? existingRead.value : {};
400
+
401
+ const specPath = path.join(cwd, ".gjc", "specs", `deep-interview-${resolved.slug}.md`);
379
402
  const content = resolved.spec.endsWith("\n") ? resolved.spec : `${resolved.spec}\n`;
380
- await fs.writeFile(specPath, content);
403
+ await writeArtifact(specPath, content, {
404
+ cwd,
405
+ audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
406
+ });
381
407
 
382
408
  const sha256 = createHash("sha256").update(content).digest("hex");
383
409
  const createdAt = new Date().toISOString();
384
- await fs.appendFile(
385
- path.join(specsDir, "deep-interview-index.jsonl"),
386
- `${JSON.stringify({ slug: resolved.slug, stage: resolved.stage, path: specPath, created_at: createdAt, sha256 })}\n`,
410
+ await appendJsonl(
411
+ path.join(cwd, ".gjc", "specs", "deep-interview-index.jsonl"),
412
+ { slug: resolved.slug, stage: resolved.stage, path: specPath, created_at: createdAt, sha256 },
413
+ { cwd, audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "deep-interview" } },
387
414
  );
388
415
 
389
- const statePath = deepInterviewStatePath(cwd, resolved.sessionId);
390
- const existing = await readJsonObject(statePath);
391
416
  const payload: Record<string, unknown> = {
392
417
  ...existing,
393
418
  active: true,
394
419
  current_phase: "handoff",
395
420
  skill: "deep-interview",
396
- version: typeof existing.version === "number" ? existing.version : 1,
421
+ version: WORKFLOW_STATE_VERSION,
397
422
  spec_slug: resolved.slug,
398
423
  spec_path: specPath,
399
424
  spec_sha256: sha256,
@@ -402,7 +427,24 @@ export async function persistDeepInterviewSpec(
402
427
  updated_at: createdAt,
403
428
  };
404
429
  if (resolved.sessionId) payload.session_id = resolved.sessionId;
405
- await writeJsonAtomic(statePath, payload);
430
+ await writeWorkflowEnvelopeAtomic(statePath, payload, {
431
+ cwd,
432
+ receipt: {
433
+ cwd,
434
+ skill: "deep-interview",
435
+ owner: "gjc-runtime",
436
+ command: "gjc deep-interview persist-spec-state",
437
+ sessionId: resolved.sessionId,
438
+ nowIso: createdAt,
439
+ },
440
+ audit: {
441
+ category: "state",
442
+ verb: "write",
443
+ owner: "gjc-runtime",
444
+ skill: "deep-interview",
445
+ forced: resolved.force,
446
+ },
447
+ });
406
448
  await syncDeepInterviewHud({
407
449
  cwd,
408
450
  sessionId: resolved.sessionId,
@@ -421,16 +463,13 @@ export async function persistDeepInterviewSpec(
421
463
  }
422
464
 
423
465
  async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepInterviewArgs): Promise<string> {
424
- const stateDir = resolved.sessionId
425
- ? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
426
- : path.join(cwd, ".gjc", "state");
427
- await fs.mkdir(stateDir, { recursive: true });
428
- const statePath = path.join(stateDir, "deep-interview-state.json");
466
+ const statePath = deepInterviewStatePath(cwd, resolved.sessionId);
429
467
  const now = new Date().toISOString();
430
468
  const payload: Record<string, unknown> = {
431
469
  active: true,
432
470
  current_phase: "interviewing",
433
471
  skill: "deep-interview",
472
+ version: WORKFLOW_STATE_VERSION,
434
473
  resolution: resolved.resolution,
435
474
  threshold: resolved.threshold,
436
475
  threshold_source: resolved.thresholdSource,
@@ -448,7 +487,18 @@ async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepIntervi
448
487
  (payload.state as Record<string, unknown>).language = resolved.language;
449
488
  }
450
489
  if (resolved.sessionId) payload.session_id = resolved.sessionId;
451
- await fs.writeFile(statePath, `${JSON.stringify(payload, null, 2)}\n`);
490
+ await writeWorkflowEnvelopeAtomic(statePath, payload, {
491
+ cwd,
492
+ receipt: {
493
+ cwd,
494
+ skill: "deep-interview",
495
+ owner: "gjc-runtime",
496
+ command: "gjc deep-interview seed",
497
+ sessionId: resolved.sessionId,
498
+ nowIso: now,
499
+ },
500
+ audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
501
+ });
452
502
  return statePath;
453
503
  }
454
504
 
@@ -493,6 +543,8 @@ async function handleSpecWrite(args: readonly string[], cwd: string): Promise<De
493
543
  slug: persisted.slug,
494
544
  path: persisted.path,
495
545
  sha256: persisted.sha256,
546
+ spec_path: persisted.path,
547
+ sha: persisted.sha256,
496
548
  created_at: persisted.createdAt,
497
549
  state_path: persisted.statePath,
498
550
  };
@@ -530,10 +582,14 @@ async function handleSpecWrite(args: readonly string[], cwd: string): Promise<De
530
582
  }
531
583
 
532
584
  const stdout = resolved.json
533
- ? `${JSON.stringify(summary, null, 2)}\n`
585
+ ? `${JSON.stringify(summary)}\n`
534
586
  : [
535
- `Persisted deep-interview ${persisted.stage} spec at ${persisted.path}.`,
536
- shouldHandoff ? "Handed off deep-interview to ralplan (deliberate)." : undefined,
587
+ `deep-interview spec_path=${persisted.path}`,
588
+ `sha=${persisted.sha256}`,
589
+ `state_path=${persisted.statePath}`,
590
+ shouldHandoff
591
+ ? `handoff=ralplan run_id=${summary.handoff?.run_id ?? ""} state_path=${summary.handoff?.state_path ?? ""}`
592
+ : undefined,
537
593
  "",
538
594
  ]
539
595
  .filter((line): line is string => Boolean(line))
@@ -572,14 +628,14 @@ export async function runNativeDeepInterviewCommand(
572
628
  idea: resolved.idea,
573
629
  language: resolved.language,
574
630
  state_path: statePath,
575
- handoff: "Run `/skill:deep-interview` inside the GJC agent to drive the Socratic interview loop.",
631
+ handoff: "/skill:deep-interview",
576
632
  };
577
633
  const stdout = resolved.json
578
- ? `${JSON.stringify(summary, null, 2)}\n`
634
+ ? `${JSON.stringify(summary)}\n`
579
635
  : [
580
- `Seeded deep-interview ${resolved.resolution} run at ${statePath}.`,
581
- `Threshold: ${(resolved.threshold * 100).toFixed(0)}% (source: ${resolved.thresholdSource}).`,
582
- "Run `/skill:deep-interview` inside the GJC agent to execute the interview.",
636
+ `deep-interview seed state_path=${statePath}`,
637
+ `resolution=${resolved.resolution} threshold=${resolved.threshold} threshold_source=${resolved.thresholdSource}`,
638
+ "handoff=/skill:deep-interview",
583
639
  "",
584
640
  ].join("\n");
585
641
  return { status: 0, stdout };