plumbbob 0.9.3 → 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 +92 -89
- package/dist/cli-core.js +25 -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 +55 -22
- 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 +21 -20
- package/dist/lib/latch.js +19 -19
- package/dist/lib/orient.js +36 -17
- package/dist/lib/reentry.js +70 -0
- 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 +27 -27
- 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 +31 -30
- package/dist/verbs/finish.js +28 -41
- 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 +297 -0
- package/dist/verbs/revert.js +13 -13
- package/dist/verbs/spike.js +25 -19
- 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/hooks/post-edit.sh +4 -4
- package/hooks/pre-bash-commit.sh +3 -3
- package/node_modules/checkride/.claude-plugin/plugin.json +11 -0
- package/node_modules/checkride/CHANGELOG.md +1404 -0
- package/node_modules/checkride/LICENSE +201 -21
- package/node_modules/checkride/README.md +197 -38
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts +15 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts.map +1 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js +17 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js.map +1 -0
- package/node_modules/checkride/dist/adapters.d.ts +30 -10
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +62 -23
- package/node_modules/checkride/dist/adapters.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/claude.d.ts +88 -0
- package/node_modules/checkride/dist/agent-setup/claude.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/claude.js +305 -0
- package/node_modules/checkride/dist/agent-setup/claude.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/command.d.ts +43 -0
- package/node_modules/checkride/dist/agent-setup/command.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/command.js +77 -0
- package/node_modules/checkride/dist/agent-setup/command.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts +69 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js +247 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/files.d.ts +50 -0
- package/node_modules/checkride/dist/agent-setup/files.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/files.js +80 -0
- package/node_modules/checkride/dist/agent-setup/files.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/harness.d.ts +101 -0
- package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/harness.js +93 -0
- package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/hook.d.ts +45 -52
- package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/hook.js +64 -95
- package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.d.ts +10 -4
- package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.js +9 -4
- package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts +81 -0
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js +604 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/skills.d.ts +31 -0
- package/node_modules/checkride/dist/agent-setup/skills.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/skills.js +56 -0
- package/node_modules/checkride/dist/agent-setup/skills.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/configured.d.ts +30 -0
- package/node_modules/checkride/dist/artifacts/configured.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/configured.js +45 -0
- package/node_modules/checkride/dist/artifacts/configured.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/format.d.ts +33 -0
- package/node_modules/checkride/dist/artifacts/format.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/format.js +56 -0
- package/node_modules/checkride/dist/artifacts/format.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/freshness.d.ts +32 -0
- package/node_modules/checkride/dist/artifacts/freshness.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/freshness.js +37 -0
- package/node_modules/checkride/dist/artifacts/freshness.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/index.d.ts +22 -0
- package/node_modules/checkride/dist/artifacts/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/index.js +19 -0
- package/node_modules/checkride/dist/artifacts/index.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/json.d.ts +31 -0
- package/node_modules/checkride/dist/artifacts/json.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/json.js +47 -0
- package/node_modules/checkride/dist/artifacts/json.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/raw.d.ts +60 -0
- package/node_modules/checkride/dist/artifacts/raw.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/raw.js +91 -0
- package/node_modules/checkride/dist/artifacts/raw.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/summary.d.ts +69 -0
- package/node_modules/checkride/dist/artifacts/summary.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/summary.js +108 -0
- package/node_modules/checkride/dist/artifacts/summary.js.map +1 -0
- package/node_modules/checkride/dist/atomic.d.ts +16 -1
- package/node_modules/checkride/dist/atomic.d.ts.map +1 -1
- package/node_modules/checkride/dist/atomic.js +25 -3
- package/node_modules/checkride/dist/atomic.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.d.ts +9 -5
- package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.js +163 -60
- package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +20 -11
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.js +73 -23
- 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 +7 -5
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/index.js +5 -4
- package/node_modules/checkride/dist/baseline/index.js.map +1 -1
- package/node_modules/checkride/dist/baseline/store.d.ts +41 -10
- package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/store.js +65 -24
- package/node_modules/checkride/dist/baseline/store.js.map +1 -1
- package/node_modules/checkride/dist/baseline-command.d.ts +5 -5
- package/node_modules/checkride/dist/baseline-command.js +7 -7
- package/node_modules/checkride/dist/baseline-command.js.map +1 -1
- package/node_modules/checkride/dist/cli.d.ts +21 -4
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +350 -36
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +114 -9
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +142 -23
- package/node_modules/checkride/dist/config.js.map +1 -1
- package/node_modules/checkride/dist/digest/digest.d.ts +5 -5
- package/node_modules/checkride/dist/digest/digest.js +6 -6
- package/node_modules/checkride/dist/digest/index.d.ts +2 -2
- package/node_modules/checkride/dist/digest/index.js +2 -2
- package/node_modules/checkride/dist/doctor.d.ts +27 -6
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +231 -27
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/gate.d.ts +180 -0
- package/node_modules/checkride/dist/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/gate.js +617 -0
- package/node_modules/checkride/dist/gate.js.map +1 -0
- package/node_modules/checkride/dist/init.d.ts +79 -12
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +385 -81
- package/node_modules/checkride/dist/init.js.map +1 -1
- package/node_modules/checkride/dist/json.d.ts +21 -0
- package/node_modules/checkride/dist/json.d.ts.map +1 -0
- package/node_modules/checkride/dist/json.js +21 -0
- package/node_modules/checkride/dist/json.js.map +1 -0
- package/node_modules/checkride/dist/links.d.ts +31 -5
- package/node_modules/checkride/dist/links.d.ts.map +1 -1
- package/node_modules/checkride/dist/links.js +141 -32
- package/node_modules/checkride/dist/links.js.map +1 -1
- package/node_modules/checkride/dist/node-pin.d.ts +129 -0
- package/node_modules/checkride/dist/node-pin.d.ts.map +1 -0
- package/node_modules/checkride/dist/node-pin.js +211 -0
- package/node_modules/checkride/dist/node-pin.js.map +1 -0
- package/node_modules/checkride/dist/orchestrator.d.ts +53 -10
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +312 -113
- package/node_modules/checkride/dist/orchestrator.js.map +1 -1
- package/node_modules/checkride/dist/pack.d.ts +8 -9
- package/node_modules/checkride/dist/pack.d.ts.map +1 -1
- package/node_modules/checkride/dist/pack.js +53 -17
- package/node_modules/checkride/dist/pack.js.map +1 -1
- package/node_modules/checkride/dist/pm/detect.d.ts +6 -1
- package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/detect.js +6 -1
- package/node_modules/checkride/dist/pm/detect.js.map +1 -1
- package/node_modules/checkride/dist/pm/index.d.ts +7 -2
- package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/index.js +6 -2
- package/node_modules/checkride/dist/pm/index.js.map +1 -1
- package/node_modules/checkride/dist/pm/launch.d.ts +72 -0
- package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/launch.js +108 -0
- package/node_modules/checkride/dist/pm/launch.js.map +1 -0
- package/node_modules/checkride/dist/pm/tools.d.ts +73 -0
- package/node_modules/checkride/dist/pm/tools.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/tools.js +124 -0
- package/node_modules/checkride/dist/pm/tools.js.map +1 -0
- package/node_modules/checkride/dist/pm/translate.d.ts +35 -9
- package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/translate.js +78 -18
- package/node_modules/checkride/dist/pm/translate.js.map +1 -1
- package/node_modules/checkride/dist/proc.d.ts +30 -0
- package/node_modules/checkride/dist/proc.d.ts.map +1 -0
- package/node_modules/checkride/dist/proc.js +42 -0
- package/node_modules/checkride/dist/proc.js.map +1 -0
- package/node_modules/checkride/dist/qa/cli.d.ts +20 -0
- package/node_modules/checkride/dist/qa/cli.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/cli.js +24 -0
- package/node_modules/checkride/dist/qa/cli.js.map +1 -0
- package/node_modules/checkride/dist/qa/dead.d.ts +39 -0
- package/node_modules/checkride/dist/qa/dead.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/dead.js +88 -0
- package/node_modules/checkride/dist/qa/dead.js.map +1 -0
- package/node_modules/checkride/dist/qa/dupes.d.ts +45 -0
- package/node_modules/checkride/dist/qa/dupes.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/dupes.js +62 -0
- package/node_modules/checkride/dist/qa/dupes.js.map +1 -0
- package/node_modules/checkride/dist/qa/health.d.ts +85 -0
- package/node_modules/checkride/dist/qa/health.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/health.js +109 -0
- package/node_modules/checkride/dist/qa/health.js.map +1 -0
- package/node_modules/checkride/dist/qa/index.d.ts +23 -0
- package/node_modules/checkride/dist/qa/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/index.js +22 -0
- package/node_modules/checkride/dist/qa/index.js.map +1 -0
- package/node_modules/checkride/dist/qa/mutation.d.ts +80 -0
- package/node_modules/checkride/dist/qa/mutation.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/mutation.js +195 -0
- package/node_modules/checkride/dist/qa/mutation.js.map +1 -0
- package/node_modules/checkride/dist/qa/qa.d.ts +71 -0
- package/node_modules/checkride/dist/qa/qa.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/qa.js +158 -0
- package/node_modules/checkride/dist/qa/qa.js.map +1 -0
- package/node_modules/checkride/dist/qa/read.d.ts +40 -0
- package/node_modules/checkride/dist/qa/read.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/read.js +47 -0
- package/node_modules/checkride/dist/qa/read.js.map +1 -0
- package/node_modules/checkride/dist/qa/render.d.ts +26 -0
- package/node_modules/checkride/dist/qa/render.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/render.js +310 -0
- package/node_modules/checkride/dist/qa/render.js.map +1 -0
- 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/security.d.ts +45 -0
- package/node_modules/checkride/dist/security.d.ts.map +1 -0
- package/node_modules/checkride/dist/security.js +86 -0
- package/node_modules/checkride/dist/security.js.map +1 -0
- package/node_modules/checkride/dist/smoke.d.ts +6 -6
- package/node_modules/checkride/dist/smoke.js +6 -6
- package/node_modules/checkride/dist/snippets.d.ts +27 -30
- package/node_modules/checkride/dist/snippets.d.ts.map +1 -1
- package/node_modules/checkride/dist/snippets.js +26 -30
- package/node_modules/checkride/dist/snippets.js.map +1 -1
- package/node_modules/checkride/dist/tool-json.d.ts +33 -0
- package/node_modules/checkride/dist/tool-json.d.ts.map +1 -0
- package/node_modules/checkride/dist/tool-json.js +58 -0
- package/node_modules/checkride/dist/tool-json.js.map +1 -0
- package/node_modules/checkride/dist/triage/cli.d.ts +21 -0
- package/node_modules/checkride/dist/triage/cli.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/cli.js +26 -0
- package/node_modules/checkride/dist/triage/cli.js.map +1 -0
- package/node_modules/checkride/dist/triage/doctor-fold.d.ts +51 -0
- package/node_modules/checkride/dist/triage/doctor-fold.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/doctor-fold.js +88 -0
- package/node_modules/checkride/dist/triage/doctor-fold.js.map +1 -0
- package/node_modules/checkride/dist/triage/env.d.ts +54 -0
- package/node_modules/checkride/dist/triage/env.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/env.js +81 -0
- package/node_modules/checkride/dist/triage/env.js.map +1 -0
- package/node_modules/checkride/dist/triage/gate.d.ts +61 -0
- package/node_modules/checkride/dist/triage/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/gate.js +121 -0
- package/node_modules/checkride/dist/triage/gate.js.map +1 -0
- package/node_modules/checkride/dist/triage/index.d.ts +22 -0
- package/node_modules/checkride/dist/triage/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/index.js +20 -0
- package/node_modules/checkride/dist/triage/index.js.map +1 -0
- package/node_modules/checkride/dist/triage/render.d.ts +19 -0
- package/node_modules/checkride/dist/triage/render.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/render.js +419 -0
- package/node_modules/checkride/dist/triage/render.js.map +1 -0
- package/node_modules/checkride/dist/triage/triage.d.ts +83 -0
- package/node_modules/checkride/dist/triage/triage.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/triage.js +131 -0
- package/node_modules/checkride/dist/triage/triage.js.map +1 -0
- package/node_modules/checkride/package.json +18 -15
- package/node_modules/checkride/schema/checkride.config.schema.json +70 -5
- package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
- package/node_modules/checkride/skills/check/SKILL.md +265 -0
- package/node_modules/checkride/skills/qa/SKILL.md +231 -0
- package/node_modules/checkride/templates/shared/cspell.json +1 -0
- package/node_modules/checkride/templates/shared/oxlintrc.json +1 -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 +85 -76
- package/skills/doctor/SKILL.md +11 -11
- package/skills/finish/SKILL.md +21 -18
- package/skills/harvest/SKILL.md +13 -11
- package/skills/park/SKILL.md +10 -10
- package/skills/plan/SKILL.md +69 -59
- package/skills/recover/SKILL.md +28 -0
- package/skills/refine/SKILL.md +21 -18
- 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 +20 -15
- package/skills/verify/SKILL.md +56 -46
- package/templates/build-log.md +7 -7
- package/templates/intent.md +52 -41
- package/templates/spike-report.md +10 -10
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hook scripts checkride generates into a consumer repo.
|
|
3
|
+
*
|
|
4
|
+
* checkride owns these files and overwrites them on every `agent-setup`/`init`,
|
|
5
|
+
* so consumer customization belongs beside them, never in them. That is also why
|
|
6
|
+
* the settings entry each harness gets is a stable one-liner invoking a script
|
|
7
|
+
* rather than the behavior inline: a refresh rewrites the script freely without
|
|
8
|
+
* ever clobbering an entry a human may have edited.
|
|
9
|
+
*
|
|
10
|
+
* **Every script here is one a harness's configuration could not express.** A
|
|
11
|
+
* generated file in someone's repo is a standing cost — reviewed, kept in sync,
|
|
12
|
+
* wondered about — so it is written only where config falls short. Claude Code
|
|
13
|
+
* takes just the gate: its `protect` is a `permissions.deny` rule and its
|
|
14
|
+
* `dirty` an inline command. Cursor, whose config is hooks and nothing else,
|
|
15
|
+
* still takes all three. The gate is the one both need, because it alone has a
|
|
16
|
+
* branch neither config can carry (see {@link gateTail}).
|
|
17
|
+
*/
|
|
18
|
+
import { DIRTY_MARKER, GATE_RETRY_ENV } from '../gate.js';
|
|
19
|
+
import { execCommand, runScript } from '../pm/index.js';
|
|
20
|
+
/**
|
|
21
|
+
* The repo root, however the calling harness spells it. Cursor sets
|
|
22
|
+
* `CURSOR_PROJECT_DIR` (and `CLAUDE_PROJECT_DIR` as an alias); Claude Code sets
|
|
23
|
+
* only the latter. The final `.` fallback keeps the script working under an
|
|
24
|
+
* older harness that sets neither, where the cwd is already the project.
|
|
25
|
+
*/
|
|
26
|
+
const PROJECT_DIR = '"${CURSOR_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"';
|
|
27
|
+
/**
|
|
28
|
+
* `<pm> exec checkride <args…>`, spelled for `pm`'s launcher. Quieted: the gate
|
|
29
|
+
* parses this command's stdout, so pnpm's narration must not lead it.
|
|
30
|
+
*/
|
|
31
|
+
function checkrideCommand(pm, args) {
|
|
32
|
+
return execCommand(pm, ['checkride', ...args], { quiet: true });
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
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.
|
|
40
|
+
*
|
|
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.
|
|
46
|
+
*/
|
|
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')}\`.`;
|
|
50
|
+
/**
|
|
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.
|
|
54
|
+
*
|
|
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.
|
|
62
|
+
*/
|
|
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
|
+
];
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Translate the gate's exit status into `harness`'s protocol.
|
|
169
|
+
*
|
|
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.
|
|
179
|
+
*/
|
|
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',
|
|
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.');
|
|
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}`;
|
|
251
|
+
return [
|
|
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.'`,
|
|
257
|
+
'fi',
|
|
258
|
+
'',
|
|
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.'`,
|
|
275
|
+
'',
|
|
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',
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Enter the repo, or report a verdict rather than a bare status.
|
|
287
|
+
*
|
|
288
|
+
* Spelled as a block rather than `cd … || exit 2` for two reasons: a bare status
|
|
289
|
+
* tells the agent nothing, and under Cursor a non-zero stop hook is a *broken*
|
|
290
|
+
* hook, so the one branch that used to exit 2 there was the one branch that
|
|
291
|
+
* silently let a turn end.
|
|
292
|
+
*/
|
|
293
|
+
function enterRepo() {
|
|
294
|
+
return [`if ! cd ${PROJECT_DIR}; then`, ` block '${noRepo}'`, 'fi'];
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* How the gate's stdout is taken: captured, under both harnesses.
|
|
298
|
+
*
|
|
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.
|
|
309
|
+
*/
|
|
310
|
+
function invokeGate(pm, args) {
|
|
311
|
+
return [`body=$(${checkrideCommand(pm, args)})`, 'status=$?'];
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The gate script: `cd` to the repo, hand off to `checkride gate`, and translate
|
|
315
|
+
* the result into the harness's protocol.
|
|
316
|
+
*
|
|
317
|
+
* `dirtyGuard` (on when the `dirty` hook is written alongside — the default)
|
|
318
|
+
* makes the gate conditional on the edit marker, so pure-conversation turns
|
|
319
|
+
* don't pay for a full pipeline run. Without the marker hook the guard would
|
|
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.
|
|
327
|
+
*/
|
|
328
|
+
export function gateScript(pm, opts) {
|
|
329
|
+
const args = ['gate', '--harness', opts.harness, ...((opts.dirtyGuard ?? true) ? ['--if-dirty'] : [])];
|
|
330
|
+
return [
|
|
331
|
+
'#!/bin/sh',
|
|
332
|
+
`# checkride-gate.sh — the ${opts.harness} stop-hook gate.`,
|
|
333
|
+
'#',
|
|
334
|
+
'# checkride owns this file: `checkride agent-setup` (and `checkride init`)',
|
|
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.',
|
|
338
|
+
'#',
|
|
339
|
+
"# Runs the repo's `check` script as a hard gate, and reports the verdict in",
|
|
340
|
+
`# ${opts.harness}'s hook protocol. See \`checkride gate --help\`.`,
|
|
341
|
+
'',
|
|
342
|
+
...gatePreamble(opts.harness),
|
|
343
|
+
'',
|
|
344
|
+
...enterRepo(),
|
|
345
|
+
'',
|
|
346
|
+
...(opts.preflight === undefined ? [] : [...preflight(opts.preflight), '']),
|
|
347
|
+
...invokeGate(pm, args),
|
|
348
|
+
...gateTail(opts.harness, pm),
|
|
349
|
+
'',
|
|
350
|
+
].join('\n');
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* The dirty script: mark that this turn edited a file. A stop hook fires on
|
|
354
|
+
* every turn, including pure-conversation ones; the marker is what lets the gate
|
|
355
|
+
* skip those instead of taxing every reply with a full pipeline run.
|
|
356
|
+
*
|
|
357
|
+
* It exits 0 unconditionally. A failure to record an edit must never block the
|
|
358
|
+
* edit itself — the cost of a missed marker is one skipped gate, and the cost of
|
|
359
|
+
* a hard failure here is a repo where no file can be written.
|
|
360
|
+
*/
|
|
361
|
+
export function dirtyScript() {
|
|
362
|
+
return [
|
|
363
|
+
'#!/bin/sh',
|
|
364
|
+
'# checkride-dirty.sh — record that this turn edited a file.',
|
|
365
|
+
'#',
|
|
366
|
+
'# checkride owns this file: `checkride agent-setup` (and `checkride init`)',
|
|
367
|
+
'# overwrite it on every run.',
|
|
368
|
+
'#',
|
|
369
|
+
'# The gate reads this marker under `--if-dirty` and clears it on green.',
|
|
370
|
+
'',
|
|
371
|
+
`cd ${PROJECT_DIR} || exit 0`,
|
|
372
|
+
`mkdir -p "$(dirname ${DIRTY_MARKER})" && touch ${DIRTY_MARKER}`,
|
|
373
|
+
'exit 0',
|
|
374
|
+
'',
|
|
375
|
+
].join('\n');
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Keys Cursor may use for the target path in a tool call's `tool_input`.
|
|
379
|
+
*
|
|
380
|
+
* Cursor documents the shape for `Shell` but not for `Write`/`Delete`, so the
|
|
381
|
+
* list is deliberately broad and order-independent: the script takes the first
|
|
382
|
+
* string it finds. It keeps Claude Code's documented `file_path` and
|
|
383
|
+
* `notebook_path` too, since a harness that borrowed the schema would send
|
|
384
|
+
* those. An unrecognized shape yields no target and the script fails open, which
|
|
385
|
+
* is the correct failure — a protect hook that cannot read its input must not
|
|
386
|
+
* become a repo where nothing can be written.
|
|
387
|
+
*
|
|
388
|
+
* **The Cursor half of this list is a guess, and a wrong guess is silent**: an
|
|
389
|
+
* unmatched shape allows the write with no output, so `protect` would simply
|
|
390
|
+
* stop protecting and nothing would say so. The tests here can only assert the
|
|
391
|
+
* keys checkride *chose*, never the ones Cursor sends, so this is settled by one
|
|
392
|
+
* live `preToolUse` payload and not by anything in this repo. Until it is,
|
|
393
|
+
* `protect` is best-effort under Cursor — see docs/cursor.md. (Neither harness
|
|
394
|
+
* matches its shell tool, so a redirect writes the baseline under both; that gap
|
|
395
|
+
* is older and accepted.)
|
|
396
|
+
*/
|
|
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'];
|
|
411
|
+
/** The deny message, shared by both harnesses. */
|
|
412
|
+
const DENY_MESSAGE = "'checkride: ' + rel + ' is checkride-owned accounting. Never edit the baseline or .check ' +\n" +
|
|
413
|
+
" 'artifacts to make a check pass — fix the finding instead (the ratchet prunes the baseline on its own).'";
|
|
414
|
+
/**
|
|
415
|
+
* How Cursor spells "deny this tool call": a JSON body on stdout with exit 0. It
|
|
416
|
+
* reads a non-zero hook as a *broken* hook and proceeds, so the denial has to
|
|
417
|
+
* travel in the body, where `agent_message` is what the model sees.
|
|
418
|
+
*/
|
|
419
|
+
function denyTail() {
|
|
420
|
+
return [
|
|
421
|
+
' const message = ' + DENY_MESSAGE + ';',
|
|
422
|
+
' process.stdout.write(JSON.stringify({',
|
|
423
|
+
" permission: 'deny',",
|
|
424
|
+
' user_message: message,',
|
|
425
|
+
' agent_message: message,',
|
|
426
|
+
' }));',
|
|
427
|
+
' process.exit(0);',
|
|
428
|
+
];
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* The `protect` deny script: "never add to the baseline to make a check pass" as
|
|
432
|
+
* enforcement, not README advice. A Node script (not sh) because the hook
|
|
433
|
+
* protocol is JSON on stdin, and every checkride repo has node by construction;
|
|
434
|
+
* `.cjs` so it runs regardless of the repo's module type. Only edit tools are
|
|
435
|
+
* matched — reads are never denied, because triage depends on reading `.check/`
|
|
436
|
+
* artifacts.
|
|
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
|
+
*
|
|
452
|
+
* **Cursor only.** Claude Code enforces the same paths through
|
|
453
|
+
* `permissions.deny`, which is checked below the hook layer and costs nothing
|
|
454
|
+
* per tool call, so it needs no script. Cursor has no documented equivalent —
|
|
455
|
+
* its config is hooks — so the script survives for the harness that still
|
|
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.)
|
|
459
|
+
*/
|
|
460
|
+
export function protectScript() {
|
|
461
|
+
return [
|
|
462
|
+
'#!/usr/bin/env node',
|
|
463
|
+
"// checkride-protect.cjs — deny agent edits to checkride's accounting files.",
|
|
464
|
+
'// checkride owns this file: `checkride agent-setup` (and `checkride init`)',
|
|
465
|
+
'// overwrite it on every run.',
|
|
466
|
+
'//',
|
|
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.',
|
|
470
|
+
"'use strict';",
|
|
471
|
+
"const { realpathSync } = require('node:fs');",
|
|
472
|
+
"const { basename, dirname, isAbsolute, relative, resolve, sep } = require('node:path');",
|
|
473
|
+
'',
|
|
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)};`,
|
|
477
|
+
'',
|
|
478
|
+
'// Resolve symlinks in `p`, so a symlinked prefix cannot make an in-repo path',
|
|
479
|
+
'// look external. On macOS the repo root routinely arrives in two spellings —',
|
|
480
|
+
'// the harness passes /var/…, the environment reports /private/var/… — and a',
|
|
481
|
+
'// raw string comparison across the two rejects every path in the repo.',
|
|
482
|
+
'//',
|
|
483
|
+
'// `p` need not exist: this hook runs *before* the write, and the target is',
|
|
484
|
+
'// often a file (or a whole directory) that is about to be created. So walk up',
|
|
485
|
+
'// to the first ancestor that does exist, resolve that, and re-append the rest.',
|
|
486
|
+
'function canon(p) {',
|
|
487
|
+
' let dir = resolve(p);',
|
|
488
|
+
' const rest = [];',
|
|
489
|
+
' for (;;) {',
|
|
490
|
+
' try {',
|
|
491
|
+
' return resolve(realpathSync(dir), ...rest);',
|
|
492
|
+
' } catch {',
|
|
493
|
+
' const up = dirname(dir);',
|
|
494
|
+
' if (up === dir) return resolve(p);',
|
|
495
|
+
' rest.unshift(basename(dir));',
|
|
496
|
+
' dir = up;',
|
|
497
|
+
' }',
|
|
498
|
+
' }',
|
|
499
|
+
'}',
|
|
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
|
+
'',
|
|
579
|
+
"let raw = '';",
|
|
580
|
+
"process.stdin.on('data', (chunk) => { raw += chunk; });",
|
|
581
|
+
"process.stdin.on('end', () => {",
|
|
582
|
+
' let input;',
|
|
583
|
+
' try {',
|
|
584
|
+
' input = JSON.parse(raw);',
|
|
585
|
+
' } catch {',
|
|
586
|
+
' process.exit(0); // fail open: a broken hook must not brick every edit',
|
|
587
|
+
' }',
|
|
588
|
+
" if (input === null || typeof input !== 'object') process.exit(0);",
|
|
589
|
+
' const root = canon(process.env.CURSOR_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd());',
|
|
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);',
|
|
599
|
+
...denyTail(),
|
|
600
|
+
'});',
|
|
601
|
+
'',
|
|
602
|
+
].join('\n');
|
|
603
|
+
}
|
|
604
|
+
//# sourceMappingURL=scripts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/agent-setup/scripts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill delivery for harnesses that have no plugin system.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code gets `check` and `qa` from the bundled plugin
|
|
5
|
+
* (`.claude-plugin/plugin.json`), installed once and shared across every repo.
|
|
6
|
+
* Cursor has no equivalent — it discovers skills from directories on disk — so
|
|
7
|
+
* for Cursor the same two skills are written into the repo, from the same source
|
|
8
|
+
* files the plugin ships. One authored copy, two delivery mechanisms.
|
|
9
|
+
*
|
|
10
|
+
* The only edit made on the way through is the frontmatter `name`: a bare
|
|
11
|
+
* `check` would take the `/check` slash command in every repo it lands in, which
|
|
12
|
+
* is not a name checkride should be squatting. The plugin gets namespacing for
|
|
13
|
+
* free (`/checkride:check`); here it has to be spelled out.
|
|
14
|
+
*/
|
|
15
|
+
import type { HookFile } from './files.js';
|
|
16
|
+
/** Where Cursor discovers project skills, relative to the repo root. */
|
|
17
|
+
export declare const CURSOR_SKILLS_DIR = ".cursor/skills";
|
|
18
|
+
/**
|
|
19
|
+
* Rewrite the `name:` field inside the leading frontmatter block, and only
|
|
20
|
+
* there — a `name:` further down is prose in the skill body, not metadata.
|
|
21
|
+
*/
|
|
22
|
+
export declare function renameSkill(body: string, name: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Write the bundled skills into `cwd`'s Cursor skills directory. A source skill
|
|
25
|
+
* that cannot be read is skipped rather than fatal: a broken or trimmed install
|
|
26
|
+
* should cost the skills, not the whole `agent-setup`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function writeCursorSkills(cwd: string, opts?: {
|
|
29
|
+
dryRun?: boolean;
|
|
30
|
+
}): Promise<HookFile[]>;
|
|
31
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/agent-setup/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAalD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAWzG"}
|