create-agent-rig 0.9.0 → 0.10.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 +466 -3
- package/README.md +267 -299
- package/package.json +8 -14
- package/packages/cli/dist/commands/create.js +56 -88
- package/packages/cli/dist/commands/doctor.js +213 -0
- package/packages/cli/dist/commands/init.js +151 -54
- package/packages/cli/dist/commands/integrations.js +468 -0
- package/packages/cli/dist/commands/memory.js +63 -4
- package/packages/cli/dist/commands/setup-wizard.js +61 -0
- package/packages/cli/dist/commands/uninstall.js +1223 -0
- package/packages/cli/dist/commands/upgrade.js +489 -89
- package/packages/cli/dist/index.js +657 -49
- package/packages/cli/dist/integrations/declaration.js +158 -0
- package/packages/cli/dist/integrations/doctor-guards.js +126 -0
- package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
- package/packages/cli/dist/integrations/mcp-json.js +107 -0
- package/packages/cli/dist/integrations/memory-doctor.js +124 -0
- package/packages/cli/dist/integrations/registry.js +22 -0
- package/packages/cli/dist/integrations/spawn.js +228 -0
- package/packages/cli/dist/integrations/spec-kit.js +280 -0
- package/packages/cli/dist/integrations/verify.js +193 -0
- package/packages/cli/dist/integrations/windows-job.js +275 -0
- package/packages/cli/dist/lib/elevated-paths.js +74 -0
- package/packages/cli/dist/lib/install-set.js +1 -44
- package/packages/cli/dist/lib/manifest.js +86 -9
- package/packages/cli/dist/lib/prompts.js +0 -23
- package/packages/cli/dist/lib/safe-path.js +193 -2
- package/packages/cli/dist/lib/safe-text.js +78 -0
- package/packages/cli/dist/lib/substitute.js +1 -52
- package/packages/cli/dist/lib/summary.js +5 -5
- package/packages/cli/dist/templates.js +4 -14
- package/scripts/prepare.mjs +28 -12
- package/templates/agent-os/subagent-routing.json +4 -4
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +13 -9
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
- package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
- package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
- package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
- package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
- package/templates/agent-os/universal/.claude/rules/workflow.md +47 -22
- package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
- package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
- package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +43 -0
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +18 -1
- package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
- package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +273 -35
- package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
- package/templates/agent-os/universal/.claude/settings.json +0 -8
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +13 -9
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
- package/templates/agent-os/universal/.codex/hooks.json +0 -10
- package/templates/agent-os/universal/AGENTS.md +208 -107
- package/templates/agent-os/universal/CLAUDE.md +15 -162
- package/templates/agent-os/universal/PLAN.md +17 -10
- package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
- package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
- package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
- package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
- package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
- package/templates/agent-os/universal/layers.json +25 -30
- package/templates/hash-history.json +73 -24
- package/templates/release-ledger.json +3 -1
- package/packages/cli/dist/lib/composition.js +0 -20
- package/packages/cli/dist/lib/targets.js +0 -28
- package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
- package/packages/cli/dist/policy/core/adapter.js +0 -18
- package/packages/cli/dist/policy/core/coverage.js +0 -253
- package/packages/cli/dist/policy/core/decision-record.js +0 -287
- package/packages/cli/dist/policy/core/declaration.js +0 -127
- package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
- package/packages/cli/dist/policy/core/probe.js +0 -442
- package/packages/cli/dist/policy/core/registry.js +0 -115
- package/packages/cli/dist/policy/core/validation.js +0 -275
- package/packages/cli/dist/policy/core/vocabulary.js +0 -123
- package/packages/cli/dist/policy/harness/claude.js +0 -47
- package/packages/cli/dist/policy/harness/codex.js +0 -87
- package/packages/cli/dist/policy/harness/index.js +0 -15
- package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
- package/packages/cli/dist/policy/index.js +0 -17
- package/templates/agent-os/init/AGENTS.md +0 -201
- package/templates/agent-os/init/CLAUDE.md +0 -201
- package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
- package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
- package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
- package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
- package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
- package/templates/skeleton/aws-serverless/README.md +0 -181
- package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
- package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
- package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
- package/templates/skeleton/aws-serverless/gitignore +0 -56
- package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
- package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
- package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
- package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
- package/templates/skeleton/aws-serverless/infra/package.json +0 -18
- package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
- package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
- package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
- package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
- package/templates/skeleton/aws-serverless/package.json +0 -30
- package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
- package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
- package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
- package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
- package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
- package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
- package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
- package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
- package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
- package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
- package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
- package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
- package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
- package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
- package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
- package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
- package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
- package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
- package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
- package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
- package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
- package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
- package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
- package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
- package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
- package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
- package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
- package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
- package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
- package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
- package/templates/skeleton/node-service/README.md +0 -104
- package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/node-service/apps/web/package.json +0 -19
- package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
- package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/node-service/eslint.config.mjs +0 -20
- package/templates/skeleton/node-service/gitignore +0 -54
- package/templates/skeleton/node-service/package.json +0 -30
- package/templates/skeleton/node-service/packages/core/package.json +0 -11
- package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
- package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
- package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
- package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/node-service/packages/db/package.json +0 -12
- package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
- package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
- package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
- package/templates/skeleton/node-service/packages/shared/package.json +0 -11
- package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
- package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
- package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
- package/templates/skeleton/node-service/services/api/package.json +0 -17
- package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
- package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
- package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
- package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
- package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
- package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
- package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
- package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
- package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
- package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
- package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
- package/templates/skeleton/node-service/services/worker/package.json +0 -16
- package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
- package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
- package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
- package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
- package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
- package/templates/skeleton/node-service/tsconfig.base.json +0 -15
- package/templates/skeleton/node-service/tsconfig.json +0 -13
- package/templates/skeleton/node-service/vitest.config.ts +0 -12
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# SessionStart hook output: one JSON wire format, both harnesses
|
|
2
|
+
|
|
3
|
+
Status: accepted for RP-185.
|
|
4
|
+
|
|
5
|
+
## Decision
|
|
6
|
+
|
|
7
|
+
`inject-rules.mjs` (the SessionStart hook that re-injects the autonomy rules on
|
|
8
|
+
startup, resume and compaction) prints one JSON object to stdout, unconditionally,
|
|
9
|
+
for both Claude Code and Codex:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"…"}}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
No trailing newline, no other top-level fields, no provider branching. The
|
|
16
|
+
`additionalContext` value carries exactly the text the hook used to write
|
|
17
|
+
directly to stdout (the `[agent-os] …` banner, the notice, the excerpted rules).
|
|
18
|
+
|
|
19
|
+
## Why
|
|
20
|
+
|
|
21
|
+
Measured, 2026-09-17, Codex 0.154.0 on Windows: with the previous plain-text
|
|
22
|
+
output — stdout beginning with the literal characters `[agent-os] Autonomy
|
|
23
|
+
rules refresh …` — Codex printed `Hook failed └ hook returned invalid session
|
|
24
|
+
start JSON output`, and the autonomy refresh never reached the session's
|
|
25
|
+
context. Claude Code was unaffected by that same plain-text form. A second
|
|
26
|
+
SessionStart hook in the same measured session, one that already emitted
|
|
27
|
+
`{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":…}}`,
|
|
28
|
+
did reach the session — which is what pointed at the wire format rather than at
|
|
29
|
+
"Codex rejects plain text" as the fix.
|
|
30
|
+
|
|
31
|
+
That framing matters because it is not what Codex's own documentation says.
|
|
32
|
+
[Codex's hooks reference](https://learn.chatgpt.com/docs/hooks) states plainly
|
|
33
|
+
that plain text on stdout is accepted for `session_start` and is added as
|
|
34
|
+
developer context — so "Codex does not support plain-text SessionStart output"
|
|
35
|
+
would be false, and this file does not claim it. What the page does not
|
|
36
|
+
document is *how* Codex decides a given stdout is JSON rather than plain text,
|
|
37
|
+
or what happens when that decision goes the wrong way. The measured symptom —
|
|
38
|
+
an output whose first character is `[` reported as *invalid* JSON, rather than
|
|
39
|
+
silently read as plain text — is consistent with a sniff that treats a leading
|
|
40
|
+
`[` (or `{`) as a signal to attempt a JSON parse, and reports a failure rather
|
|
41
|
+
than falling back when that parse does not succeed. That mechanism is inferred
|
|
42
|
+
from the symptom, not read off the page, and is stated here as an inference,
|
|
43
|
+
not a documented fact.
|
|
44
|
+
|
|
45
|
+
## Why the JSON envelope, not a reworded plain-text banner
|
|
46
|
+
|
|
47
|
+
The alternative fix — keep printing plain text, just not starting with `[` —
|
|
48
|
+
would have worked too, but it leaves the same undocumented sniffing behaviour
|
|
49
|
+
one character choice away from breaking again, for a reason nobody would think
|
|
50
|
+
to look for. The JSON form sidesteps the ambiguity entirely rather than
|
|
51
|
+
tip-toeing around it, and it turns out to need no provider branching, because
|
|
52
|
+
both harnesses already document the identical shape:
|
|
53
|
+
|
|
54
|
+
- **Codex** ([learn.chatgpt.com/docs/hooks](https://learn.chatgpt.com/docs/hooks)):
|
|
55
|
+
gives the worked example
|
|
56
|
+
`{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"Load the workspace conventions before editing."}}`
|
|
57
|
+
as JSON output for a `session_start` hook.
|
|
58
|
+
- **Claude Code** ([code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks)):
|
|
59
|
+
documents the same nested shape for `SessionStart` — `hookSpecificOutput`
|
|
60
|
+
with `hookEventName` (must equal `"SessionStart"`) and `additionalContext` —
|
|
61
|
+
and states that JSON is detected when stdout starts with `{` and ends with
|
|
62
|
+
`}`; anything else is treated as plain text and added to context the same
|
|
63
|
+
way. `systemMessage` and `terminalSequence` are also accepted fields there,
|
|
64
|
+
unused here because nothing today needs them.
|
|
65
|
+
|
|
66
|
+
One shape, honoured by both harnesses' own documentation, is what
|
|
67
|
+
`invariants.md` ("One mechanism, one implementation") asks for whenever a
|
|
68
|
+
single source can serve two consumers — the alternative, a provider check that
|
|
69
|
+
picks plain text for one harness and JSON for the other, would be exactly the
|
|
70
|
+
kind of guessed, undocumented branching this repository's rules warn against
|
|
71
|
+
("never invent APIs or behavior").
|
|
72
|
+
|
|
73
|
+
## What this does not claim
|
|
74
|
+
|
|
75
|
+
- Not that Codex rejects plain text in general — its own docs say otherwise.
|
|
76
|
+
- Not a documented account of Codex's JSON-vs-plain-text sniffing — that
|
|
77
|
+
mechanism is not published; only the fix (always emit the one documented
|
|
78
|
+
JSON shape) is asserted, not the internal reason it was needed.
|
|
79
|
+
- Not that `source: "startup"`, `"resume"` and `"compact"` are guaranteed
|
|
80
|
+
identical by either harness's spec beyond what each page states; both are
|
|
81
|
+
silent on any per-source difference in output handling, and the hook applies
|
|
82
|
+
the same output uniformly across all three because nothing in either
|
|
83
|
+
contract says to do otherwise.
|
|
84
|
+
|
|
85
|
+
## The exit path: exitCode vs exit(), and what it trades
|
|
86
|
+
|
|
87
|
+
The envelope change alone re-armed the original defect at a different trigger.
|
|
88
|
+
`inject-rules.mjs` ended with `process.exit(main())`, and `process.exit()` tears
|
|
89
|
+
the process down without waiting for a queued `stdout.write()` to drain. Under
|
|
90
|
+
the old plain-text wire format a write a pipe's buffer could not hold in one
|
|
91
|
+
piece degraded to *partial rules text* — readable, if incomplete. Under the
|
|
92
|
+
JSON envelope the same truncation is *invalid JSON* — precisely the state
|
|
93
|
+
Codex was measured rejecting wholesale, just moved from "the output starts
|
|
94
|
+
with `[`" to "the output was cut off mid-object". Exit code 0 either way, so
|
|
95
|
+
nothing downstream reports it.
|
|
96
|
+
|
|
97
|
+
Reproduced independently by two review passes at HEAD before the fix: a
|
|
98
|
+
consumer that does not start reading until well after the child would have
|
|
99
|
+
exited loses everything past the pipe's buffer — at one measurement, a 74 KB
|
|
100
|
+
rules file delivered 0 bytes; at another, 65536 of 73893. Both parse as
|
|
101
|
+
`Unterminated string`.
|
|
102
|
+
|
|
103
|
+
The fix is `process.exitCode = main()` in place of `process.exit(main())`.
|
|
104
|
+
Every path through `main()` returns `0`, so the exit STATUS does not change.
|
|
105
|
+
What changes is whether the process terminates before the write finishes:
|
|
106
|
+
`exitCode` lets Node's event loop drain naturally, which is what lets a large
|
|
107
|
+
payload actually reach a reader. Verified against the same probe shape that
|
|
108
|
+
found the defect, at four payload sizes (7 083 B through 1 002 552 B) and
|
|
109
|
+
three consumer shapes (a non-draining reader, a slow reader at 4 KiB/50 ms,
|
|
110
|
+
and a plain file redirect): every case delivered the complete envelope and
|
|
111
|
+
parsed. Pinned in the generator's `hooks.test.ts` (absent in a generated rig)
|
|
112
|
+
› "delivers the whole envelope even when the reader does not drain until
|
|
113
|
+
process.exit(main()) would already have torn the process down", which goes
|
|
114
|
+
red (`Unterminated string`, at a byte count that is host-dependent — kernel
|
|
115
|
+
pipe buffer size and scheduler timing both vary) if the single line is
|
|
116
|
+
reverted. The pinned payload is sized for a deterministic kill rather than a
|
|
117
|
+
merely likely one: a smaller payload truncated on nearly every reversion run
|
|
118
|
+
on every host checked, but not every one, and a pin the defect can slip
|
|
119
|
+
through occasionally is a pin that will eventually be green on a real
|
|
120
|
+
revert. That pin's coverage is Linux-shaped: Node documents pipe writes as
|
|
121
|
+
synchronous on Windows and asynchronous on POSIX, so the same reversion is
|
|
122
|
+
expected to have little or nothing to catch on a Windows lane — the test's
|
|
123
|
+
own comment says so, so a future reader does not mistake a Linux-only kill
|
|
124
|
+
for cross-platform cover.
|
|
125
|
+
|
|
126
|
+
**What this trades away, stated plainly rather than left to be discovered:**
|
|
127
|
+
`process.exit()` also GUARANTEED teardown, and `exitCode` does not. A consumer
|
|
128
|
+
that never reads stdout at all no longer gets a fast, wrong exit 0 — it gets
|
|
129
|
+
a hook that stays alive indefinitely, waiting on the write. Measured: still
|
|
130
|
+
running 8 seconds in in one review's reproduction, at 74 KB and 1 MB payloads
|
|
131
|
+
with nobody draining; completing the instant a reader appeared. A probe built
|
|
132
|
+
to refuse reading until the child would already have exited measurably
|
|
133
|
+
DEADLOCKS this version, where `process.exit()` would have terminated
|
|
134
|
+
(truncated, but terminated). Nothing in this file bounds that wait — the
|
|
135
|
+
calling harness's own hook timeout does. Not reachable at the size this hook
|
|
136
|
+
ships today (a few KB, well under a second to write), but a real behaviour
|
|
137
|
+
change on a project whose `autonomy.md` grows large, or whose harness stops
|
|
138
|
+
reading a hook's stdout entirely. The trade is made on purpose: a loud hang
|
|
139
|
+
bounded by the harness's own timeout is preferred over a silent, truncated
|
|
140
|
+
"success" with no bound on how wrong it can be.
|
|
141
|
+
|
|
142
|
+
A second, smaller consequence of the same change: a reader that vanishes
|
|
143
|
+
MID-write (a closed pipe, a harness that kills this process before reading)
|
|
144
|
+
now surfaces as an unhandled `error` event on `process.stdout` — exit 1 with
|
|
145
|
+
a Node stack trace on stderr, where the old `process.exit()` path exited 0
|
|
146
|
+
silently in the same situation. The handler is narrow rather than blanket,
|
|
147
|
+
and the difference is measured, not theoretical: an earlier draft of this
|
|
148
|
+
fix silenced every stdout error unconditionally, and security review found
|
|
149
|
+
that with stdout redirected to `/dev/full` — a genuine write failure
|
|
150
|
+
(ENOSPC), with the reader still fully attached — that blanket form exited 0
|
|
151
|
+
with nothing delivered and no diagnostic, the exact silent-loss shape this
|
|
152
|
+
whole file exists to avoid, moved one write call over. The handler now
|
|
153
|
+
distinguishes the two: EPIPE (the reader is gone; there is nothing left to
|
|
154
|
+
report to) stays silent, and anything else is written to stderr and marks
|
|
155
|
+
the exit non-zero. Pinned in the generator's `hooks.test.ts` (absent in a
|
|
156
|
+
generated rig) › "silently exits 0 when the reader is gone before the write
|
|
157
|
+
starts (EPIPE)" and › "reports a genuine stdout write failure on stderr and
|
|
158
|
+
marks the exit non-zero, rather than looking like a healthy session".
|
|
159
|
+
|
|
160
|
+
**Left for a separate decision, not for this one:** seven sibling hooks in
|
|
161
|
+
this same directory still end in `process.exit(…)` with no wait for a
|
|
162
|
+
pending write — `block-no-verify.mjs`, `guard-rulebook.mjs`,
|
|
163
|
+
`guard-subagent-model.mjs`, `guard-bash.mjs`, `guard-secret-file.mjs`
|
|
164
|
+
(`process.exit(status)`), `gate-stop-dod.mjs` (`process.exit(code)`) and
|
|
165
|
+
`warn-subagent-routing.mjs` (the first four and the last end
|
|
166
|
+
`process.exit(main())`). Their payloads are short (a refusal message, not a
|
|
167
|
+
whole rules file), so the exposure is far smaller, but the reasoning above
|
|
168
|
+
now lives in one hook's comments only — `invariants.md`'s "one mechanism,
|
|
169
|
+
one implementation" would ask for the same pattern everywhere it applies.
|
|
170
|
+
This change deliberately does not touch the other seven: changing every
|
|
171
|
+
`process.exit()` call in the hooks directory in a PR whose stated purpose is
|
|
172
|
+
a SessionStart wire-format fix is exactly the scope creep `autonomy.md`'s
|
|
173
|
+
Tier-2 discipline exists to catch. Recorded here so the inconsistency is a
|
|
174
|
+
known, named backlog item rather than something the next reader has to
|
|
175
|
+
rediscover.
|
|
176
|
+
|
|
177
|
+
## Risk and rollback
|
|
178
|
+
|
|
179
|
+
Tier 2 (`templates/agent-os/universal/.claude/hooks/` is a declared elevated
|
|
180
|
+
path) for both decisions this record carries, each with its own risk and its
|
|
181
|
+
own rollback:
|
|
182
|
+
|
|
183
|
+
**The wire format.** The blast radius is narrow: this hook's own stdout
|
|
184
|
+
contract, read only by the two harnesses' SessionStart machinery. If either
|
|
185
|
+
harness's documented shape turns out to differ from what was fetched here, or
|
|
186
|
+
a future harness version stops accepting it, rollback is reverting
|
|
187
|
+
`inject-rules.mjs` to write plain text again — a one-line change, the same
|
|
188
|
+
one this decision replaces. The old plain-text form is pinned by a regression
|
|
189
|
+
test precisely so it is not reintroduced by accident while chasing an
|
|
190
|
+
unrelated fix: the generator's `hooks.test.ts` (absent in a generated rig) ›
|
|
191
|
+
"never regresses to the old bare [agent-os]-prefixed plain-text stdout".
|
|
192
|
+
|
|
193
|
+
**The exit path** ("The exit path: exitCode vs exit(), and what it trades",
|
|
194
|
+
above). The blast radius is this hook's shutdown behaviour, not its output
|
|
195
|
+
shape: a consumer that never drains stdout at all now holds this process
|
|
196
|
+
alive rather than letting it exit truncated, bounded only by the calling
|
|
197
|
+
harness's own hook timeout. If that trade turns out to be wrong — a harness
|
|
198
|
+
with no such timeout, or one where a hung hook process is worse than a
|
|
199
|
+
truncated one — rollback is reverting the single `process.exitCode = main()`
|
|
200
|
+
line to `process.exit(main())`, independently of the wire-format decision
|
|
201
|
+
above; the two lines do not depend on each other. That reintroduces the
|
|
202
|
+
flush defect this record measures, so a revert of this line alone should
|
|
203
|
+
also remove or explicitly override the test that pins it: the generator's
|
|
204
|
+
`hooks.test.ts` (absent in a generated rig) › "delivers the whole envelope
|
|
205
|
+
even when the reader does not drain until process.exit(main()) would already
|
|
206
|
+
have torn the process down".
|
|
@@ -11,7 +11,7 @@ Each named subagent this rig ships pins the model and the effort it reads with:
|
|
|
11
11
|
| Role | Claude Code | Codex |
|
|
12
12
|
| --- | --- | --- |
|
|
13
13
|
| `code-reviewer`, `security-scanner`, a stack's infrastructure reviewer | `claude-opus-5`, `high` | `gpt-5.6-sol`, `high` |
|
|
14
|
-
| `test-writer`, `prose-reviewer` | `claude-sonnet-5`, `high` | `gpt-5.6-terra`, `high` |
|
|
14
|
+
| `test-writer`, `implementation-agent`, `prose-reviewer` | `claude-sonnet-5`, `high` | `gpt-5.6-terra`, `high` |
|
|
15
15
|
| a subagent with no definition | `claude-sonnet-5`; effort follows the session | `gpt-5.6-terra`, `medium` |
|
|
16
16
|
|
|
17
17
|
In this project the pins are the files themselves: `model:` and `effort:` in
|
|
@@ -93,8 +93,11 @@ environment: `subagent-routing.test.ts` (absent in a generated rig) ›
|
|
|
93
93
|
In a generated project the definitions are the policy (next section), so an agent
|
|
94
94
|
a project adds with a pin is a role, and a role whose definition is changed to
|
|
95
95
|
`model: inherit` has stopped being one — by that project's reviewed decision.
|
|
96
|
-
- **The built-in agents get no definitions here.**
|
|
97
|
-
|
|
96
|
+
- **The built-in agents get no definitions here.** Ordinary implementation has its
|
|
97
|
+
own role, `implementation-agent`, on the same tier as `test-writer`: frequent,
|
|
98
|
+
bounded work, not the reviewer tier. A general-purpose subagent remains for
|
|
99
|
+
ad-hoc work no role covers, and follows the unnamed default like any subagent
|
|
100
|
+
without a definition.
|
|
98
101
|
- **The warning is a warning.** A session started with `CLAUDE_CODE_EFFORT_LEVEL` set
|
|
99
102
|
still runs every gate — at that level.
|
|
100
103
|
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Why the workflow layer is opt-in, and where the line falls
|
|
2
|
+
|
|
3
|
+
The rule lives in `CLAUDE.md`, under "The opt-in workflow layer (experimental)".
|
|
4
|
+
This file explains why `layers.json` (RP-180) draws the line where it does,
|
|
5
|
+
and why a few files that look like PR-lifecycle automation stayed in Lean Core
|
|
6
|
+
instead of moving with the rest of it.
|
|
7
|
+
|
|
8
|
+
## What moved, and why
|
|
9
|
+
|
|
10
|
+
Before this split, `templates/agent-os/universal/layers.json` had one array,
|
|
11
|
+
and `init`/`create` installed all of it unconditionally — including the queue
|
|
12
|
+
adapter, the `loop` and `pr-ship` skills, run-state, the run journal,
|
|
13
|
+
revalidation, claim-records and the PR-lifecycle helpers named below. None of
|
|
14
|
+
that is required by Lean Core: a thin CRUD service or a solo-maintained
|
|
15
|
+
library gets the same review gates and stop rules with none of the
|
|
16
|
+
autonomous, cooperative machinery, and shipping it anyway made every fresh
|
|
17
|
+
rig carry queue-selection semantics it never asked for.
|
|
18
|
+
|
|
19
|
+
The workflow layer is what a project opts into with `init --layer workflow`:
|
|
20
|
+
the queue adapter (`.claude/scripts/queue/`, `.claude/queue.json`), the
|
|
21
|
+
`loop` and `pr-ship` skills, `run-state.mjs`, the run journal
|
|
22
|
+
(`run-journal.mjs` — see the one exception below —, `journal/README.md`),
|
|
23
|
+
revalidation and claim-records, and the PR-lifecycle helpers
|
|
24
|
+
`decision-router.mjs`, `detect-missed-gate.mjs` and
|
|
25
|
+
`reconcile-external-prs.mjs`.
|
|
26
|
+
|
|
27
|
+
## The exception: why `verdict.mjs`, `lib/verdict.mjs`, `lib/gate-coverage.mjs`
|
|
28
|
+
## and `run-journal.mjs` stayed in Lean Core
|
|
29
|
+
|
|
30
|
+
These four read as PR-lifecycle automation — the ticket's own wording groups
|
|
31
|
+
"PR-lifecycle helpers" with the workflow layer — and the first draft of this
|
|
32
|
+
split moved them too. It does not survive contact with what Lean Core
|
|
33
|
+
actually installs, for one mechanical reason: `check-premises` is a Lean Core
|
|
34
|
+
skill (a session runs it standalone, with no queue and no loop, "before the
|
|
35
|
+
Red step" and again "before the gate"), and its own second entry point tells
|
|
36
|
+
the reader to run `node .claude/scripts/verdict.mjs check <report>
|
|
37
|
+
check-premises` on its own output. `.claude/scripts/verdict.mjs` is a single
|
|
38
|
+
module with **static, top-level imports** of `./lib/gate-coverage.mjs` and
|
|
39
|
+
`./run-journal.mjs` — Node resolves the whole file before any subcommand
|
|
40
|
+
runs, so a core-only install that shipped `verdict.mjs` without those two
|
|
41
|
+
would break `check-premises`'s own documented command on the very first
|
|
42
|
+
`check` subcommand, not only on `coverage`.
|
|
43
|
+
|
|
44
|
+
The three reviewer agent specs (`code-reviewer.md`, `security-scanner.md`,
|
|
45
|
+
`prose-reviewer.md`) are Lean Core for the same reason `check-premises` is —
|
|
46
|
+
review applies to any PR, loop-driven or not — and all three cite
|
|
47
|
+
`.claude/scripts/verdict.mjs` as the shape their own report is checked
|
|
48
|
+
against.
|
|
49
|
+
|
|
50
|
+
So the dependency runs from Lean Core outward: `check-premises` (core) needs
|
|
51
|
+
`verdict.mjs` (core) to load at all, `verdict.mjs`'s own module graph needs
|
|
52
|
+
`lib/gate-coverage.mjs` and `run-journal.mjs`, and neither of those two
|
|
53
|
+
imports anything outside itself (`node:fs`, `node:path`, and in
|
|
54
|
+
`gate-coverage.mjs`'s case nothing at all — the mention of
|
|
55
|
+
`decision-router.mjs` in its header is a comment, not an import). Moving the
|
|
56
|
+
leaves and leaving the root behind was the option that did not exist;
|
|
57
|
+
`invariants.md`'s "a file a core guard imports must stay core" generalises
|
|
58
|
+
past hooks to this case on the same reasoning.
|
|
59
|
+
|
|
60
|
+
`decision-router.mjs` itself has no such pull: `lib/gate-coverage.mjs`
|
|
61
|
+
mentions it only in a comment, and the one real static import of it
|
|
62
|
+
(`.claude/hooks/lib/edit-input.mjs`) is also a comment, not code. It moved
|
|
63
|
+
with the rest of the workflow layer, and `workflow.md`'s "PR flow" section
|
|
64
|
+
states the Lean Core fallback when it is not installed: every change still
|
|
65
|
+
reaches `code-reviewer`, and a human or the session decides which additional
|
|
66
|
+
reviewers apply, by the same triggers `decision-router` would have read.
|
|
67
|
+
|
|
68
|
+
`docs/decisions/gate-coverage.md` stayed with `pr-ship` (workflow) rather
|
|
69
|
+
than `lib/gate-coverage.mjs` (core) for a different reason: nothing in Lean
|
|
70
|
+
Core cites the record, only `pr-ship`'s own skill file does. The module and
|
|
71
|
+
the rationale for *why it exists* travel separately on purpose — the module
|
|
72
|
+
is a dependency of a core skill's documented command, the essay about why a
|
|
73
|
+
hook version of the same check was rejected is not.
|
|
74
|
+
|
|
75
|
+
## The freeze this split does not touch
|
|
76
|
+
|
|
77
|
+
RP-53 holds revalidation and claim-records' *behavior* frozen through the
|
|
78
|
+
RP-26 gate (2026-10-27): no feature expansion, no experiment restart, no bias
|
|
79
|
+
introduced by this or any other 0.10 change. This split moves their
|
|
80
|
+
*install-time layer* only — which `layers.json` array they sit in — and
|
|
81
|
+
changes not one line of `revalidate.mjs`, `revalidation-report.mjs`,
|
|
82
|
+
`lib/revalidation-evidence.mjs`, `lib/revalidation-points.mjs` or
|
|
83
|
+
`lib/claim-records.mjs` beyond that relocation. After the gate: GO retains
|
|
84
|
+
them in the optional workflow layer as they are today; NO-GO removes them in
|
|
85
|
+
a bounded follow-up. Missing evidence is not a NO-GO.
|
|
86
|
+
|
|
87
|
+
## What the manifest records, and the direction that matters
|
|
88
|
+
|
|
89
|
+
`RigManifest.layers` records which layer(s) a rig installed, and `upgrade`
|
|
90
|
+
refreshes only what it names. The one detail worth restating here because
|
|
91
|
+
getting it backwards is silent and destructive: a manifest written **before**
|
|
92
|
+
this field existed recorded nothing about layers because there was only one
|
|
93
|
+
payload to record — so `parseManifest` reads that absence as `['process',
|
|
94
|
+
'workflow']`, never as `['process']`. A fresh `init` with no
|
|
95
|
+
`--layer workflow` flag writes `['process']` explicitly. The two defaults
|
|
96
|
+
point in opposite directions on purpose, and swapping them would make the
|
|
97
|
+
very next `upgrade` on an existing dogfood repository report every workflow
|
|
98
|
+
file it already has as `retired` and stop managing it.
|
|
99
|
+
|
|
100
|
+
## Interaction with `uninstall` (RP-181, merged after this split was written)
|
|
101
|
+
|
|
102
|
+
`uninstall` needs no separate awareness of `layers` at all, and gains none:
|
|
103
|
+
it walks `manifest.files` and `manifest.kept` directly (`commands/uninstall.ts`),
|
|
104
|
+
and both of those already name exactly the paths a given install wrote or
|
|
105
|
+
found, regardless of which `layers.json` array a path happens to live in. Three
|
|
106
|
+
cases, all covered by the same unmodified byte-hash check:
|
|
107
|
+
|
|
108
|
+
- **A Core-only rig** (no `--layer workflow`) never has a workflow-layer path in
|
|
109
|
+
`manifest.files` in the first place — `initManifest` only reads the layers
|
|
110
|
+
`effectiveLayers` resolved — so `uninstall` has nothing workflow-shaped to
|
|
111
|
+
remove and reports nothing about it.
|
|
112
|
+
- **A rig that opted into the workflow layer** has those paths in
|
|
113
|
+
`manifest.files` exactly like any process-layer path, and `uninstall` removes
|
|
114
|
+
or preserves them by the same pristine-bytes check as everything else it
|
|
115
|
+
owns.
|
|
116
|
+
- **An inherited pre-0.10 rig** (a manifest with no `layers` key at all) had its
|
|
117
|
+
workflow files recorded in `files` the only way any release before this one
|
|
118
|
+
ever wrote a manifest — there was no `kept`/`files` split by layer to begin
|
|
119
|
+
with — so `uninstall` already treats them as owned, the same as it always
|
|
120
|
+
has. Nothing here widens what `uninstall` is willing to remove, and nothing
|
|
121
|
+
narrows it: a manifest either names a path in `files`/`kept` with the right
|
|
122
|
+
hash, or `uninstall` leaves it alone, and that rule does not read `layers`.
|
|
123
|
+
|
|
124
|
+
Pinned in `packages/cli/test/uninstall.test.ts` (absent in a generated rig —
|
|
125
|
+
this is the generator's own test suite, not a payload path) › "removes an
|
|
126
|
+
installed workflow-layer file exactly like any Core file, when the rig opted
|
|
127
|
+
in", › "a workflow-layer file never installed here (Core-only rig) is simply
|
|
128
|
+
absent from the manifest, never reported \"not a path this release installs\"",
|
|
129
|
+
and › "an inherited pre-layers manifest (no `layers` key) still owns its
|
|
130
|
+
workflow files, exactly like any other installed path".
|
|
131
|
+
|
|
132
|
+
## There is no opt-out short of `uninstall`
|
|
133
|
+
|
|
134
|
+
Once a rig has the workflow layer — `init --layer workflow`, or inherited
|
|
135
|
+
from before RP-180 — nothing in `init` or `upgrade` ever drops a layer a
|
|
136
|
+
READABLE manifest already recorded; `effectiveLayers` (`init.ts`) is
|
|
137
|
+
additive by construction, full stop. `detectLayersOnDisk` (`upgrade.ts`) is
|
|
138
|
+
a narrower claim: it only ever runs when there is NO readable manifest at
|
|
139
|
+
all, and even then it does not treat "a layer's files exist" as license to
|
|
140
|
+
keep them regardless of how many — it is additive in the same direction
|
|
141
|
+
(never narrows what a READABLE manifest said), but on the bootstrapped path
|
|
142
|
+
it decides per layer by quorum (`LAYER_ADOPTION_QUORUM`, more than half of
|
|
143
|
+
the layer's own files present), and can legitimately decide a layer is NOT
|
|
144
|
+
this rig's: a Core-only rig with one or a few stray files that happen to
|
|
145
|
+
share a workflow-layer path is left at Core, its stray files untouched and
|
|
146
|
+
unrecorded (round 4, blocker A — round 3's fix asked only "does at least one
|
|
147
|
+
file exist", which re-adopted the whole layer from a single stray path).
|
|
148
|
+
The only supported way back to Core-only for a rig the manifest genuinely
|
|
149
|
+
still names as having the workflow layer is `uninstall` (removing the
|
|
150
|
+
workflow files this rig owns) followed by a fresh `init` with no `--layer`
|
|
151
|
+
flag — see "The exact opt-out procedure, measured" below for what that
|
|
152
|
+
takes when `uninstall` had to preserve something.
|
|
153
|
+
|
|
154
|
+
**Hand-editing `layers` in `.claude/.rig-manifest.json` down to `["process"]`
|
|
155
|
+
is not that opt-out, and it does not do what it looks like it does.** The
|
|
156
|
+
files themselves are untouched by the edit itself — nothing deletes them —
|
|
157
|
+
but the next `upgrade` reads the manifest's `layers` as authoritative
|
|
158
|
+
(`upgrade.ts`'s `initInstallSet(repoDir, project, layers)`), so every
|
|
159
|
+
workflow path drops out of that plan's own install set and is reported
|
|
160
|
+
`retired`, reason `"no longer shipped by this release — the rig no longer
|
|
161
|
+
manages it; it is now yours"` — the same verdict RP-177 gave a deleted stack
|
|
162
|
+
overlay, applied here to files that are simply no longer read as this rig's
|
|
163
|
+
layer. `applyUpgrade` then writes a manifest whose `files` map has no entry
|
|
164
|
+
for any of the roughly three dozen workflow paths at all — measured, on a
|
|
165
|
+
clean `--layer workflow` install, at exactly 89 file entries down to 56
|
|
166
|
+
(`packages/cli/test/upgrade.test.ts`, absent in a generated rig, pins "a
|
|
167
|
+
clean workflow-layer install hand-edited down to a core-only layers array
|
|
168
|
+
goes from 89 manifest entries to 56"; the figure moved by one from an
|
|
169
|
+
earlier 86/53 when RP-186 added `docs/decisions/agents-md-canonical.md` to
|
|
170
|
+
the process layer, and by two more when `implementation-agent` joined it).
|
|
171
|
+
The files stay on disk, silently un-hashed and unowned —
|
|
172
|
+
and a LATER `uninstall`, reading the same manifest, has nothing there to
|
|
173
|
+
recognise them by: they read as an ordinary foreign/untracked path, not as
|
|
174
|
+
something this release ever installed, and are left alone. The manifest
|
|
175
|
+
itself does not warn about this because it has no opinion on why `layers`
|
|
176
|
+
changed; the tool trusts its own evidence file. This is measured behaviour,
|
|
177
|
+
not a guess: `packages/cli/test/upgrade.test.ts` (absent in a generated rig)
|
|
178
|
+
pins the `retired` verdict and the orphaning it causes in "hand-editing
|
|
179
|
+
`layers` down to `["process"]` on a rig that already has the workflow
|
|
180
|
+
layer retires every workflow file — on disk, unowned, never deleted".
|
|
181
|
+
|
|
182
|
+
**The mirror hand-edit — `layers: ["workflow"]` alone, dropping `process` —
|
|
183
|
+
does the identical thing in the other direction, on a still-readable
|
|
184
|
+
manifest.** `initInstallSet` reads exactly the array it is given; a `layers`
|
|
185
|
+
that never names `process` un-owns every Core file the same way dropping
|
|
186
|
+
`workflow` un-owns every workflow file: the next `upgrade` retires them from
|
|
187
|
+
the plan and drops them from `files`, silently, files untouched on disk.
|
|
188
|
+
(This is not the bootstrapped path's own normalisation — `detectLayersOnDisk`
|
|
189
|
+
always forces `process` into a layer set it INFERS from disk; a READABLE
|
|
190
|
+
manifest is trusted as written, `process` included, and nothing here
|
|
191
|
+
prevents an operator from writing one that omits it.)
|
|
192
|
+
|
|
193
|
+
## The exact opt-out procedure, measured (RP-180 round 4, blocker B)
|
|
194
|
+
|
|
195
|
+
`uninstall --yes` followed by a fresh `init` (no `--layer` flag) reaches
|
|
196
|
+
Core-only in exactly ONE of the two cases that matter, and round 3 stated it
|
|
197
|
+
as though it always worked. Measured on the built CLI, both ways:
|
|
198
|
+
|
|
199
|
+
- **Nothing on the rig was ever edited.** `uninstall --yes` removes every
|
|
200
|
+
file it owns — Core and workflow alike — and, because nothing was left to
|
|
201
|
+
preserve, deletes the manifest too. A fresh `init` then finds no manifest
|
|
202
|
+
and no workflow files: Core-only, as documented. A fresh `init` then
|
|
203
|
+
installs Core only.
|
|
204
|
+
- **Anything was edited — one file is enough.** `uninstall --yes` preserves
|
|
205
|
+
that one file (and reports it) and, because something was preserved,
|
|
206
|
+
**keeps the manifest** — still recording `layers: ["process","
|
|
207
|
+
workflow"]`. The next plain `init` reads that surviving manifest
|
|
208
|
+
(`effectiveLayers` in `init.ts`: `previous?.layers.includes('workflow')`)
|
|
209
|
+
and reinstalls the entire workflow layer right back. `uninstall --yes` leaves
|
|
210
|
+
the one preserved file plus the manifest, and a plain `init` afterward
|
|
211
|
+
brings the whole workflow layer back, `layers` still both. The
|
|
212
|
+
"fresh `init`" instruction alone does NOT reach Core-only here — round 3
|
|
213
|
+
said it did.
|
|
214
|
+
|
|
215
|
+
**The procedure that actually reaches Core-only when something was
|
|
216
|
+
preserved: pass `--detach`.** `uninstall --yes --detach` performs the
|
|
217
|
+
identical safe removal and then deletes the manifest regardless of what is
|
|
218
|
+
left behind, handing the preserved file(s) over to the operator outright
|
|
219
|
+
(they stay on disk, no longer named by anything). The next plain `init`
|
|
220
|
+
then finds no manifest at all and installs Core only. A fresh `init` then installs
|
|
221
|
+
Core, leaves the preserved file untouched, and records `layers: ["process"]`. No new flag
|
|
222
|
+
was added for this — `--detach` already existed (RP-181) for the identical
|
|
223
|
+
reason: a manifest kept alive only by something it has no business
|
|
224
|
+
prescribing further action over.
|
|
225
|
+
|
|
226
|
+
Equivalently, without `--detach`: remove the file(s) `uninstall --yes`
|
|
227
|
+
reported as preserved, then remove `.claude/.rig-manifest.json` by hand,
|
|
228
|
+
then run a plain `init`. Both procedures were measured to reach the same
|
|
229
|
+
end state; `--detach` is the one to recommend because it is one command
|
|
230
|
+
instead of a report a human has to read and act on by hand.
|
|
231
|
+
|
|
232
|
+
Pinned end-to-end on the built CLI in `test/e2e/uninstall.test.ts` (absent in
|
|
233
|
+
a generated rig) › `describe('the opt-out procedure to Core-only, measured
|
|
234
|
+
(RP-180 round 4, blocker B)')`: both cases end in `layers: ["process"]` and
|
|
235
|
+
no un-preserved workflow file left on disk.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
".claude/skills/new-invariant/guard-invariant.example.mjs",
|
|
8
8
|
".claude/skills/new-invariant/guard-invariant.example.test.mjs",
|
|
9
9
|
".claude/agents/test-writer.md",
|
|
10
|
+
".claude/agents/implementation-agent.md",
|
|
10
11
|
".claude/agents/code-reviewer.md",
|
|
11
12
|
".claude/agents/security-scanner.md",
|
|
12
13
|
".claude/agents/prose-reviewer.md",
|
|
@@ -20,41 +21,52 @@
|
|
|
20
21
|
".claude/hooks/warn-subagent-routing.mjs",
|
|
21
22
|
".claude/hooks/lib/edit-input.mjs",
|
|
22
23
|
".claude/hooks/lib/hook-input.mjs",
|
|
23
|
-
".claude/skills/pr-ship/SKILL.md",
|
|
24
|
-
".claude/skills/loop/SKILL.md",
|
|
25
24
|
".claude/skills/check-premises/SKILL.md",
|
|
26
25
|
".claude/skills/worktree-task/SKILL.md",
|
|
27
26
|
".agents/skills/new-invariant/SKILL.md",
|
|
28
27
|
".agents/skills/new-invariant/guard-invariant.example.mjs",
|
|
29
28
|
".agents/skills/new-invariant/guard-invariant.example.test.mjs",
|
|
30
|
-
".agents/skills/pr-ship/SKILL.md",
|
|
31
|
-
".agents/skills/loop/SKILL.md",
|
|
32
29
|
".agents/skills/check-premises/SKILL.md",
|
|
33
30
|
".agents/skills/worktree-task/SKILL.md",
|
|
34
31
|
".codex/agents/test-writer.toml",
|
|
32
|
+
".codex/agents/implementation-agent.toml",
|
|
35
33
|
".codex/agents/code-reviewer.toml",
|
|
36
34
|
".codex/agents/security-scanner.toml",
|
|
37
35
|
".codex/agents/prose-reviewer.toml",
|
|
38
|
-
".claude/scripts/detect-missed-gate.mjs",
|
|
39
|
-
".claude/scripts/decision-router.mjs",
|
|
40
|
-
".claude/scripts/reconcile-external-prs.mjs",
|
|
41
36
|
".claude/scripts/stop-flag.mjs",
|
|
42
37
|
".claude/scripts/unattended-flag.mjs",
|
|
43
38
|
".claude/scripts/git-env.mjs",
|
|
44
|
-
".claude/scripts/preflight.mjs",
|
|
45
39
|
".claude/scripts/doctor.mjs",
|
|
46
40
|
".claude/scripts/run-journal.mjs",
|
|
47
|
-
".claude/scripts/run-state.mjs",
|
|
48
41
|
".claude/scripts/verdict.mjs",
|
|
42
|
+
".claude/scripts/lib/gate-coverage.mjs",
|
|
43
|
+
".claude/scripts/lib/verdict.mjs",
|
|
44
|
+
".claude/scripts/lib/secrets.mjs",
|
|
45
|
+
".claude/scripts/lib/shell-tools.mjs",
|
|
46
|
+
"PLAN.md",
|
|
47
|
+
"docs/decisions/fail-open-guards.md",
|
|
48
|
+
"docs/decisions/codex-adapter.md",
|
|
49
|
+
"docs/decisions/subagent-routing.md",
|
|
50
|
+
"docs/decisions/review-lanes.md",
|
|
51
|
+
"docs/decisions/workflow-layer-split.md",
|
|
52
|
+
"docs/decisions/session-start-wire-format.md",
|
|
53
|
+
"docs/decisions/agents-md-canonical.md"
|
|
54
|
+
],
|
|
55
|
+
"workflow": [
|
|
56
|
+
".claude/skills/pr-ship/SKILL.md",
|
|
57
|
+
".claude/skills/loop/SKILL.md",
|
|
58
|
+
".agents/skills/pr-ship/SKILL.md",
|
|
59
|
+
".agents/skills/loop/SKILL.md",
|
|
60
|
+
".claude/scripts/detect-missed-gate.mjs",
|
|
61
|
+
".claude/scripts/decision-router.mjs",
|
|
62
|
+
".claude/scripts/reconcile-external-prs.mjs",
|
|
63
|
+
".claude/scripts/preflight.mjs",
|
|
64
|
+
".claude/scripts/run-state.mjs",
|
|
49
65
|
".claude/scripts/revalidate.mjs",
|
|
50
66
|
".claude/scripts/revalidation-report.mjs",
|
|
51
|
-
".claude/scripts/lib/gate-coverage.mjs",
|
|
52
67
|
".claude/scripts/lib/claim-records.mjs",
|
|
53
68
|
".claude/scripts/lib/revalidation-evidence.mjs",
|
|
54
69
|
".claude/scripts/lib/revalidation-points.mjs",
|
|
55
|
-
".claude/scripts/lib/verdict.mjs",
|
|
56
|
-
".claude/scripts/lib/secrets.mjs",
|
|
57
|
-
".claude/scripts/lib/shell-tools.mjs",
|
|
58
70
|
".claude/scripts/queue/core.mjs",
|
|
59
71
|
".claude/scripts/queue/plan-md.mjs",
|
|
60
72
|
".claude/scripts/queue/github-issues.mjs",
|
|
@@ -66,30 +78,13 @@
|
|
|
66
78
|
".claude/scripts/queue/gate-rounds.mjs",
|
|
67
79
|
".claude/queue.json",
|
|
68
80
|
".rig/revalidation.json",
|
|
69
|
-
"PLAN.md",
|
|
70
81
|
"journal/README.md",
|
|
71
82
|
"docs/decisions/gate-coverage.md",
|
|
72
|
-
"docs/decisions/fail-open-guards.md",
|
|
73
|
-
"docs/decisions/codex-adapter.md",
|
|
74
|
-
"docs/decisions/subagent-routing.md",
|
|
75
83
|
"docs/decisions/closing-a-task.md",
|
|
76
84
|
"docs/decisions/content-blind-revalidation.md",
|
|
77
|
-
"docs/decisions/review-lanes.md",
|
|
78
85
|
"docs/decisions/run-directory.md",
|
|
79
86
|
"docs/decisions/spacing-rations-mechanisms.md",
|
|
80
87
|
"docs/decisions/stop-conditions-in-a-file.md",
|
|
81
88
|
"docs/decisions/two-empty-endings.md"
|
|
82
|
-
],
|
|
83
|
-
"architecture": [
|
|
84
|
-
".claude/rules/architecture.md",
|
|
85
|
-
".claude/hooks/guard-core-purity.mjs",
|
|
86
|
-
".claude/hooks/guard-web-boundary.mjs"
|
|
87
|
-
],
|
|
88
|
-
"meta": [
|
|
89
|
-
".claude/settings.json",
|
|
90
|
-
".codex/config.toml",
|
|
91
|
-
".codex/hooks.json",
|
|
92
|
-
"CLAUDE.md",
|
|
93
|
-
"AGENTS.md"
|
|
94
89
|
]
|
|
95
90
|
}
|