@gajae-code/coding-agent 0.2.5 → 0.3.0
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 +10 -0
- package/dist/types/async/job-manager.d.ts +84 -2
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/settings-schema.d.ts +6 -0
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/deep-interview/render-middleware.d.ts +5 -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/state-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +24 -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-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +137 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- 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 +30 -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 +2 -29
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +8 -0
- 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 +24 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/types.d.ts +55 -3
- package/dist/types/tools/subagent.d.ts +11 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +298 -6
- 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/harness.ts +592 -0
- package/src/commands/team.ts +36 -39
- package/src/config/settings-schema.ts +7 -0
- package/src/config/settings.ts +5 -0
- package/src/deep-interview/render-middleware.ts +366 -0
- package/src/defaults/gjc/skills/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +78 -11
- 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/deep-interview-runtime.ts +40 -21
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +25 -10
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +132 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +733 -21
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +718 -0
- package/src/gjc-runtime/team-runtime.ts +1083 -89
- package/src/gjc-runtime/ultragoal-runtime.ts +348 -19
- package/src/gjc-runtime/workflow-manifest.generated.json +1497 -0
- package/src/gjc-runtime/workflow-manifest.ts +425 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +137 -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 +553 -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 +97 -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 +24 -41
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/components/assistant-message.ts +5 -1
- package/src/modes/components/hook-selector.ts +72 -2
- package/src/modes/controllers/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +9 -1
- package/src/modes/controllers/selector-controller.ts +2 -1
- package/src/modes/interactive-mode.ts +1 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/tools/subagent.md +33 -3
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +231 -33
- 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 +91 -13
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +26 -0
- package/src/task/executor.ts +50 -8
- package/src/task/index.ts +120 -8
- package/src/task/render.ts +6 -3
- package/src/task/types.ts +56 -3
- package/src/tools/ask.ts +28 -7
- package/src/tools/subagent.ts +255 -64
|
@@ -56,6 +56,7 @@ requiring a separate linked execution loop up front. GJC team supports current-w
|
|
|
56
56
|
|
|
57
57
|
- **Canonical launch:** use plain `gjc team ...` / `$team ...` for the coordinated worker.
|
|
58
58
|
- **Verification ownership:** keep one lane focused on tests, regression coverage, and evidence before shutdown.
|
|
59
|
+
- **Typed lanes:** model delivery, verification, architecture, or specialist work as task `lane` metadata plus `required_role` / `allowed_roles`; claiming enforces owner, role, dependency, and lease order.
|
|
59
60
|
- **Escalation:** use a new explicit follow-up task only when later manual work still needs a persistent single-owner fix/verification loop.
|
|
60
61
|
- **Deprecation:** nested team execution commands have been removed. Use plain `gjc team ...` for coordinated execution.
|
|
61
62
|
|
|
@@ -135,6 +136,9 @@ When `$team` is used as a follow-up mode from ralplan, carry forward the approve
|
|
|
135
136
|
- `.gjc/state/team/<team>/manifest.v2.json`
|
|
136
137
|
- `.gjc/state/team/<team>/tasks/task-1.json`
|
|
137
138
|
- `.gjc/state/team/<team>/mailbox/worker-1.json`
|
|
139
|
+
- `.gjc/state/team/<team>/workers/<worker>/status.json`
|
|
140
|
+
- `.gjc/state/team/<team>/workers/<worker>/lifecycle.json`
|
|
141
|
+
- `.gjc/state/team/<team>/workers/<worker>/heartbeat.json`
|
|
138
142
|
4. Resolve the worker command from `GJC_TEAM_WORKER_COMMAND` or the active `gjc` entrypoint.
|
|
139
143
|
5. Split the current tmux window like GJC team: worker 1 is split horizontally to the right of the leader, workers 2..N are vertically stacked in the right column, then `select-layout main-vertical` and `main-pane-width` keep leader-left/worker-right at roughly 50/50.
|
|
140
144
|
6. Launch the worker with:
|
|
@@ -148,7 +152,7 @@ When `$team` is used as a follow-up mode from ralplan, carry forward the approve
|
|
|
148
152
|
- diverged worker history is cherry-picked into the leader
|
|
149
153
|
- idle/done/failed worker worktrees are cross-rebased onto the updated leader after integration; working workers are skipped
|
|
150
154
|
- conflicts are aborted, recorded, and reported to the leader mailbox without falsely advancing `last_integrated_head`
|
|
151
|
-
8. Store pane/target/integration evidence in config/manifest/snapshot: `tmux_session`, `tmux_session_name`, `tmux_target`, leader pane id, worker pane ids, and `integration_by_worker`.
|
|
155
|
+
8. Store pane/target/integration/lifecycle evidence in config/manifest/snapshot: `tmux_session`, `tmux_session_name`, `tmux_target`, leader pane id, worker pane ids, `worker_lifecycle_by_id`, and `integration_by_worker`.
|
|
152
156
|
9. Return control to the leader; follow-up uses `status`, `resume`, `shutdown`, and `gjc team api`.
|
|
153
157
|
|
|
154
158
|
Important:
|
|
@@ -163,14 +167,15 @@ Important:
|
|
|
163
167
|
|
|
164
168
|
Follow this exact lifecycle when running `$team`:
|
|
165
169
|
|
|
166
|
-
1. Start team and verify startup evidence (team line, tmux target, worker pane id, state dir).
|
|
170
|
+
1. Start team and verify startup evidence (team line, tmux target, worker pane id, state dir, `worker_lifecycle_by_id.<worker>.lifecycle_state=ready` after startup ACK).
|
|
167
171
|
2. Monitor task progress with runtime/state tools first (`gjc team status <team>`, `gjc team resume <team>`, task files).
|
|
168
|
-
3. Wait for terminal task state before shutdown:
|
|
172
|
+
3. Wait for terminal task state and integration settlement before shutdown:
|
|
169
173
|
- `pending=0`
|
|
170
174
|
- `in_progress=0`
|
|
171
175
|
- `failed=0` (or explicitly acknowledged failure path)
|
|
176
|
+
- no pending integration request/conflict (`status` / `resume` must not report `phase=awaiting_integration`)
|
|
172
177
|
4. Only then run `gjc team shutdown <team>`.
|
|
173
|
-
5. Verify shutdown evidence and preserved state (`phase=complete`, worker status `stopped`). If shutdown is forced before task completion, expect `phase=cancelled` or `phase=failed`, not `complete`.
|
|
178
|
+
5. Verify shutdown evidence and preserved state (`phase=complete`, worker runtime status `stopped`, lifecycle `stopped` with a matching graceful shutdown request id). If shutdown is forced before evidence-backed task completion, expect `phase=cancelled` or `phase=failed`; if tasks are complete but integration is still pending or conflicted, expect `phase=awaiting_integration`, not `complete`.
|
|
174
179
|
|
|
175
180
|
Do not run `shutdown` while the worker is actively writing updates unless user explicitly requested abort/cancel. Do not treat ad-hoc pane typing as primary control flow when runtime/state evidence is available.
|
|
176
181
|
|
|
@@ -181,24 +186,28 @@ While a team is running, keep checking live team state until terminal completion
|
|
|
181
186
|
Minimum acceptable loop:
|
|
182
187
|
|
|
183
188
|
```bash
|
|
184
|
-
sleep 30 && gjc team
|
|
189
|
+
sleep 30 && gjc team monitor <team-name>
|
|
185
190
|
```
|
|
191
|
+
The mutating monitor path also performs bounded liveness recovery: expired task claims, stale heartbeat claims, and missing recorded worker panes are requeued instead of leaving work permanently `in_progress`.
|
|
186
192
|
|
|
187
193
|
## Operational Commands
|
|
188
194
|
|
|
189
195
|
```bash
|
|
190
196
|
gjc team status <team-name>
|
|
197
|
+
gjc team monitor <team-name>
|
|
191
198
|
gjc team resume <team-name>
|
|
192
199
|
gjc team shutdown <team-name>
|
|
193
200
|
```
|
|
194
201
|
|
|
195
202
|
Semantics:
|
|
196
203
|
|
|
197
|
-
- `status`:
|
|
198
|
-
- `
|
|
204
|
+
- `status`: read-only snapshot path; it does not recover claims, replay notifications, integrate worker commits, or sync HUD state.
|
|
205
|
+
- `monitor`: mutating monitor path; reads team snapshot, recovers expired/stale worker claims, applies pending worker worktree integration, replays notifications, syncs HUD state, and returns task counts, worker state, tmux target/pane evidence, `worker_lifecycle_by_id`, and `integration_by_worker`.
|
|
206
|
+
- `resume`: mutating monitor path; performs the same liveness-recovery and integration-aware live snapshot for reconnect/inspection flows.
|
|
199
207
|
- `list`: pure read path; lists known teams without integrating worker commits.
|
|
200
|
-
- API/read-only snapshot operations are pure unless explicitly documented as a monitor
|
|
201
|
-
- `
|
|
208
|
+
- API/read-only snapshot operations are pure unless explicitly documented as a monitor path.
|
|
209
|
+
- `claim-task`: mutating task path; before granting a new claim, it recovers expired claims and rejects claims from workers already classified as not live.
|
|
210
|
+
- `shutdown`: writes per-worker graceful `shutdown-request.json`, moves lifecycle through `draining` to `stopped`, kills the recorded worker pane when it still belongs to the stored tmux target, removes clean created worktrees, marks worker runtime status stopped, and sets phase from task, lifecycle, and integration state: `complete` only when all tasks have verified `completion_evidence`, every worker has matching graceful shutdown lifecycle evidence, and no integration request/conflict is pending; `awaiting_integration` when tasks and lifecycle are complete but leader integration still requires action; `failed` when tasks failed/blocked or completed tasks lack valid evidence; and `cancelled` when work remains pending or in progress. It preserves `.gjc/state/team/<team>` as evidence.
|
|
202
211
|
|
|
203
212
|
## Data Plane and Control Plane
|
|
204
213
|
|
|
@@ -214,15 +223,20 @@ Semantics:
|
|
|
214
223
|
- `.gjc/state/team/<team>/manifest.v2.json`
|
|
215
224
|
- `.gjc/state/team/<team>/phase.json`
|
|
216
225
|
- `.gjc/state/team/<team>/events.jsonl`
|
|
226
|
+
- `.gjc/state/team/<team>/trace.jsonl`
|
|
227
|
+
- `.gjc/state/team/<team>/trace-errors.jsonl`
|
|
217
228
|
- `.gjc/state/team/<team>/telemetry.jsonl`
|
|
218
229
|
- `.gjc/state/team/<team>/monitor-snapshot.json`
|
|
219
230
|
- `.gjc/state/team/<team>/integration-report.md`
|
|
220
|
-
- `.gjc/state/team/<team>/tasks/task-1.json`
|
|
221
|
-
- `.gjc/state/team/<team>/evidence/tasks/task-1.json`
|
|
231
|
+
- `.gjc/state/team/<team>/tasks/task-1.json` (includes structured `completion_evidence` after completed transitions)
|
|
222
232
|
- `.gjc/state/team/<team>/mailbox/worker-1/<message-id>.json`
|
|
223
233
|
- `.gjc/state/team/<team>/mailbox/worker-1.json` (legacy compatibility view)
|
|
224
234
|
- `.gjc/state/team/<team>/notifications/<notification-id>.json`
|
|
225
235
|
- `.gjc/state/team/<team>/workers/<worker>/startup-ack.json`
|
|
236
|
+
- `.gjc/state/team/<team>/workers/<worker>/status.json`
|
|
237
|
+
- `.gjc/state/team/<team>/workers/<worker>/lifecycle.json`
|
|
238
|
+
- `.gjc/state/team/<team>/workers/<worker>/heartbeat.json`
|
|
239
|
+
- `.gjc/state/team/<team>/workers/<worker>/shutdown-request.json`
|
|
226
240
|
- `.gjc/state/team/<team>/workers/<worker>/nudges/<fingerprint>.json`
|
|
227
241
|
- `.gjc/reports/team-commit-hygiene/<team>.ledger.json`
|
|
228
242
|
|
|
@@ -233,17 +247,28 @@ Use `gjc team api` for machine-readable task lifecycle operations.
|
|
|
233
247
|
```bash
|
|
234
248
|
gjc team api worker-startup-ack --input '{"team_name":"my-team","worker_id":"worker-1","protocol_version":"1"}' --json
|
|
235
249
|
gjc team api claim-task --input '{"team_name":"my-team","worker_id":"worker-1"}' --json
|
|
236
|
-
gjc team api transition-task-status --input '{"team_name":"my-team","task_id":"task-1","to":"completed","worker_id":"worker-1","claim_token":"<claim-token>","
|
|
250
|
+
gjc team api transition-task-status --input '{"team_name":"my-team","task_id":"task-1","to":"completed","worker_id":"worker-1","claim_token":"<claim-token>","completion_evidence":{"summary":"Completed requested work and verified it locally.","items":[{"kind":"command","status":"passed","summary":"Focused test passed","command":"bun test packages/coding-agent/test/gjc-runtime/team-runtime.test.ts"}],"files":["packages/coding-agent/test/gjc-runtime/team-runtime.test.ts"],"notes":"Include at least one passed command or verified inspection/artifact item."}}' --json
|
|
251
|
+
gjc team api update-worker-status --input '{"team_name":"my-team","worker_id":"worker-1","status":"working","current_task_id":"task-1"}' --json
|
|
252
|
+
gjc team api recover-stale-claims --input '{"team_name":"my-team"}' --json
|
|
253
|
+
gjc team api read-traces --input '{"team_name":"my-team"}' --json
|
|
254
|
+
gjc team api create-task --input '{"team_name":"my-team","subject":"Verify delivery","description":"Run verification","owner":"worker-1","lane":"verification","required_role":"executor","depends_on":["task-1"]}' --json
|
|
237
255
|
```
|
|
238
256
|
|
|
239
257
|
Canonical worker lifecycle operations:
|
|
240
258
|
|
|
241
|
-
- `worker-startup-ack` before task work
|
|
259
|
+
- `worker-startup-ack` before task work; this records startup ACK and moves `workers/<worker>/lifecycle.json` to `ready`
|
|
242
260
|
- `claim-task`
|
|
243
|
-
- `
|
|
261
|
+
- `update-worker-status` when the worker starts/stops a task-local activity; this updates worker-reported `status.json` without replacing the runtime lifecycle source of truth
|
|
262
|
+
- `recover-stale-claims` is leader/runtime-owned; it clears expired claim files, requeues in-progress tasks claimed by stale workers, and records `task_claim_recovered` events without modifying terminal task records or completion evidence
|
|
263
|
+
- `transition-task-status` with the claim token, worker id, and structured `completion_evidence` object
|
|
244
264
|
- `release-task-claim`
|
|
265
|
+
Claim eligibility is ordered and must not be bypassed: explicit task id selection, task status/terminal checks, owner/assignee checks, lane/role checks, dependency/blocked checks, then active lease creation. `lane` is descriptive metadata; `required_role` and `allowed_roles` are the enforced worker role gates.
|
|
245
266
|
|
|
246
|
-
|
|
267
|
+
Completion evidence is stored inline on the task record as `completion_evidence`. It must include a non-empty `summary`, an `items` array, and at least one item with `status: "passed"` or `status: "verified"`. Valid item kinds are `command`, `inspection`, and `artifact`; command items require `command`. The camel-case alias `completionEvidence` is accepted by the API input, but legacy string `evidence` and separate evidence files are not part of the public completion contract.
|
|
268
|
+
|
|
269
|
+
GJC-team interop operations are also available for mailbox, native notification, worker heartbeat/status, stale-claim recovery, startup ACK, events, monitor snapshots, approvals, and shutdown request/ack flows; run `gjc team api --help` for the full operation list.
|
|
270
|
+
|
|
271
|
+
Structured trace records in `trace.jsonl` are append-only schema version 1 entries. Each trace references the legacy `events.jsonl` source via `source_event_id`, keeps `event_type`, worker/task ids, and includes `evidence_refs` for completion evidence or claim recovery when available. Trace append failures are isolated in `trace-errors.jsonl` and do not break `events.jsonl` compatibility.
|
|
247
272
|
|
|
248
273
|
## GJC-native concept parity
|
|
249
274
|
|
|
@@ -262,9 +287,10 @@ Forbidden assumptions: do not copy OMX paths, Codex notify payload formats, OMX
|
|
|
262
287
|
Worker protocol:
|
|
263
288
|
|
|
264
289
|
- Send startup ACK with `worker-startup-ack` before task work.
|
|
290
|
+
- Report worker activity with `update-worker-status`; this is the worker-reported status plane, not the runtime lifecycle state.
|
|
265
291
|
- Claim pending work with `claim-task`.
|
|
266
292
|
- Transition the task to `completed`, `failed`, or `blocked` with `transition-task-status`, including claim token and evidence for completion.
|
|
267
|
-
- Commit or leave worktree changes in the worker worktree; the leader `
|
|
293
|
+
- Commit or leave worktree changes in the worker worktree; the leader `monitor`/`resume` path will auto-checkpoint dirty worktrees and integrate committed history where possible.
|
|
268
294
|
- Record implementation/verification evidence in normal task output and state files; leader integration/conflict notifications are delivered through `.gjc/state/team/<team>/mailbox/leader-fixed.json`.
|
|
269
295
|
|
|
270
296
|
## Environment Knobs
|
|
@@ -291,7 +317,7 @@ Operator note (important for GJC panes):
|
|
|
291
317
|
- **Split failure:** startup records a failed phase if state was already initialized, rolls back created worktrees, and never kills the leader tmux session.
|
|
292
318
|
- **Worker API ENOENT:** team state is missing or `GJC_TEAM_STATE_ROOT` points somewhere else. Check `.gjc/state/team/<team>/` before assuming worker failure.
|
|
293
319
|
- **Stale pane on shutdown:** shutdown only kills a recorded worker pane when it still belongs to the stored `tmux_target` and is not the leader pane. Stale panes outside that target require manual inspection.
|
|
294
|
-
- **Integration conflict:** `gjc team
|
|
320
|
+
- **Integration conflict:** `gjc team monitor <team>` / `resume` aborts the failing merge, cherry-pick, or worker rebase; `gjc team status <team>` is read-only inspection. Inspect `.gjc/state/team/<team>/integration-report.md`, `.gjc/state/team/<team>/events.jsonl`, `.gjc/state/team/<team>/mailbox/leader-fixed.json`, and `.gjc/reports/team-commit-hygiene/<team>.ledger.json`.
|
|
295
321
|
|
|
296
322
|
### Safe Manual Intervention (last resort)
|
|
297
323
|
|
|
@@ -330,8 +356,8 @@ tmux list-panes -F '#{pane_id} #{pane_current_command} #{pane_start_command}'
|
|
|
330
356
|
tmux kill-pane -t %450
|
|
331
357
|
tmux kill-pane -t %451
|
|
332
358
|
|
|
333
|
-
# 3) Remove stale team state only after preserving needed evidence
|
|
334
|
-
|
|
359
|
+
# 3) Remove stale team state only after preserving needed evidence, using the state runtime
|
|
360
|
+
# cleanup verb documented by the current manifest
|
|
335
361
|
|
|
336
362
|
# 4) Retry
|
|
337
363
|
gjc team executor "fresh retry"
|
|
@@ -349,8 +375,8 @@ When operating this skill, provide concrete progress evidence:
|
|
|
349
375
|
|
|
350
376
|
1. Team started line (`Team started: <name>`)
|
|
351
377
|
2. tmux target and worker pane id
|
|
352
|
-
3. task state from `gjc team status <team
|
|
353
|
-
4. shutdown outcome (`phase=complete`, worker status `stopped`) when the run is terminal; incomplete shutdowns must report `phase=cancelled`/`failed`
|
|
378
|
+
3. task state from read-only `gjc team status <team>`, mutating `gjc team monitor <team>`, or `.gjc/state/team/<team>/tasks/task-1.json`
|
|
379
|
+
4. shutdown outcome (`phase=complete`, worker status `stopped`) when the run is terminal; incomplete shutdowns must report `phase=cancelled`/`failed`, and integration-blocked shutdowns must report `phase=awaiting_integration`
|
|
354
380
|
|
|
355
381
|
Do not claim success without file/pane evidence.
|
|
356
382
|
Do not claim clean completion if shutdown occurred with `in_progress>0`.
|
|
@@ -137,26 +137,29 @@ Workers do not own ultragoal goal state, do not create worker ultragoal ledgers,
|
|
|
137
137
|
|
|
138
138
|
## Mandatory completion cleanup and review gate
|
|
139
139
|
|
|
140
|
-
An ultragoal story cannot be checkpointed `complete` until the active agent has run the quality gate:
|
|
140
|
+
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
141
|
|
|
142
|
-
1. Run targeted verification for the story.
|
|
142
|
+
1. Run targeted implementation verification for the story.
|
|
143
143
|
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
144
|
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:
|
|
145
|
+
4. Delegate an `architect` review covering all three lanes:
|
|
150
146
|
- architecture-side: system boundaries, layering, data/control flow, operational risks.
|
|
151
147
|
- product-side: user-visible behavior, acceptance criteria, edge cases, regressions.
|
|
152
148
|
- code-side: maintainability, tests, integration points, and unsafe shortcuts.
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
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.
|
|
150
|
+
6. The executor QA/red-team lane must prove evidence by the real surface under test:
|
|
151
|
+
- GUI/web surfaces require browser automation plus a screenshot or image verdict.
|
|
152
|
+
- CLI surfaces require logs or terminal transcripts from real invocation.
|
|
153
|
+
- API/package surfaces require external consumer or black-box tests through the public interface.
|
|
154
|
+
- Algorithm/math surfaces require boundary, property, adversarial, and failure-mode cases.
|
|
155
|
+
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.
|
|
156
|
+
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.
|
|
157
|
+
9. If any lane finds an issue, do **not** checkpoint `complete` and do **not** call `goal({"op":"complete"})`. Record durable blocker work instead:
|
|
155
158
|
```sh
|
|
156
159
|
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
160
|
```
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
10. Complete or steer through the blocker story, then rerun the full blocking verification loop. Repeat until all verifier lanes are clean.
|
|
162
|
+
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
163
|
|
|
161
164
|
The native `checkpoint --status complete` command rejects missing or shallow gates. `--quality-gate-json` must include:
|
|
162
165
|
|
|
@@ -178,6 +181,70 @@ The native `checkpoint --status complete` command rejects missing or shallow gat
|
|
|
178
181
|
"evidence": "executor-built e2e and red-team QA commands/results",
|
|
179
182
|
"e2eCommands": ["bun test:e2e"],
|
|
180
183
|
"redTeamCommands": ["bun test:red-team"],
|
|
184
|
+
"artifactRefs": [
|
|
185
|
+
{
|
|
186
|
+
"id": "browser-run",
|
|
187
|
+
"kind": "browser-automation",
|
|
188
|
+
"path": "artifacts/browser-run.json",
|
|
189
|
+
"description": "browser automation transcript invoking the approved user-facing flow"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"id": "gui-screenshot",
|
|
193
|
+
"kind": "screenshot",
|
|
194
|
+
"path": "artifacts/gui-screenshot.png",
|
|
195
|
+
"description": "screenshot or image-verdict evidence for the GUI/web result"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"id": "adversarial-report",
|
|
199
|
+
"kind": "failure-mode-test",
|
|
200
|
+
"path": "artifacts/adversarial-report.txt",
|
|
201
|
+
"description": "boundary, property, adversarial, or failure-mode result"
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"contractCoverage": [
|
|
205
|
+
{
|
|
206
|
+
"id": "contract-goal",
|
|
207
|
+
"contractRef": "approved plan/spec/acceptance criterion or user-facing contract id",
|
|
208
|
+
"obligation": "required behavior from the approved contract",
|
|
209
|
+
"status": "covered",
|
|
210
|
+
"surfaceEvidenceRefs": ["surface-gui"],
|
|
211
|
+
"adversarialCaseRefs": ["case-invalid-input"]
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"id": "contract-out-of-scope",
|
|
215
|
+
"contractRef": "contract intentionally outside this story",
|
|
216
|
+
"obligation": "explicitly omitted approved-contract surface",
|
|
217
|
+
"status": "not_applicable",
|
|
218
|
+
"reason": "why this contract does not apply to the current story"
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"surfaceEvidence": [
|
|
222
|
+
{
|
|
223
|
+
"id": "surface-gui",
|
|
224
|
+
"contractRef": "user-facing surface or public interface under test",
|
|
225
|
+
"surface": "gui|web|cli|api|package|algorithm|math",
|
|
226
|
+
"invocation": "real browser action, CLI command, API/package consumer call, or algorithm/property check",
|
|
227
|
+
"verdict": "passed",
|
|
228
|
+
"artifactRefs": ["browser-run", "gui-screenshot"]
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"id": "surface-out-of-scope",
|
|
232
|
+
"contractRef": "surface intentionally outside this story",
|
|
233
|
+
"surface": "gui|web|cli|api|package|algorithm|math",
|
|
234
|
+
"status": "not_applicable",
|
|
235
|
+
"reason": "why this surface does not apply to the current story"
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
"adversarialCases": [
|
|
239
|
+
{
|
|
240
|
+
"id": "case-invalid-input",
|
|
241
|
+
"contractRef": "approved plan/spec/acceptance criterion or user-facing contract id",
|
|
242
|
+
"scenario": "boundary/property/adversarial/failure-mode input or user action",
|
|
243
|
+
"expectedBehavior": "contract-required rejection, handling, or invariant preservation",
|
|
244
|
+
"verdict": "passed",
|
|
245
|
+
"artifactRefs": ["adversarial-report"]
|
|
246
|
+
}
|
|
247
|
+
],
|
|
181
248
|
"blockers": []
|
|
182
249
|
},
|
|
183
250
|
"iteration": {
|
|
@@ -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. */
|
|
@@ -2,10 +2,12 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { Settings } from "../config/settings";
|
|
5
6
|
import { syncSkillActiveState } from "../skill-state/active-state";
|
|
6
7
|
import { buildDeepInterviewHudSummary } from "../skill-state/workflow-hud";
|
|
7
8
|
import { runNativeRalplanCommand } from "./ralplan-runtime";
|
|
8
9
|
import { runNativeStateCommand } from "./state-runtime";
|
|
10
|
+
import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Native implementation of `gjc deep-interview`.
|
|
@@ -104,13 +106,6 @@ async function readJsonObject(filePath: string): Promise<Record<string, unknown>
|
|
|
104
106
|
return {};
|
|
105
107
|
}
|
|
106
108
|
|
|
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
109
|
async function resolveSpecContent(rawSpec: string, cwd: string): Promise<string> {
|
|
115
110
|
const candidate = path.isAbsolute(rawSpec) ? rawSpec : path.resolve(cwd, rawSpec);
|
|
116
111
|
try {
|
|
@@ -202,9 +197,29 @@ async function readSettingsAmbiguityThreshold(
|
|
|
202
197
|
return { threshold: candidate, source: settingsPath };
|
|
203
198
|
}
|
|
204
199
|
|
|
200
|
+
async function readModernSettingsAmbiguityThreshold(
|
|
201
|
+
cwd: string,
|
|
202
|
+
): Promise<{ threshold: number; source: string } | undefined> {
|
|
203
|
+
const settings = await Settings.init({ cwd });
|
|
204
|
+
const modernConfigPath = path.join(settings.getAgentDir(), "config.yml");
|
|
205
|
+
let parsed: unknown;
|
|
206
|
+
try {
|
|
207
|
+
parsed = (await import("bun")).YAML.parse(await fs.readFile(modernConfigPath, "utf-8"));
|
|
208
|
+
} catch {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
const candidate = (parsed as { gjc?: { deepInterview?: { ambiguityThreshold?: unknown } } })?.gjc?.deepInterview
|
|
212
|
+
?.ambiguityThreshold;
|
|
213
|
+
if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0 || candidate > 1)
|
|
214
|
+
return undefined;
|
|
215
|
+
return { threshold: candidate, source: modernConfigPath };
|
|
216
|
+
}
|
|
217
|
+
|
|
205
218
|
async function resolveConfiguredAmbiguityThreshold(
|
|
206
219
|
cwd: string,
|
|
207
220
|
): Promise<{ threshold: number; source: string } | undefined> {
|
|
221
|
+
const modernValue = await readModernSettingsAmbiguityThreshold(cwd);
|
|
222
|
+
if (modernValue) return modernValue;
|
|
208
223
|
const projectSettings = path.join(cwd, ".gjc", "settings.json");
|
|
209
224
|
const projectValue = await readSettingsAmbiguityThreshold(projectSettings);
|
|
210
225
|
if (projectValue) return projectValue;
|
|
@@ -373,17 +388,19 @@ export async function persistDeepInterviewSpec(
|
|
|
373
388
|
cwd: string,
|
|
374
389
|
resolved: ResolvedDeepInterviewSpecWriteArgs,
|
|
375
390
|
): Promise<PersistedDeepInterviewSpec> {
|
|
376
|
-
const
|
|
377
|
-
await fs.mkdir(specsDir, { recursive: true });
|
|
378
|
-
const specPath = path.join(specsDir, `deep-interview-${resolved.slug}.md`);
|
|
391
|
+
const specPath = path.join(cwd, ".gjc", "specs", `deep-interview-${resolved.slug}.md`);
|
|
379
392
|
const content = resolved.spec.endsWith("\n") ? resolved.spec : `${resolved.spec}\n`;
|
|
380
|
-
await
|
|
393
|
+
await writeArtifact(specPath, content, {
|
|
394
|
+
cwd,
|
|
395
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
|
|
396
|
+
});
|
|
381
397
|
|
|
382
398
|
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
383
399
|
const createdAt = new Date().toISOString();
|
|
384
|
-
await
|
|
385
|
-
path.join(
|
|
386
|
-
|
|
400
|
+
await appendJsonl(
|
|
401
|
+
path.join(cwd, ".gjc", "specs", "deep-interview-index.jsonl"),
|
|
402
|
+
{ slug: resolved.slug, stage: resolved.stage, path: specPath, created_at: createdAt, sha256 },
|
|
403
|
+
{ cwd, audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "deep-interview" } },
|
|
387
404
|
);
|
|
388
405
|
|
|
389
406
|
const statePath = deepInterviewStatePath(cwd, resolved.sessionId);
|
|
@@ -402,7 +419,10 @@ export async function persistDeepInterviewSpec(
|
|
|
402
419
|
updated_at: createdAt,
|
|
403
420
|
};
|
|
404
421
|
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
405
|
-
await writeJsonAtomic(statePath, payload
|
|
422
|
+
await writeJsonAtomic(statePath, payload, {
|
|
423
|
+
cwd,
|
|
424
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
|
|
425
|
+
});
|
|
406
426
|
await syncDeepInterviewHud({
|
|
407
427
|
cwd,
|
|
408
428
|
sessionId: resolved.sessionId,
|
|
@@ -421,11 +441,7 @@ export async function persistDeepInterviewSpec(
|
|
|
421
441
|
}
|
|
422
442
|
|
|
423
443
|
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");
|
|
444
|
+
const statePath = deepInterviewStatePath(cwd, resolved.sessionId);
|
|
429
445
|
const now = new Date().toISOString();
|
|
430
446
|
const payload: Record<string, unknown> = {
|
|
431
447
|
active: true,
|
|
@@ -448,7 +464,10 @@ async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepIntervi
|
|
|
448
464
|
(payload.state as Record<string, unknown>).language = resolved.language;
|
|
449
465
|
}
|
|
450
466
|
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
451
|
-
await
|
|
467
|
+
await writeJsonAtomic(statePath, payload, {
|
|
468
|
+
cwd,
|
|
469
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
|
|
470
|
+
});
|
|
452
471
|
return statePath;
|
|
453
472
|
}
|
|
454
473
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ModeChangeEntry,
|
|
9
9
|
type SessionEntry,
|
|
10
10
|
} from "../session/session-manager";
|
|
11
|
+
import { removeFileAudited, writeJsonAtomic } from "./state-writer";
|
|
11
12
|
|
|
12
13
|
export const GJC_SESSION_FILE_ENV = "GJC_SESSION_FILE";
|
|
13
14
|
export const GJC_SESSION_ID_ENV = "GJC_SESSION_ID";
|
|
@@ -88,8 +89,10 @@ export async function writePendingGoalModeRequest(input: {
|
|
|
88
89
|
goalsPath: input.goalsPath,
|
|
89
90
|
};
|
|
90
91
|
const filePath = requestPath(input.cwd);
|
|
91
|
-
await
|
|
92
|
-
|
|
92
|
+
await writeJsonAtomic(filePath, request, {
|
|
93
|
+
cwd: input.cwd,
|
|
94
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime" },
|
|
95
|
+
});
|
|
93
96
|
return request;
|
|
94
97
|
}
|
|
95
98
|
|
|
@@ -153,6 +156,8 @@ export async function writeCurrentSessionGoalModeState(input: {
|
|
|
153
156
|
mode: "goal",
|
|
154
157
|
data: { goal: state.goal },
|
|
155
158
|
};
|
|
159
|
+
// The session transcript file lives outside `.gjc/` (GJC_SESSION_FILE), so it is not a
|
|
160
|
+
// sanctioned-writer target; append directly.
|
|
156
161
|
await fs.appendFile(sessionFile, `${JSON.stringify(entry)}\n`);
|
|
157
162
|
return { status: "updated", goal: state.goal, sessionFile };
|
|
158
163
|
}
|
|
@@ -176,7 +181,10 @@ export async function consumePendingGoalModeRequest(cwd: string): Promise<Pendin
|
|
|
176
181
|
) {
|
|
177
182
|
return null;
|
|
178
183
|
}
|
|
179
|
-
await
|
|
184
|
+
await removeFileAudited(filePath, {
|
|
185
|
+
cwd,
|
|
186
|
+
audit: { category: "prune", verb: "remove", owner: "gjc-runtime" },
|
|
187
|
+
}).catch(error => {
|
|
180
188
|
if (!isEnoent(error)) throw error;
|
|
181
189
|
});
|
|
182
190
|
return { ...candidate, objective: candidate.objective.trim() } as PendingGoalModeRequest;
|
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { syncSkillActiveState } from "../skill-state/active-state";
|
|
5
5
|
import { buildRalplanHudSummary } from "../skill-state/workflow-hud";
|
|
6
6
|
import { isRestrictedRoleAgentBash } from "./restricted-role-agent-bash";
|
|
7
|
+
import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Native implementation of `gjc ralplan`.
|
|
@@ -173,8 +174,10 @@ async function persistActiveRunId(cwd: string, sessionId: string | undefined, ru
|
|
|
173
174
|
if (typeof existing.skill !== "string") existing.skill = "ralplan";
|
|
174
175
|
if (typeof existing.active !== "boolean") existing.active = true;
|
|
175
176
|
existing.updated_at = new Date().toISOString();
|
|
176
|
-
await
|
|
177
|
-
|
|
177
|
+
await writeJsonAtomic(statePath, existing, {
|
|
178
|
+
cwd,
|
|
179
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
180
|
+
});
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
async function resolveArtifactArgs(args: readonly string[], cwd: string): Promise<ResolvedArtifactArgs> {
|
|
@@ -220,27 +223,36 @@ interface PersistedArtifact {
|
|
|
220
223
|
|
|
221
224
|
async function persistArtifact(resolved: ResolvedArtifactArgs, cwd: string): Promise<PersistedArtifact> {
|
|
222
225
|
const runDir = path.join(cwd, ".gjc", "plans", "ralplan", resolved.runId);
|
|
223
|
-
|
|
226
|
+
|
|
224
227
|
const fileName = `stage-${pad2(resolved.stageN)}-${resolved.stage}.md`;
|
|
225
228
|
const filePath = path.join(runDir, fileName);
|
|
226
229
|
const content = resolved.artifact.endsWith("\n") ? resolved.artifact : `${resolved.artifact}\n`;
|
|
227
|
-
await
|
|
230
|
+
await writeArtifact(filePath, content, {
|
|
231
|
+
cwd,
|
|
232
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
233
|
+
});
|
|
228
234
|
|
|
229
235
|
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
230
236
|
const createdAt = new Date().toISOString();
|
|
231
|
-
const
|
|
237
|
+
const indexEntry = {
|
|
232
238
|
stage: resolved.stage,
|
|
233
239
|
stage_n: resolved.stageN,
|
|
234
240
|
path: filePath,
|
|
235
241
|
created_at: createdAt,
|
|
236
242
|
sha256,
|
|
237
|
-
}
|
|
238
|
-
await
|
|
243
|
+
};
|
|
244
|
+
await appendJsonl(path.join(runDir, "index.jsonl"), indexEntry, {
|
|
245
|
+
cwd,
|
|
246
|
+
audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "ralplan" },
|
|
247
|
+
});
|
|
239
248
|
|
|
240
249
|
let pendingApprovalPath: string | undefined;
|
|
241
250
|
if (resolved.stage === "final") {
|
|
242
251
|
pendingApprovalPath = path.join(runDir, "pending-approval.md");
|
|
243
|
-
await
|
|
252
|
+
await writeArtifact(pendingApprovalPath, content, {
|
|
253
|
+
cwd,
|
|
254
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
255
|
+
});
|
|
244
256
|
}
|
|
245
257
|
|
|
246
258
|
return {
|
|
@@ -382,7 +394,7 @@ async function seedRalplanState(
|
|
|
382
394
|
const stateDir = resolved.sessionId
|
|
383
395
|
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
|
|
384
396
|
: path.join(cwd, ".gjc", "state");
|
|
385
|
-
|
|
397
|
+
|
|
386
398
|
const statePath = path.join(stateDir, "ralplan-state.json");
|
|
387
399
|
// Reuse an existing run id when present so a re-invocation of `gjc ralplan "task"` doesn't
|
|
388
400
|
// orphan in-progress artifacts under a fresh run id.
|
|
@@ -403,7 +415,10 @@ async function seedRalplanState(
|
|
|
403
415
|
if (resolved.architectKind) payload.architect_kind = resolved.architectKind;
|
|
404
416
|
if (resolved.criticKind) payload.critic_kind = resolved.criticKind;
|
|
405
417
|
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
406
|
-
await
|
|
418
|
+
await writeJsonAtomic(statePath, payload, {
|
|
419
|
+
cwd,
|
|
420
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
421
|
+
});
|
|
407
422
|
return { statePath, runId };
|
|
408
423
|
}
|
|
409
424
|
|