create-agent-rig 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +152 -1
- package/README.md +99 -4
- package/package.json +4 -3
- package/packages/cli/dist/commands/init.js +43 -7
- package/packages/cli/dist/commands/memory.js +182 -0
- package/packages/cli/dist/commands/setup.js +45 -0
- package/packages/cli/dist/commands/upgrade.js +22 -4
- package/packages/cli/dist/index.js +110 -3
- package/packages/cli/dist/lib/manifest.js +23 -5
- package/packages/cli/dist/lib/subsystems.js +269 -0
- package/packages/cli/dist/lib/version.js +15 -0
- package/packages/cli/dist/policy/benchmark/corpus.js +165 -0
- package/packages/cli/dist/policy/core/coverage.js +253 -0
- package/packages/cli/dist/policy/core/decision-record.js +130 -44
- package/packages/cli/dist/policy/core/declaration.js +58 -17
- package/packages/cli/dist/policy/core/evidence-matrix.js +94 -0
- package/packages/cli/dist/policy/core/probe.js +442 -0
- package/packages/cli/dist/policy/core/validation.js +194 -1
- package/packages/cli/dist/policy/core/vocabulary.js +70 -3
- package/packages/cli/dist/policy/harness/claude.js +9 -1
- package/packages/cli/dist/policy/harness/codex.js +48 -1
- package/packages/cli/dist/policy/harness/shared-hooks.js +18 -0
- package/packages/cli/dist/policy/index.js +9 -2
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +2 -0
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +2 -0
- package/templates/agent-os/subagent-routing.json +32 -0
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +41 -4
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +2 -0
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +2 -0
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +2 -0
- package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -0
- package/templates/agent-os/universal/.claude/hooks/guard-subagent-model.mjs +234 -0
- package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +75 -32
- package/templates/agent-os/universal/.claude/hooks/warn-subagent-routing.mjs +120 -0
- package/templates/agent-os/universal/.claude/rules/workflow.md +5 -0
- package/templates/agent-os/universal/.claude/scripts/preflight.mjs +27 -3
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +43 -0
- package/templates/agent-os/universal/.claude/scripts/queue/gate-rounds.mjs +70 -2
- package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +12 -4
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +18 -1
- package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +269 -32
- package/templates/agent-os/universal/.claude/scripts/unattended-flag.mjs +64 -1
- package/templates/agent-os/universal/.claude/settings.json +16 -0
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +41 -4
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -0
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +2 -0
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +2 -0
- package/templates/agent-os/universal/.codex/config.toml +3 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +31 -5
- package/templates/agent-os/universal/docs/decisions/subagent-routing.md +142 -0
- package/templates/agent-os/universal/layers.json +4 -0
- package/templates/hash-history.json +71 -22
- package/templates/release-ledger.json +3 -1
- package/templates/skeleton/node-service/services/api/test/artifact.test.ts +3 -4
- package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +40 -0
- package/templates/skeleton/node-service/services/api/test/package-manager.ts +51 -0
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +9 -8
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Subagent routing: every gate pins its model and effort
|
|
2
|
+
|
|
3
|
+
Status: accepted for RP-173 (Claude Code). The Codex half, RP-166, is recorded in
|
|
4
|
+
`codex-adapter.md`. Both harnesses' routing comes from one role table: the Codex
|
|
5
|
+
profiles are generated from it, and the Claude agent definitions are checked against it.
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
|
|
9
|
+
Each named subagent this rig ships pins the model and the effort it reads with:
|
|
10
|
+
|
|
11
|
+
| Role | Claude Code | Codex |
|
|
12
|
+
| --- | --- | --- |
|
|
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` |
|
|
15
|
+
| a subagent with no definition | `claude-sonnet-5`; effort follows the session | `gpt-5.6-terra`, `medium` |
|
|
16
|
+
|
|
17
|
+
In this project the pins are the files themselves: `model:` and `effort:` in
|
|
18
|
+
`.claude/agents/<role>.md`, `model` and `model_reasoning_effort` in
|
|
19
|
+
`.codex/agents/<role>.toml`, `env.CLAUDE_CODE_SUBAGENT_MODEL` in
|
|
20
|
+
`.claude/settings.json`, and the `[agents]` defaults in `.codex/config.toml`.
|
|
21
|
+
|
|
22
|
+
The driver session's own model and effort are not pinned. They belong to whoever
|
|
23
|
+
starts the session.
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
A gate that inherits the driver's model returns a verdict whose meaning changes
|
|
28
|
+
whenever the driver does. Pinning makes the reader behind a SHIP a property of the
|
|
29
|
+
rulebook rather than of how a session happened to be launched.
|
|
30
|
+
|
|
31
|
+
- **Full model IDs, not aliases.** An alias moves when a new model ships, and every
|
|
32
|
+
verdict would change meaning without a diff. Moving a role to another model is a
|
|
33
|
+
policy change.
|
|
34
|
+
- **The larger model reads correctness, security and infrastructure; the smaller one
|
|
35
|
+
does bounded, frequent work** — the same split as the Codex tiers.
|
|
36
|
+
- **`high` for every gate, and escalation is a policy change.** A dispatch has no way
|
|
37
|
+
to set a subagent's effort — the `per-dispatch-effort` row below — so raising a
|
|
38
|
+
gate's effort is an edit to its definition, and nothing here invents a per-call knob.
|
|
39
|
+
|
|
40
|
+
## How Claude Code resolves a pin, and what voids one
|
|
41
|
+
|
|
42
|
+
Claude Code's changelog states two of the rules:
|
|
43
|
+
|
|
44
|
+
- A subagent's model comes from the per-dispatch `model` first, then the
|
|
45
|
+
definition's `model:`, then `CLAUDE_CODE_SUBAGENT_MODEL`, then the parent session —
|
|
46
|
+
and that order holds from **2.1.251**. Before it the environment variable overrode
|
|
47
|
+
definitions, so the unnamed default this rig sets would replace every gate's pin.
|
|
48
|
+
That is why 2.1.251 is the version the session-start check warns below; it is
|
|
49
|
+
the release that makes the MODEL pins outrank the shipped default, and nothing more
|
|
50
|
+
is claimed for it — the `effort:` field and the `Agent` tool name the guard matches
|
|
51
|
+
were observed working on 2.1.269 and 2.1.270 only.
|
|
52
|
+
- `CLAUDE_CODE_SUBAGENT_MODEL_FORCE`, added in 2.1.257, applies one model to every
|
|
53
|
+
subagent and ignores each definition's `model:`.
|
|
54
|
+
|
|
55
|
+
The rest is measured. Each row records a live Claude Code run, read by a script from
|
|
56
|
+
what Claude Code itself wrote — a subagent transcript, a hook's payload or a hook's
|
|
57
|
+
environment — never from a subagent's own report. The rows are the generator's
|
|
58
|
+
`docs/capability-evidence.json` (absent in a generated rig), keyed by `mechanism`
|
|
59
|
+
and `surface`:
|
|
60
|
+
|
|
61
|
+
- `subagent-model-pin` and `subagent-effort-pin` hold under a driver on another model
|
|
62
|
+
and effort (surface `.claude/agents/test-writer.md`);
|
|
63
|
+
- `subagent-model-pin` does not hold with `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`, and
|
|
64
|
+
`subagent-effort-pin` does not hold with `CLAUDE_CODE_EFFORT_LEVEL=low` — each
|
|
65
|
+
variable replaced only the pin it names;
|
|
66
|
+
- `subagent-model-pin` does not hold against a call-site `model` when no guard
|
|
67
|
+
refuses the dispatch;
|
|
68
|
+
- `unnamed-subagent-model` holds and `unnamed-subagent-effort` is unsupported: a
|
|
69
|
+
subagent with no definition ran on the shipped default at the session's effort.
|
|
70
|
+
|
|
71
|
+
Two mechanisms follow, both in `.claude/hooks/`. `guard-subagent-model` refuses a
|
|
72
|
+
per-dispatch `model` for an agent whose definition pins one (`call-site-model-guard`).
|
|
73
|
+
`warn-subagent-routing` warns at session start when either variable is set, when
|
|
74
|
+
Claude Code is older than 2.1.251, or when its version cannot be read from `AI_AGENT`
|
|
75
|
+
(`claude-code-version-signal`); it never blocks, because the variables are the
|
|
76
|
+
operator's to set and the warning makes their cost visible instead of taking the
|
|
77
|
+
decision. Pinned in `subagent-routing-hooks.test.ts` (absent in a generated rig)
|
|
78
|
+
› "blocks a call-site model on a project agent that pins one, and says to re-dispatch without it"
|
|
79
|
+
and › "warns when %s is set, and never blocks the session".
|
|
80
|
+
|
|
81
|
+
Neither hook is wired for Codex, which has no dispatch hook and no Claude
|
|
82
|
+
environment: `subagent-routing.test.ts` (absent in a generated rig) ›
|
|
83
|
+
"keeps both routing hooks out of the Codex projection, which has no Agent tool and no Claude environment".
|
|
84
|
+
|
|
85
|
+
## What this does not do
|
|
86
|
+
|
|
87
|
+
- **The effort of a subagent with no definition is not pinned.** It runs at the
|
|
88
|
+
session's effort (`unnamed-subagent-effort`). That is recorded as unsupported, not
|
|
89
|
+
worked around.
|
|
90
|
+
- **A definition says what was asked for; only the transcript says what ran.** A
|
|
91
|
+
verdict does not yet carry the model and effort it was produced on.
|
|
92
|
+
- **The guard's notion of a role is "a project agent whose definition pins a model".**
|
|
93
|
+
In a generated project the definitions are the policy (next section), so an agent
|
|
94
|
+
a project adds with a pin is a role, and a role whose definition is changed to
|
|
95
|
+
`model: inherit` has stopped being one — by that project's reviewed decision.
|
|
96
|
+
- **The built-in agents get no definitions here.** A general-purpose subagent follows
|
|
97
|
+
the unnamed default like any subagent without a definition.
|
|
98
|
+
- **The warning is a warning.** A session started with `CLAUDE_CODE_EFFORT_LEVEL` set
|
|
99
|
+
still runs every gate — at that level.
|
|
100
|
+
|
|
101
|
+
## Changing a role in this project
|
|
102
|
+
|
|
103
|
+
The item this record implements asked that a project lower a default by editing
|
|
104
|
+
"its policy", with "the test following". Here that is realised as follows, and the
|
|
105
|
+
difference from a literal reading is deliberate:
|
|
106
|
+
|
|
107
|
+
- **The agent definitions are this project's policy.** It receives no copy of the
|
|
108
|
+
generator's role table and no routing check: a second copy beside the definitions,
|
|
109
|
+
with no projector here to keep the two aligned, is the drift `rules/invariants.md`
|
|
110
|
+
forbids ("one mechanism, one implementation"), and the Codex half already shipped
|
|
111
|
+
that way.
|
|
112
|
+
- **To move a role**, edit `model:` / `effort:` in `.claude/agents/<role>.md` and the
|
|
113
|
+
matching `.codex/agents/<role>.toml` in one reviewed change; both trees are
|
|
114
|
+
elevated paths.
|
|
115
|
+
- **The test that follows the table lives in the generator**, where the expected
|
|
116
|
+
values are read from the table rather than restated.
|
|
117
|
+
- **`upgrade` reports the edited agent file as yours and leaves it alone**, while an
|
|
118
|
+
agent file nobody edited is replaced by the release's pinned one —
|
|
119
|
+
`subagent-routing-install.test.ts` (absent in a generated rig) ›
|
|
120
|
+
"replaces an untouched pre-pin agent with the pinned one" and ›
|
|
121
|
+
"reports a pre-pin agent the user edited as a conflict and leaves its bytes alone".
|
|
122
|
+
|
|
123
|
+
## In the generator
|
|
124
|
+
|
|
125
|
+
`templates/agent-os/subagent-routing.json` is the one table.
|
|
126
|
+
`scripts/sync-codex-adapter.mjs --check` loads it through `scripts/subagent-routing.mjs`,
|
|
127
|
+
derives the Codex profiles from it, and refuses a Claude agent without `model` or
|
|
128
|
+
`effort`, a value that differs from its role, a role without an agent or an agent
|
|
129
|
+
without a role, an effort the pinned model does not support, an unknown field in
|
|
130
|
+
either harness's mapping, and shipped settings that miss the unnamed default or set
|
|
131
|
+
either voiding variable —
|
|
132
|
+
`subagent-routing.test.ts` (absent in a generated rig) › "refuses a routing policy with %s"
|
|
133
|
+
and › "refuses a Claude agent template whose model disagrees with the routing policy".
|
|
134
|
+
|
|
135
|
+
## Risk and rollback
|
|
136
|
+
|
|
137
|
+
A pinned model can be unavailable to an account. In the generator the recovery is
|
|
138
|
+
changing the table and, in the same change, the Claude agent definitions checked
|
|
139
|
+
against it; in a project it is changing the definitions as above — never a
|
|
140
|
+
per-call override. Rollback in a project is deleting the `model:` / `effort:` lines,
|
|
141
|
+
the `env` entry, and the two hooks with their wiring; every gate then inherits the
|
|
142
|
+
session again.
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
".claude/hooks/inject-rules.mjs",
|
|
17
17
|
".claude/hooks/guard-secret-file.mjs",
|
|
18
18
|
".claude/hooks/guard-rulebook.mjs",
|
|
19
|
+
".claude/hooks/guard-subagent-model.mjs",
|
|
20
|
+
".claude/hooks/warn-subagent-routing.mjs",
|
|
19
21
|
".claude/hooks/lib/edit-input.mjs",
|
|
20
22
|
".claude/hooks/lib/hook-input.mjs",
|
|
21
23
|
".claude/skills/pr-ship/SKILL.md",
|
|
@@ -69,6 +71,7 @@
|
|
|
69
71
|
"docs/decisions/gate-coverage.md",
|
|
70
72
|
"docs/decisions/fail-open-guards.md",
|
|
71
73
|
"docs/decisions/codex-adapter.md",
|
|
74
|
+
"docs/decisions/subagent-routing.md",
|
|
72
75
|
"docs/decisions/closing-a-task.md",
|
|
73
76
|
"docs/decisions/content-blind-revalidation.md",
|
|
74
77
|
"docs/decisions/review-lanes.md",
|
|
@@ -84,6 +87,7 @@
|
|
|
84
87
|
],
|
|
85
88
|
"meta": [
|
|
86
89
|
".claude/settings.json",
|
|
90
|
+
".codex/config.toml",
|
|
87
91
|
".codex/hooks.json",
|
|
88
92
|
"CLAUDE.md",
|
|
89
93
|
"AGENTS.md"
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"0.6.1",
|
|
11
11
|
"0.6.2",
|
|
12
12
|
"0.7.0",
|
|
13
|
-
"0.7.1"
|
|
13
|
+
"0.7.1",
|
|
14
|
+
"0.8.0",
|
|
15
|
+
"0.9.0"
|
|
14
16
|
],
|
|
15
17
|
"files": {
|
|
16
18
|
".agents/skills/check-premises/SKILL.md": {
|
|
@@ -27,7 +29,9 @@
|
|
|
27
29
|
"47922b182915cb92696f851174f2e88c4ae8c67dc119ef39a3a597ef1300fab5",
|
|
28
30
|
"57f6add0504db45e7577474f97705fe474310e4cd3310cdb348a2c64eabe88cd",
|
|
29
31
|
"18f4300de4c4c7ea380d726fb109fa224da950ef0a4cea5015ab1d408b823be5",
|
|
30
|
-
"0a1b6575f6df8139766c8e71ba7320e14d2a5a5ed8ab26b6b43f7a006153d37d"
|
|
32
|
+
"0a1b6575f6df8139766c8e71ba7320e14d2a5a5ed8ab26b6b43f7a006153d37d",
|
|
33
|
+
"a0d11ca1745095cc5b68777915161f16a6d1602c21f6d273f475d1d07c4b8546",
|
|
34
|
+
"b47bdf0d7a2f6c5b2034b8f6d683ad2b3c83bd65d15baf7de9b58cc4689d0ab1"
|
|
31
35
|
]
|
|
32
36
|
},
|
|
33
37
|
".agents/skills/new-invariant/SKILL.md": {
|
|
@@ -81,7 +85,8 @@
|
|
|
81
85
|
"hashes": [
|
|
82
86
|
"2286fbc01c0342b9bac9909890876c079615db5b398e3499a0f2fddb1a334098",
|
|
83
87
|
"4c6241da56060b44e77f9b5f7a8cde7d2923bd605b8b9bae70d00329784c35be",
|
|
84
|
-
"03f87fbc976d8d65287b032ae3612511fc8f7e57772470b2282b660343fc32db"
|
|
88
|
+
"03f87fbc976d8d65287b032ae3612511fc8f7e57772470b2282b660343fc32db",
|
|
89
|
+
"5ec51bc08c9a934bfcbb2077fc7edce48c0e0cc3ed117d7dfc8a2895f2d9c01b"
|
|
85
90
|
]
|
|
86
91
|
},
|
|
87
92
|
".claude/agents/code-reviewer.md": {
|
|
@@ -91,7 +96,8 @@
|
|
|
91
96
|
"be3d2992d1f278dde46b555df1840b02f91403db1ec2fd4d489fb5efa940f019",
|
|
92
97
|
"ec403373ef6bc0575d61c6ca11f82792817b272221fad8317ef6c45388b52628",
|
|
93
98
|
"f125cfbce848cdaa91fd81e961808b4dbde452287959ec2cf10b905ee3ede789",
|
|
94
|
-
"8ede9297480c889ec6d6caf8d076e7d69111fb472933dcf5a48b9a8425fe50c2"
|
|
99
|
+
"8ede9297480c889ec6d6caf8d076e7d69111fb472933dcf5a48b9a8425fe50c2",
|
|
100
|
+
"f12cb04871578eca3c9c36a1c2d60f5ae15a0e9094ecfc7c704c341756de4e3e"
|
|
95
101
|
]
|
|
96
102
|
},
|
|
97
103
|
".claude/agents/prose-reviewer.md": {
|
|
@@ -100,7 +106,8 @@
|
|
|
100
106
|
"8d5762849afd328fdc1aabf053ad207f2e306e013665e3ff934b06cbb1bbda38",
|
|
101
107
|
"0e4307cd9cf8ef723d79e98b1af46a34c6912a6aa925e2179d939cb83c8f2a44",
|
|
102
108
|
"7e8eaa2b4af9d65e1d10bf50c32c1d138fb2f8b9ba6e6d0880cade0524324d9d",
|
|
103
|
-
"ed1c05d3938c0b4f277f1bf4af8641460def830569049ff0697c9bda14518b78"
|
|
109
|
+
"ed1c05d3938c0b4f277f1bf4af8641460def830569049ff0697c9bda14518b78",
|
|
110
|
+
"7aed3bfc955efc94bbeda3f866d25e2b253c88facaa2beed73939182a9f845b2"
|
|
104
111
|
]
|
|
105
112
|
},
|
|
106
113
|
".claude/agents/security-scanner.md": {
|
|
@@ -108,13 +115,15 @@
|
|
|
108
115
|
"hashes": [
|
|
109
116
|
"43e5d59315483e369ad19f3070175f146e88deabd3a12dc09339ac4fac0f2aee",
|
|
110
117
|
"e01467a38a0183be46ba45b05d44f663fbf4b139ceaecb91b5d096f3286a3fcd",
|
|
111
|
-
"844e215af7d80bd85f62ca937c79bea59c8d8cd7c97353937ce62f7747eb9df7"
|
|
118
|
+
"844e215af7d80bd85f62ca937c79bea59c8d8cd7c97353937ce62f7747eb9df7",
|
|
119
|
+
"7c0bc189521955f2d126bac0ff97ab4fa252b05067692a5a3bb6cda232ddfaa3"
|
|
112
120
|
]
|
|
113
121
|
},
|
|
114
122
|
".claude/agents/test-writer.md": {
|
|
115
123
|
"since": "0.2.0",
|
|
116
124
|
"hashes": [
|
|
117
|
-
"333feef12383dea51791ae4923c44ba542438de793f0792fb51d96aa15b8b1b8"
|
|
125
|
+
"333feef12383dea51791ae4923c44ba542438de793f0792fb51d96aa15b8b1b8",
|
|
126
|
+
"fce976938fdd1154641e405a557b67161daa34cf84de9ad35fa14287d13b7f9c"
|
|
118
127
|
]
|
|
119
128
|
},
|
|
120
129
|
".claude/hooks/block-no-verify.mjs": {
|
|
@@ -178,6 +187,12 @@
|
|
|
178
187
|
"5bf2896ec5e5f94fab20b52cd22418a4392778b40744602a087e320eeca3f688"
|
|
179
188
|
]
|
|
180
189
|
},
|
|
190
|
+
".claude/hooks/guard-subagent-model.mjs": {
|
|
191
|
+
"since": "0.9.0",
|
|
192
|
+
"hashes": [
|
|
193
|
+
"345cbd5f43171adb01cd46ad485fb61299f7135c00ff4217765993551aca3b38"
|
|
194
|
+
]
|
|
195
|
+
},
|
|
181
196
|
".claude/hooks/guard-web-boundary.mjs": {
|
|
182
197
|
"since": "0.2.0",
|
|
183
198
|
"hashes": [
|
|
@@ -200,7 +215,8 @@
|
|
|
200
215
|
"hashes": [
|
|
201
216
|
"4f836bc3ff75a868ffe035b179ac9e7dc1ad7715d520677ff9f6aa5bfdb200eb",
|
|
202
217
|
"0c191d326a7bb0bbfc110e2210f44b4aa39276e8db3779aaa4545cffd4e11863",
|
|
203
|
-
"572c671f8f2cad190fe2f144071954ac4d3b1357cab80a19bad584dd3b3768c8"
|
|
218
|
+
"572c671f8f2cad190fe2f144071954ac4d3b1357cab80a19bad584dd3b3768c8",
|
|
219
|
+
"6695698d2b38c79a12292601048dd0082a0cd18bf9e69c047d47679c2e6ecf67"
|
|
204
220
|
]
|
|
205
221
|
},
|
|
206
222
|
".claude/hooks/lib/hook-input.mjs": {
|
|
@@ -210,6 +226,12 @@
|
|
|
210
226
|
"3be3d4af4a25536d2a2bac0441a1370be7efafb5f8aa737b195bdce77db0091b"
|
|
211
227
|
]
|
|
212
228
|
},
|
|
229
|
+
".claude/hooks/warn-subagent-routing.mjs": {
|
|
230
|
+
"since": "0.9.0",
|
|
231
|
+
"hashes": [
|
|
232
|
+
"b95165243ebb60264c78b82ce01407669457fe33c0bf86ff2a5c117963c6bc6d"
|
|
233
|
+
]
|
|
234
|
+
},
|
|
213
235
|
".claude/queue.json": {
|
|
214
236
|
"since": "0.3.0",
|
|
215
237
|
"hashes": [
|
|
@@ -229,7 +251,8 @@
|
|
|
229
251
|
"ce2104ce697d3e5ed9210e85fe0daeb66b432a36868493f06fec54073a5fff31",
|
|
230
252
|
"530f63a4b29a5cc67867fa7b23575db81d42609ae401bfc4089b05194c586c21",
|
|
231
253
|
"a7359408d56e6bdbf268019ad9193d5fbf4a872385a65ba08564204e6a8f5b5f",
|
|
232
|
-
"619999500386b3d2867bbd4799f86a800fe7e168083c6e35fb2e629a2c926c5f"
|
|
254
|
+
"619999500386b3d2867bbd4799f86a800fe7e168083c6e35fb2e629a2c926c5f",
|
|
255
|
+
"532dc5cdffb1464fb68889628664b1e7e345272c806cf2ccb7a063c6a3a7f1bf"
|
|
233
256
|
]
|
|
234
257
|
},
|
|
235
258
|
".claude/rules/aws-cdk.md": {
|
|
@@ -264,7 +287,8 @@
|
|
|
264
287
|
"hashes": [
|
|
265
288
|
"dbe8025e10583e03fee5056e88aa6b73e4399453711d9156d82528a02d6e0402",
|
|
266
289
|
"9e60d0300e856a9b3f81ab748bf2c0a9de27233ad0aa58a8c7141413fa0f3cae",
|
|
267
|
-
"c8e9849a8fccd4e1e60b1ddc82937eb15aceb7f7c2afbd151b05e1bc966bfd2a"
|
|
290
|
+
"c8e9849a8fccd4e1e60b1ddc82937eb15aceb7f7c2afbd151b05e1bc966bfd2a",
|
|
291
|
+
"aa34a1eab572964b328ffa0044e96acb3e1c31491df0183460f06e5e6bba3b76"
|
|
268
292
|
]
|
|
269
293
|
},
|
|
270
294
|
".claude/scripts/decision-router.mjs": {
|
|
@@ -349,7 +373,8 @@
|
|
|
349
373
|
"ceb6d35fa028f9c5e5e2d5965c627095d5ba80c37191a584627b4bc645b6be15",
|
|
350
374
|
"efa193dc543237d82dcb371fa1df6483ccd43c1db88d0e218f5d89769564974c",
|
|
351
375
|
"bed44df40412a7edac96914de21edc796df604ddc275ff1a02764dfcc4d4ede7",
|
|
352
|
-
"b3893c365de37ef6d9d04ab933296b9fcf09a0886197d7e12313fa63eacdbd89"
|
|
376
|
+
"b3893c365de37ef6d9d04ab933296b9fcf09a0886197d7e12313fa63eacdbd89",
|
|
377
|
+
"37eeb4bdb50d7f6b1427bd02ec9815f39ad9142f35691ffadd8c0b626c6cdb00"
|
|
353
378
|
]
|
|
354
379
|
},
|
|
355
380
|
".claude/scripts/queue/as-of.mjs": {
|
|
@@ -378,7 +403,8 @@
|
|
|
378
403
|
".claude/scripts/queue/gate-rounds.mjs": {
|
|
379
404
|
"since": "0.5.0",
|
|
380
405
|
"hashes": [
|
|
381
|
-
"7577674d5477cfc388594e4c301601e329d60741dfa0b2844be2cc94a3ab6283"
|
|
406
|
+
"7577674d5477cfc388594e4c301601e329d60741dfa0b2844be2cc94a3ab6283",
|
|
407
|
+
"deeb7a10772f906c7630bb3c295be1b61d7ebf0541b2a34debcb40c80e6d8f0e"
|
|
382
408
|
]
|
|
383
409
|
},
|
|
384
410
|
".claude/scripts/queue/github-issues.mjs": {
|
|
@@ -398,7 +424,8 @@
|
|
|
398
424
|
"7db3c17b781e6876b4822afd09e6affa97e46607b63d443975d987daf1f14464",
|
|
399
425
|
"9edd70b5960f367958e8e7500150b4edd285c99869cca803e40e0537d2d347b0",
|
|
400
426
|
"4cf917ac06708e1068dfe9f8bbebc9376c4748526bd669b9bea32929ba996d0a",
|
|
401
|
-
"b4a0789be7ad4b401de872edb06665bca349048178b4dd6c5db848ca44f7c977"
|
|
427
|
+
"b4a0789be7ad4b401de872edb06665bca349048178b4dd6c5db848ca44f7c977",
|
|
428
|
+
"19a04cdbdfd936213cea3a9a00a583a060a4a8d0356d6a6922ae1c2f593f16f1"
|
|
402
429
|
]
|
|
403
430
|
},
|
|
404
431
|
".claude/scripts/queue/jira.mjs": {
|
|
@@ -478,7 +505,8 @@
|
|
|
478
505
|
"hashes": [
|
|
479
506
|
"060b001927d8b471f1c1a8c8851fbb4266d3b2edd674cccbd6c16c44b57b91dd",
|
|
480
507
|
"7721ee19fd1bcf6902be3d359def40742d79b88f5d83e7e3ca22ba45ca3211aa",
|
|
481
|
-
"250292c9959916240bfc0fdf5987a864f6d54799de3d932dc6b85b776e62eaa9"
|
|
508
|
+
"250292c9959916240bfc0fdf5987a864f6d54799de3d932dc6b85b776e62eaa9",
|
|
509
|
+
"ad58b25f9ce30a1549b5047daa1e5b43c30352306aeceebacd638db86087e01f"
|
|
482
510
|
]
|
|
483
511
|
},
|
|
484
512
|
".claude/scripts/verdict.mjs": {
|
|
@@ -495,7 +523,8 @@
|
|
|
495
523
|
"55a6938b4d9967291a26e93301e34e5ffdef74a88f8c773a4d11b5d87d864fff",
|
|
496
524
|
"713718fbb2cda132f05665b29b8f1108a6305729b5e7c789d2053d117c13e08e",
|
|
497
525
|
"e588ab05bee707a099c80c9ea399008be31488e616f3db3c0598f66f3d1b3a52",
|
|
498
|
-
"a180578d6e6de6a21900ff2f8a8ade2242170d16d2dd95574e5004435d870b9b"
|
|
526
|
+
"a180578d6e6de6a21900ff2f8a8ade2242170d16d2dd95574e5004435d870b9b",
|
|
527
|
+
"44695f83e445c7da3257556a3decc70e79ef46d19da394d4d5dd4c9268c0b429"
|
|
499
528
|
]
|
|
500
529
|
},
|
|
501
530
|
".claude/skills/check-premises/SKILL.md": {
|
|
@@ -517,7 +546,9 @@
|
|
|
517
546
|
"47922b182915cb92696f851174f2e88c4ae8c67dc119ef39a3a597ef1300fab5",
|
|
518
547
|
"57f6add0504db45e7577474f97705fe474310e4cd3310cdb348a2c64eabe88cd",
|
|
519
548
|
"18f4300de4c4c7ea380d726fb109fa224da950ef0a4cea5015ab1d408b823be5",
|
|
520
|
-
"0a1b6575f6df8139766c8e71ba7320e14d2a5a5ed8ab26b6b43f7a006153d37d"
|
|
549
|
+
"0a1b6575f6df8139766c8e71ba7320e14d2a5a5ed8ab26b6b43f7a006153d37d",
|
|
550
|
+
"a0d11ca1745095cc5b68777915161f16a6d1602c21f6d273f475d1d07c4b8546",
|
|
551
|
+
"b47bdf0d7a2f6c5b2034b8f6d683ad2b3c83bd65d15baf7de9b58cc4689d0ab1"
|
|
521
552
|
]
|
|
522
553
|
},
|
|
523
554
|
".claude/skills/new-invariant/SKILL.md": {
|
|
@@ -573,14 +604,16 @@
|
|
|
573
604
|
"since": "0.5.0",
|
|
574
605
|
"hashes": [
|
|
575
606
|
"902dbace9532ec8e7a3c0cfd6ea0934227f087e345c0619923ec03512b26ba3e",
|
|
576
|
-
"55778d7fb9f778a33213590cc0a2dcf8e01026a1a93eed1c0f811ba4f1070721"
|
|
607
|
+
"55778d7fb9f778a33213590cc0a2dcf8e01026a1a93eed1c0f811ba4f1070721",
|
|
608
|
+
"5fbbb1431833692d56cd0d15249f1d024e53bb0ab63349842910a9fc90547f6a"
|
|
577
609
|
]
|
|
578
610
|
},
|
|
579
611
|
".codex/agents/code-reviewer.toml": {
|
|
580
612
|
"since": "0.5.0",
|
|
581
613
|
"hashes": [
|
|
582
614
|
"e0e3475404c95402f33d77ebb14001e289d26bb429205a0a73bdcef99b4a62c7",
|
|
583
|
-
"9ec92775791907f068d85ba58489236bd2bee69a8d769554ac530e8eadd5e374"
|
|
615
|
+
"9ec92775791907f068d85ba58489236bd2bee69a8d769554ac530e8eadd5e374",
|
|
616
|
+
"180c182cc909cc0b6c06cfa286f528f22b77189eee49cac067fdaaf2920f8bae"
|
|
584
617
|
]
|
|
585
618
|
},
|
|
586
619
|
".codex/agents/prose-reviewer.toml": {
|
|
@@ -588,20 +621,29 @@
|
|
|
588
621
|
"hashes": [
|
|
589
622
|
"80b4631502d95c619ad060ce02cc8702b12574758ba7df8337ed63efa15d764d",
|
|
590
623
|
"cfd516f72acdfd197c59ac3cbdd39d7b175976dbd3d9eef52825dfdc4f1e5b60",
|
|
591
|
-
"8687eacf0ad707a4f887e0714e82fca23fd005fa9ec8824faedfe99ca46333e1"
|
|
624
|
+
"8687eacf0ad707a4f887e0714e82fca23fd005fa9ec8824faedfe99ca46333e1",
|
|
625
|
+
"7c37e4dbd94980bee16396d0d46d97c5079e295d3eed4b3ed8f98d701e57b10d"
|
|
592
626
|
]
|
|
593
627
|
},
|
|
594
628
|
".codex/agents/security-scanner.toml": {
|
|
595
629
|
"since": "0.5.0",
|
|
596
630
|
"hashes": [
|
|
597
631
|
"9ee408789f237a0b55aac6a2472682a08f8696e1d1ff4918ea72766ac8535499",
|
|
598
|
-
"bc9aacca1ca3ed4ceac485f7cd1c6ff5a5405ac7be83419d6145499cc560ee91"
|
|
632
|
+
"bc9aacca1ca3ed4ceac485f7cd1c6ff5a5405ac7be83419d6145499cc560ee91",
|
|
633
|
+
"b529f02056948ff0ca179c96d9d47962c9c8064feccb68d3c970e7b1a2513fc9"
|
|
599
634
|
]
|
|
600
635
|
},
|
|
601
636
|
".codex/agents/test-writer.toml": {
|
|
602
637
|
"since": "0.5.0",
|
|
603
638
|
"hashes": [
|
|
604
|
-
"bb89d93f57d70885d98cfbe428caa524e9c00e1ce30da235ed3318c1ba3c9606"
|
|
639
|
+
"bb89d93f57d70885d98cfbe428caa524e9c00e1ce30da235ed3318c1ba3c9606",
|
|
640
|
+
"ec9e61b2c16c97ed26d5bdda2b0f715a41b80c0af549d0f2bbb96d6ca4c4d8d7"
|
|
641
|
+
]
|
|
642
|
+
},
|
|
643
|
+
".codex/config.toml": {
|
|
644
|
+
"since": "0.9.0",
|
|
645
|
+
"hashes": [
|
|
646
|
+
"c41e34ef5c3bb606852269adeaf5678ce172dc7a7937514ac879152bbddea7f2"
|
|
605
647
|
]
|
|
606
648
|
},
|
|
607
649
|
".codex/hooks.json": {
|
|
@@ -655,7 +697,8 @@
|
|
|
655
697
|
"since": "0.5.0",
|
|
656
698
|
"hashes": [
|
|
657
699
|
"30e55d866d9a2c7359e594181070c63b1974d9df36e000caf419229284303a2e",
|
|
658
|
-
"ebf0ef61407944373c862d37e6a80d4965ece72304b931428c6b3fcc5d9527c4"
|
|
700
|
+
"ebf0ef61407944373c862d37e6a80d4965ece72304b931428c6b3fcc5d9527c4",
|
|
701
|
+
"46b68f9130d16a96278e03d0ddb14d99b678c5a239e4667aa79733e632e67505"
|
|
659
702
|
]
|
|
660
703
|
},
|
|
661
704
|
"docs/decisions/content-blind-revalidation.md": {
|
|
@@ -701,6 +744,12 @@
|
|
|
701
744
|
"dc0c9f5988fc983eb5100a085e274691b1e88a3b54ab2797e00972c4d69d61c4"
|
|
702
745
|
]
|
|
703
746
|
},
|
|
747
|
+
"docs/decisions/subagent-routing.md": {
|
|
748
|
+
"since": "0.9.0",
|
|
749
|
+
"hashes": [
|
|
750
|
+
"97bb882dd066a65a740780a836199ecef8887392c1ece02a92670ecf153988d3"
|
|
751
|
+
]
|
|
752
|
+
},
|
|
704
753
|
"docs/decisions/two-empty-endings.md": {
|
|
705
754
|
"since": "0.5.0",
|
|
706
755
|
"hashes": [
|
|
@@ -10,5 +10,7 @@
|
|
|
10
10
|
"0.6.1": "f1d1e3dbd2161545d77ee5e7f90fa8e74b8a9f3e",
|
|
11
11
|
"0.6.2": "2a1fc8e10cd2b65deb5c95d937fd7de39f2c92a2",
|
|
12
12
|
"0.7.0": "6589db36e1daa63a99ec595191db1cccf7373196",
|
|
13
|
-
"0.7.1": "52e879b6c103f6ba70493007b6a6466c57ea9824"
|
|
13
|
+
"0.7.1": "52e879b6c103f6ba70493007b6a6466c57ea9824",
|
|
14
|
+
"0.8.0": "870f9a3ecae2881908ece8ec3e2ac13f84f505f5",
|
|
15
|
+
"0.9.0": "c27f391e7395accaf09354f255a07e1b9e4710c1"
|
|
14
16
|
}
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
// CD brief §2: node-service's deployable artifact must actually build and run.
|
|
2
2
|
// This bundles the server, boots the bundle over a real socket, and closes the
|
|
3
3
|
// path — proving the artifact is genuine, not a stub.
|
|
4
|
-
import {
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
5
|
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import type { AddressInfo } from 'node:net';
|
|
8
8
|
import { createServer } from 'node:net';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
-
import { promisify } from 'node:util';
|
|
12
11
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
12
|
+
import { runPackageManager } from './package-manager.js';
|
|
13
13
|
|
|
14
|
-
const exec = promisify(execFile);
|
|
15
14
|
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
16
15
|
const dist = path.join(projectRoot, 'dist');
|
|
17
16
|
|
|
@@ -38,7 +37,7 @@ describe('deployable artifact (dist/)', () => {
|
|
|
38
37
|
() => true,
|
|
39
38
|
() => false,
|
|
40
39
|
);
|
|
41
|
-
if (!built) await
|
|
40
|
+
if (!built) await runPackageManager(['build:artifact'], { cwd: projectRoot });
|
|
42
41
|
}, 180_000);
|
|
43
42
|
|
|
44
43
|
afterAll(async () => {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { runPackageManager } from './package-manager.js';
|
|
7
|
+
|
|
8
|
+
const literalArgs = ['space value', '&|<>^%!()', 'single "double"', ''];
|
|
9
|
+
|
|
10
|
+
describe('package-manager transport for the artifact fallback', () => {
|
|
11
|
+
let work: string | undefined;
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
if (work !== undefined) await rm(work, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('honors the trusted current pnpm CLI on every platform with literal argv and preserves a failing child output', async () => {
|
|
18
|
+
work = await mkdtemp(path.join(tmpdir(), 'artifact-package-manager-'));
|
|
19
|
+
const cli = path.join(work, 'pnpm-cli.cjs');
|
|
20
|
+
await writeFile(
|
|
21
|
+
cli,
|
|
22
|
+
[
|
|
23
|
+
'process.stdout.write(JSON.stringify(process.argv.slice(2)));',
|
|
24
|
+
"process.stderr.write('artifact transport stderr');",
|
|
25
|
+
'process.exit(7);',
|
|
26
|
+
].join(''),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const failure = await runPackageManager(literalArgs, {
|
|
30
|
+
cwd: work,
|
|
31
|
+
env: { ...process.env, npm_execpath: cli },
|
|
32
|
+
}).catch((error: unknown) => error);
|
|
33
|
+
|
|
34
|
+
expect(failure).toMatchObject({
|
|
35
|
+
code: 7,
|
|
36
|
+
stdout: JSON.stringify(literalArgs),
|
|
37
|
+
stderr: 'artifact transport stderr',
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
|
|
6
|
+
const exec = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
const currentPnpmCli = (environment: NodeJS.ProcessEnv): string | undefined => {
|
|
9
|
+
const currentCli = environment.npm_execpath;
|
|
10
|
+
if (currentCli && /^pnpm(?:-cli)?\.(?:cjs|js)$/i.test(path.basename(currentCli)) && existsSync(currentCli)) {
|
|
11
|
+
return currentCli;
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const pnpmCli = (environment: NodeJS.ProcessEnv): string => {
|
|
17
|
+
const currentCli = currentPnpmCli(environment);
|
|
18
|
+
if (currentCli) return currentCli;
|
|
19
|
+
|
|
20
|
+
const installedCli = path.join(
|
|
21
|
+
path.dirname(process.execPath),
|
|
22
|
+
'node_modules',
|
|
23
|
+
'pnpm',
|
|
24
|
+
'bin',
|
|
25
|
+
'pnpm.cjs',
|
|
26
|
+
);
|
|
27
|
+
if (existsSync(installedCli)) return installedCli;
|
|
28
|
+
|
|
29
|
+
const corepackCli = path.join(
|
|
30
|
+
path.dirname(process.execPath),
|
|
31
|
+
'node_modules',
|
|
32
|
+
'corepack',
|
|
33
|
+
'dist',
|
|
34
|
+
'pnpm.js',
|
|
35
|
+
);
|
|
36
|
+
if (existsSync(corepackCli)) return corepackCli;
|
|
37
|
+
|
|
38
|
+
throw new Error('Cannot locate pnpm JavaScript CLI; npm_execpath must name the installed pnpm CLI');
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Runs the workspace package manager without passing its arguments through a Windows shell. */
|
|
42
|
+
export const runPackageManager = (
|
|
43
|
+
args: string[],
|
|
44
|
+
options: Parameters<typeof exec>[2],
|
|
45
|
+
): ReturnType<typeof exec> => {
|
|
46
|
+
const environment = options?.env ?? process.env;
|
|
47
|
+
const currentCli = currentPnpmCli(environment);
|
|
48
|
+
if (currentCli) return exec(process.execPath, [currentCli, ...args], options);
|
|
49
|
+
if (process.platform !== 'win32') return exec('pnpm', args, options);
|
|
50
|
+
return exec(process.execPath, [pnpmCli(environment), ...args], options);
|
|
51
|
+
};
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
// The composition root resolves the built web bundle relative to itself; the
|
|
2
2
|
// resolution is a pure function so it can be tested without importing main.ts.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// POSIX runner, so observing the difference would mean passing a path flavour
|
|
7
|
-
// into `defaultStaticDirFor`, i.e. a parameter that exists only for the test.
|
|
8
|
-
// The decision was to leave that hook out; percent-decoding below is the same
|
|
9
|
-
// bug's POSIX-visible half, and it fails against the old expression.
|
|
4
|
+
// This native absolute fixture exercises the platform root spelling and
|
|
5
|
+
// percent-decoding without injecting a path flavor into the production helper.
|
|
10
6
|
import path from 'node:path';
|
|
7
|
+
import { pathToFileURL } from 'node:url';
|
|
11
8
|
import { describe, expect, it } from 'vitest';
|
|
12
9
|
import { defaultStaticDirFor } from '../src/static-dir.js';
|
|
13
10
|
|
|
11
|
+
const moduleUrl = pathToFileURL(
|
|
12
|
+
path.join(path.parse(process.cwd()).root, 'a b', 'proj', 'services', 'api', 'src', 'main.ts'),
|
|
13
|
+
).href;
|
|
14
|
+
|
|
14
15
|
describe('resolving the default static dir from the module url', () => {
|
|
15
16
|
it('decodes percent-escapes in the path (a directory may contain a space)', () => {
|
|
16
|
-
const resolved = defaultStaticDirFor(
|
|
17
|
+
const resolved = defaultStaticDirFor(moduleUrl);
|
|
17
18
|
|
|
18
19
|
expect(resolved).toContain('a b');
|
|
19
20
|
expect(resolved).not.toContain('%20');
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
it('points three levels up from services/api/src at apps/web/out', () => {
|
|
23
|
-
const resolved = defaultStaticDirFor(
|
|
24
|
+
const resolved = defaultStaticDirFor(moduleUrl);
|
|
24
25
|
|
|
25
26
|
expect(resolved.endsWith(path.join('a b', 'proj', 'apps', 'web', 'out'))).toBe(true);
|
|
26
27
|
expect(path.isAbsolute(resolved)).toBe(true);
|