@gajae-code/coding-agent 0.12.1 → 0.12.4
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 +39 -0
- package/dist/types/config/settings-schema.d.ts +2 -2
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -3
- package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +5 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -2
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -2
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +22 -3
- package/dist/types/task/index.d.ts +4 -0
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/package.json +8 -8
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-acp-conformance.ts +5 -0
- package/src/async/job-manager.ts +22 -20
- package/src/config/provider-ranking.ts +1 -0
- package/src/config/settings-schema.ts +2 -2
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/ralplan/SKILL.md +9 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -5
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +2 -1
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +80 -10
- package/src/gjc-runtime/ultragoal-evidence.ts +110 -49
- package/src/gjc-runtime/ultragoal-runtime.ts +467 -22
- package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
- package/src/gjc-runtime/workflow-manifest.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/modes/DESIGN.md +53 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/tool-status-header.ts +0 -1
- package/src/modes/controllers/command-controller.ts +38 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +186 -34
- package/src/modes/controllers/selector-controller.ts +115 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -2
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +1 -0
- package/src/session/agent-session.ts +22 -1
- package/src/session/internal/managed-session-scope.ts +158 -107
- package/src/session/internal/managed-session-storage.ts +445 -57
- package/src/session/session-storage.ts +29 -44
- package/src/task/index.ts +257 -31
- package/src/task/output-manager.ts +24 -14
- package/src/task/receipt.ts +11 -1
- package/src/tools/index.ts +2 -0
|
@@ -46,7 +46,7 @@ gjc ralplan --write --session-id <owner-session-id> --run-id <run-id> --stage <t
|
|
|
46
46
|
gjc ralplan --write --session-id <owner-session-id> --run-id <run-id> --stage <type> --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Use stages `planner`, `architect`, `critic`, `revision`, `post-interview`, `adr`, or `final`; increment `--stage_n` each consensus pass. The writer accepts inline markdown, an artifact path prepared outside `.gjc/`, or `--artifact-env GJC_RALPLAN_ARTIFACT`, persists `stage-<NN>-<stage>.md` plus `index.jsonl` under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/`, and copies `final` to `pending-approval.md`. Ralplan mutation blocking is enforced in code; use temp directories (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) only for oversized scratch artifacts, never the repo or `.gjc/`. Staging via the `write` tool or a quoted-delimiter bash heredoc (`cat > /tmp/plan.md <<'EOF' … EOF`) into those temp roots is tolerated by the planning-phase guard.
|
|
49
|
+
Use stages `planner`, `architect`, `critic`, `disposition`, `revision`, `post-interview`, `adr`, or `final`; increment `--stage_n` each consensus pass. The writer accepts inline markdown (or JSON for `disposition`), an artifact path prepared outside `.gjc/`, or `--artifact-env GJC_RALPLAN_ARTIFACT`, persists `stage-<NN>-<stage>.md` plus `index.jsonl` under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/`, and copies `final` to `pending-approval.md`. Ralplan mutation blocking is enforced in code; use temp directories (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) only for oversized scratch artifacts, never the repo or `.gjc/`. Staging via the `write` tool or a quoted-delimiter bash heredoc (`cat > /tmp/plan.md <<'EOF' … EOF`) into those temp roots is tolerated by the planning-phase guard.
|
|
50
50
|
|
|
51
51
|
Restricted read-only role agents (`planner`, `architect`, `critic`) must pass markdown through `GJC_RALPLAN_ARTIFACT` with `--artifact-env GJC_RALPLAN_ARTIFACT`; their restricted bash environment disables artifact file-path ingestion.
|
|
52
52
|
|
|
@@ -71,9 +71,11 @@ The consensus workflow:
|
|
|
71
71
|
- **Sequential fallback**: if Critic must evaluate Architect findings, verdict, antithesis, tradeoffs, synthesis, status, or any Architect-produced artifact, await the Architect result before issuing that Architect-dependent Critic pass.
|
|
72
72
|
- Every Architect/Critic assignment, including each pass-2+ re-review assignment in step 5, MUST instruct the reviewer to include `--lane-verdict <token>` on its existing `gjc ralplan --write`: Architect passes its Architectural Status token (`CLEAR`/`WATCH`/`BLOCK`), and Critic passes its verdict token (`OKAY`/`ITERATE`/`REJECT`). The flag is optional so legacy invocations stay valid.
|
|
73
73
|
4. **Review join gate**: before consensus, revision, reconciliation, finalization, or approval, verify both Architect and Critic receipts/verdicts exist for the same Planner artifact/pass (`path`, `sha256`, `stage_n`). A non-`CLEAR` Architect verdict, non-`APPROVE` Architect decision, or any non-`OKAY` Critic verdict routes back to Planner revision; do not finalize from only one review lane.
|
|
74
|
+
- **Typed conflict gate (#2902)**: when Architect and Critic findings prescribe incompatible actions (`add` vs `remove`, or `remove` vs `change`) against the same stable plan target id, do **not** treat the join as clean and do **not** start revision until a `disposition` stage is persisted for that pass. Collect typed findings (stable `findingId`, `targetId`, `action`, `severity`, `evidence`, `sourceRole`, source receipt) from both review artifacts, derive conflicts, and require one explicit disposition per conflict (`accept_architect` | `accept_critic` | `synthesize` | `defer_user` | `reject_both`) with `rationale`, `decisionOwner`, and `affectedSections`. Persist via `gjc ralplan --write --stage disposition --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json` using schema `ralplan.review_conflicts.v1`. Source receipts must be authoritative same-pass attestations: `plannerStageN` equals CLI `--stage_n`, each finding's `sourceReceipt.stage` equals `sourceRole`, `sourceReceipt.stageN` equals `plannerStageN`, and path/sha256 resolve against the run's persisted Architect/Critic `index.jsonl` rows. The writer fails closed if any conflict remains open, a disposition references an unknown conflict, or provenance is mismatched/spoofed. Product intent/scope remains owned by the user + approval gate; the ralplan leader owns reconciliation; reviewers advise and block.
|
|
74
75
|
5. **Re-review loop** (max 5 iterations; **runtime-enforced**): Any non-`OKAY` Critic verdict (`ITERATE` or `REJECT`) or Architect result that is not `CLEAR`/`APPROVE` MUST run the same full closed loop. Pass 2+ resumes the SAME persisted Architect and Critic lane subagents with the mandatory re-review context bundle and runs sequentially Architect -> Critic: await the Architect result and its receipt/path before assigning Critic; Critic receives the current-pass Architect receipt/path and performs the rule-5 counter-review before consolidated feedback routes to Planner revision. From pass 2, both reviewers are bound by the five-rule ratchet: delta-only review, novelty justification, verdict monotonicity, severity scoping, and Critic counter-review of Architect scope inflation; unjustified inflation does not force a revision.
|
|
75
76
|
a. Collect Architect + Critic feedback
|
|
76
|
-
b.
|
|
77
|
+
b. When typed conflicts exist, persist dispositions (step 4 typed conflict gate) before revision so the Planner receives a machine-checkable conflict set, not prose alone
|
|
78
|
+
c. Revise the plan by resuming the SAME persisted Planner subagent with consolidated Architect + Critic feedback **and** any disposition receipts (see **Persisted role agents** below); fall back to a fresh Planner spawn only per the fallback routing table
|
|
77
79
|
|
|
78
80
|
**Re-review context bundle (pass 2+; mandatory):** Every pass-2+ Architect or Critic assignment MUST include:
|
|
79
81
|
1. the explicit review pass number `N` for that lane, stated literally as `review pass N` in the assignment text, where **N is the ordinal review pass for that lane across the entire ralplan run/re-review loop** (equivalently the opener-iteration ordinal): the review of the initial Planner artifact is `review pass 1`, the review of the first revised Planner artifact is `review pass 2`, and so on; **N never resets within an opener iteration and never resets when a new `revision` opener begins in the same run** — it increments monotonically with every review the lane performs in the run. This ordinal is a workflow counter distinct from the runtime lane budget (which counts lane writes per opener iteration, WI-5): at the default budget the two coincide numerically, but the ratchet ("from pass 2") always keys off the run-level N so normal post-revision re-reviews activate delta-only review, monotonicity, and the sequential cadence;
|
|
@@ -84,12 +86,12 @@ The consensus workflow:
|
|
|
84
86
|
6. Critic pass-2+ only: the current-pass Architect receipt/path, awaited first per the sequential cadence, so the rule-5 counter-review is evaluable.
|
|
85
87
|
|
|
86
88
|
**The re-review context bundle remains mandatory regardless of whether a reviewer is resumed or uses a fresh-spawn fallback.** A fresh-spawn fallback always receives everything required to apply delta-only review (rule 1), novelty justification (rule 2), monotonicity (rule 3), severity scoping (rule 4), and counter-review (rule 5).
|
|
87
|
-
|
|
89
|
+
d. For pass 2+, resume (or fresh-spawn only per the routing table) Architect -> Critic sequentially: await the Architect result and receipt/path, then issue Critic with the mandatory context bundle, including the current-pass Architect receipt/path. Critic performs the rule-5 counter-review before consolidated feedback routes to Planner revision.
|
|
88
90
|
- Persist each Planner revision with `gjc ralplan --write --stage revision --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json` before re-review, then pass the receipt/path forward instead of duplicating the full revision markdown in the parent conversation.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
e. Re-join Architect and Critic verdicts for the same revised Planner artifact/pass (including a fresh disposition stage if new conflicts appear)
|
|
92
|
+
f. Repeat this loop until Critic returns `OKAY` **and** Architect is `CLEAR`/`APPROVE` for the same Planner artifact/pass, or 5 iterations are reached
|
|
93
|
+
g. If 5 iterations are reached without Critic `OKAY` plus Architect `CLEAR`/`APPROVE`, **stop opening further planner/revision passes**. Preserve the best version as a terminal `PLANNING-STUCK` result; do not route it to automatic or explicit execution.
|
|
94
|
+
h. **Runtime budget (#3165):** native `gjc ralplan --write` refuses a new `planner`/`revision` that would open consensus iteration **> max** (default **5**, overridable via `gjc.ralplan.maxIterations` in project/user `.gjc/settings.json`, integer 1..20). Cap uses the same iteration definition as the HUD (`planner`/`revision` openers in `index.jsonl`). Overflow exits **3**, prints operator-visible **`PLANNING-STUCK`** on stdout (and stderr detail; JSON includes `planning_stuck: true`), and still allows `architect`/`critic` within an already-opened pass plus `post-interview`/`adr`/`final` so the best plan can be escalated to `pending approval` without dispatch. A new `--run-id` starts a fresh budget.
|
|
93
95
|
6. **Post-ralplan interview** (intent reconciliation gate): After the review join gate has both Critic `OKAY` and Architect `CLEAR`/`APPROVE` for the same Planner artifact/pass, and before the plan is finalized, reconcile the consensus plan against the user's actual intent. The goal is to make sure ralplan did not silently bake in assumptions that conflict with what the user wants.
|
|
94
96
|
a. **Collect open items** from the run: every assumption the Planner/Architect/Critic resolved by assumption rather than by stated fact, every ambiguity flagged during review, and every decision the loop made without explicit user input. Source these from the persisted `planner`/`architect`/`critic`/`revision` stage artifacts, not from memory.
|
|
95
97
|
b. **Cross-check prior context for conflicts**: glob `.gjc/_session-{sessionid}/specs/deep-interview-*.md` and other prior specs/plans/context relevant by topic. For each, list points where the consensus plan contradicts, weakens, or expands beyond a previously crystallized decision, constraint, or non-goal. Cite the conflicting artifact and line/section.
|
|
@@ -230,9 +230,22 @@ Team remains explicit and separate: Team is not auto-launched, not a hidden pipe
|
|
|
230
230
|
|
|
231
231
|
Heavyweight review runs **once per boundary**, not once per story. In aggregate mode the whole required-goal set is one implicit boundary by default: every checkpoint before the run's final required goal may present the lightweight `deferredToBatch` gate, and only the final goal carries the full strict gate. Nothing needs to be declared to get this — it is the default.
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
A deferred gate is just the proof the runtime cannot know: that targeted verification ran. Everything mechanical — `kind`, the batch tuple, `deferredLanes`, and the whole `changeSet` block (`paths`, `changeSetHash`) — is auto-filled from durable state and the computed cumulative git diff. Never hand-compute a hash. The minimal valid gate:
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"deferredToBatch": {
|
|
238
|
+
"ranLanes": ["targetedVerification"],
|
|
239
|
+
"targetedVerification": {
|
|
240
|
+
"status": "passed",
|
|
241
|
+
"commands": ["bun test <targeted suite>"],
|
|
242
|
+
"evidence": "what was verified and how it passed"
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`deferredToBatch.ranLanes` lists the lanes you actually ran (`targetedVerification`, plus optionally `aiSlopCleaner` / `iteration`); declaration and evidence must match in both directions. `ranLanes` can never claim `architectReview` or `executorQa`, and a deferred gate can never contain `architectReview`, `executorQa`, or `validationBatchClose` — review always belongs to the boundary, and deferring never manufactures approvals. Any optional field you do supply must match reality; a wrong value fails closed. Check with `gjc ultragoal quality-gate validate` before checkpointing.
|
|
236
249
|
|
|
237
250
|
### Validation batches (explicit phase/module boundaries)
|
|
238
251
|
|
|
@@ -247,7 +260,7 @@ gjc ultragoal create-goals --brief-file <path> --validation-batch-json '[{"schem
|
|
|
247
260
|
Checkpoint contract summary — the full contract lives in the `validation-batch-contracts` fragment (`skill-fragments/ultragoal/validation-batch-contracts.md`); load it before checkpointing any batch member:
|
|
248
261
|
|
|
249
262
|
- **Non-final members** checkpoint `complete` with a single top-level `deferredToBatch` quality gate (kind `validation-batch-deferred`) proving targeted verification, a declaration-matched lane set, and a cumulative-since-base change set — never `architectReview`, `executorQa`, or `validationBatchClose`; deferring never manufactures fake review approvals.
|
|
250
|
-
- **The final member** (`finalGoalId`) checkpoints `complete` with the normal full strict gate PLUS a top-level `validationBatchClose` proof covering all members; out-of-order close is rejected, close state is append-only proof on the final member only, and batch invalidation is fail-closed.
|
|
263
|
+
- **The final member** (`finalGoalId`) checkpoints `complete` with the normal full strict gate PLUS a top-level `validationBatchClose` proof covering all members; out-of-order close is rejected, close state is append-only proof on the final member only, and batch invalidation is fail-closed. Like the deferred gate, every close field except `coverageEvidence` is auto-filled from durable receipts and the computed diff — the minimal close is `{"validationBatchClose":{"coverageEvidence":"..."}}` alongside the strict gate.
|
|
251
264
|
|
|
252
265
|
### Intra-goal validation-lane parallelism
|
|
253
266
|
|
|
@@ -291,7 +304,7 @@ One generation freezes the change set and reviews it exactly once:
|
|
|
291
304
|
5. Delegate an `executor` QA/red-team lane with typed `executionMode: "ultragoal-red-team"` (preferred) — or assignment text that explicitly labels Ultragoal completion QA/red-team — to build and run the e2e/red-teaming QA suite appropriate for the story. A bare `executorQa` field-name mention is not enough to activate the mode. 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.
|
|
292
305
|
6. The executor QA/red-team lane must prove evidence by the real surface under test:
|
|
293
306
|
- GUI/web surfaces require a valid automation transcript plus a non-uniform screenshot. Bare `inlineEvidence` text or typed receipts never prove live GUI/web execution.
|
|
294
|
-
- CLI surfaces require runtime argv replay
|
|
307
|
+
- CLI surfaces require a safe runtime argv replay (`schemaVersion: 1`, `kind: "cli-replay"`, `replaySafe: true`) or the existing audited `replayExempt` path with a screenshot, automation, or PTY structural fallback. Runtime replay is limited to the pinned Bun runtime for `bun --version` or literal `bun -e "console.log(...)"`; the gate never executes model-authored test files. Shells, interpreters with code strings, path-qualified executables, package/git/network mutation commands, `bun test`, and arbitrary argv are rejected. Structured `test-report` fallback remains unsupported pending a separately reviewed provenance design.
|
|
295
308
|
- Native/desktop/tui surfaces require a structurally valid screenshot, PTY capture with terminal control codes, or app-automation transcript.
|
|
296
309
|
- API/package surfaces require a real artifact file or typed receipt whose artifact `kind` contains one of `api`, `package`, `consumer`, `black-box`, or `test-report`; examples: `api-package-test-report`, `package-consumer-report`, `black-box-api-receipt`. Algorithm/math surfaces require a real artifact file or typed receipt whose artifact `kind` contains one of `property`, `boundary`, `edge`, `adversarial`, `failure`, `math`, `algorithm`, or `test-report`; examples: `property-test-report`, `algorithm-boundary-report`. Bare `inlineEvidence` text alone is not sufficient for any surface.
|
|
297
310
|
- The mandatory **computer-use** red-team suite (`kill-switch-bypass`, `suspended-enforcement`, `permission-revoked`, …) is conditional, not universal: require it only when computer/desktop control is genuinely part of the product surface being dogfooded. For every other product type, prove the change through the matching live surface instead — browser-use automation for web/GUI, bash/CLI live invocation or argv replay for CLI, and real artifacts or typed receipts for API/package/algorithm/math. Editing docs, prompts, or skills that merely mention computer-use does not by itself make the computer-use suite applicable; pick the red-team surface that matches what the change actually ships.
|
|
@@ -364,7 +377,11 @@ Validate before you checkpoint. `gjc ultragoal quality-gate validate --quality-g
|
|
|
364
377
|
|
|
365
378
|
Provide one `artifactRefs` entry per live surface actually exercised, using the surface-appropriate `kind` and evidence rules from steps 6–7 above; the CLI rejects missing or shallow gates. `status: "not_applicable"` rows are allowed only in `contractCoverage` and `surfaceEvidence` and each requires `contractRef` plus `reason`.
|
|
366
379
|
|
|
367
|
-
For CLI replay artifacts, the JSON at `path` must be an object like `{"schemaVersion":1,"kind":"cli-replay","replaySafe":true,"command":["bun","-e","console.log(\"ultragoal-cli-ok\")"],"cwd":".","env":{"LC_ALL":"C"},"timeoutMs":30000,"expectedExitCode":0,"recordedStdout":"ultragoal-cli-ok\n","recordedStderr":"","invariants":[{"type":"substring","value":"ultragoal-cli-ok"},{"type":"
|
|
380
|
+
For safe CLI replay artifacts, the JSON at `path` must be an object like `{"schemaVersion":1,"kind":"cli-replay","replaySafe":true,"command":["bun","-e","console.log(\"ultragoal-cli-ok\")"],"cwd":".","env":{"LC_ALL":"C"},"timeoutMs":30000,"expectedExitCode":0,"recordedStdout":"ultragoal-cli-ok\n","recordedStderr":"","invariants":[{"type":"substring","value":"ultragoal-cli-ok"},{"type":"not_substring","value":"error"}]}`. `replaySafe: true` is required but is never authority by itself: executable replay is limited to the pinned Bun runtime for `bun --version` or deterministic literal `bun -e "console.log(...)"`. Shells, nested interpreters, path-qualified executables, test source, install/publish commands, git mutation, network clients, and every other argv are rejected. The declared cwd and artifact files are realpath-confined beneath the repository, but the safe probe itself runs from a fresh empty temporary cwd/home so repository `bunfig.toml` preloads and user configuration cannot execute. Mixed inline/nested/file-backed rows fail closed, POSIX timeout cleanup signals the replay process group, stdout and stderr are validated after normalization, output is capped at 1 MiB, and the child environment is scrubbed to `CI`, `NO_COLOR`, `GJC_ULTRAGOAL_REPLAY`, trusted temporary `HOME`/`TMPDIR`, plus optional `LANG`, `LC_ALL`, `LC_CTYPE`, and `TZ`.
|
|
381
|
+
|
|
382
|
+
Compiled GJC binaries fail executable replay closed because their `process.execPath` launches GJC rather than a Bun CLI. Those runs must use the existing audited `replayExempt` structural fallback; the validator never resolves an untrusted `bun` from `PATH`.
|
|
383
|
+
|
|
384
|
+
Focused `bun test` execution is blocked because repository test source is still arbitrary host code without an operating-system sandbox. The current `replayExempt` contract continues to require an existing screenshot, automation transcript, or PTY structural fallback; a `test-report` or `bun-test-report` JSON file is intentionally not accepted yet. Keep that design work open until test-result provenance, output binding, and consumer authority can be made fail-closed. Allowed `reasonCode` values remain `unsafe_side_effect`, `requires_credentials`, `requires_network`, `non_deterministic_external`, `destructive`, `interactive_only`, and `platform_unavailable`.
|
|
368
385
|
|
|
369
386
|
## Terminal critic gate
|
|
370
387
|
|
|
@@ -6,7 +6,8 @@ Internal Ultragoal sub-skill fragment (`kind: "skill-fragment"`, parent skill `u
|
|
|
6
6
|
|
|
7
7
|
- **Non-final members** checkpoint `complete` with a single top-level `deferredToBatch` quality gate (kind `validation-batch-deferred`): a passing `targetedVerification` lane plus a cumulative-since-base change set. An ai-slop-cleaner pass and a full verification rerun are boundary duties and are optional here; when either is supplied it must pass and be blocker-free. A `deferredToBatch` gate must NOT contain `architectReview`, `executorQa`, or `validationBatchClose` — deferring never manufactures fake review approvals.
|
|
8
8
|
- **Lane declaration is fail-closed.** `deferredToBatch.ranLanes` declares which lanes actually ran. A declared lane whose evidence is missing is rejected, evidence for an undeclared lane is rejected, `targetedVerification` must always be declared when `ranLanes` is present, and `ranLanes` can never declare `architectReview` or `executorQa`. Omitting `ranLanes` is allowed only for a gate that carries the mandatory targeted lane alone.
|
|
9
|
-
- **
|
|
9
|
+
- **Derivable fields are auto-filled — never hand-compute a hash.** When omitted, the runtime fills `kind`, the batch tuple, `deferredLanes`, and the whole `changeSet` block (`memberGoalId`, `cumulativeFromBase`, `paths` from the computed cumulative diff, `changeSetHash`); the minimal deferred gate is `{"deferredToBatch":{"targetedVerification":{"status":"passed","commands":["..."],"evidence":"..."}}}`. A supplied value must still match reality; `changeSet.paths` rows may be plain path strings or `{path, status}` objects.
|
|
10
|
+
- **The final member** (`finalGoalId`) checkpoints `complete` with the normal full strict gate PLUS a top-level `validationBatchClose` proof that covers all member IDs, member metadata hashes, member receipt/checkpoint-ledger-event IDs, per-member change-set hashes, and union change-set coverage. Every close field except `coverageEvidence` is auto-filled from durable receipts and the computed cumulative diff when omitted — the minimal close is `{"validationBatchClose":{"coverageEvidence":"..."}}` alongside the strict gate, and a supplied value must still match durable state. The final close only starts once every non-final member is already `complete` with a structurally fresh deferred receipt (out-of-order close is rejected).
|
|
10
11
|
- Close state is append-only proof: it lives in the final member's checkpoint receipt and matching `goal_checkpointed` ledger row only. Never stamp `closedReceiptId`/`closedAt` or any close-state field onto member goals, and never append a separate close ledger event.
|
|
11
12
|
- Change sets are cumulative-since-base: each member's `changeSet.paths` is the whole-worktree diff vs base (`cumulativeFromBase: true`), `memberGoalId` is a label not a per-path attribution, and `unionChangeSet.paths` carries no per-goal attribution.
|
|
12
13
|
- Batch invalidation is fail-closed: steering mutations that would invalidate a batch are rejected while any member holds a fresh deferred receipt.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { shortenPath } from "../tools/render-utils";
|
|
4
5
|
|
|
5
6
|
export type GjcLaunchWorktreeMode =
|
|
6
7
|
| { enabled: false }
|
|
@@ -138,6 +139,107 @@ function hasBranchInUse(entries: GitWorktreeEntry[], branchName: string, worktre
|
|
|
138
139
|
return entries.some(entry => entry.branchRef === expectedRef && path.resolve(entry.path) !== resolvedPath);
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
function fileSystemErrorCode(error: unknown): string | null {
|
|
143
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
|
|
144
|
+
? error.code
|
|
145
|
+
: null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function formatBucketPath(bucketPath: string): string {
|
|
149
|
+
return JSON.stringify(shortenPath(bucketPath));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function brokenBucketSymlinkError(bucketPath: string): Error {
|
|
153
|
+
return new Error(
|
|
154
|
+
[
|
|
155
|
+
"worktree_bucket_broken_symlink",
|
|
156
|
+
"The GJC launch worktree bucket is a symbolic link whose target cannot be resolved; it may be unmounted or offloaded cold storage.",
|
|
157
|
+
`Path: ${formatBucketPath(bucketPath)}`,
|
|
158
|
+
"Safe remediation: restore or remount the link target, or inspect and remove the dangling link with platform-appropriate filesystem tools, then relaunch. GJC did not delete or replace the entry.",
|
|
159
|
+
].join("\n"),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function bucketNotDirectoryError(bucketPath: string, symlinkTarget = false): Error {
|
|
164
|
+
return new Error(
|
|
165
|
+
[
|
|
166
|
+
"worktree_bucket_not_directory",
|
|
167
|
+
symlinkTarget
|
|
168
|
+
? "The GJC launch worktree bucket is a symbolic link whose target is not a directory."
|
|
169
|
+
: "The GJC launch worktree bucket path exists but is not a directory.",
|
|
170
|
+
`Path: ${formatBucketPath(bucketPath)}`,
|
|
171
|
+
"Safe remediation: inspect the obstructing entry and move or remove it with platform-appropriate filesystem tools, then relaunch. GJC did not delete or replace the entry.",
|
|
172
|
+
].join("\n"),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function inspectBucketDir(bucketPath: string): "missing" | "usable" {
|
|
177
|
+
let entry: fs.Stats;
|
|
178
|
+
try {
|
|
179
|
+
entry = fs.lstatSync(bucketPath);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (fileSystemErrorCode(error) === "ENOENT") return "missing";
|
|
182
|
+
throw new Error(
|
|
183
|
+
[
|
|
184
|
+
"worktree_bucket_inspection_failed",
|
|
185
|
+
`GJC could not inspect the launch worktree bucket${fileSystemErrorCode(error) ? ` (${fileSystemErrorCode(error)})` : ""}.`,
|
|
186
|
+
`Path: ${formatBucketPath(bucketPath)}`,
|
|
187
|
+
"Safe remediation: verify that the bucket parent is accessible, then relaunch. GJC did not modify the entry.",
|
|
188
|
+
].join("\n"),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (entry.isDirectory()) return "usable";
|
|
192
|
+
if (!entry.isSymbolicLink()) throw bucketNotDirectoryError(bucketPath);
|
|
193
|
+
|
|
194
|
+
let target: fs.Stats;
|
|
195
|
+
try {
|
|
196
|
+
target = fs.statSync(bucketPath);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const code = fileSystemErrorCode(error);
|
|
199
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP") throw brokenBucketSymlinkError(bucketPath);
|
|
200
|
+
throw new Error(
|
|
201
|
+
[
|
|
202
|
+
"worktree_bucket_target_inspection_failed",
|
|
203
|
+
`GJC could not inspect the launch worktree bucket link target${code ? ` (${code})` : ""}.`,
|
|
204
|
+
`Path: ${formatBucketPath(bucketPath)}`,
|
|
205
|
+
"Safe remediation: verify that the link target is accessible, then relaunch. GJC did not modify the link.",
|
|
206
|
+
].join("\n"),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (target.isDirectory()) return "usable";
|
|
210
|
+
throw bucketNotDirectoryError(bucketPath, true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function ensureBucketDirUsable(bucketPath: string): void {
|
|
214
|
+
inspectBucketDir(bucketPath);
|
|
215
|
+
try {
|
|
216
|
+
fs.mkdirSync(bucketPath, { recursive: true });
|
|
217
|
+
} catch (error) {
|
|
218
|
+
// The entry can change between lstat/stat and mkdir. Re-inspect so a
|
|
219
|
+
// racing broken link or non-directory is still reported actionably.
|
|
220
|
+
inspectBucketDir(bucketPath);
|
|
221
|
+
const code = fileSystemErrorCode(error);
|
|
222
|
+
throw new Error(
|
|
223
|
+
[
|
|
224
|
+
"worktree_bucket_create_failed",
|
|
225
|
+
`GJC could not create or reuse the launch worktree bucket${code ? ` (${code})` : ""}.`,
|
|
226
|
+
`Path: ${formatBucketPath(bucketPath)}`,
|
|
227
|
+
"Safe remediation: verify parent permissions and bucket accessibility, then relaunch. GJC did not delete or replace any entry.",
|
|
228
|
+
].join("\n"),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
if (inspectBucketDir(bucketPath) === "missing") {
|
|
232
|
+
throw new Error(
|
|
233
|
+
[
|
|
234
|
+
"worktree_bucket_changed_during_preflight",
|
|
235
|
+
"The GJC launch worktree bucket disappeared while launch was preparing it.",
|
|
236
|
+
`Path: ${formatBucketPath(bucketPath)}`,
|
|
237
|
+
"Safe remediation: stabilize the bucket mount or parent directory, then relaunch. GJC did not delete or replace any entry.",
|
|
238
|
+
].join("\n"),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
141
243
|
function pruneStaleWorktreePath(repoRoot: string): void {
|
|
142
244
|
runGit(repoRoot, ["worktree", "prune"]);
|
|
143
245
|
}
|
|
@@ -267,7 +369,7 @@ export function ensureLaunchWorktree(
|
|
|
267
369
|
throw new Error(`branch_in_use:${plan.branchName}`);
|
|
268
370
|
}
|
|
269
371
|
|
|
270
|
-
|
|
372
|
+
ensureBucketDirUsable(path.dirname(plan.worktreePath));
|
|
271
373
|
const branchAlreadyExisted = plan.branchName ? branchExists(plan.repoRoot, plan.branchName) : false;
|
|
272
374
|
const args = ["worktree", "add"];
|
|
273
375
|
if (plan.detached) args.push("--detach", plan.worktreePath, plan.baseRef);
|