plumbbob 0.9.3 → 0.9.5
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 +15 -13
- package/dist/cli-core.js +19 -2
- package/dist/lib/check.js +46 -13
- package/dist/lib/intent.js +1 -1
- package/dist/lib/latch.js +3 -3
- package/dist/lib/reentry.js +70 -0
- package/dist/verbs/agent.js +2 -2
- package/dist/verbs/doctor.js +4 -4
- package/dist/verbs/finish.js +1 -1
- package/dist/verbs/recover.js +297 -0
- package/dist/verbs/revert.js +2 -2
- package/dist/verbs/spike.js +8 -2
- 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 +1003 -0
- package/node_modules/checkride/LICENSE +201 -21
- package/node_modules/checkride/README.md +171 -30
- 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 +18 -6
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +42 -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 +61 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js +189 -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 +84 -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 +58 -0
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js +299 -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 +161 -58
- package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +7 -7
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.js +16 -19
- package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
- package/node_modules/checkride/dist/baseline/index.d.ts +4 -4
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/index.js +4 -4
- package/node_modules/checkride/dist/baseline/index.js.map +1 -1
- package/node_modules/checkride/dist/baseline/store.d.ts +12 -4
- package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/store.js +16 -9
- 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 +17 -4
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +259 -18
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +84 -8
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +122 -18
- 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 +5 -5
- 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 +25 -6
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +193 -27
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/gate.d.ts +135 -0
- package/node_modules/checkride/dist/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/gate.js +490 -0
- package/node_modules/checkride/dist/gate.js.map +1 -0
- package/node_modules/checkride/dist/init.d.ts +72 -12
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +324 -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 +40 -10
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +196 -102
- 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 +6 -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 +51 -0
- package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/launch.js +92 -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/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 +5 -5
- package/node_modules/checkride/dist/smoke.js +5 -5
- package/node_modules/checkride/dist/snippets.d.ts +26 -29
- 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 +17 -15
- package/node_modules/checkride/schema/checkride.config.schema.json +62 -5
- package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
- package/node_modules/checkride/skills/check/SKILL.md +257 -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/package.json +2 -2
- package/skills/build/SKILL.md +23 -19
- package/skills/finish/SKILL.md +3 -1
- package/skills/harvest/SKILL.md +2 -1
- package/skills/plan/SKILL.md +8 -7
- package/skills/recover/SKILL.md +28 -0
- package/skills/refine/SKILL.md +5 -4
- package/skills/spike/SKILL.md +1 -1
- package/skills/step/SKILL.md +4 -4
- package/skills/verify/SKILL.md +19 -14
- package/templates/intent.md +8 -7
|
@@ -0,0 +1,299 @@
|
|
|
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 } from '../gate.js';
|
|
19
|
+
import { execCommand } 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
|
+
* What a harness is told when `checkride gate` itself could not run.
|
|
36
|
+
*
|
|
37
|
+
* Emitted through `printf '%s\n' '…'` — single-quoted, never double — because
|
|
38
|
+
* the text carries backticks and `sh` runs a backtick span inside double quotes
|
|
39
|
+
* as a command. The first draft of this message used `echo "…"` and the shell
|
|
40
|
+
* silently ate the two quoted command names, substituting their output. Keep it
|
|
41
|
+
* free of single quotes for the same reason.
|
|
42
|
+
*/
|
|
43
|
+
const UNRUNNABLE = 'checkride: the gate could not run — `checkride gate` is not resolvable in this repo. ' +
|
|
44
|
+
'Install checkride (or re-run `checkride agent-setup`) before finishing.';
|
|
45
|
+
/**
|
|
46
|
+
* The one line that hands `harness` the {@link UNRUNNABLE} verdict, and the exit
|
|
47
|
+
* status that goes with it.
|
|
48
|
+
*
|
|
49
|
+
* The statuses differ because the harnesses do: Claude Code blocks on 2, while
|
|
50
|
+
* Cursor reads any non-zero stop hook as a *broken* hook and ends the turn —
|
|
51
|
+
* there, blocking has to ride in the body with an exit of 0. Kept as a pair so
|
|
52
|
+
* no branch can pick one without the other.
|
|
53
|
+
*/
|
|
54
|
+
function unrunnable(harness) {
|
|
55
|
+
return harness === 'cursor'
|
|
56
|
+
? { emit: `printf '%s\\n' '${JSON.stringify({ followup_message: UNRUNNABLE })}'`, exit: 0 }
|
|
57
|
+
: { emit: `printf '%s\\n' '${UNRUNNABLE}' >&2`, exit: 2 };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Translate the gate's exit status into `harness`'s protocol.
|
|
61
|
+
*
|
|
62
|
+
* The interesting branch is the last one. `checkride gate` exits 0 or 2 by
|
|
63
|
+
* design; anything else means it never ran — an uninstalled checkride, a broken
|
|
64
|
+
* launcher — and that must **block**, not pass. Claude Code reads a plain exit 1
|
|
65
|
+
* as "hook failed, carry on", which would leave a repo whose gate had silently
|
|
66
|
+
* stopped gating: precisely the vacuous green checkride exists to prevent. So
|
|
67
|
+
* the gate alone fails closed. (`protect` fails open, deliberately: the cost of
|
|
68
|
+
* a broken protect hook is a repo where nothing can be written at all.)
|
|
69
|
+
*/
|
|
70
|
+
function gateTail(harness) {
|
|
71
|
+
const { emit, exit } = unrunnable(harness);
|
|
72
|
+
if (harness === 'cursor') {
|
|
73
|
+
return [
|
|
74
|
+
'# On success checkride has already written its JSON to stdout, which is what',
|
|
75
|
+
'# Cursor reads. Only the never-ran case is left to answer for.',
|
|
76
|
+
'[ "$status" -eq 0 ] && exit 0',
|
|
77
|
+
emit,
|
|
78
|
+
`exit ${exit}`,
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
return [
|
|
82
|
+
'# Claude Code parses a hook body only on exit 0, and only a body can carry a',
|
|
83
|
+
'# user-visible message alongside the block. So when checkride produced one,',
|
|
84
|
+
'# forward it and exit 0: the verdict rides in `decision`, not in the status.',
|
|
85
|
+
'if [ -n "$body" ]; then',
|
|
86
|
+
" printf '%s\\n' \"$body\"",
|
|
87
|
+
' case "$status" in',
|
|
88
|
+
' 0|2) exit 0 ;;',
|
|
89
|
+
' esac',
|
|
90
|
+
'fi',
|
|
91
|
+
'',
|
|
92
|
+
'# No body — an older checkride that reports only through the exit code.',
|
|
93
|
+
'[ "$status" -eq 0 ] && exit 0',
|
|
94
|
+
'[ "$status" -eq 2 ] && exit 2',
|
|
95
|
+
'',
|
|
96
|
+
"# Neither of checkride's two gate codes: it never ran. Block anyway.",
|
|
97
|
+
emit,
|
|
98
|
+
`exit ${exit}`,
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Enter the repo, or report the same "could not run" verdict as any other way
|
|
103
|
+
* the gate can fail to start.
|
|
104
|
+
*
|
|
105
|
+
* Spelled as a block rather than `cd … || exit 2` for two reasons: a bare status
|
|
106
|
+
* tells the agent nothing, and under Cursor a non-zero stop hook is a *broken*
|
|
107
|
+
* hook, so the one branch that used to exit 2 there was the one branch that
|
|
108
|
+
* silently let a turn end.
|
|
109
|
+
*/
|
|
110
|
+
function enterRepo(harness) {
|
|
111
|
+
const { emit, exit } = unrunnable(harness);
|
|
112
|
+
return [`if ! cd ${PROJECT_DIR}; then`, ` ${emit}`, ` exit ${exit}`, 'fi'];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* How the gate's stdout is taken.
|
|
116
|
+
*
|
|
117
|
+
* Cursor's script lets it stream straight through: Cursor reads the hook's own
|
|
118
|
+
* stdout, and there is nothing for the script to decide. Claude Code's captures
|
|
119
|
+
* it, because the script has to *choose an exit code based on whether a body
|
|
120
|
+
* came back* — see {@link gateTail}. Capturing costs nothing that is visible:
|
|
121
|
+
* the pipeline's human-readable progress is on stderr and streams live either
|
|
122
|
+
* way; stdout carries only the one-line JSON verdict, written at the very end.
|
|
123
|
+
*/
|
|
124
|
+
function invokeGate(pm, harness, args) {
|
|
125
|
+
const command = checkrideCommand(pm, args);
|
|
126
|
+
return harness === 'cursor'
|
|
127
|
+
? [command, 'status=$?']
|
|
128
|
+
: [`body=$(${command})`, 'status=$?'];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The gate script: `cd` to the repo, hand off to `checkride gate`, and translate
|
|
132
|
+
* the result into the harness's protocol.
|
|
133
|
+
*
|
|
134
|
+
* `dirtyGuard` (on when the `dirty` hook is written alongside — the default)
|
|
135
|
+
* makes the gate conditional on the edit marker, so pure-conversation turns
|
|
136
|
+
* don't pay for a full pipeline run. Without the marker hook the guard would
|
|
137
|
+
* disarm the gate entirely, so a `--hook gate` selection writes it unguarded.
|
|
138
|
+
*/
|
|
139
|
+
export function gateScript(pm, opts) {
|
|
140
|
+
const args = ['gate', '--harness', opts.harness, ...((opts.dirtyGuard ?? true) ? ['--if-dirty'] : [])];
|
|
141
|
+
return [
|
|
142
|
+
'#!/bin/sh',
|
|
143
|
+
`# checkride-gate.sh — the ${opts.harness} stop-hook gate.`,
|
|
144
|
+
'#',
|
|
145
|
+
'# checkride owns this file: `checkride agent-setup` (and `checkride init`)',
|
|
146
|
+
'# overwrite it on every run. Customize via a sibling script or the',
|
|
147
|
+
'# environment, not by editing here — edits are lost on the next refresh.',
|
|
148
|
+
'#',
|
|
149
|
+
"# Runs the repo's `check` script as a hard gate, and reports the verdict in",
|
|
150
|
+
`# ${opts.harness}'s hook protocol. See \`checkride gate --help\`.`,
|
|
151
|
+
'',
|
|
152
|
+
...enterRepo(opts.harness),
|
|
153
|
+
'',
|
|
154
|
+
...invokeGate(pm, opts.harness, args),
|
|
155
|
+
...gateTail(opts.harness),
|
|
156
|
+
'',
|
|
157
|
+
].join('\n');
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The dirty script: mark that this turn edited a file. A stop hook fires on
|
|
161
|
+
* every turn, including pure-conversation ones; the marker is what lets the gate
|
|
162
|
+
* skip those instead of taxing every reply with a full pipeline run.
|
|
163
|
+
*
|
|
164
|
+
* It exits 0 unconditionally. A failure to record an edit must never block the
|
|
165
|
+
* edit itself — the cost of a missed marker is one skipped gate, and the cost of
|
|
166
|
+
* a hard failure here is a repo where no file can be written.
|
|
167
|
+
*/
|
|
168
|
+
export function dirtyScript() {
|
|
169
|
+
return [
|
|
170
|
+
'#!/bin/sh',
|
|
171
|
+
'# checkride-dirty.sh — record that this turn edited a file.',
|
|
172
|
+
'#',
|
|
173
|
+
'# checkride owns this file: `checkride agent-setup` (and `checkride init`)',
|
|
174
|
+
'# overwrite it on every run.',
|
|
175
|
+
'#',
|
|
176
|
+
'# The gate reads this marker under `--if-dirty` and clears it on green.',
|
|
177
|
+
'',
|
|
178
|
+
`cd ${PROJECT_DIR} || exit 0`,
|
|
179
|
+
`mkdir -p "$(dirname ${DIRTY_MARKER})" && touch ${DIRTY_MARKER}`,
|
|
180
|
+
'exit 0',
|
|
181
|
+
'',
|
|
182
|
+
].join('\n');
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Keys Cursor may use for the target path in a tool call's `tool_input`.
|
|
186
|
+
*
|
|
187
|
+
* Cursor documents the shape for `Shell` but not for `Write`/`Delete`, so the
|
|
188
|
+
* list is deliberately broad and order-independent: the script takes the first
|
|
189
|
+
* string it finds. It keeps Claude Code's documented `file_path` and
|
|
190
|
+
* `notebook_path` too, since a harness that borrowed the schema would send
|
|
191
|
+
* those. An unrecognized shape yields no target and the script fails open, which
|
|
192
|
+
* is the correct failure — a protect hook that cannot read its input must not
|
|
193
|
+
* become a repo where nothing can be written.
|
|
194
|
+
*
|
|
195
|
+
* **The Cursor half of this list is a guess, and a wrong guess is silent**: an
|
|
196
|
+
* unmatched shape allows the write with no output, so `protect` would simply
|
|
197
|
+
* stop protecting and nothing would say so. The tests here can only assert the
|
|
198
|
+
* keys checkride *chose*, never the ones Cursor sends, so this is settled by one
|
|
199
|
+
* live `preToolUse` payload and not by anything in this repo. Until it is,
|
|
200
|
+
* `protect` is best-effort under Cursor — see docs/cursor.md. (Neither harness
|
|
201
|
+
* matches its shell tool, so a redirect writes the baseline under both; that gap
|
|
202
|
+
* is older and accepted.)
|
|
203
|
+
*/
|
|
204
|
+
const PATH_KEYS = ['file_path', 'notebook_path', 'path', 'target_file', 'filePath', 'relative_workspace_path'];
|
|
205
|
+
/** The deny message, shared by both harnesses. */
|
|
206
|
+
const DENY_MESSAGE = "'checkride: ' + rel + ' is checkride-owned accounting. Never edit the baseline or .check ' +\n" +
|
|
207
|
+
" 'artifacts to make a check pass — fix the finding instead (the ratchet prunes the baseline on its own).'";
|
|
208
|
+
/**
|
|
209
|
+
* How Cursor spells "deny this tool call": a JSON body on stdout with exit 0. It
|
|
210
|
+
* reads a non-zero hook as a *broken* hook and proceeds, so the denial has to
|
|
211
|
+
* travel in the body, where `agent_message` is what the model sees.
|
|
212
|
+
*/
|
|
213
|
+
function denyTail() {
|
|
214
|
+
return [
|
|
215
|
+
' const message = ' + DENY_MESSAGE + ';',
|
|
216
|
+
' process.stdout.write(JSON.stringify({',
|
|
217
|
+
" permission: 'deny',",
|
|
218
|
+
' user_message: message,',
|
|
219
|
+
' agent_message: message,',
|
|
220
|
+
' }));',
|
|
221
|
+
' process.exit(0);',
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The `protect` deny script: "never add to the baseline to make a check pass" as
|
|
226
|
+
* enforcement, not README advice. A Node script (not sh) because the hook
|
|
227
|
+
* protocol is JSON on stdin, and every checkride repo has node by construction;
|
|
228
|
+
* `.cjs` so it runs regardless of the repo's module type. Only edit tools are
|
|
229
|
+
* matched — reads are never denied, because triage depends on reading `.check/`
|
|
230
|
+
* artifacts.
|
|
231
|
+
*
|
|
232
|
+
* **Cursor only.** Claude Code enforces the same paths through
|
|
233
|
+
* `permissions.deny`, which is checked below the hook layer and costs nothing
|
|
234
|
+
* per tool call, so it needs no script. Cursor has no documented equivalent —
|
|
235
|
+
* its config is hooks — so the script survives for the harness that still
|
|
236
|
+
* requires one. If Cursor grows a file-path deny list, this file goes away.
|
|
237
|
+
*/
|
|
238
|
+
export function protectScript() {
|
|
239
|
+
return [
|
|
240
|
+
'#!/usr/bin/env node',
|
|
241
|
+
"// checkride-protect.cjs — deny agent edits to checkride's accounting files.",
|
|
242
|
+
'// checkride owns this file: `checkride agent-setup` (and `checkride init`)',
|
|
243
|
+
'// overwrite it on every run.',
|
|
244
|
+
'//',
|
|
245
|
+
'// Pre-tool hook for Cursor. Reads the tool call as JSON on stdin and',
|
|
246
|
+
'// denies the write when it targets the baseline or .check/. Reads are never',
|
|
247
|
+
'// matched — triage depends on reading .check artifacts.',
|
|
248
|
+
"'use strict';",
|
|
249
|
+
"const { realpathSync } = require('node:fs');",
|
|
250
|
+
"const { basename, dirname, isAbsolute, relative, resolve, sep } = require('node:path');",
|
|
251
|
+
'',
|
|
252
|
+
`const PATH_KEYS = ${JSON.stringify(PATH_KEYS)};`,
|
|
253
|
+
'',
|
|
254
|
+
'// Resolve symlinks in `p`, so a symlinked prefix cannot make an in-repo path',
|
|
255
|
+
'// look external. On macOS the repo root routinely arrives in two spellings —',
|
|
256
|
+
'// the harness passes /var/…, the environment reports /private/var/… — and a',
|
|
257
|
+
'// raw string comparison across the two rejects every path in the repo.',
|
|
258
|
+
'//',
|
|
259
|
+
'// `p` need not exist: this hook runs *before* the write, and the target is',
|
|
260
|
+
'// often a file (or a whole directory) that is about to be created. So walk up',
|
|
261
|
+
'// to the first ancestor that does exist, resolve that, and re-append the rest.',
|
|
262
|
+
'function canon(p) {',
|
|
263
|
+
' let dir = resolve(p);',
|
|
264
|
+
' const rest = [];',
|
|
265
|
+
' for (;;) {',
|
|
266
|
+
' try {',
|
|
267
|
+
' return resolve(realpathSync(dir), ...rest);',
|
|
268
|
+
' } catch {',
|
|
269
|
+
' const up = dirname(dir);',
|
|
270
|
+
' if (up === dir) return resolve(p);',
|
|
271
|
+
' rest.unshift(basename(dir));',
|
|
272
|
+
' dir = up;',
|
|
273
|
+
' }',
|
|
274
|
+
' }',
|
|
275
|
+
'}',
|
|
276
|
+
'',
|
|
277
|
+
"let raw = '';",
|
|
278
|
+
"process.stdin.on('data', (chunk) => { raw += chunk; });",
|
|
279
|
+
"process.stdin.on('end', () => {",
|
|
280
|
+
' let input;',
|
|
281
|
+
' try {',
|
|
282
|
+
' input = JSON.parse(raw);',
|
|
283
|
+
' } catch {',
|
|
284
|
+
' process.exit(0); // fail open: a broken hook must not brick every edit',
|
|
285
|
+
' }',
|
|
286
|
+
' const tool = (input && input.tool_input) || {};',
|
|
287
|
+
" const target = PATH_KEYS.map((k) => tool[k]).find((v) => typeof v === 'string' && v.length > 0);",
|
|
288
|
+
' if (target === undefined) process.exit(0);',
|
|
289
|
+
' const root = canon(process.env.CURSOR_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd());',
|
|
290
|
+
' const abs = canon(isAbsolute(target) ? target : resolve(root, target));',
|
|
291
|
+
" const rel = relative(root, abs).split(sep).join('/');",
|
|
292
|
+
" const denied = rel === 'checkride.baseline.json' || rel === '.check' || rel.startsWith('.check/');",
|
|
293
|
+
' if (!denied) process.exit(0);',
|
|
294
|
+
...denyTail(),
|
|
295
|
+
'});',
|
|
296
|
+
'',
|
|
297
|
+
].join('\n');
|
|
298
|
+
}
|
|
299
|
+
//# 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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { dirname, join } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { putFile, readIfExists } from './files.js';
|
|
18
|
+
/** Where Cursor discovers project skills, relative to the repo root. */
|
|
19
|
+
export const CURSOR_SKILLS_DIR = '.cursor/skills';
|
|
20
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
/** The package's own `skills/` directory, which ships in the published tarball. */
|
|
22
|
+
const SKILLS_DIR = join(HERE, '..', '..', 'skills');
|
|
23
|
+
/** Source skill → the name it takes once it is a repo-local Cursor skill. */
|
|
24
|
+
const SKILLS = [
|
|
25
|
+
{ source: 'check', name: 'checkride-check' },
|
|
26
|
+
{ source: 'qa', name: 'checkride-qa' },
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Rewrite the `name:` field inside the leading frontmatter block, and only
|
|
30
|
+
* there — a `name:` further down is prose in the skill body, not metadata.
|
|
31
|
+
*/
|
|
32
|
+
export function renameSkill(body, name) {
|
|
33
|
+
const frontmatter = /^(---\r?\n)([\s\S]*?)(\r?\n---)/.exec(body);
|
|
34
|
+
if (!frontmatter)
|
|
35
|
+
return body;
|
|
36
|
+
const [, open, fields, close] = frontmatter;
|
|
37
|
+
const renamed = (fields ?? '').replace(/^name:.*$/m, `name: ${name}`);
|
|
38
|
+
return `${open}${renamed}${close}${body.slice(frontmatter[0].length)}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Write the bundled skills into `cwd`'s Cursor skills directory. A source skill
|
|
42
|
+
* that cannot be read is skipped rather than fatal: a broken or trimmed install
|
|
43
|
+
* should cost the skills, not the whole `agent-setup`.
|
|
44
|
+
*/
|
|
45
|
+
export async function writeCursorSkills(cwd, opts = {}) {
|
|
46
|
+
const dryRun = opts.dryRun ?? false;
|
|
47
|
+
const written = await Promise.all(SKILLS.map(async (skill) => {
|
|
48
|
+
const body = await readIfExists(join(SKILLS_DIR, skill.source, 'SKILL.md'));
|
|
49
|
+
if (body === null)
|
|
50
|
+
return null;
|
|
51
|
+
const rel = `${CURSOR_SKILLS_DIR}/${skill.name}/SKILL.md`;
|
|
52
|
+
return putFile(cwd, rel, renameSkill(body, skill.name), { dryRun });
|
|
53
|
+
}));
|
|
54
|
+
return written.filter((f) => f !== null);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/agent-setup/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEnD,wEAAwE;AACxE,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAElD,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,mFAAmF;AACnF,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,MAAM,MAAM,GAAgD;IAC1D,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC5C,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE;CACvC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACpD,MAAM,WAAW,GAAG,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,WAAW,CAAC;IAC5C,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACtE,OAAO,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,OAA6B,EAAE;IAClF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;IACpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACzB,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;QAC5E,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,GAAG,GAAG,GAAG,iBAAiB,IAAI,KAAK,CAAC,IAAI,WAAW,CAAC;QAC1D,OAAO,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC,CAAC,CACH,CAAC;IACF,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the repo *declared* it checks — as opposed to what a given run selected.
|
|
3
|
+
*
|
|
4
|
+
* The summary records the slots a run selected; it says nothing about the ones
|
|
5
|
+
* the repo opted into and did not run. Both readers need that difference:
|
|
6
|
+
* triage uses it to name the slots a narrow run left uncovered, and the quality
|
|
7
|
+
* extractor uses it to tell "opted in but not run this time" apart from "never
|
|
8
|
+
* opted in at all" — three of its four artifacts come from opt-in slots, so the
|
|
9
|
+
* distinction decides whether a gap is a finding or the repo's normal state.
|
|
10
|
+
*
|
|
11
|
+
* `null` throughout means *unknowable*, never *empty*: a repo with no
|
|
12
|
+
* `checkride.config.json` runs the default catalogue, and neither reader will
|
|
13
|
+
* guess at it.
|
|
14
|
+
*
|
|
15
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Slots the repo names in `checkride.config.json` and has not switched off
|
|
19
|
+
* (`false` disables one), sorted. `null` when the repo has no config file, or
|
|
20
|
+
* when the config is unparseable — that is the gate's problem to report, not
|
|
21
|
+
* the reader's to crash on.
|
|
22
|
+
*
|
|
23
|
+
* `cwd` is resolved first because `loadConfig` throws outright on a relative
|
|
24
|
+
* one, and both readers take their `cwd` from a command-line argument. Left
|
|
25
|
+
* unresolved, `dist/<reader>/cli.js .` would land in the catch below and report
|
|
26
|
+
* the repo's slots as *unknowable* — a silently degraded answer, which is the
|
|
27
|
+
* failure mode these readers exist to prevent.
|
|
28
|
+
*/
|
|
29
|
+
export declare function configuredSlots(cwd: string): string[] | null;
|
|
30
|
+
//# sourceMappingURL=configured.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../../src/artifacts/configured.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAW5D"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the repo *declared* it checks — as opposed to what a given run selected.
|
|
3
|
+
*
|
|
4
|
+
* The summary records the slots a run selected; it says nothing about the ones
|
|
5
|
+
* the repo opted into and did not run. Both readers need that difference:
|
|
6
|
+
* triage uses it to name the slots a narrow run left uncovered, and the quality
|
|
7
|
+
* extractor uses it to tell "opted in but not run this time" apart from "never
|
|
8
|
+
* opted in at all" — three of its four artifacts come from opt-in slots, so the
|
|
9
|
+
* distinction decides whether a gap is a finding or the repo's normal state.
|
|
10
|
+
*
|
|
11
|
+
* `null` throughout means *unknowable*, never *empty*: a repo with no
|
|
12
|
+
* `checkride.config.json` runs the default catalogue, and neither reader will
|
|
13
|
+
* guess at it.
|
|
14
|
+
*
|
|
15
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
16
|
+
*/
|
|
17
|
+
import { resolve } from 'node:path';
|
|
18
|
+
import { loadConfig } from '../config.js';
|
|
19
|
+
/**
|
|
20
|
+
* Slots the repo names in `checkride.config.json` and has not switched off
|
|
21
|
+
* (`false` disables one), sorted. `null` when the repo has no config file, or
|
|
22
|
+
* when the config is unparseable — that is the gate's problem to report, not
|
|
23
|
+
* the reader's to crash on.
|
|
24
|
+
*
|
|
25
|
+
* `cwd` is resolved first because `loadConfig` throws outright on a relative
|
|
26
|
+
* one, and both readers take their `cwd` from a command-line argument. Left
|
|
27
|
+
* unresolved, `dist/<reader>/cli.js .` would land in the catch below and report
|
|
28
|
+
* the repo's slots as *unknowable* — a silently degraded answer, which is the
|
|
29
|
+
* failure mode these readers exist to prevent.
|
|
30
|
+
*/
|
|
31
|
+
export function configuredSlots(cwd) {
|
|
32
|
+
try {
|
|
33
|
+
const checks = loadConfig(resolve(cwd))?.checks;
|
|
34
|
+
if (!checks)
|
|
35
|
+
return null;
|
|
36
|
+
return Object.entries(checks)
|
|
37
|
+
.filter(([, value]) => value !== false)
|
|
38
|
+
.map(([slot]) => slot)
|
|
39
|
+
.toSorted();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=configured.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configured.js","sourceRoot":"","sources":["../../src/artifacts/configured.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC;QAChD,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;aAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC;aACtC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC;aACrB,QAAQ,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-scale rendering of the quantities both readers report: file sizes,
|
|
3
|
+
* elapsed time, and a bounded tail of captured output.
|
|
4
|
+
*
|
|
5
|
+
* Sizes and ages carry the judgment in a reader's table — "2.3 MB" and "4d" are
|
|
6
|
+
* the whole reason an agent knows not to open a file — so they get one
|
|
7
|
+
* implementation rather than one per reader.
|
|
8
|
+
*
|
|
9
|
+
* {@link tail} is truncation, never normalization: it drops whole lines from
|
|
10
|
+
* the front and says how many, leaving the bytes it keeps exactly as the tool
|
|
11
|
+
* emitted them.
|
|
12
|
+
*
|
|
13
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
14
|
+
*/
|
|
15
|
+
/** `483 B`, `5.1 KB`, `2.3 MB` — one decimal above a kilobyte, none below. */
|
|
16
|
+
export declare function formatBytes(bytes: number): string;
|
|
17
|
+
/** `340ms`, `1.4s`, `12m`, `4.1d` — the coarsest unit that keeps a whole part. */
|
|
18
|
+
export declare function formatDuration(ms: number): string;
|
|
19
|
+
/** A bounded excerpt of captured output, with what it left out stated. */
|
|
20
|
+
export type Excerpt = {
|
|
21
|
+
text: string;
|
|
22
|
+
/** Lines dropped from the front, so the caller can say so rather than imply completeness. */
|
|
23
|
+
omittedLines: number;
|
|
24
|
+
/** Size of the full input, so "1.2 KB of 40 KB" is sayable. */
|
|
25
|
+
totalBytes: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The last `maxLines` non-blank lines of `text`, further trimmed from the front
|
|
29
|
+
* until the excerpt fits `maxBytes`. A compiler, a test runner and a crashing
|
|
30
|
+
* harness all put the thing worth reading at the end.
|
|
31
|
+
*/
|
|
32
|
+
export declare function tail(text: string, maxLines: number, maxBytes: number): Excerpt;
|
|
33
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/artifacts/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjD;AASD,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,0EAA0E;AAC1E,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ9E"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-scale rendering of the quantities both readers report: file sizes,
|
|
3
|
+
* elapsed time, and a bounded tail of captured output.
|
|
4
|
+
*
|
|
5
|
+
* Sizes and ages carry the judgment in a reader's table — "2.3 MB" and "4d" are
|
|
6
|
+
* the whole reason an agent knows not to open a file — so they get one
|
|
7
|
+
* implementation rather than one per reader.
|
|
8
|
+
*
|
|
9
|
+
* {@link tail} is truncation, never normalization: it drops whole lines from
|
|
10
|
+
* the front and says how many, leaving the bytes it keeps exactly as the tool
|
|
11
|
+
* emitted them.
|
|
12
|
+
*
|
|
13
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
14
|
+
*/
|
|
15
|
+
const UNITS = [
|
|
16
|
+
{ limit: 1024 ** 3, suffix: 'GB' },
|
|
17
|
+
{ limit: 1024 ** 2, suffix: 'MB' },
|
|
18
|
+
{ limit: 1024, suffix: 'KB' },
|
|
19
|
+
];
|
|
20
|
+
/** `483 B`, `5.1 KB`, `2.3 MB` — one decimal above a kilobyte, none below. */
|
|
21
|
+
export function formatBytes(bytes) {
|
|
22
|
+
for (const { limit, suffix } of UNITS) {
|
|
23
|
+
if (bytes >= limit)
|
|
24
|
+
return `${(bytes / limit).toFixed(1)} ${suffix}`;
|
|
25
|
+
}
|
|
26
|
+
return `${bytes} B`;
|
|
27
|
+
}
|
|
28
|
+
const DURATIONS = [
|
|
29
|
+
{ limit: 86_400_000, suffix: 'd' },
|
|
30
|
+
{ limit: 3_600_000, suffix: 'h' },
|
|
31
|
+
{ limit: 60_000, suffix: 'm' },
|
|
32
|
+
{ limit: 1000, suffix: 's' },
|
|
33
|
+
];
|
|
34
|
+
/** `340ms`, `1.4s`, `12m`, `4.1d` — the coarsest unit that keeps a whole part. */
|
|
35
|
+
export function formatDuration(ms) {
|
|
36
|
+
for (const { limit, suffix } of DURATIONS) {
|
|
37
|
+
if (ms >= limit)
|
|
38
|
+
return `${(ms / limit).toFixed(1)}${suffix}`;
|
|
39
|
+
}
|
|
40
|
+
return `${Math.round(ms)}ms`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The last `maxLines` non-blank lines of `text`, further trimmed from the front
|
|
44
|
+
* until the excerpt fits `maxBytes`. A compiler, a test runner and a crashing
|
|
45
|
+
* harness all put the thing worth reading at the end.
|
|
46
|
+
*/
|
|
47
|
+
export function tail(text, maxLines, maxBytes) {
|
|
48
|
+
const lines = text.split('\n').filter((line) => line.trim() !== '');
|
|
49
|
+
const totalBytes = Buffer.byteLength(text, 'utf8');
|
|
50
|
+
let kept = lines.slice(-maxLines);
|
|
51
|
+
while (kept.length > 1 && Buffer.byteLength(kept.join('\n'), 'utf8') > maxBytes) {
|
|
52
|
+
kept = kept.slice(1);
|
|
53
|
+
}
|
|
54
|
+
return { text: kept.join('\n'), omittedLines: lines.length - kept.length, totalBytes };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/artifacts/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,KAAK,GAAiD;IAC1D,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;IAClC,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;IAClC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;CAC9B,CAAC;AAEF,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACtC,IAAI,KAAK,IAAI,KAAK;YAAE,OAAO,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;IACvE,CAAC;IACD,OAAO,GAAG,KAAK,IAAI,CAAC;AACtB,CAAC;AAED,MAAM,SAAS,GAAiD;IAC9D,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE;IAClC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE;IACjC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE;IAC9B,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;CAC7B,CAAC;AAEF,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC1C,IAAI,EAAE,IAAI,KAAK;YAAE,OAAO,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC;IAChE,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC;AAC/B,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,QAAgB,EAAE,QAAgB;IACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,IAAI,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;QAChF,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;AACzF,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The freshness window — which `.check/` files belong to the run that wrote the
|
|
3
|
+
* summary sitting beside them.
|
|
4
|
+
*
|
|
5
|
+
* The window is `[timestamp - total_duration_ms, ∞)`: the run's *start*, not
|
|
6
|
+
* its end, because `timestamp` is stamped once every check has finished (see
|
|
7
|
+
* `buildSummary` in `../orchestrator.ts`) and every artifact the run just wrote
|
|
8
|
+
* is therefore older than it. Anything outside the window is labelled with its
|
|
9
|
+
* age, never silently dropped.
|
|
10
|
+
*
|
|
11
|
+
* Why a window at all, and why this derivation is contract-legal:
|
|
12
|
+
* `docs/plugin.md` §How the readers decide an artifact is stale.
|
|
13
|
+
*
|
|
14
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
15
|
+
*/
|
|
16
|
+
import type { Summary } from '../orchestrator.js';
|
|
17
|
+
/** An artifact's standing relative to the run that wrote the summary. */
|
|
18
|
+
export type Freshness = {
|
|
19
|
+
/** `unknown` when the summary's `timestamp` could not be parsed. */
|
|
20
|
+
state: 'fresh' | 'stale' | 'unknown';
|
|
21
|
+
/** How long before the run started the file was written; `null` unless stale. */
|
|
22
|
+
ageMs: number | null;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Epoch milliseconds at which the run began, or `null` when `timestamp` is not
|
|
26
|
+
* a parseable date. Callers treat `null` as "cannot judge freshness" rather
|
|
27
|
+
* than guessing in either direction.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runWindowStart(summary: Summary): number | null;
|
|
30
|
+
/** Classify one artifact's mtime against the window from {@link runWindowStart}. */
|
|
31
|
+
export declare function classifyFreshness(mtimeMs: number, windowStart: number | null): Freshness;
|
|
32
|
+
//# sourceMappingURL=freshness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../../src/artifacts/freshness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG;IACtB,oEAAoE;IACpE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IACrC,iFAAiF;IACjF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAKF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI9D;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAIxF"}
|