plumbbob 0.9.5 → 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/.claude-plugin/plugin.json +1 -1
- package/README.md +87 -86
- package/dist/cli-core.js +8 -8
- package/dist/cli.js +1 -1
- package/dist/lib/agents.js +26 -26
- package/dist/lib/buildlog.js +11 -11
- package/dist/lib/buildlogsync.js +8 -8
- package/dist/lib/check.js +14 -14
- package/dist/lib/commitbody.js +72 -0
- package/dist/lib/commitmsg.js +7 -7
- package/dist/lib/git.js +44 -15
- package/dist/lib/intent.js +20 -19
- package/dist/lib/latch.js +16 -16
- package/dist/lib/orient.js +36 -17
- package/dist/lib/reentry.js +3 -3
- package/dist/lib/settings.js +16 -16
- package/dist/lib/sidecar.js +54 -54
- package/dist/lib/templates.js +1 -1
- package/dist/verbs/agent.js +25 -25
- package/dist/verbs/build.js +9 -9
- package/dist/verbs/check.js +6 -6
- package/dist/verbs/checkpoint.js +48 -47
- package/dist/verbs/doctor.js +27 -26
- package/dist/verbs/finish.js +27 -40
- package/dist/verbs/handoff.js +9 -8
- package/dist/verbs/init.js +9 -9
- package/dist/verbs/park.js +3 -3
- package/dist/verbs/recover.js +11 -11
- package/dist/verbs/revert.js +12 -12
- package/dist/verbs/spike.js +17 -17
- package/dist/verbs/start.js +49 -19
- package/dist/verbs/status.js +8 -8
- package/dist/verbs/turn.js +16 -16
- package/dist/verbs/use.js +4 -4
- package/node_modules/checkride/.claude-plugin/plugin.json +1 -1
- package/node_modules/checkride/CHANGELOG.md +401 -0
- package/node_modules/checkride/README.md +27 -9
- package/node_modules/checkride/dist/adapters.d.ts +12 -4
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +21 -1
- package/node_modules/checkride/dist/adapters.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts +9 -1
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/cursor.js +59 -1
- package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/harness.d.ts +1 -1
- package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/harness.js +10 -1
- package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts +23 -0
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/scripts.js +387 -82
- package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.js +2 -2
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +14 -5
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.js +58 -5
- package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
- package/node_modules/checkride/dist/baseline/history.d.ts +98 -0
- package/node_modules/checkride/dist/baseline/history.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/history.js +232 -0
- package/node_modules/checkride/dist/baseline/history.js.map +1 -0
- package/node_modules/checkride/dist/baseline/index.d.ts +4 -2
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/index.js +2 -1
- package/node_modules/checkride/dist/baseline/index.js.map +1 -1
- package/node_modules/checkride/dist/baseline/store.d.ts +30 -7
- package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/store.js +50 -16
- package/node_modules/checkride/dist/baseline/store.js.map +1 -1
- package/node_modules/checkride/dist/cli.d.ts +5 -1
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +98 -25
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +33 -4
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +21 -6
- package/node_modules/checkride/dist/config.js.map +1 -1
- package/node_modules/checkride/dist/digest/digest.js +1 -1
- package/node_modules/checkride/dist/doctor.d.ts +2 -0
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +39 -1
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/gate.d.ts +49 -4
- package/node_modules/checkride/dist/gate.d.ts.map +1 -1
- package/node_modules/checkride/dist/gate.js +143 -16
- package/node_modules/checkride/dist/gate.js.map +1 -1
- package/node_modules/checkride/dist/init.d.ts +8 -1
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +68 -7
- package/node_modules/checkride/dist/init.js.map +1 -1
- package/node_modules/checkride/dist/node-pin.d.ts +1 -1
- package/node_modules/checkride/dist/node-pin.js +1 -1
- package/node_modules/checkride/dist/orchestrator.d.ts +13 -0
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +118 -13
- package/node_modules/checkride/dist/orchestrator.js.map +1 -1
- package/node_modules/checkride/dist/pm/index.d.ts +1 -0
- package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/index.js.map +1 -1
- package/node_modules/checkride/dist/pm/launch.d.ts +23 -2
- package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/launch.js +16 -0
- package/node_modules/checkride/dist/pm/launch.js.map +1 -1
- package/node_modules/checkride/dist/pm/translate.js +1 -1
- package/node_modules/checkride/dist/pm/translate.js.map +1 -1
- package/node_modules/checkride/dist/qa/mutation.d.ts +4 -4
- package/node_modules/checkride/dist/qa/mutation.js +4 -4
- package/node_modules/checkride/dist/recover.d.ts +48 -0
- package/node_modules/checkride/dist/recover.d.ts.map +1 -0
- package/node_modules/checkride/dist/recover.js +220 -0
- package/node_modules/checkride/dist/recover.js.map +1 -0
- package/node_modules/checkride/dist/smoke.d.ts +1 -1
- package/node_modules/checkride/dist/smoke.js +1 -1
- package/node_modules/checkride/dist/snippets.d.ts +1 -1
- package/node_modules/checkride/package.json +3 -2
- package/node_modules/checkride/schema/checkride.config.schema.json +9 -1
- package/node_modules/checkride/skills/check/SKILL.md +9 -1
- package/node_modules/checkride/templates/shared/styles/Repo/Drift.yml +41 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Latin.yml +14 -0
- package/node_modules/checkride/templates/shared/styles/Repo/LyHyphen.yml +28 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Minted.yml +33 -0
- package/node_modules/checkride/templates/shared/styles/Repo/ThereIs.yml +16 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Weasel.yml +26 -0
- package/node_modules/checkride/templates/shared/vale.ini +34 -0
- package/package.json +10 -9
- package/skills/build/SKILL.md +69 -64
- package/skills/doctor/SKILL.md +11 -11
- package/skills/finish/SKILL.md +20 -19
- package/skills/harvest/SKILL.md +13 -12
- package/skills/park/SKILL.md +10 -10
- package/skills/plan/SKILL.md +63 -54
- package/skills/recover/SKILL.md +6 -6
- package/skills/refine/SKILL.md +18 -16
- package/skills/revert/SKILL.md +6 -6
- package/skills/spike/SKILL.md +9 -9
- package/skills/status/SKILL.md +5 -5
- package/skills/step/SKILL.md +17 -12
- package/skills/verify/SKILL.md +45 -40
- package/templates/build-log.md +7 -7
- package/templates/intent.md +49 -39
- package/templates/spike-report.md +10 -10
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* still takes all three. The gate is the one both need, because it alone has a
|
|
16
16
|
* branch neither config can carry (see {@link gateTail}).
|
|
17
17
|
*/
|
|
18
|
-
import { DIRTY_MARKER } from '../gate.js';
|
|
19
|
-
import { execCommand } from '../pm/index.js';
|
|
18
|
+
import { DIRTY_MARKER, GATE_RETRY_ENV } from '../gate.js';
|
|
19
|
+
import { execCommand, runScript } from '../pm/index.js';
|
|
20
20
|
/**
|
|
21
21
|
* The repo root, however the calling harness spells it. Cursor sets
|
|
22
22
|
* `CURSOR_PROJECT_DIR` (and `CLAUDE_PROJECT_DIR` as an alias); Claude Code sets
|
|
@@ -32,100 +32,283 @@ function checkrideCommand(pm, args) {
|
|
|
32
32
|
return execCommand(pm, ['checkride', ...args], { quiet: true });
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Every message this script can emit is passed to `printf` as an *argument*,
|
|
36
|
+
* never inlined into a double-quoted format string, so its backticks survive:
|
|
37
|
+
* `sh` runs a backtick span inside double quotes as a command, but it does not
|
|
38
|
+
* re-scan the result of a variable expansion. An early draft used `echo "…"`
|
|
39
|
+
* with the text inline and the shell silently ate two quoted command names.
|
|
36
40
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
41
|
+
* Two characters are therefore banned from every message here, and a test
|
|
42
|
+
* enforces both. A single quote would end the `'…'` the call site wraps it in. A
|
|
43
|
+
* double quote (or a backslash) would break {@link standDown}, which builds its
|
|
44
|
+
* JSON body with `printf '{"systemMessage":"%s"}'` rather than carrying a
|
|
45
|
+
* second, pre-encoded copy of the same sentence out of step with the first.
|
|
42
46
|
*/
|
|
43
|
-
const
|
|
44
|
-
|
|
47
|
+
const NOTHING_RAN = 'Nothing ran: no check executed and no artifact was written, so `.check/` holds nothing from this turn.';
|
|
48
|
+
/** `<pm> install`, which is how all four package managers spell it. */
|
|
49
|
+
const installFix = (pm) => `Run \`${pm} install\` (checkride is a devDependency of this repo), then run \`${runScript(pm, 'check')}\`.`;
|
|
45
50
|
/**
|
|
46
|
-
* The
|
|
47
|
-
*
|
|
51
|
+
* The repo has no checkride to run, because its dependencies were never
|
|
52
|
+
* installed — someone pulled a branch that added checkride, or cloned fresh, and
|
|
53
|
+
* started a session before installing.
|
|
48
54
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
55
|
+
* This stands the gate down instead of blocking, and it is the one case where
|
|
56
|
+
* that is clearly right. Blocking exists to make an agent fix what it broke; an
|
|
57
|
+
* absent toolchain is not something the turn broke, and the remedy is an install
|
|
58
|
+
* rather than an edit. Blocking on it re-asks the same agent every turn, forever,
|
|
59
|
+
* for a change that cannot clear it — the loop that ends with a contributor
|
|
60
|
+
* removing the gate. See `../gate.ts` {@link reportStandDown} for the same
|
|
61
|
+
* judgement one layer down.
|
|
53
62
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
const notInstalled = (pm) => [
|
|
64
|
+
'checkride: the gate could not run — checkride is not installed in this repo.',
|
|
65
|
+
NOTHING_RAN,
|
|
66
|
+
installFix(pm),
|
|
67
|
+
'Not blocking: the fix is an install rather than an edit, so blocking would only repeat this ' +
|
|
68
|
+
'message. Nothing was verified this turn.',
|
|
69
|
+
].join(' ');
|
|
70
|
+
/**
|
|
71
|
+
* checkride *is* installed and still did not answer with one of its own two gate
|
|
72
|
+
* codes — a broken launcher, a corrupt install, a crash. This one blocks: it is
|
|
73
|
+
* rare, it is not self-explanatory, and unlike a missing install there is no
|
|
74
|
+
* known remedy to name, so the honest move is to stop and be looked at. The
|
|
75
|
+
* retry guard in {@link gatePreamble} still bounds it.
|
|
76
|
+
*/
|
|
77
|
+
const notAnswering = (pm, harness) => [
|
|
78
|
+
'checkride: the gate could not run — checkride is installed here but `checkride gate` did not answer.',
|
|
79
|
+
NOTHING_RAN,
|
|
80
|
+
`Run \`${execCommand(pm, ['checkride', 'gate', '--harness', harness])}\` in a terminal to see the failure directly.`,
|
|
81
|
+
].join(' ');
|
|
82
|
+
/** The harness pointed the hook at a directory that is not there. */
|
|
83
|
+
const noRepo = 'checkride: the gate could not run — the project directory the harness named does not exist or ' +
|
|
84
|
+
'cannot be entered, so there is no repo to check.';
|
|
85
|
+
/**
|
|
86
|
+
* The shell functions every branch below answers through: one for a verdict that
|
|
87
|
+
* blocks, one for a verdict that gives up on blocking.
|
|
88
|
+
*
|
|
89
|
+
* `block` consults `retry` first, which is what bounds *every* could-not-run
|
|
90
|
+
* cause rather than only the ones enumerated here. Claude Code sends
|
|
91
|
+
* `stop_hook_active` on the Stop payload and Cursor sends `loop_count`; either
|
|
92
|
+
* one says the harness has already auto-submitted a turn on this hook, and a
|
|
93
|
+
* second identical block has been demonstrated not to help. A red pipeline never
|
|
94
|
+
* reaches this — that loop is the gate working, and is deliberately unbounded.
|
|
95
|
+
*
|
|
96
|
+
* Under Cursor `stand_down` consults it too, for a different reason. Blocking
|
|
97
|
+
* there is a `followup_message`, and so is the only way to reach a *user*, so
|
|
98
|
+
* the same guard has to serve both: it stops `block` repeating itself, and it
|
|
99
|
+
* rations `stand_down` to the single message that names the fix. `retry` is
|
|
100
|
+
* conservative in exactly the direction that keeps that safe — Cursor's
|
|
101
|
+
* `loop_count` counts every follow-up in the conversation rather than the
|
|
102
|
+
* consecutive ones, so an over-count costs a quiet stand-down, never a loop.
|
|
103
|
+
*/
|
|
104
|
+
function gatePreamble(harness) {
|
|
105
|
+
const cursor = harness === 'cursor';
|
|
106
|
+
return [
|
|
107
|
+
'# The Stop payload arrives as JSON on stdin and says whether the harness has',
|
|
108
|
+
'# already been round this loop once. Read it before anything else can consume',
|
|
109
|
+
'# it, and only from a pipe: `cat` on a terminal would hang a hand-run forever.',
|
|
110
|
+
"payload=''",
|
|
111
|
+
'[ -t 0 ] || payload=$(cat)',
|
|
112
|
+
'',
|
|
113
|
+
'# True once the harness has already auto-submitted a turn on this hook.',
|
|
114
|
+
'retry() {',
|
|
115
|
+
' printf %s "$payload" |',
|
|
116
|
+
` grep -Eq '"stop_hook_active"[[:space:]]*:[[:space:]]*true|"loop_count"[[:space:]]*:[[:space:]]*[1-9]'`,
|
|
117
|
+
'}',
|
|
118
|
+
'',
|
|
119
|
+
'# Give up on blocking, and say so. Nothing here is silent: a turn that ended',
|
|
120
|
+
'# unverified must never look like one that passed.',
|
|
121
|
+
'stand_down() {',
|
|
122
|
+
...(cursor
|
|
123
|
+
? [
|
|
124
|
+
' # Cursor names no channel that shows hook stderr to a user — only a Hooks',
|
|
125
|
+
' # output panel under Customize — so stderr alone would hide this from the',
|
|
126
|
+
' # one party who can fix an environment. `followup_message` does reach the',
|
|
127
|
+
' # chat, and it also *submits a new turn*, so it goes out exactly once:',
|
|
128
|
+
' # `loop_count` says a follow-up already went, and a second is the loop.',
|
|
129
|
+
' retry || printf \'{"followup_message":"%s"}\\n\' "$1"',
|
|
130
|
+
' printf \'%s\\n\' "$1" >&2',
|
|
131
|
+
]
|
|
132
|
+
: [
|
|
133
|
+
' # `systemMessage` reaches the user — the one party who can fix an',
|
|
134
|
+
' # environment. With no `decision` in the body, Claude Code does not block.',
|
|
135
|
+
' printf \'{"systemMessage":"%s"}\\n\' "$1"',
|
|
136
|
+
' printf \'%s\\n\' "$1" >&2',
|
|
137
|
+
]),
|
|
138
|
+
' exit 0',
|
|
139
|
+
'}',
|
|
140
|
+
'',
|
|
141
|
+
'# Block the turn — unless this is the second consecutive attempt, in which',
|
|
142
|
+
'# case blocking is the thing already shown not to work.',
|
|
143
|
+
'block() {',
|
|
144
|
+
' retry && stand_down "$1 Standing down rather than blocking a second time on the same verdict."',
|
|
145
|
+
...(cursor
|
|
146
|
+
? [
|
|
147
|
+
' # Cursor reads any non-zero stop hook as a *broken* hook and ends the turn,',
|
|
148
|
+
' # so the block has to ride in the body with an exit of 0.',
|
|
149
|
+
' printf \'{"followup_message":"%s"}\\n\' "$1"',
|
|
150
|
+
' exit 0',
|
|
151
|
+
]
|
|
152
|
+
: [' printf \'%s\\n\' "$1" >&2', ' exit 2']),
|
|
153
|
+
'}',
|
|
154
|
+
...(cursor
|
|
155
|
+
? [
|
|
156
|
+
'',
|
|
157
|
+
'# checkride also stands *itself* down, for a refusal it detects from inside',
|
|
158
|
+
'# (an `engines` pin the hook Node fails, a corepack it cannot verify) — and',
|
|
159
|
+
'# it never sees the stop payload, so hand it the one fact that bounds that',
|
|
160
|
+
'# message to a single turn the same way. It sends a followup only on a 0.',
|
|
161
|
+
`if retry; then ${GATE_RETRY_ENV}=1; else ${GATE_RETRY_ENV}=0; fi`,
|
|
162
|
+
`export ${GATE_RETRY_ENV}`,
|
|
163
|
+
]
|
|
164
|
+
: []),
|
|
165
|
+
];
|
|
58
166
|
}
|
|
59
167
|
/**
|
|
60
168
|
* Translate the gate's exit status into `harness`'s protocol.
|
|
61
169
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
170
|
+
* `checkride gate` exits 0 or 2 by design, and only on those two is its stdout a
|
|
171
|
+
* hook body worth forwarding. Anything else means checkride never ran, and
|
|
172
|
+
* whatever is on stdout then is the *launcher's* error — pnpm writes
|
|
173
|
+
* `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL` there, ahead of a stray `undefined` on
|
|
174
|
+
* pnpm 11. Forwarding that puts unparseable text on the one channel a harness
|
|
175
|
+
* reads as protocol, so the status is checked first and the body is forwarded
|
|
176
|
+
* only when checkride is the one that wrote it. That is why {@link invokeGate}
|
|
177
|
+
* captures under both harnesses: output already streamed past the script cannot
|
|
178
|
+
* be withheld by it.
|
|
69
179
|
*/
|
|
70
|
-
function gateTail(harness) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
'#
|
|
75
|
-
'#
|
|
76
|
-
'
|
|
77
|
-
|
|
78
|
-
|
|
180
|
+
function gateTail(harness, pm) {
|
|
181
|
+
const answered = harness === 'cursor'
|
|
182
|
+
? [
|
|
183
|
+
'# On 0 checkride has already written its JSON body, and on 2 the followup',
|
|
184
|
+
'# body. Either way it answered, so hand back what it wrote, unchanged.',
|
|
185
|
+
'# A green that had nothing to do writes nothing, and that is a body too.',
|
|
186
|
+
'case "$status" in',
|
|
187
|
+
' 0|2)',
|
|
188
|
+
' [ -n "$body" ] && printf \'%s\\n\' "$body"',
|
|
189
|
+
' exit 0',
|
|
190
|
+
' ;;',
|
|
191
|
+
'esac',
|
|
192
|
+
]
|
|
193
|
+
: [
|
|
194
|
+
'# Claude Code parses a hook body only on exit 0, and only a body can carry a',
|
|
195
|
+
'# user-visible message alongside the block. So when checkride produced one,',
|
|
196
|
+
'# forward it and exit 0: the verdict rides in `decision`, not in the status.',
|
|
197
|
+
'case "$status" in',
|
|
198
|
+
' 0|2)',
|
|
199
|
+
' if [ -n "$body" ]; then',
|
|
200
|
+
" printf '%s\\n' \"$body\"",
|
|
201
|
+
' exit 0',
|
|
202
|
+
' fi',
|
|
203
|
+
' # No body — an older checkride that reports only through the exit code.',
|
|
204
|
+
' exit "$status"',
|
|
205
|
+
' ;;',
|
|
206
|
+
'esac',
|
|
79
207
|
];
|
|
208
|
+
return [
|
|
209
|
+
...answered,
|
|
210
|
+
'',
|
|
211
|
+
"# Neither of checkride's two gate codes: checkride itself never ran. Which of",
|
|
212
|
+
'# the two reasons it was decides whether blocking could accomplish anything.',
|
|
213
|
+
'if [ -e node_modules/.bin/checkride ] || [ -e .pnp.cjs ] || [ -e .pnp.js ]; then',
|
|
214
|
+
` block '${notAnswering(pm, harness)}'`,
|
|
215
|
+
'fi',
|
|
216
|
+
`stand_down '${notInstalled(pm)}'`,
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Characters a `gate.preflight` path may not contain, because the generated
|
|
221
|
+
* script embeds it inside `'…'` and inside a message that later becomes a JSON
|
|
222
|
+
* string field.
|
|
223
|
+
*
|
|
224
|
+
* Rejected loudly rather than escaped or stripped. Every one of these in a
|
|
225
|
+
* repo-relative script path is a mistake, and a mangled path would fail later as
|
|
226
|
+
* "preflight not found", pointing at the wrong thing.
|
|
227
|
+
*/
|
|
228
|
+
const PREFLIGHT_HOSTILE = /['"\\`\n]/;
|
|
229
|
+
/**
|
|
230
|
+
* The repo-owned preflight, run from the repo root before checkride is started.
|
|
231
|
+
*
|
|
232
|
+
* Its exit code is read in the gate's own vocabulary — 0 runs the gate, 2 blocks
|
|
233
|
+
* the turn, anything else stands it down — so there is one meaning per code
|
|
234
|
+
* across the whole system rather than a second convention to learn. Both
|
|
235
|
+
* non-zero branches use the script's own output as the message and never start
|
|
236
|
+
* checkride, which is what lets a preflight answer for a repo where checkride
|
|
237
|
+
* *cannot* start.
|
|
238
|
+
*
|
|
239
|
+
* A configured path that is not there **blocks**, and deliberately: treating it
|
|
240
|
+
* as a stand-down would mean a typo in one config key silently disarms the gate
|
|
241
|
+
* forever. A missing file is repo-fixable, so blocking is the consistent answer.
|
|
242
|
+
*/
|
|
243
|
+
function preflight(path) {
|
|
244
|
+
if (PREFLIGHT_HOSTILE.test(path)) {
|
|
245
|
+
throw new Error(`checkride.config.json: gate.preflight '${path}' contains a quote, backslash, backtick or newline. ` +
|
|
246
|
+
'Use a plain repo-relative path.');
|
|
80
247
|
}
|
|
248
|
+
// `sh` resolves a command with no `/` against PATH, which is never what a
|
|
249
|
+
// repo-relative config value means.
|
|
250
|
+
const target = path.includes('/') ? path : `./${path}`;
|
|
81
251
|
return [
|
|
82
|
-
'#
|
|
83
|
-
'#
|
|
84
|
-
'#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
' case "$status" in',
|
|
88
|
-
' 0|2) exit 0 ;;',
|
|
89
|
-
' esac',
|
|
252
|
+
'# The repo-owned preflight, from `gate.preflight` in checkride.config.json.',
|
|
253
|
+
'# checkride owns this script and overwrites it; the preflight is where a repo',
|
|
254
|
+
'# says something before the gate and keeps it across every refresh.',
|
|
255
|
+
`if [ ! -e '${target}' ]; then`,
|
|
256
|
+
` block 'checkride: the gate could not run — checkride.config.json names a gate.preflight (${target}) that is not there. Add the script, or drop the key.'`,
|
|
90
257
|
'fi',
|
|
91
258
|
'',
|
|
92
|
-
'#
|
|
93
|
-
'
|
|
94
|
-
'
|
|
259
|
+
'# A preflight prints for humans; this has to survive a JSON string field. The',
|
|
260
|
+
'# two characters that would break one are dropped and newlines folded.',
|
|
261
|
+
'say() {',
|
|
262
|
+
` printf %s "$1" | tr -d '"\\\\' | tr '\\n' ' '`,
|
|
263
|
+
'}',
|
|
264
|
+
'',
|
|
265
|
+
'# Honour the shebang when the file is executable, and fall back to `sh` when it',
|
|
266
|
+
'# is not — a checked-in script routinely arrives without its exec bit.',
|
|
267
|
+
`if [ -x '${target}' ]; then`,
|
|
268
|
+
` said=$('${target}' 2>&1)`,
|
|
269
|
+
'else',
|
|
270
|
+
` said=$(sh '${target}' 2>&1)`,
|
|
271
|
+
'fi',
|
|
272
|
+
'code=$?',
|
|
273
|
+
`[ "$code" -eq 0 ] || [ -n "$said" ] ||`,
|
|
274
|
+
` said='checkride: the gate could not run — the preflight ${target} exited non-zero and printed nothing.'`,
|
|
95
275
|
'',
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
276
|
+
'# 0 runs the gate; 2 blocks; anything else stands down. checkride is never',
|
|
277
|
+
'# started on a non-zero branch.',
|
|
278
|
+
'case "$code" in',
|
|
279
|
+
' 0) ;;',
|
|
280
|
+
' 2) block "$(say "$said")" ;;',
|
|
281
|
+
' *) stand_down "$(say "$said")" ;;',
|
|
282
|
+
'esac',
|
|
99
283
|
];
|
|
100
284
|
}
|
|
101
285
|
/**
|
|
102
|
-
* Enter the repo, or report
|
|
103
|
-
* the gate can fail to start.
|
|
286
|
+
* Enter the repo, or report a verdict rather than a bare status.
|
|
104
287
|
*
|
|
105
288
|
* Spelled as a block rather than `cd … || exit 2` for two reasons: a bare status
|
|
106
289
|
* tells the agent nothing, and under Cursor a non-zero stop hook is a *broken*
|
|
107
290
|
* hook, so the one branch that used to exit 2 there was the one branch that
|
|
108
291
|
* silently let a turn end.
|
|
109
292
|
*/
|
|
110
|
-
function enterRepo(
|
|
111
|
-
|
|
112
|
-
return [`if ! cd ${PROJECT_DIR}; then`, ` ${emit}`, ` exit ${exit}`, 'fi'];
|
|
293
|
+
function enterRepo() {
|
|
294
|
+
return [`if ! cd ${PROJECT_DIR}; then`, ` block '${noRepo}'`, 'fi'];
|
|
113
295
|
}
|
|
114
296
|
/**
|
|
115
|
-
* How the gate's stdout is taken.
|
|
297
|
+
* How the gate's stdout is taken: captured, under both harnesses.
|
|
116
298
|
*
|
|
117
|
-
*
|
|
118
|
-
* stdout
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
299
|
+
* Letting it stream through is tempting under Cursor, which reads the hook's own
|
|
300
|
+
* stdout and would need no forwarding — but a *failed launch* writes there too,
|
|
301
|
+
* and a script that never held that output cannot keep it off a channel with
|
|
302
|
+
* defined semantics. Capturing is what gives {@link gateTail} the choice, and
|
|
303
|
+
* `block`/`stand_down` sole ownership of stdout on the paths where checkride
|
|
304
|
+
* never answered.
|
|
305
|
+
*
|
|
306
|
+
* It costs nothing visible: the pipeline's human-readable progress is on stderr
|
|
307
|
+
* and streams live either way, and stdout carries only the one-line JSON
|
|
308
|
+
* verdict, written at the very end.
|
|
123
309
|
*/
|
|
124
|
-
function invokeGate(pm,
|
|
125
|
-
|
|
126
|
-
return harness === 'cursor'
|
|
127
|
-
? [command, 'status=$?']
|
|
128
|
-
: [`body=$(${command})`, 'status=$?'];
|
|
310
|
+
function invokeGate(pm, args) {
|
|
311
|
+
return [`body=$(${checkrideCommand(pm, args)})`, 'status=$?'];
|
|
129
312
|
}
|
|
130
313
|
/**
|
|
131
314
|
* The gate script: `cd` to the repo, hand off to `checkride gate`, and translate
|
|
@@ -135,6 +318,12 @@ function invokeGate(pm, harness, args) {
|
|
|
135
318
|
* makes the gate conditional on the edit marker, so pure-conversation turns
|
|
136
319
|
* don't pay for a full pipeline run. Without the marker hook the guard would
|
|
137
320
|
* disarm the gate entirely, so a `--hook gate` selection writes it unguarded.
|
|
321
|
+
*
|
|
322
|
+
* `preflight` is the repo's own script, from `gate.preflight` in
|
|
323
|
+
* checkride.config.json, baked in here rather than looked up at run time — see
|
|
324
|
+
* {@link preflight} and `../gate.ts` `gatePreflight`. It runs *before* the edit
|
|
325
|
+
* marker is consulted, because a repo that cannot run the gate at all wants to
|
|
326
|
+
* say so on every turn, not only on the ones that edited a file.
|
|
138
327
|
*/
|
|
139
328
|
export function gateScript(pm, opts) {
|
|
140
329
|
const args = ['gate', '--harness', opts.harness, ...((opts.dirtyGuard ?? true) ? ['--if-dirty'] : [])];
|
|
@@ -143,16 +332,20 @@ export function gateScript(pm, opts) {
|
|
|
143
332
|
`# checkride-gate.sh — the ${opts.harness} stop-hook gate.`,
|
|
144
333
|
'#',
|
|
145
334
|
'# checkride owns this file: `checkride agent-setup` (and `checkride init`)',
|
|
146
|
-
'# overwrite it on every run. Customize
|
|
147
|
-
'#
|
|
335
|
+
'# overwrite it on every run. Customize through checkride.config.json (a `gate`',
|
|
336
|
+
'# key narrows what runs) or the environment (CHECKRIDE_NODE_BIN), not by',
|
|
337
|
+
'# editing here — edits are lost on the next refresh.',
|
|
148
338
|
'#',
|
|
149
339
|
"# Runs the repo's `check` script as a hard gate, and reports the verdict in",
|
|
150
340
|
`# ${opts.harness}'s hook protocol. See \`checkride gate --help\`.`,
|
|
151
341
|
'',
|
|
152
|
-
...
|
|
342
|
+
...gatePreamble(opts.harness),
|
|
153
343
|
'',
|
|
154
|
-
...
|
|
155
|
-
|
|
344
|
+
...enterRepo(),
|
|
345
|
+
'',
|
|
346
|
+
...(opts.preflight === undefined ? [] : [...preflight(opts.preflight), '']),
|
|
347
|
+
...invokeGate(pm, args),
|
|
348
|
+
...gateTail(opts.harness, pm),
|
|
156
349
|
'',
|
|
157
350
|
].join('\n');
|
|
158
351
|
}
|
|
@@ -202,6 +395,19 @@ export function dirtyScript() {
|
|
|
202
395
|
* is older and accepted.)
|
|
203
396
|
*/
|
|
204
397
|
const PATH_KEYS = ['file_path', 'notebook_path', 'path', 'target_file', 'filePath', 'relative_workspace_path'];
|
|
398
|
+
/**
|
|
399
|
+
* Shell verbs that can destroy *any* path they are handed, so every positional
|
|
400
|
+
* argument is a write target. `mv` belongs here rather than with the
|
|
401
|
+
* destination-only verbs because moving an accounting file away removes it just
|
|
402
|
+
* as effectively as overwriting it.
|
|
403
|
+
*/
|
|
404
|
+
const ALL_ARGS_VERBS = ['rm', 'unlink', 'shred', 'mv', 'truncate', 'tee', 'rmdir'];
|
|
405
|
+
/**
|
|
406
|
+
* Shell verbs whose sources are *read* and whose last positional argument is the
|
|
407
|
+
* only thing written. `cp .check/summary.json /tmp/x` has to stay allowed — it
|
|
408
|
+
* is a backup, not an edit — and treating its source as a target would deny it.
|
|
409
|
+
*/
|
|
410
|
+
const DEST_ONLY_VERBS = ['cp', 'install', 'ln', 'rsync'];
|
|
205
411
|
/** The deny message, shared by both harnesses. */
|
|
206
412
|
const DENY_MESSAGE = "'checkride: ' + rel + ' is checkride-owned accounting. Never edit the baseline or .check ' +\n" +
|
|
207
413
|
" 'artifacts to make a check pass — fix the finding instead (the ratchet prunes the baseline on its own).'";
|
|
@@ -229,11 +435,27 @@ function denyTail() {
|
|
|
229
435
|
* matched — reads are never denied, because triage depends on reading `.check/`
|
|
230
436
|
* artifacts.
|
|
231
437
|
*
|
|
438
|
+
* Two Cursor events land here. `preToolUse` carries a file-tool call, whose
|
|
439
|
+
* target is read from {@link PATH_KEYS}. `beforeShellExecution` carries a
|
|
440
|
+
* command line, which is scanned for a write *to* an accounting path — the gap
|
|
441
|
+
* a tool-name matcher cannot close, since `echo … > checkride.baseline.json` is
|
|
442
|
+
* a shell call and not a `Write`.
|
|
443
|
+
*
|
|
444
|
+
* The command scan is deliberately timid, because a guard that fires on a wrong
|
|
445
|
+
* parse is worse than a known hole. It denies only on demonstrated write intent
|
|
446
|
+
* — a `>`/`>>` redirect target, or a positional argument of a known mutating
|
|
447
|
+
* verb — and anything it cannot read that way is allowed. `cat`, `grep`, `jq`
|
|
448
|
+
* and `cp … /tmp/x` over `.check/` all pass, which they must: triage reads those
|
|
449
|
+
* artifacts. The hook's own `matcher` narrows the input further, so a command
|
|
450
|
+
* that never names an accounting path is not parsed at all.
|
|
451
|
+
*
|
|
232
452
|
* **Cursor only.** Claude Code enforces the same paths through
|
|
233
453
|
* `permissions.deny`, which is checked below the hook layer and costs nothing
|
|
234
454
|
* per tool call, so it needs no script. Cursor has no documented equivalent —
|
|
235
455
|
* its config is hooks — so the script survives for the harness that still
|
|
236
456
|
* requires one. If Cursor grows a file-path deny list, this file goes away.
|
|
457
|
+
* (Claude Code's deny rules have the same shell-redirect hole and no script to
|
|
458
|
+
* close it with; that half of the gap is still open. See docs/cursor.md.)
|
|
237
459
|
*/
|
|
238
460
|
export function protectScript() {
|
|
239
461
|
return [
|
|
@@ -242,14 +464,16 @@ export function protectScript() {
|
|
|
242
464
|
'// checkride owns this file: `checkride agent-setup` (and `checkride init`)',
|
|
243
465
|
'// overwrite it on every run.',
|
|
244
466
|
'//',
|
|
245
|
-
'//
|
|
246
|
-
'//
|
|
247
|
-
'//
|
|
467
|
+
'// Guard hook for Cursor. Reads the hook payload as JSON on stdin and denies',
|
|
468
|
+
'// the call when it *writes* to the baseline or .check/. Reads are never',
|
|
469
|
+
'// denied — triage depends on reading .check artifacts.',
|
|
248
470
|
"'use strict';",
|
|
249
471
|
"const { realpathSync } = require('node:fs');",
|
|
250
472
|
"const { basename, dirname, isAbsolute, relative, resolve, sep } = require('node:path');",
|
|
251
473
|
'',
|
|
252
474
|
`const PATH_KEYS = ${JSON.stringify(PATH_KEYS)};`,
|
|
475
|
+
`const ALL_ARGS_VERBS = ${JSON.stringify(ALL_ARGS_VERBS)};`,
|
|
476
|
+
`const DEST_ONLY_VERBS = ${JSON.stringify(DEST_ONLY_VERBS)};`,
|
|
253
477
|
'',
|
|
254
478
|
'// Resolve symlinks in `p`, so a symlinked prefix cannot make an in-repo path',
|
|
255
479
|
'// look external. On macOS the repo root routinely arrives in two spellings —',
|
|
@@ -274,6 +498,84 @@ export function protectScript() {
|
|
|
274
498
|
' }',
|
|
275
499
|
'}',
|
|
276
500
|
'',
|
|
501
|
+
'// Split a command line into tokens, keeping quoted spans whole and emitting',
|
|
502
|
+
'// each unquoted operator as a token of its own — so `echo x>f` and `echo x > f`',
|
|
503
|
+
'// tokenize alike, and a `>` inside quotes is never mistaken for a redirect.',
|
|
504
|
+
'function tokenize(command) {',
|
|
505
|
+
' const tokens = [];',
|
|
506
|
+
" let cur = '';",
|
|
507
|
+
' let quote = null;',
|
|
508
|
+
" const flush = () => { if (cur !== '') { tokens.push(cur); cur = ''; } };",
|
|
509
|
+
' for (let i = 0; i < command.length; i += 1) {',
|
|
510
|
+
' const c = command[i];',
|
|
511
|
+
' if (quote !== null) {',
|
|
512
|
+
' if (c === quote) quote = null;',
|
|
513
|
+
' else cur += c;',
|
|
514
|
+
' } else if (/["\']/.test(c)) {',
|
|
515
|
+
' quote = c;',
|
|
516
|
+
" } else if (c === '\\\\') {",
|
|
517
|
+
" i += 1; cur += command[i] || '';",
|
|
518
|
+
' } else if (/\\s/.test(c)) {',
|
|
519
|
+
' flush();',
|
|
520
|
+
' } else if (/[<>|&;]/.test(c)) {',
|
|
521
|
+
' flush();',
|
|
522
|
+
' let op = c;',
|
|
523
|
+
' while (command[i + 1] === c) { op += c; i += 1; }',
|
|
524
|
+
' tokens.push(op);',
|
|
525
|
+
' } else {',
|
|
526
|
+
' cur += c;',
|
|
527
|
+
' }',
|
|
528
|
+
' }',
|
|
529
|
+
' flush();',
|
|
530
|
+
' return tokens;',
|
|
531
|
+
'}',
|
|
532
|
+
'',
|
|
533
|
+
'const isOperator = (t) => /^[<>|&;]+$/.test(t);',
|
|
534
|
+
'',
|
|
535
|
+
'// Break a token stream at the operators that start a new command, so the verb',
|
|
536
|
+
'// of `cat x | tee .check/f` is read as `tee` and not as `cat`.',
|
|
537
|
+
'function segments(tokens) {',
|
|
538
|
+
' const out = [[]];',
|
|
539
|
+
' for (const t of tokens) {',
|
|
540
|
+
' if (/^[|&;]+$/.test(t)) out.push([]);',
|
|
541
|
+
' else out[out.length - 1].push(t);',
|
|
542
|
+
' }',
|
|
543
|
+
' return out;',
|
|
544
|
+
'}',
|
|
545
|
+
'',
|
|
546
|
+
'// Every path a command line demonstrably writes to. Anything this cannot read',
|
|
547
|
+
'// as a write yields nothing, and the caller then allows the command.',
|
|
548
|
+
'function commandTargets(command) {',
|
|
549
|
+
' const targets = [];',
|
|
550
|
+
' for (const segment of segments(tokenize(command))) {',
|
|
551
|
+
' for (let i = 0; i < segment.length; i += 1) {',
|
|
552
|
+
" if (segment[i] !== '>' && segment[i] !== '>>') continue;",
|
|
553
|
+
' const next = segment[i + 1];',
|
|
554
|
+
' if (next !== undefined && !isOperator(next)) targets.push(next);',
|
|
555
|
+
' }',
|
|
556
|
+
' const words = segment.filter((t) => !isOperator(t));',
|
|
557
|
+
' // Step over any leading `VAR=value` assignments to reach the verb itself.',
|
|
558
|
+
' let i = 0;',
|
|
559
|
+
' while (i < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[i])) i += 1;',
|
|
560
|
+
" const verb = (words[i] || '').split('/').pop();",
|
|
561
|
+
' const rest = words.slice(i + 1);',
|
|
562
|
+
" const args = rest.filter((a) => !a.startsWith('-'));",
|
|
563
|
+
' if (ALL_ARGS_VERBS.indexOf(verb) >= 0) targets.push(...args);',
|
|
564
|
+
' else if (DEST_ONLY_VERBS.indexOf(verb) >= 0 && args.length > 0) targets.push(args[args.length - 1]);',
|
|
565
|
+
" else if ((verb === 'sed' || verb === 'perl') && rest.some((a) => /^-[a-zA-Z]*i/.test(a))) targets.push(...args);",
|
|
566
|
+
" else if (verb === 'dd') targets.push(...rest.filter((a) => a.startsWith('of=')).map((a) => a.slice(3)));",
|
|
567
|
+
' }',
|
|
568
|
+
' return targets;',
|
|
569
|
+
'}',
|
|
570
|
+
'',
|
|
571
|
+
"// `target`'s repo-relative path when it is checkride accounting, else undefined.",
|
|
572
|
+
'function accounting(target, root, base) {',
|
|
573
|
+
' const abs = canon(isAbsolute(target) ? target : resolve(base, target));',
|
|
574
|
+
" const rel = relative(root, abs).split(sep).join('/');",
|
|
575
|
+
" if (rel === 'checkride.baseline.json' || rel === '.check' || rel.startsWith('.check/')) return rel;",
|
|
576
|
+
' return undefined;',
|
|
577
|
+
'}',
|
|
578
|
+
'',
|
|
277
579
|
"let raw = '';",
|
|
278
580
|
"process.stdin.on('data', (chunk) => { raw += chunk; });",
|
|
279
581
|
"process.stdin.on('end', () => {",
|
|
@@ -283,14 +585,17 @@ export function protectScript() {
|
|
|
283
585
|
' } catch {',
|
|
284
586
|
' process.exit(0); // fail open: a broken hook must not brick every edit',
|
|
285
587
|
' }',
|
|
286
|
-
|
|
287
|
-
" const target = PATH_KEYS.map((k) => tool[k]).find((v) => typeof v === 'string' && v.length > 0);",
|
|
288
|
-
' if (target === undefined) process.exit(0);',
|
|
588
|
+
" if (input === null || typeof input !== 'object') process.exit(0);",
|
|
289
589
|
' const root = canon(process.env.CURSOR_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd());',
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
" const
|
|
293
|
-
'
|
|
590
|
+
" // A shell command resolves against the shell's own cwd, which Cursor sends;",
|
|
591
|
+
' // a file-tool path has always been resolved against the repo root.',
|
|
592
|
+
" const base = typeof input.cwd === 'string' && input.cwd.length > 0 ? canon(input.cwd) : root;",
|
|
593
|
+
' const tool = input.tool_input || {};',
|
|
594
|
+
" const targets = input.hook_event_name === 'beforeShellExecution'",
|
|
595
|
+
" ? commandTargets(typeof input.command === 'string' ? input.command : '')",
|
|
596
|
+
" : PATH_KEYS.map((k) => tool[k]).filter((v) => typeof v === 'string' && v.length > 0);",
|
|
597
|
+
' const rel = targets.map((t) => accounting(t, root, base)).find((r) => r !== undefined);',
|
|
598
|
+
' if (rel === undefined) process.exit(0);',
|
|
294
599
|
...denyTail(),
|
|
295
600
|
'});',
|
|
296
601
|
'',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/agent-setup/scripts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAoB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/agent-setup/scripts.ts"],"names":[],"mappings":"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"}
|
|
@@ -191,9 +191,9 @@ function collectDeadKeys(j) {
|
|
|
191
191
|
* Extract dead-code findings and the authoritative issue count (`total_issues`).
|
|
192
192
|
* Findings come from the top-level detail arrays (`unused_exports`,
|
|
193
193
|
* `unused_dev_dependencies`, `circular_dependencies`, …) rather than the summary
|
|
194
|
-
* keys: the summary aggregates a few categories under one name (
|
|
194
|
+
* keys: the summary aggregates a few categories under one name (for example dev and
|
|
195
195
|
* optional deps both count as `unused_dependencies`) whose items live in
|
|
196
|
-
* separately
|
|
196
|
+
* separately named arrays, so keying off the summary would miss them. Iterating
|
|
197
197
|
* every array except the {@link UNCOUNTED_ARRAYS} covers whatever categories
|
|
198
198
|
* fallow reports — including ones added in a future minor — without a
|
|
199
199
|
* hand-maintained list of the ones that do count. A category whose items carry
|