plumbbob 0.9.2 → 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 +298 -24
- 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 +5 -4
- package/dist/verbs/check.js +6 -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,70 @@
|
|
|
1
|
+
// The gate's re-entrancy marker: which repos have a check run in flight,
|
|
2
|
+
// recorded on the environment so every process in the tree can see it.
|
|
3
|
+
//
|
|
4
|
+
// The failure it exists to stop is not slowness, it is unbounded recursion. A
|
|
5
|
+
// repo whose `check` setting is its own full pipeline (`pnpm check`) has a gate
|
|
6
|
+
// that runs its test suite; anything in that suite which gates THAT SAME REPO —
|
|
7
|
+
// a test driving `checkpoint` against the process cwd — spawns a second full
|
|
8
|
+
// pipeline, whose suite spawns a third. Each generation forks wider than the
|
|
9
|
+
// last, and nothing in the loop is aware there is a loop. The only thing that
|
|
10
|
+
// ended the first observed occurrence was checkride's 600s per-check cap
|
|
11
|
+
// killing one generation from the outside.
|
|
12
|
+
//
|
|
13
|
+
// It is scoped BY ROOT, and that is the whole design. A flat "a gate is
|
|
14
|
+
// running" flag is wrong in the one case that matters most: plumbbob's own
|
|
15
|
+
// suite legitimately gates dozens of fixture repos, and when the suite itself
|
|
16
|
+
// is running under this repo's gate it inherits the marker — so a flat flag
|
|
17
|
+
// refuses every one of them and turns a green suite red. Recursion is a repo
|
|
18
|
+
// re-entering ITS OWN gate; a different root is ordinary nested work and must
|
|
19
|
+
// pass through untouched.
|
|
20
|
+
//
|
|
21
|
+
// The marker rides the environment rather than a module-level variable because
|
|
22
|
+
// the recursion crosses process boundaries (spawned shells, checkride's tool
|
|
23
|
+
// subprocesses, vitest's worker forks), and a variable in one heap is invisible
|
|
24
|
+
// to all of them. Node builtins only.
|
|
25
|
+
import { resolve } from 'node:path';
|
|
26
|
+
/** The environment key. Namespaced so nothing else can set it by accident. */
|
|
27
|
+
const MARKER = 'PLUMBBOB_GATE_ROOTS';
|
|
28
|
+
/** The separator. A newline cannot appear in a path, unlike `:` on a weird one. */
|
|
29
|
+
const SEP = '\n';
|
|
30
|
+
/**
|
|
31
|
+
* The roots with a gate in flight, as absolute paths.
|
|
32
|
+
*/
|
|
33
|
+
function activeRoots(env) {
|
|
34
|
+
const raw = env[MARKER] ?? '';
|
|
35
|
+
return raw.split(SEP).filter((r) => r.length > 0);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* True when `root` already has a check gate running in this process tree.
|
|
39
|
+
*
|
|
40
|
+
* Compared as resolved absolute paths, so the same repo reached by a relative
|
|
41
|
+
* path, a symlink-free alias, or a trailing slash still counts as itself.
|
|
42
|
+
* `env` is injectable so the guard is testable without mutating the real one.
|
|
43
|
+
*/
|
|
44
|
+
export function gateIsRunningFor(root, env = process.env) {
|
|
45
|
+
return activeRoots(env).includes(resolve(root));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Run `body` with `root` recorded as gated, restoring the prior value after.
|
|
49
|
+
*
|
|
50
|
+
* The marker is set on `process.env` itself, not on a copy handed to one child:
|
|
51
|
+
* the in-process checkride path spawns its own tool subprocesses, and they
|
|
52
|
+
* inherit the parent's environment at spawn time. `finally` restores rather
|
|
53
|
+
* than deletes, so nesting a second root inside the first cannot un-mark the
|
|
54
|
+
* first when the inner call returns.
|
|
55
|
+
*/
|
|
56
|
+
export async function withGateMarker(root, body) {
|
|
57
|
+
const prior = process.env[MARKER];
|
|
58
|
+
process.env[MARKER] = [...activeRoots(process.env), resolve(root)].join(SEP);
|
|
59
|
+
try {
|
|
60
|
+
return await body();
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
if (prior === undefined) {
|
|
64
|
+
delete process.env[MARKER];
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
process.env[MARKER] = prior;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/verbs/agent.js
CHANGED
|
@@ -103,12 +103,12 @@ async function run(cwd, args) {
|
|
|
103
103
|
async function runOne(root, slug, step, spec) {
|
|
104
104
|
const resolution = resolveAgent(root, spec.name, spec.flagPath !== undefined ? { flagPath: spec.flagPath } : {});
|
|
105
105
|
if (!resolution.ok) {
|
|
106
|
-
return degrade(spec.ambient, `plumbbob: ${resolution.error}`, `plumbbob: bound agent "${spec.name}" did not resolve — ${resolution.error} Skipping
|
|
106
|
+
return degrade(spec.ambient, `plumbbob: ${resolution.error}`, `plumbbob: bound agent "${spec.name}" did not resolve — ${resolution.error} Skipping — D54 (bindings-degrade-soft): the loop works without it.`);
|
|
107
107
|
}
|
|
108
108
|
const { manifest, dir } = resolution.agent;
|
|
109
109
|
const resolved = resolveMode(spec.mode, manifest);
|
|
110
110
|
if (!resolved.ok) {
|
|
111
|
-
return degrade(spec.ambient, resolved.error, `plumbbob: bound agent "${spec.name}" — ${resolved.error.replace(/^plumbbob:\s*/, '')} Skipping (
|
|
111
|
+
return degrade(spec.ambient, resolved.error, `plumbbob: bound agent "${spec.name}" — ${resolved.error.replace(/^plumbbob:\s*/, '')} Skipping — D54 (bindings-degrade-soft).`);
|
|
112
112
|
}
|
|
113
113
|
const mode = resolved.mode;
|
|
114
114
|
let intent;
|
|
@@ -215,8 +215,9 @@ function degrade(ambient, hard, soft) {
|
|
|
215
215
|
* agent name and the value flags.
|
|
216
216
|
*
|
|
217
217
|
* A value flag missing its value, or `--step` given a non-number, is a loud
|
|
218
|
-
* error (returned as a string) rather than a silent default.
|
|
219
|
-
*
|
|
218
|
+
* error (returned as a string) rather than a silent default. An undeclared
|
|
219
|
+
* `--flag` never arrives here — `run` screens argv against the verb spec in
|
|
220
|
+
* cli-core.ts and refuses before dispatch.
|
|
220
221
|
*/
|
|
221
222
|
function parseRunArgs(args) {
|
|
222
223
|
const positionals = [];
|
package/dist/verbs/check.js
CHANGED
|
@@ -32,8 +32,12 @@ export async function check(cwd, args = []) {
|
|
|
32
32
|
/**
|
|
33
33
|
* Map argv onto checkride's flags: bare booleans plus comma-separated slot lists.
|
|
34
34
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* Only the declared flags are read here; an undeclared one never reaches this
|
|
36
|
+
* function, because `run` screens argv against the verb spec in cli-core.ts and
|
|
37
|
+
* refuses before dispatch. This used to tolerate unknown args on the grounds
|
|
38
|
+
* that running the gate mattered more than flag hygiene — but the same tolerance
|
|
39
|
+
* on the mutating verbs let `checkpoint --help` commit, so the screen is now
|
|
40
|
+
* central and uniform.
|
|
37
41
|
*/
|
|
38
42
|
function parseFlags(args) {
|
|
39
43
|
return {
|
package/dist/verbs/doctor.js
CHANGED
|
@@ -423,7 +423,7 @@ function agentReport(cwd) {
|
|
|
423
423
|
const listings = listAgents(root);
|
|
424
424
|
if (listings.length === 0)
|
|
425
425
|
return { lines: [], failed: 0 };
|
|
426
|
-
const lines = ['', 'plumbbob doctor — agents (
|
|
426
|
+
const lines = ['', 'plumbbob doctor — agents — D48 (doctor-validates-agents)'];
|
|
427
427
|
let failed = 0;
|
|
428
428
|
for (const listing of listings) {
|
|
429
429
|
const problem = agentProblem(listing, root);
|
|
@@ -451,7 +451,7 @@ async function gateReport(cwd, env) {
|
|
|
451
451
|
const root = findRepoRoot(cwd);
|
|
452
452
|
if (root === null)
|
|
453
453
|
return { lines: [], failed: 0 };
|
|
454
|
-
const lines = ['', 'plumbbob doctor — check gate (
|
|
454
|
+
const lines = ['', 'plumbbob doctor — check gate — D32 (checkride-gate)'];
|
|
455
455
|
const command = resolveString(root, 'check', '');
|
|
456
456
|
if (command.length > 0) {
|
|
457
457
|
lines.push(` ✓ gate: '${command}' — the "check" setting overrides checkride`);
|
|
@@ -523,13 +523,13 @@ function latchReport(cwd) {
|
|
|
523
523
|
const line = turn === null
|
|
524
524
|
? ' ○ latch: dormant — guidance only (no turn ledger yet; it ticks on your first prompt when the UserPromptSubmit hook is wired — re-run after one to confirm)'
|
|
525
525
|
: ` ✓ latch: live (turn ${turn})`;
|
|
526
|
-
const lines = ['', 'plumbbob doctor — approval latch (
|
|
526
|
+
const lines = ['', 'plumbbob doctor — approval latch — D64 (approval-latch)', line];
|
|
527
527
|
// The latch ignores a settings `auto`: a model can write that file, and a grant
|
|
528
528
|
// the model can forge is no grant — self-approval comes only from the human's
|
|
529
529
|
// typed `/build --auto`. Surface a set value so a human relying on it isn't
|
|
530
530
|
// silently changed — informational, never a problem.
|
|
531
531
|
if (resolveBoolean(root, 'auto', false)) {
|
|
532
|
-
lines.push(' ○ auto: set in settings but not a grant since D67 — self-approve per run with `/plumbbob:build --auto`');
|
|
532
|
+
lines.push(' ○ auto: set in settings but not a grant since D67 (auto-not-a-grant) — self-approve per run with `/plumbbob:build --auto`');
|
|
533
533
|
}
|
|
534
534
|
return { lines, failed: 0 };
|
|
535
535
|
}
|
package/dist/verbs/finish.js
CHANGED
|
@@ -35,7 +35,7 @@ export function finish(cwd, args = []) {
|
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
37
|
process.stderr.write('plumbbob: note — no report.md found; finishing without one ' +
|
|
38
|
-
'(/plumbbob:finish normally writes the report first). No gate (
|
|
38
|
+
'(/plumbbob:finish normally writes the report first). No gate — D9 (finish-no-gate).\n');
|
|
39
39
|
}
|
|
40
40
|
// The final commit: stage the report just written plus the build folder's tail —
|
|
41
41
|
// a step's commit sweeps its own bookkeeping along with the work, so the last
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// `plumbbob recover` — read the control plane and say, plainly, whether it is
|
|
2
|
+
// telling the truth. Nothing else reads these files as a set: `doctor` checks
|
|
3
|
+
// the install and the gate, `status` reports what the markers claim without
|
|
4
|
+
// questioning them. So a session that crashed, lost its context window, or was
|
|
5
|
+
// switched away mid-step can sit in a state no verb detects — the worst being a
|
|
6
|
+
// cursor pointing at a build that no longer exists, which renders a perfectly
|
|
7
|
+
// plausible empty dashboard instead of an error.
|
|
8
|
+
//
|
|
9
|
+
// Diagnosis is free and repair is asked for by name (`--fix`), the same posture
|
|
10
|
+
// `doctor --migrate` takes. What `--fix` may touch is deliberately narrow: only
|
|
11
|
+
// the untracked control files this tool owns and can rewrite from what it
|
|
12
|
+
// already knows. It never touches a tracked artifact (intent, build-log,
|
|
13
|
+
// checkpoints, reports), never touches git, and never advances the loop — a
|
|
14
|
+
// recovery verb that could land a step would be a second, quieter checkpoint.
|
|
15
|
+
//
|
|
16
|
+
// Spike leftovers are reported and never removed: those worktrees live outside
|
|
17
|
+
// the repo root and may hold work nobody copied out, so naming them is help and
|
|
18
|
+
// deleting them is a gamble the human should take by hand.
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
20
|
+
import { findRepoRoot } from "../lib/git.js";
|
|
21
|
+
import { parseSteps } from "../lib/orient.js";
|
|
22
|
+
import { activeBuild, checkpointsPath, clearHandoff, clearTick, grantPath, handoffPath, hasSession, intentPath, inSpike, listBuilds, readTurn, setActiveBuild, setGrant, stepPath, tickPath, } from "../lib/sidecar.js";
|
|
23
|
+
import { spikeBranches, spikeWorktrees } from "./spike.js";
|
|
24
|
+
/**
|
|
25
|
+
* Report every inconsistency in the control plane, and with `--fix` repair the
|
|
26
|
+
* ones that can be repaired without judgment.
|
|
27
|
+
*
|
|
28
|
+
* Exits 0 when the control plane is consistent (or when every problem found was
|
|
29
|
+
* fixed), 1 while any problem is still standing — so a scripted caller can tell
|
|
30
|
+
* "clean" from "you still have to look at this".
|
|
31
|
+
*/
|
|
32
|
+
export function recover(cwd, args) {
|
|
33
|
+
const root = findRepoRoot(cwd);
|
|
34
|
+
if (root === null) {
|
|
35
|
+
process.stderr.write('plumbbob: not a git repository — recover reads a repo\'s control plane.\n');
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
const fix = args.includes('--fix');
|
|
39
|
+
const findings = [...sessionFindings(root), ...spikeOrphanFindings(root)];
|
|
40
|
+
const lines = ['', 'plumbbob recover — control plane'];
|
|
41
|
+
let standing = 0;
|
|
42
|
+
for (const finding of findings) {
|
|
43
|
+
if (finding.ok) {
|
|
44
|
+
lines.push(` ✓ ${finding.name}: ${finding.detail}`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (fix && finding.repair !== undefined) {
|
|
48
|
+
finding.repair();
|
|
49
|
+
lines.push(` ✓ ${finding.name}: ${finding.repaired ?? finding.detail}`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
standing += 1;
|
|
53
|
+
lines.push(` ✗ ${finding.name}: ${finding.detail}`);
|
|
54
|
+
if (finding.hint !== undefined)
|
|
55
|
+
lines.push(` → ${finding.hint}`);
|
|
56
|
+
}
|
|
57
|
+
const fixable = findings.filter((f) => !f.ok && f.repair !== undefined).length;
|
|
58
|
+
lines.push('', summary(standing, fixable, fix), '');
|
|
59
|
+
process.stdout.write(lines.join('\n'));
|
|
60
|
+
return standing > 0 ? 1 : 0;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The closing line: what is still wrong, and whether `--fix` would help.
|
|
64
|
+
*/
|
|
65
|
+
function summary(standing, fixable, fix) {
|
|
66
|
+
if (standing === 0) {
|
|
67
|
+
return fix ? 'control plane consistent — everything fixable was fixed.' : 'control plane consistent — nothing to recover.';
|
|
68
|
+
}
|
|
69
|
+
const problems = `${standing} problem${standing === 1 ? '' : 's'} still standing`;
|
|
70
|
+
if (fix || fixable === 0)
|
|
71
|
+
return `${problems} — each needs a call only you can make (see the → lines).`;
|
|
72
|
+
return `${problems}; ${fixable} of them recover --fix can repair. Nothing was changed.`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Everything that depends on there being a session: the cursor, the phase
|
|
76
|
+
* markers, and the per-build control files.
|
|
77
|
+
*
|
|
78
|
+
* With no session there is no control plane to be wrong about — only the spike
|
|
79
|
+
* leftovers, which outlive `finish` and are checked separately.
|
|
80
|
+
*/
|
|
81
|
+
function sessionFindings(root) {
|
|
82
|
+
if (!hasSession(root)) {
|
|
83
|
+
return [{ name: 'session', ok: true, detail: 'none open — nothing in flight to reconcile' }];
|
|
84
|
+
}
|
|
85
|
+
const slug = activeBuild(root);
|
|
86
|
+
const findings = [];
|
|
87
|
+
// The cursor first: everything below reads through it, so a cursor pointing
|
|
88
|
+
// nowhere makes the rest meaningless rather than merely wrong.
|
|
89
|
+
const resolvable = slug !== null && existsSync(intentPath(root, slug));
|
|
90
|
+
if (!resolvable) {
|
|
91
|
+
return [cursorFinding(root, slug)];
|
|
92
|
+
}
|
|
93
|
+
findings.push({ name: 'cursor', ok: true, detail: `build "${slug}" resolves` });
|
|
94
|
+
const stepInFlight = existsSync(stepPath(root, slug));
|
|
95
|
+
findings.push(...phaseFindings(root, slug, stepInFlight));
|
|
96
|
+
findings.push(...leftoverFindings(root, slug, stepInFlight));
|
|
97
|
+
return findings;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The cursor names a build that is not there.
|
|
101
|
+
*
|
|
102
|
+
* This is the quiet one: `status` takes the cursor at its word, so every read
|
|
103
|
+
* comes back empty and the dashboard renders as a fresh, untouched build rather
|
|
104
|
+
* than an error. Repairable only when exactly one real build remains — with
|
|
105
|
+
* several, which one you meant is a judgment call.
|
|
106
|
+
*/
|
|
107
|
+
function cursorFinding(root, slug) {
|
|
108
|
+
const named = slug === null ? 'nothing' : `"${slug}"`;
|
|
109
|
+
const builds = listBuilds(root).filter((b) => existsSync(intentPath(root, b)));
|
|
110
|
+
const detail = `points at ${named}, which has no intent.md — status will render an empty dashboard rather than refuse`;
|
|
111
|
+
if (builds.length === 1) {
|
|
112
|
+
const only = builds[0];
|
|
113
|
+
return {
|
|
114
|
+
name: 'cursor',
|
|
115
|
+
ok: false,
|
|
116
|
+
detail,
|
|
117
|
+
hint: `fix: plumbbob recover --fix (re-points the cursor at "${only}", the only build left) — or plumbbob use <slug>`,
|
|
118
|
+
repair: () => setActiveBuild(root, only),
|
|
119
|
+
repaired: `re-pointed at "${only}"`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const hint = builds.length === 0
|
|
123
|
+
? 'no build folder survives here — plumbbob finish closes the session, or start a new one'
|
|
124
|
+
: `pick the one you meant: plumbbob use <slug>. Builds: ${builds.join(', ')}`;
|
|
125
|
+
return { name: 'cursor', ok: false, detail, hint };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Contradictions in what phase the build claims to be in.
|
|
129
|
+
*
|
|
130
|
+
* Phase is derived from the markers rather than stored, so two markers at once
|
|
131
|
+
* is not an impossible state — it is an unreadable one, and `status` resolves
|
|
132
|
+
* it by showing the spike and hiding the step entirely.
|
|
133
|
+
*/
|
|
134
|
+
function phaseFindings(root, slug, stepInFlight) {
|
|
135
|
+
const findings = [];
|
|
136
|
+
const spiking = inSpike(root, slug);
|
|
137
|
+
if (spiking && stepInFlight) {
|
|
138
|
+
findings.push({
|
|
139
|
+
name: 'phase',
|
|
140
|
+
ok: false,
|
|
141
|
+
detail: `both a spike and step ${readMarker(stepPath(root, slug))} are marked in flight — status shows the spike and hides the step`,
|
|
142
|
+
hint: 'close the spike first (plumbbob spike done), which returns you to the step still in flight',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
findings.push({
|
|
147
|
+
name: 'phase',
|
|
148
|
+
ok: true,
|
|
149
|
+
detail: spiking ? 'in a spike' : stepInFlight ? `step ${readMarker(stepPath(root, slug))} in flight` : 'at the boundary',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// A STEP the plan no longer contains: `refine` can rewrite `## Steps` under a
|
|
153
|
+
// step that is already in flight, and nothing reconciles the marker with it.
|
|
154
|
+
const marker = Number(readMarker(stepPath(root, slug)));
|
|
155
|
+
if (stepInFlight && Number.isInteger(marker) && marker > 0) {
|
|
156
|
+
const planned = parseSteps(readOr(intentPath(root, slug))).map((s) => s.n);
|
|
157
|
+
if (planned.length > 0 && !planned.includes(marker)) {
|
|
158
|
+
findings.push({
|
|
159
|
+
name: 'step marker',
|
|
160
|
+
ok: false,
|
|
161
|
+
detail: `step ${marker} is in flight but the plan has no step ${marker} (planned: ${planned.join(', ')})`,
|
|
162
|
+
hint: 'the plan was rewritten under the step — sharpen it with /plumbbob:step, or plumbbob revert to drop the step',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return findings;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Control files left behind by a verb that did not get to clean them up.
|
|
170
|
+
*
|
|
171
|
+
* Each of these fails quietly rather than loudly, which is why they are worth a
|
|
172
|
+
* verb: an orphaned handoff ledger feeds a finished step's agent output into
|
|
173
|
+
* the next one, and a stale TICK arms the approval latch against a span that
|
|
174
|
+
* already closed.
|
|
175
|
+
*/
|
|
176
|
+
function leftoverFindings(root, slug, stepInFlight) {
|
|
177
|
+
const findings = [];
|
|
178
|
+
// The handoff ledger is step-scoped and cleared by `checkpoint` alone — a
|
|
179
|
+
// revert or an abandoned step leaves it for the next step to inherit.
|
|
180
|
+
const handoffs = handoffCount(root, slug);
|
|
181
|
+
if (!stepInFlight && handoffs > 0) {
|
|
182
|
+
findings.push({
|
|
183
|
+
name: 'handoff ledger',
|
|
184
|
+
ok: false,
|
|
185
|
+
detail: `${handoffs} agent envelope${handoffs === 1 ? '' : 's'} left over at the boundary — the next step would thread them into its context`,
|
|
186
|
+
hint: 'fix: plumbbob recover --fix (clears the step-scoped ledger)',
|
|
187
|
+
repair: () => clearHandoff(root, slug),
|
|
188
|
+
repaired: `cleared ${handoffs} leftover envelope${handoffs === 1 ? '' : 's'}`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
findings.push({ name: 'handoff ledger', ok: true, detail: stepInFlight ? 'in scope for the step in flight' : 'empty' });
|
|
193
|
+
}
|
|
194
|
+
// TICK is stamped when a step is entered and when `start` opens the plan
|
|
195
|
+
// pause, and cleared by the checkpoint that lands either one. At the boundary
|
|
196
|
+
// with the plan already committed, neither pause is open, so a TICK left here
|
|
197
|
+
// is measuring a span that closed.
|
|
198
|
+
const planLanded = readOr(checkpointsPath(root, slug)).split('\n').some((l) => l.startsWith('plan '));
|
|
199
|
+
if (!stepInFlight && planLanded && existsSync(tickPath(root, slug))) {
|
|
200
|
+
findings.push({
|
|
201
|
+
name: 'latch tick',
|
|
202
|
+
ok: false,
|
|
203
|
+
detail: 'a TICK stamp survives at the boundary — the approval latch is armed against a pause that already closed',
|
|
204
|
+
hint: 'fix: plumbbob recover --fix (clears the stale stamp; the latch re-arms on the next build)',
|
|
205
|
+
repair: () => clearTick(root, slug),
|
|
206
|
+
repaired: 'cleared the stale stamp',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
findings.push({ name: 'latch tick', ok: true, detail: stepInFlight ? 'stamped for the step in flight' : 'clear' });
|
|
211
|
+
}
|
|
212
|
+
// GRANT is rewritten on every turn tick, so it self-clears — but only while
|
|
213
|
+
// the hook runs. Without a TURN ledger nothing will ever clear it, and the
|
|
214
|
+
// latch reads a standing grant as self-approval.
|
|
215
|
+
if (existsSync(grantPath(root)) && readTurn(root) === null) {
|
|
216
|
+
findings.push({
|
|
217
|
+
name: 'self-approval grant',
|
|
218
|
+
ok: false,
|
|
219
|
+
detail: 'a GRANT survives with no turn ledger to clear it — the latch would honor it as self-approval',
|
|
220
|
+
hint: 'fix: plumbbob recover --fix (drops the grant; type /plumbbob:build --auto to mint a fresh one)',
|
|
221
|
+
repair: () => setGrant(root, null),
|
|
222
|
+
repaired: 'dropped the stranded grant',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return findings;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Spike worktrees and branches with no open spike to own them.
|
|
229
|
+
*
|
|
230
|
+
* `spike done` is the only cleanup and it refuses without the marker, so a
|
|
231
|
+
* spike interrupted by `finish`, a cursor switch, or a half-failed open strands
|
|
232
|
+
* these where no verb can reach them. Reported with the exact commands and
|
|
233
|
+
* never removed: the directories sit outside the repo and may hold the only
|
|
234
|
+
* copy of what the spike learned.
|
|
235
|
+
*/
|
|
236
|
+
function spikeOrphanFindings(root) {
|
|
237
|
+
if (inSpike(root, activeBuild(root))) {
|
|
238
|
+
return [{ name: 'spike leftovers', ok: true, detail: 'a spike is open — plumbbob spike done owns its cleanup' }];
|
|
239
|
+
}
|
|
240
|
+
const worktrees = spikeWorktrees(root);
|
|
241
|
+
const branches = spikeBranches(root);
|
|
242
|
+
if (worktrees.length === 0 && branches.length === 0) {
|
|
243
|
+
return [{ name: 'spike leftovers', ok: true, detail: 'none' }];
|
|
244
|
+
}
|
|
245
|
+
const parts = [
|
|
246
|
+
worktrees.length > 0 ? `${worktrees.length} worktree${worktrees.length === 1 ? '' : 's'} (${worktrees.join(', ')})` : '',
|
|
247
|
+
branches.length > 0 ? `${branches.length} branch${branches.length === 1 ? '' : 'es'} (${branches.join(', ')})` : '',
|
|
248
|
+
].filter((p) => p.length > 0);
|
|
249
|
+
const commands = [
|
|
250
|
+
...worktrees.map((path) => `git worktree remove --force ${path}`),
|
|
251
|
+
...(branches.length > 0 ? [`git branch -D ${branches.join(' ')}`] : []),
|
|
252
|
+
];
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
name: 'spike leftovers',
|
|
256
|
+
ok: false,
|
|
257
|
+
detail: `${parts.join(' and ')} survive with no spike open — plumbbob spike done cannot reach them`,
|
|
258
|
+
hint: `remove by hand once you have salvaged anything you want: ${commands.join(' && ')}`,
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* How many agent envelopes the step-scoped handoff ledger holds; 0 when it is
|
|
264
|
+
* absent or unreadable.
|
|
265
|
+
*/
|
|
266
|
+
function handoffCount(root, slug) {
|
|
267
|
+
const raw = readOr(handoffPath(root, slug));
|
|
268
|
+
if (raw.trim().length === 0)
|
|
269
|
+
return 0;
|
|
270
|
+
try {
|
|
271
|
+
const parsed = JSON.parse(raw);
|
|
272
|
+
return Array.isArray(parsed) ? parsed.length : 1;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* A control marker's trimmed content, or '?' when it cannot be read — a marker
|
|
280
|
+
* exists to be reported on, so an unreadable one must not throw mid-report.
|
|
281
|
+
*/
|
|
282
|
+
function readMarker(path) {
|
|
283
|
+
const raw = readOr(path).trim();
|
|
284
|
+
return raw.length > 0 ? raw : '?';
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* File content, or '' when absent — recover reads state that may be missing by
|
|
288
|
+
* definition, so every read degrades rather than throws.
|
|
289
|
+
*/
|
|
290
|
+
function readOr(path) {
|
|
291
|
+
try {
|
|
292
|
+
return readFileSync(path, 'utf8');
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return '';
|
|
296
|
+
}
|
|
297
|
+
}
|
package/dist/verbs/revert.js
CHANGED
|
@@ -95,8 +95,8 @@ export function revert(cwd, args) {
|
|
|
95
95
|
*
|
|
96
96
|
* The match is by name alone, and since the skills dropped their `pb-` prefix
|
|
97
97
|
* those names are ordinary words — a user's own `.claude/skills/plan/` is
|
|
98
|
-
* carried across the reset too. That errs toward keeping work
|
|
99
|
-
* the safe direction to be wrong in. Only paths that currently exist are returned.
|
|
98
|
+
* carried across the reset too. That errs toward keeping work — never destroy —
|
|
99
|
+
* so it is the safe direction to be wrong in. Only paths that currently exist are returned.
|
|
100
100
|
*/
|
|
101
101
|
function plumbbobOwnedPaths(root) {
|
|
102
102
|
const paths = [sidecarDir(root)];
|
package/dist/verbs/spike.js
CHANGED
|
@@ -201,8 +201,12 @@ function spikeReportsMissingVerdict(root, buildSlug) {
|
|
|
201
201
|
* Worktree paths whose checked-out branch is under spike/ — parsed from the
|
|
202
202
|
* porcelain output (blank-line-separated `worktree <path>` / `branch <ref>`
|
|
203
203
|
* blocks).
|
|
204
|
+
*
|
|
205
|
+
* Exported for `recover`, which reports the ones this verb can no longer reach
|
|
206
|
+
* (a spike interrupted before `spike done` strands them). Sharing the reader
|
|
207
|
+
* keeps one definition of what counts as a spike worktree.
|
|
204
208
|
*/
|
|
205
|
-
function spikeWorktrees(root) {
|
|
209
|
+
export function spikeWorktrees(root) {
|
|
206
210
|
const out = git(root, ['worktree', 'list', '--porcelain']);
|
|
207
211
|
const paths = [];
|
|
208
212
|
let current = null;
|
|
@@ -218,8 +222,10 @@ function spikeWorktrees(root) {
|
|
|
218
222
|
}
|
|
219
223
|
/**
|
|
220
224
|
* List the local `spike/<slug>` branch names.
|
|
225
|
+
*
|
|
226
|
+
* Exported alongside spikeWorktrees, for the same reason.
|
|
221
227
|
*/
|
|
222
|
-
function spikeBranches(root) {
|
|
228
|
+
export function spikeBranches(root) {
|
|
223
229
|
const out = git(root, ['for-each-ref', '--format=%(refname:short)', 'refs/heads/spike/']);
|
|
224
230
|
return out.length === 0 ? [] : out.split('\n').filter((b) => b.length > 0);
|
|
225
231
|
}
|
package/hooks/post-edit.sh
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# post-edit.sh — light feedback (
|
|
2
|
+
# post-edit.sh — light feedback — D25 (light-then-heavy). PostToolUse, non-blocking, ALWAYS exits 0.
|
|
3
3
|
# Runs file-scoped oxlint + ast-grep on the changed file and injects any failures
|
|
4
|
-
# into the model's context via additionalContext (verified API
|
|
4
|
+
# into the model's context via additionalContext (verified API — D25 (light-then-heavy)). No-ops when
|
|
5
5
|
# the tools are absent or there is no session. tsc is deferred to the heavy tier
|
|
6
|
-
# (
|
|
6
|
+
# — D25 (light-then-heavy): it has no true single-file mode and would tax every keystroke.
|
|
7
7
|
|
|
8
8
|
# The repo root is the nearest ancestor whose .plumbbob/STATE is NON-EMPTY — the
|
|
9
|
-
# per-worktree signal that a tracked build is live here (
|
|
9
|
+
# per-worktree signal that a tracked build is live here — D28 (state-cursor). STATE's existence
|
|
10
10
|
# means "a session is active"; its content is the active-build cursor, so a non-empty
|
|
11
11
|
# STATE is exactly "a tracked build is live" (the `--local` layout leaves STATE empty,
|
|
12
12
|
# and this file-scoped check is a tracked-build affordance). `-s` tests both existence
|
package/hooks/pre-bash-commit.sh
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# pre-bash-commit.sh — the git-commit ask-hook (
|
|
2
|
+
# pre-bash-commit.sh — the git-commit ask-hook — D66 (oob-commits-surfaced). PreToolUse on Bash: when a
|
|
3
3
|
# step is in flight and the model reaches for a raw `git commit`, turn that commit
|
|
4
4
|
# into a permission *question*, never a wall. checkpoint owns the landing (the latch
|
|
5
5
|
# lives there); this only nudges the human to route through it. ALWAYS exits 0 and
|
|
6
|
-
# never `deny`s — the human decides, C5 stays intact (
|
|
6
|
+
# never `deny`s — the human decides, C5 (additive-git) stays intact — D66 (oob-commits-surfaced). `plumbbob checkpoint`'s
|
|
7
7
|
# own internal `git commit` spawn never passes through hooks, so nothing self-trips.
|
|
8
8
|
|
|
9
9
|
# The repo root is the nearest ancestor with an active session — `.plumbbob/STATE`,
|
|
@@ -27,7 +27,7 @@ root=$(find_root) || exit 0 # no active session here: nothing to guard
|
|
|
27
27
|
|
|
28
28
|
# The in-flight signal is STEP (cleared when the step checkpoints): inside the
|
|
29
29
|
# active build's folder when STATE names one, else the flat sidecar STEP of the
|
|
30
|
-
# `--local` layout. STATE's content IS the cursor (
|
|
30
|
+
# `--local` layout. STATE's content IS the cursor — D28 (state-cursor) — read the first line, trim
|
|
31
31
|
# whitespace, and treat the legacy `active` sentinel (pre-STATE-cursor) as no cursor.
|
|
32
32
|
slug=$(head -n1 "$root/.plumbbob/STATE" 2>/dev/null | tr -d '[:space:]')
|
|
33
33
|
[ "$slug" = "active" ] && slug=""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "checkride",
|
|
3
|
+
"displayName": "checkride",
|
|
4
|
+
"version": "0.10.2",
|
|
5
|
+
"description": "Bounded, contract-aware readers for what checkride already writes: triage a red gate and read quality signal without loading megabytes of raw artifacts.",
|
|
6
|
+
"author": { "name": "Rob McLarty", "email": "hello@robmclarty.com", "url": "https://robmclarty.com" },
|
|
7
|
+
"homepage": "https://robmclarty.github.io/checkride/",
|
|
8
|
+
"repository": "https://github.com/robmclarty/checkride",
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"keywords": ["claude-code", "claude", "agent", "checkride", "ci", "triage"]
|
|
11
|
+
}
|