@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.
- package/CHANGELOG.md +28 -0
- package/dist/types/async/job-manager.d.ts +91 -2
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +10 -4
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +6 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
- package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
- package/dist/types/harness-control-plane/classifier.d.ts +13 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
- package/dist/types/harness-control-plane/finalize.d.ts +47 -0
- package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
- package/dist/types/harness-control-plane/operate.d.ts +35 -0
- package/dist/types/harness-control-plane/owner.d.ts +46 -0
- package/dist/types/harness-control-plane/preserve.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +88 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
- package/dist/types/harness-control-plane/seams.d.ts +21 -0
- package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
- package/dist/types/harness-control-plane/storage.d.ts +53 -0
- package/dist/types/harness-control-plane/types.d.ts +162 -0
- package/dist/types/hooks/skill-keywords.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +23 -29
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +19 -1
- package/dist/types/skill-state/active-state.d.ts +2 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +198 -14
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +26 -1
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +334 -6
- package/src/cli/args.ts +9 -2
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/config-cli.ts +10 -2
- package/src/cli.ts +2 -0
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +862 -0
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +54 -39
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +13 -3
- package/src/config/settings.ts +5 -0
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +372 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/extensibility/custom-tools/types.ts +1 -0
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +235 -43
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +179 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +1155 -46
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +749 -0
- package/src/gjc-runtime/team-runtime.ts +1255 -189
- package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
- package/src/gjc-runtime/workflow-manifest.ts +427 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +148 -0
- package/src/harness-control-plane/finalize.ts +222 -0
- package/src/harness-control-plane/frame-mapper.ts +286 -0
- package/src/harness-control-plane/operate.ts +225 -0
- package/src/harness-control-plane/owner.ts +600 -0
- package/src/harness-control-plane/preserve.ts +102 -0
- package/src/harness-control-plane/receipts.ts +216 -0
- package/src/harness-control-plane/rpc-adapter.ts +276 -0
- package/src/harness-control-plane/seams.ts +39 -0
- package/src/harness-control-plane/session-lease.ts +388 -0
- package/src/harness-control-plane/state-machine.ts +98 -0
- package/src/harness-control-plane/storage.ts +257 -0
- package/src/harness-control-plane/types.ts +214 -0
- package/src/hooks/skill-keywords.ts +4 -2
- package/src/hooks/skill-state.ts +197 -64
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/assistant-message.ts +5 -1
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +133 -20
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +43 -1
- package/src/modes/controllers/input-controller.ts +105 -9
- package/src/modes/controllers/selector-controller.ts +31 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +28 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +2 -0
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/tools/subagent.md +39 -4
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +8 -0
- package/src/session/agent-session.ts +445 -71
- package/src/session/session-manager.ts +13 -1
- package/src/skill-state/active-state.ts +58 -65
- package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +33 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +79 -13
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +376 -74
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +54 -134
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +104 -10
- package/src/tools/ask.ts +88 -27
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- 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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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": {
|
package/src/eval/py/executor.ts
CHANGED
|
@@ -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(
|
|
474
|
+
...(await sink.dump(notice)),
|
|
455
475
|
};
|
|
456
476
|
}
|
|
457
477
|
|
package/src/eval/py/kernel.ts
CHANGED
|
@@ -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,
|
|
@@ -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. */
|
|
@@ -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
|
|
377
|
-
|
|
378
|
-
|
|
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
|
|
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
|
|
385
|
-
path.join(
|
|
386
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
585
|
+
? `${JSON.stringify(summary)}\n`
|
|
534
586
|
: [
|
|
535
|
-
`
|
|
536
|
-
|
|
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: "
|
|
631
|
+
handoff: "/skill:deep-interview",
|
|
576
632
|
};
|
|
577
633
|
const stdout = resolved.json
|
|
578
|
-
? `${JSON.stringify(summary
|
|
634
|
+
? `${JSON.stringify(summary)}\n`
|
|
579
635
|
: [
|
|
580
|
-
`
|
|
581
|
-
`
|
|
582
|
-
"
|
|
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 };
|