plumbbob 0.9.3 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +92 -89
- package/dist/cli-core.js +25 -8
- package/dist/cli.js +1 -1
- package/dist/lib/agents.js +26 -26
- package/dist/lib/buildlog.js +11 -11
- package/dist/lib/buildlogsync.js +8 -8
- package/dist/lib/check.js +55 -22
- package/dist/lib/commitbody.js +72 -0
- package/dist/lib/commitmsg.js +7 -7
- package/dist/lib/git.js +44 -15
- package/dist/lib/intent.js +21 -20
- package/dist/lib/latch.js +19 -19
- package/dist/lib/orient.js +36 -17
- package/dist/lib/reentry.js +70 -0
- package/dist/lib/settings.js +16 -16
- package/dist/lib/sidecar.js +54 -54
- package/dist/lib/templates.js +1 -1
- package/dist/verbs/agent.js +27 -27
- package/dist/verbs/build.js +9 -9
- package/dist/verbs/check.js +6 -6
- package/dist/verbs/checkpoint.js +48 -47
- package/dist/verbs/doctor.js +31 -30
- package/dist/verbs/finish.js +28 -41
- package/dist/verbs/handoff.js +9 -8
- package/dist/verbs/init.js +9 -9
- package/dist/verbs/park.js +3 -3
- package/dist/verbs/recover.js +297 -0
- package/dist/verbs/revert.js +13 -13
- package/dist/verbs/spike.js +25 -19
- package/dist/verbs/start.js +49 -19
- package/dist/verbs/status.js +8 -8
- package/dist/verbs/turn.js +16 -16
- package/dist/verbs/use.js +4 -4
- package/hooks/post-edit.sh +4 -4
- package/hooks/pre-bash-commit.sh +3 -3
- package/node_modules/checkride/.claude-plugin/plugin.json +11 -0
- package/node_modules/checkride/CHANGELOG.md +1404 -0
- package/node_modules/checkride/LICENSE +201 -21
- package/node_modules/checkride/README.md +197 -38
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts +15 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts.map +1 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js +17 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js.map +1 -0
- package/node_modules/checkride/dist/adapters.d.ts +30 -10
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +62 -23
- package/node_modules/checkride/dist/adapters.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/claude.d.ts +88 -0
- package/node_modules/checkride/dist/agent-setup/claude.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/claude.js +305 -0
- package/node_modules/checkride/dist/agent-setup/claude.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/command.d.ts +43 -0
- package/node_modules/checkride/dist/agent-setup/command.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/command.js +77 -0
- package/node_modules/checkride/dist/agent-setup/command.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts +69 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js +247 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/files.d.ts +50 -0
- package/node_modules/checkride/dist/agent-setup/files.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/files.js +80 -0
- package/node_modules/checkride/dist/agent-setup/files.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/harness.d.ts +101 -0
- package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/harness.js +93 -0
- package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/hook.d.ts +45 -52
- package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/hook.js +64 -95
- package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.d.ts +10 -4
- package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.js +9 -4
- package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts +81 -0
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js +604 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/skills.d.ts +31 -0
- package/node_modules/checkride/dist/agent-setup/skills.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/skills.js +56 -0
- package/node_modules/checkride/dist/agent-setup/skills.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/configured.d.ts +30 -0
- package/node_modules/checkride/dist/artifacts/configured.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/configured.js +45 -0
- package/node_modules/checkride/dist/artifacts/configured.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/format.d.ts +33 -0
- package/node_modules/checkride/dist/artifacts/format.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/format.js +56 -0
- package/node_modules/checkride/dist/artifacts/format.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/freshness.d.ts +32 -0
- package/node_modules/checkride/dist/artifacts/freshness.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/freshness.js +37 -0
- package/node_modules/checkride/dist/artifacts/freshness.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/index.d.ts +22 -0
- package/node_modules/checkride/dist/artifacts/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/index.js +19 -0
- package/node_modules/checkride/dist/artifacts/index.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/json.d.ts +31 -0
- package/node_modules/checkride/dist/artifacts/json.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/json.js +47 -0
- package/node_modules/checkride/dist/artifacts/json.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/raw.d.ts +60 -0
- package/node_modules/checkride/dist/artifacts/raw.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/raw.js +91 -0
- package/node_modules/checkride/dist/artifacts/raw.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/summary.d.ts +69 -0
- package/node_modules/checkride/dist/artifacts/summary.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/summary.js +108 -0
- package/node_modules/checkride/dist/artifacts/summary.js.map +1 -0
- package/node_modules/checkride/dist/atomic.d.ts +16 -1
- package/node_modules/checkride/dist/atomic.d.ts.map +1 -1
- package/node_modules/checkride/dist/atomic.js +25 -3
- package/node_modules/checkride/dist/atomic.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.d.ts +9 -5
- package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.js +163 -60
- package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +20 -11
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.js +73 -23
- package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
- package/node_modules/checkride/dist/baseline/history.d.ts +98 -0
- package/node_modules/checkride/dist/baseline/history.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/history.js +232 -0
- package/node_modules/checkride/dist/baseline/history.js.map +1 -0
- package/node_modules/checkride/dist/baseline/index.d.ts +7 -5
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/index.js +5 -4
- package/node_modules/checkride/dist/baseline/index.js.map +1 -1
- package/node_modules/checkride/dist/baseline/store.d.ts +41 -10
- package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/store.js +65 -24
- package/node_modules/checkride/dist/baseline/store.js.map +1 -1
- package/node_modules/checkride/dist/baseline-command.d.ts +5 -5
- package/node_modules/checkride/dist/baseline-command.js +7 -7
- package/node_modules/checkride/dist/baseline-command.js.map +1 -1
- package/node_modules/checkride/dist/cli.d.ts +21 -4
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +350 -36
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +114 -9
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +142 -23
- package/node_modules/checkride/dist/config.js.map +1 -1
- package/node_modules/checkride/dist/digest/digest.d.ts +5 -5
- package/node_modules/checkride/dist/digest/digest.js +6 -6
- package/node_modules/checkride/dist/digest/index.d.ts +2 -2
- package/node_modules/checkride/dist/digest/index.js +2 -2
- package/node_modules/checkride/dist/doctor.d.ts +27 -6
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +231 -27
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/gate.d.ts +180 -0
- package/node_modules/checkride/dist/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/gate.js +617 -0
- package/node_modules/checkride/dist/gate.js.map +1 -0
- package/node_modules/checkride/dist/init.d.ts +79 -12
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +385 -81
- package/node_modules/checkride/dist/init.js.map +1 -1
- package/node_modules/checkride/dist/json.d.ts +21 -0
- package/node_modules/checkride/dist/json.d.ts.map +1 -0
- package/node_modules/checkride/dist/json.js +21 -0
- package/node_modules/checkride/dist/json.js.map +1 -0
- package/node_modules/checkride/dist/links.d.ts +31 -5
- package/node_modules/checkride/dist/links.d.ts.map +1 -1
- package/node_modules/checkride/dist/links.js +141 -32
- package/node_modules/checkride/dist/links.js.map +1 -1
- package/node_modules/checkride/dist/node-pin.d.ts +129 -0
- package/node_modules/checkride/dist/node-pin.d.ts.map +1 -0
- package/node_modules/checkride/dist/node-pin.js +211 -0
- package/node_modules/checkride/dist/node-pin.js.map +1 -0
- package/node_modules/checkride/dist/orchestrator.d.ts +53 -10
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +312 -113
- package/node_modules/checkride/dist/orchestrator.js.map +1 -1
- package/node_modules/checkride/dist/pack.d.ts +8 -9
- package/node_modules/checkride/dist/pack.d.ts.map +1 -1
- package/node_modules/checkride/dist/pack.js +53 -17
- package/node_modules/checkride/dist/pack.js.map +1 -1
- package/node_modules/checkride/dist/pm/detect.d.ts +6 -1
- package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/detect.js +6 -1
- package/node_modules/checkride/dist/pm/detect.js.map +1 -1
- package/node_modules/checkride/dist/pm/index.d.ts +7 -2
- package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/index.js +6 -2
- package/node_modules/checkride/dist/pm/index.js.map +1 -1
- package/node_modules/checkride/dist/pm/launch.d.ts +72 -0
- package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/launch.js +108 -0
- package/node_modules/checkride/dist/pm/launch.js.map +1 -0
- package/node_modules/checkride/dist/pm/tools.d.ts +73 -0
- package/node_modules/checkride/dist/pm/tools.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/tools.js +124 -0
- package/node_modules/checkride/dist/pm/tools.js.map +1 -0
- package/node_modules/checkride/dist/pm/translate.d.ts +35 -9
- package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/translate.js +78 -18
- package/node_modules/checkride/dist/pm/translate.js.map +1 -1
- package/node_modules/checkride/dist/proc.d.ts +30 -0
- package/node_modules/checkride/dist/proc.d.ts.map +1 -0
- package/node_modules/checkride/dist/proc.js +42 -0
- package/node_modules/checkride/dist/proc.js.map +1 -0
- package/node_modules/checkride/dist/qa/cli.d.ts +20 -0
- package/node_modules/checkride/dist/qa/cli.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/cli.js +24 -0
- package/node_modules/checkride/dist/qa/cli.js.map +1 -0
- package/node_modules/checkride/dist/qa/dead.d.ts +39 -0
- package/node_modules/checkride/dist/qa/dead.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/dead.js +88 -0
- package/node_modules/checkride/dist/qa/dead.js.map +1 -0
- package/node_modules/checkride/dist/qa/dupes.d.ts +45 -0
- package/node_modules/checkride/dist/qa/dupes.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/dupes.js +62 -0
- package/node_modules/checkride/dist/qa/dupes.js.map +1 -0
- package/node_modules/checkride/dist/qa/health.d.ts +85 -0
- package/node_modules/checkride/dist/qa/health.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/health.js +109 -0
- package/node_modules/checkride/dist/qa/health.js.map +1 -0
- package/node_modules/checkride/dist/qa/index.d.ts +23 -0
- package/node_modules/checkride/dist/qa/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/index.js +22 -0
- package/node_modules/checkride/dist/qa/index.js.map +1 -0
- package/node_modules/checkride/dist/qa/mutation.d.ts +80 -0
- package/node_modules/checkride/dist/qa/mutation.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/mutation.js +195 -0
- package/node_modules/checkride/dist/qa/mutation.js.map +1 -0
- package/node_modules/checkride/dist/qa/qa.d.ts +71 -0
- package/node_modules/checkride/dist/qa/qa.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/qa.js +158 -0
- package/node_modules/checkride/dist/qa/qa.js.map +1 -0
- package/node_modules/checkride/dist/qa/read.d.ts +40 -0
- package/node_modules/checkride/dist/qa/read.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/read.js +47 -0
- package/node_modules/checkride/dist/qa/read.js.map +1 -0
- package/node_modules/checkride/dist/qa/render.d.ts +26 -0
- package/node_modules/checkride/dist/qa/render.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/render.js +310 -0
- package/node_modules/checkride/dist/qa/render.js.map +1 -0
- package/node_modules/checkride/dist/recover.d.ts +48 -0
- package/node_modules/checkride/dist/recover.d.ts.map +1 -0
- package/node_modules/checkride/dist/recover.js +220 -0
- package/node_modules/checkride/dist/recover.js.map +1 -0
- package/node_modules/checkride/dist/security.d.ts +45 -0
- package/node_modules/checkride/dist/security.d.ts.map +1 -0
- package/node_modules/checkride/dist/security.js +86 -0
- package/node_modules/checkride/dist/security.js.map +1 -0
- package/node_modules/checkride/dist/smoke.d.ts +6 -6
- package/node_modules/checkride/dist/smoke.js +6 -6
- package/node_modules/checkride/dist/snippets.d.ts +27 -30
- package/node_modules/checkride/dist/snippets.d.ts.map +1 -1
- package/node_modules/checkride/dist/snippets.js +26 -30
- package/node_modules/checkride/dist/snippets.js.map +1 -1
- package/node_modules/checkride/dist/tool-json.d.ts +33 -0
- package/node_modules/checkride/dist/tool-json.d.ts.map +1 -0
- package/node_modules/checkride/dist/tool-json.js +58 -0
- package/node_modules/checkride/dist/tool-json.js.map +1 -0
- package/node_modules/checkride/dist/triage/cli.d.ts +21 -0
- package/node_modules/checkride/dist/triage/cli.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/cli.js +26 -0
- package/node_modules/checkride/dist/triage/cli.js.map +1 -0
- package/node_modules/checkride/dist/triage/doctor-fold.d.ts +51 -0
- package/node_modules/checkride/dist/triage/doctor-fold.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/doctor-fold.js +88 -0
- package/node_modules/checkride/dist/triage/doctor-fold.js.map +1 -0
- package/node_modules/checkride/dist/triage/env.d.ts +54 -0
- package/node_modules/checkride/dist/triage/env.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/env.js +81 -0
- package/node_modules/checkride/dist/triage/env.js.map +1 -0
- package/node_modules/checkride/dist/triage/gate.d.ts +61 -0
- package/node_modules/checkride/dist/triage/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/gate.js +121 -0
- package/node_modules/checkride/dist/triage/gate.js.map +1 -0
- package/node_modules/checkride/dist/triage/index.d.ts +22 -0
- package/node_modules/checkride/dist/triage/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/index.js +20 -0
- package/node_modules/checkride/dist/triage/index.js.map +1 -0
- package/node_modules/checkride/dist/triage/render.d.ts +19 -0
- package/node_modules/checkride/dist/triage/render.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/render.js +419 -0
- package/node_modules/checkride/dist/triage/render.js.map +1 -0
- package/node_modules/checkride/dist/triage/triage.d.ts +83 -0
- package/node_modules/checkride/dist/triage/triage.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/triage.js +131 -0
- package/node_modules/checkride/dist/triage/triage.js.map +1 -0
- package/node_modules/checkride/package.json +18 -15
- package/node_modules/checkride/schema/checkride.config.schema.json +70 -5
- package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
- package/node_modules/checkride/skills/check/SKILL.md +265 -0
- package/node_modules/checkride/skills/qa/SKILL.md +231 -0
- package/node_modules/checkride/templates/shared/cspell.json +1 -0
- package/node_modules/checkride/templates/shared/oxlintrc.json +1 -1
- package/node_modules/checkride/templates/shared/styles/Repo/Drift.yml +41 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Latin.yml +14 -0
- package/node_modules/checkride/templates/shared/styles/Repo/LyHyphen.yml +28 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Minted.yml +33 -0
- package/node_modules/checkride/templates/shared/styles/Repo/ThereIs.yml +16 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Weasel.yml +26 -0
- package/node_modules/checkride/templates/shared/vale.ini +34 -0
- package/package.json +10 -9
- package/skills/build/SKILL.md +85 -76
- package/skills/doctor/SKILL.md +11 -11
- package/skills/finish/SKILL.md +21 -18
- package/skills/harvest/SKILL.md +13 -11
- package/skills/park/SKILL.md +10 -10
- package/skills/plan/SKILL.md +69 -59
- package/skills/recover/SKILL.md +28 -0
- package/skills/refine/SKILL.md +21 -18
- package/skills/revert/SKILL.md +6 -6
- package/skills/spike/SKILL.md +9 -9
- package/skills/status/SKILL.md +5 -5
- package/skills/step/SKILL.md +20 -15
- package/skills/verify/SKILL.md +56 -46
- package/templates/build-log.md +7 -7
- package/templates/intent.md +52 -41
- package/templates/spike-report.md +10 -10
|
@@ -11,18 +11,19 @@
|
|
|
11
11
|
* Generation, not transformation: every file is written once; the project owns
|
|
12
12
|
* it afterward.
|
|
13
13
|
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
14
15
|
import { existsSync, readFileSync } from 'node:fs';
|
|
15
16
|
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
16
17
|
import { basename, dirname, join } from 'node:path';
|
|
17
18
|
import { fileURLToPath } from 'node:url';
|
|
18
19
|
import { ADAPTERS, SLOTS } from './adapters.js';
|
|
19
20
|
import { planSnippets, selectDocFiles } from './snippets.js';
|
|
20
|
-
import {
|
|
21
|
+
import { detectHarnesses, writeCursorSkills, writeHooks } from './agent-setup/index.js';
|
|
21
22
|
import { BASELINE_FILE, isFingerprintable } from './baseline/index.js';
|
|
22
23
|
import { runBaseline } from './baseline-command.js';
|
|
23
|
-
import { configSchemaUrl, resolveChecks } from './config.js';
|
|
24
|
-
import { runChecks } from './orchestrator.js';
|
|
25
|
-
import { detectPackageManager } from './pm/index.js';
|
|
24
|
+
import { configSchemaUrl, loadConfig, resolveChecks } from './config.js';
|
|
25
|
+
import { runChecks, selectChecks } from './orchestrator.js';
|
|
26
|
+
import { detectPackageManager, execCommand, runScript } from './pm/index.js';
|
|
26
27
|
const NULL_OUT = { write: () => true };
|
|
27
28
|
/** Run the adopted checks once; a failing (non-skipped) check marks its slot. */
|
|
28
29
|
async function defaultProbe(slots, cwd) {
|
|
@@ -33,22 +34,158 @@ async function defaultProbe(slots, cwd) {
|
|
|
33
34
|
}
|
|
34
35
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
35
36
|
const TEMPLATES_DIR = join(HERE, '..', 'templates');
|
|
36
|
-
const
|
|
37
|
+
const STANZA_BEGIN_PREFIX = '<!-- checkride:begin';
|
|
37
38
|
const STANZA_END = '<!-- checkride:end -->';
|
|
39
|
+
/** The block, capturing its stamp (absent on stanzas written before v0.10.1) and its body. */
|
|
40
|
+
const STANZA_RE = /<!-- checkride:begin(?: hash=([0-9a-z]+))? -->([\s\S]*?)<!-- checkride:end -->/;
|
|
41
|
+
const STANZA_BEGIN_RE = /<!-- checkride:begin(?: hash=[0-9a-z]+)? -->/;
|
|
38
42
|
// ----------------------------------------------------------------------------
|
|
39
43
|
// AGENTS.md stanza (gate: idempotent)
|
|
40
44
|
// ----------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Compare two stanza bodies the way a reader would: line endings and trailing
|
|
47
|
+
* whitespace are not edits. Deliberately minimal — one checkride version stamps
|
|
48
|
+
* a block and a later one verifies that stamp, so every rule here has to keep
|
|
49
|
+
* holding across versions or a pristine stanza starts reading as an edited one.
|
|
50
|
+
*/
|
|
51
|
+
function normalizeStanza(text) {
|
|
52
|
+
return text.replace(/\r\n/g, '\n').replace(/[ \t]+$/gm, '').trim();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The stamp written into the begin marker: a digest of the body checkride
|
|
56
|
+
* generated. A later run recomputes it from what is on disk — a match means the
|
|
57
|
+
* block is still checkride's own output and is safe to refresh; a mismatch means
|
|
58
|
+
* someone edited it and refreshing would destroy their work.
|
|
59
|
+
*
|
|
60
|
+
* The `v1` prefix earns its place twice: it dates the normalization rules above
|
|
61
|
+
* (change them and bump it, so old stamps read as unverifiable rather than
|
|
62
|
+
* edited), and it guarantees the token contains a digit. cspell ignores words
|
|
63
|
+
* with digits, so a digest that happened to come up all `a`-`f` cannot fail the
|
|
64
|
+
* `spell` check of the repo checkride wrote it into.
|
|
65
|
+
*/
|
|
66
|
+
function stanzaStamp(body) {
|
|
67
|
+
return `v1${createHash('sha256').update(normalizeStanza(body)).digest('hex').slice(0, 16)}`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A stanza body without its trailing generated `Active checks in this repo: …`
|
|
71
|
+
* line — the part that is pure wording, identical in every repo running a given
|
|
72
|
+
* checkride version.
|
|
73
|
+
*
|
|
74
|
+
* The active-checks line is what makes two stanzas from the same release differ,
|
|
75
|
+
* so a registry of released wordings has to compare everything but that. If the
|
|
76
|
+
* line is missing, or something follows it inside the markers, nothing is
|
|
77
|
+
* stripped and the digest simply fails to match — which is the safe answer.
|
|
78
|
+
*/
|
|
79
|
+
function stanzaPreamble(body) {
|
|
80
|
+
return normalizeStanza(normalizeStanza(body).replace(/\nActive checks in this repo:[^\n]*$/, ''));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The wording of every stanza checkride released before it stamped them, keyed
|
|
84
|
+
* by {@link stanzaPreamble} digest. Four distinct texts across v0.1.1–v0.10.0.
|
|
85
|
+
*
|
|
86
|
+
* This is what makes the stamp's arrival a non-event for repos that never
|
|
87
|
+
* touched their stanza. Without it, every upgrading repo looked `unstamped` —
|
|
88
|
+
* an older version's wording is not distinguishable from a customization by
|
|
89
|
+
* comparison alone — so the first run after upgrading refused and wrote
|
|
90
|
+
* *nothing*, hooks and config included. That is how the 0.10.1 gate-timeout fix
|
|
91
|
+
* failed to reach the repos that most needed it: the guard protecting AGENTS.md
|
|
92
|
+
* prose was also withholding a fix for a gate that had silently stopped gating.
|
|
93
|
+
*
|
|
94
|
+
* Recognizing the wording collapses that to the case it was always meant to
|
|
95
|
+
* cover. A pre-stamp stanza whose preamble is one of these is checkride's own
|
|
96
|
+
* output, untouched, and refreshing it loses nothing; one that has been edited
|
|
97
|
+
* still fails to match and is still refused.
|
|
98
|
+
*
|
|
99
|
+
* The list is closed. Every release from 0.10.1 stamps what it writes, so no
|
|
100
|
+
* future version can add an unstamped wording — nothing here needs maintaining.
|
|
101
|
+
*/
|
|
102
|
+
const LEGACY_STANZA_PREAMBLES = new Set([
|
|
103
|
+
'v16da2ec6a25e24085', // v0.1.1–v0.1.6
|
|
104
|
+
'v190c2d48a280ff92c', // v0.2.0–v0.7.0
|
|
105
|
+
'v1d4b4eaeeccb5faa2', // v0.8.0–v0.9.6
|
|
106
|
+
'v17a3a1a85e3c3123e', // v0.10.0 (0.10.1 shares the wording but stamps it)
|
|
107
|
+
]);
|
|
108
|
+
/** Classify the stanza in `content` against the `body` checkride would write. */
|
|
109
|
+
export function inspectStanza(content, body) {
|
|
110
|
+
const match = STANZA_RE.exec(content);
|
|
111
|
+
if (!match) {
|
|
112
|
+
// A begin marker with no end is a stanza someone edited badly, not an absent
|
|
113
|
+
// one: appending a second block would be the same data loss in another form.
|
|
114
|
+
return content.includes(STANZA_BEGIN_PREFIX) ? 'edited' : 'absent';
|
|
115
|
+
}
|
|
116
|
+
const [, stamp, found = ''] = match;
|
|
117
|
+
if (stamp === undefined) {
|
|
118
|
+
if (normalizeStanza(found) === normalizeStanza(body))
|
|
119
|
+
return 'pristine';
|
|
120
|
+
return LEGACY_STANZA_PREAMBLES.has(stanzaStamp(stanzaPreamble(found))) ? 'pristine' : 'unstamped';
|
|
121
|
+
}
|
|
122
|
+
return stamp === stanzaStamp(found) ? 'pristine' : 'edited';
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The `struct` slot's section: where the boundary rules live, not what they say.
|
|
126
|
+
*
|
|
127
|
+
* checkride ships opinionated ast-grep defaults (`no-class.yml` and friends),
|
|
128
|
+
* but it does not own the repo's architecture, and the stanza is the one place
|
|
129
|
+
* a repo cannot talk back — editing it blocks the next refresh. So this points
|
|
130
|
+
* at the rule files and stops. A repo that allows classes, default exports, or
|
|
131
|
+
* bundler-style extensionless imports deletes those rules and this stays true.
|
|
132
|
+
*/
|
|
133
|
+
function structSection() {
|
|
134
|
+
return [
|
|
135
|
+
'### Module boundaries',
|
|
136
|
+
'',
|
|
137
|
+
'The `struct` check runs whatever ast-grep rules `sgconfig.yml` points at',
|
|
138
|
+
"(`rules/` by default). Those files are this repo's boundary convention —",
|
|
139
|
+
'read them rather than assuming one.',
|
|
140
|
+
'',
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The `prose` slot's voice section, present only when the repo names an
|
|
145
|
+
* `exemplars` directory in its config. The instruction runs one way — read and
|
|
146
|
+
* imitate, never edit — because the exemplars are the repo's hand-written voice
|
|
147
|
+
* reference, the fresh human signal that keeps generated prose from converging
|
|
148
|
+
* on the model's own register. A generated "improvement" to one would replace
|
|
149
|
+
* the original with a copy, which is the drift the directory exists to prevent.
|
|
150
|
+
*/
|
|
151
|
+
function proseSection(exemplars) {
|
|
152
|
+
return [
|
|
153
|
+
'### Prose voice',
|
|
154
|
+
'',
|
|
155
|
+
'Before writing or editing prose (markdown, doc comments), read the exemplars in',
|
|
156
|
+
`\`${exemplars}\` and imitate their voice — sentence rhythm, word choice, register.`,
|
|
157
|
+
'They are hand-written and human-owned: never edit, rewrite, or add to them, and',
|
|
158
|
+
'never generate new ones. When your draft and an exemplar disagree about how a',
|
|
159
|
+
'sentence should sound, the exemplar wins.',
|
|
160
|
+
'',
|
|
161
|
+
];
|
|
43
162
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
163
|
+
/**
|
|
164
|
+
* The agent-facing contract block, parameterized by the active checks and the
|
|
165
|
+
* repo's package manager.
|
|
166
|
+
*
|
|
167
|
+
* `pm` is not cosmetic. The stanza is a list of commands an agent is told to
|
|
168
|
+
* run, and `pnpm check` in an npm repo is an instruction that cannot succeed —
|
|
169
|
+
* the agent either invents a substitute or reports the pipeline as broken.
|
|
170
|
+
* Everything here routes through {@link runScript}/{@link execCommand} for that
|
|
171
|
+
* reason; nothing spells a launcher literally.
|
|
172
|
+
*
|
|
173
|
+
* The gate profile is deliberately *not* a parameter. A repo can add `gate` to
|
|
174
|
+
* checkride.config.json without re-running `agent-setup`, so a stanza naming a
|
|
175
|
+
* specific profile would go stale in exactly the direction that costs something
|
|
176
|
+
* — telling an agent the hook covered everything when it no longer does. The
|
|
177
|
+
* paragraph points at the gate's verdict instead, which is generated per run and
|
|
178
|
+
* states the narrowing itself (see `gate.ts`'s `profileClause`).
|
|
179
|
+
*/
|
|
180
|
+
export function buildStanza(activeSlots, pm = 'pnpm', proseExemplars = null) {
|
|
181
|
+
const check = runScript(pm, 'check');
|
|
182
|
+
const triage = execCommand(pm, ['checkride', 'triage']);
|
|
46
183
|
return [
|
|
47
184
|
'## Checkride: the definition of done',
|
|
48
185
|
'',
|
|
49
|
-
|
|
186
|
+
`\`${check}\` is the single source of truth for "done". Exit 0 means the work is`,
|
|
50
187
|
'complete; any other exit code means it is not. Never claim a task is finished while',
|
|
51
|
-
|
|
188
|
+
`\`${check}\` is red.`,
|
|
52
189
|
'',
|
|
53
190
|
'When it fails:',
|
|
54
191
|
'',
|
|
@@ -56,13 +193,19 @@ export function buildStanza(activeSlots) {
|
|
|
56
193
|
"2. Read that check's raw output (`.check/<slot>.json` or `.check/<slot>.stdout.txt`).",
|
|
57
194
|
'3. Fix the root cause, then re-run.',
|
|
58
195
|
'',
|
|
59
|
-
|
|
60
|
-
'`
|
|
196
|
+
`\`${triage}\` runs this procedure in full and reads \`.check/\` for you`,
|
|
197
|
+
'(`/checkride:check` and `/checkride-check` are the same thing as a skill).',
|
|
61
198
|
'',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
'
|
|
65
|
-
'
|
|
199
|
+
`Tight feedback loops: \`${check} --bail\`, \`${check} --only types,lint\`, and`,
|
|
200
|
+
`\`${check} --changed\`.`,
|
|
201
|
+
'',
|
|
202
|
+
'If a stop-gate hook is configured (`.claude/settings.json` or `.cursor/hooks.json`),',
|
|
203
|
+
'it runs the check when a turn ends — so while iterating, prefer the narrow commands',
|
|
204
|
+
"above rather than running the full check yourself every loop. Read the gate's verdict",
|
|
205
|
+
'rather than assuming it covered everything: a repo can narrow the gate with `gate` in',
|
|
206
|
+
'`checkride.config.json`, and a narrowed one prints `NOT the full check` in every',
|
|
207
|
+
`verdict. That green is not the "done" defined above; run \`${check}\` in full before you`,
|
|
208
|
+
'claim the work is finished.',
|
|
66
209
|
'',
|
|
67
210
|
'### Baseline',
|
|
68
211
|
'',
|
|
@@ -72,27 +215,29 @@ export function buildStanza(activeSlots) {
|
|
|
72
215
|
'the ratchet, so the baseline only ever shrinks. Never add to the baseline to make a',
|
|
73
216
|
'check pass; fix the finding.',
|
|
74
217
|
'',
|
|
75
|
-
'
|
|
76
|
-
|
|
77
|
-
'A module is a unit of encapsulation. A single file is a module; promote it to a',
|
|
78
|
-
'folder with a barrel `index.ts` when it grows internals worth hiding. A folder',
|
|
79
|
-
"module's `index.ts` is its only public surface — re-exports only, no logic. Import",
|
|
80
|
-
"siblings through `'../<sibling>/index.js'`, never their internals.",
|
|
81
|
-
'',
|
|
82
|
-
'Named exports only; no classes; `.js` extensions on relative imports.',
|
|
83
|
-
'',
|
|
218
|
+
...(activeSlots.includes('struct') ? structSection() : []),
|
|
219
|
+
...(proseExemplars !== null ? proseSection(proseExemplars) : []),
|
|
84
220
|
`Active checks in this repo: ${activeSlots.join(', ')}.`,
|
|
85
221
|
].join('\n');
|
|
86
222
|
}
|
|
87
223
|
/**
|
|
88
224
|
* Insert or refresh the checkride stanza in an AGENTS.md body. Idempotent:
|
|
89
|
-
* applying twice yields identical output.
|
|
225
|
+
* applying twice yields identical output. It rewrites the marked region without
|
|
226
|
+
* looking at what was there — callers gate on {@link inspectStanza} first.
|
|
90
227
|
*/
|
|
91
228
|
export function applyStanza(content, body) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
229
|
+
// A replacer function, not the string: a `$&` in the stanza would otherwise be
|
|
230
|
+
// read as a backreference.
|
|
231
|
+
const block = `${STANZA_BEGIN_PREFIX} hash=${stanzaStamp(body)} -->\n\n${body}\n\n${STANZA_END}`;
|
|
232
|
+
if (STANZA_RE.test(content)) {
|
|
233
|
+
return content.replace(STANZA_RE, () => block);
|
|
234
|
+
}
|
|
235
|
+
if (STANZA_BEGIN_RE.test(content)) {
|
|
236
|
+
// A begin marker with no end: replace the marker in place rather than
|
|
237
|
+
// appending a second block, which would leave two begins and make the next
|
|
238
|
+
// refresh swallow everything between them. What followed the orphaned marker
|
|
239
|
+
// stays where it is — now outside the markers, so nothing rewrites it again.
|
|
240
|
+
return content.replace(STANZA_BEGIN_RE, () => block);
|
|
96
241
|
}
|
|
97
242
|
if (content.trim().length === 0) {
|
|
98
243
|
return `${block}\n`;
|
|
@@ -312,18 +457,57 @@ async function writePackage(w, dir, pkgName, value) {
|
|
|
312
457
|
await put(w, join(dir, 'src', 'index.test.ts'), smokeTest(value));
|
|
313
458
|
}
|
|
314
459
|
/**
|
|
315
|
-
* Write/refresh the
|
|
316
|
-
*
|
|
317
|
-
*
|
|
460
|
+
* Write/refresh the agent hooks unless opted out (`hook === false`), remove any
|
|
461
|
+
* named for removal, for the selected harnesses (default: detected) and the
|
|
462
|
+
* repo's detected package manager. Records each file when it changed, else a
|
|
463
|
+
* no-op note in `skipped` when one is provided.
|
|
464
|
+
*
|
|
465
|
+
* `--no-hook` and `--remove-hook` compose rather than conflict: the first says
|
|
466
|
+
* "write nothing", the second "take this one out", and together they mean
|
|
467
|
+
* "remove the gate and leave the rest of the setup untouched" — which is the
|
|
468
|
+
* whole point of being able to drop a hook after the fact.
|
|
469
|
+
*/
|
|
470
|
+
async function writeHook(w, options, skipped) {
|
|
471
|
+
const remove = options.removeHooks ?? [];
|
|
472
|
+
if (options.hook === false && remove.length === 0)
|
|
473
|
+
return;
|
|
474
|
+
const hooks = options.hook === false ? [] : options.hooks;
|
|
475
|
+
const result = await writeHooks(w.cwd, {
|
|
476
|
+
dryRun: w.dryRun,
|
|
477
|
+
...(hooks ? { hooks } : {}),
|
|
478
|
+
...(remove.length > 0 ? { remove } : {}),
|
|
479
|
+
...(options.harnesses ? { harnesses: options.harnesses } : {}),
|
|
480
|
+
});
|
|
481
|
+
record(w, result.files, skipped);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Write the bundled skills for every selected harness that cannot install them
|
|
485
|
+
* as a plugin. Only Cursor needs this today; Claude Code gets the same two
|
|
486
|
+
* skills from `.claude-plugin/`, installed once for every repo.
|
|
487
|
+
*
|
|
488
|
+
* Unlike the hooks this is not covered by `--no-hook`: that flag turns off the
|
|
489
|
+
* mechanical gate, and a skill gates nothing.
|
|
318
490
|
*/
|
|
319
|
-
async function
|
|
320
|
-
|
|
491
|
+
async function writeSkills(w, options, skipped) {
|
|
492
|
+
const harnesses = options.harnesses ?? detectHarnesses(w.cwd);
|
|
493
|
+
if (!harnesses.includes('cursor'))
|
|
321
494
|
return;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
495
|
+
record(w, await writeCursorSkills(w.cwd, { dryRun: w.dryRun }), skipped);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Fold a writer's file list into the run's written/removed/skipped report. A
|
|
499
|
+
* deleted file is a change, but not a *write* — counting it as one would have
|
|
500
|
+
* `--remove-hook gate` report that it wrote a file it actually took away.
|
|
501
|
+
*/
|
|
502
|
+
function record(w, files, skipped) {
|
|
503
|
+
for (const f of files) {
|
|
504
|
+
if (!f.changed)
|
|
505
|
+
skipped?.push(`${f.path} (${f.removed === true ? 'absent' : 'unchanged'})`);
|
|
506
|
+
else if (f.removed === true)
|
|
507
|
+
w.removed.push(f.path);
|
|
508
|
+
else
|
|
509
|
+
w.written.push(f.path);
|
|
510
|
+
}
|
|
327
511
|
}
|
|
328
512
|
// ----------------------------------------------------------------------------
|
|
329
513
|
// New-project mode
|
|
@@ -361,7 +545,7 @@ async function writeNewScaffold(w, s) {
|
|
|
361
545
|
* into `.claude/settings.json` idempotently rather than overwriting.
|
|
362
546
|
*/
|
|
363
547
|
async function planCollisions(s, cwd) {
|
|
364
|
-
const plan = { cwd, dryRun: true, written: [] };
|
|
548
|
+
const plan = { cwd, dryRun: true, written: [], removed: [] };
|
|
365
549
|
await writeNewScaffold(plan, s);
|
|
366
550
|
return plan.written.filter((rel) => existsSync(join(cwd, rel)));
|
|
367
551
|
}
|
|
@@ -388,7 +572,7 @@ function resolveNewScaffold(options, cwd) {
|
|
|
388
572
|
};
|
|
389
573
|
}
|
|
390
574
|
/**
|
|
391
|
-
* Overwrite protection
|
|
575
|
+
* Overwrite protection: unless `force`, refuse rather than clobber any file
|
|
392
576
|
* the scaffold would write. Planning on a dry writer first means a collision
|
|
393
577
|
* bails before a single real byte is written — the refusal writes nothing.
|
|
394
578
|
*/
|
|
@@ -412,14 +596,24 @@ function reportNew(stdout, shape, w, cwd) {
|
|
|
412
596
|
async function initNew(options, cwd) {
|
|
413
597
|
const scaffold = resolveNewScaffold(options, cwd);
|
|
414
598
|
await assertNoCollisions(scaffold, cwd, options.force ?? false);
|
|
415
|
-
const w = { cwd, dryRun: options.dryRun ?? false, written: [] };
|
|
599
|
+
const w = { cwd, dryRun: options.dryRun ?? false, written: [], removed: [] };
|
|
416
600
|
await writeNewScaffold(w, scaffold);
|
|
417
|
-
// Claude Code Stop hook (opt-out
|
|
601
|
+
// Claude Code Stop hook (opt-out). A fresh project has no PM lockfile
|
|
418
602
|
// yet, so it resolves to the `pnpm` default — matching the generated scripts.
|
|
419
|
-
await writeHook(w, options
|
|
603
|
+
await writeHook(w, options);
|
|
604
|
+
await writeSkills(w, options);
|
|
420
605
|
if (options.stdout)
|
|
421
606
|
reportNew(options.stdout, scaffold.shape, w, cwd);
|
|
422
|
-
return {
|
|
607
|
+
return {
|
|
608
|
+
mode: 'new',
|
|
609
|
+
shape: scaffold.shape,
|
|
610
|
+
written: w.written,
|
|
611
|
+
removed: w.removed,
|
|
612
|
+
skipped: [],
|
|
613
|
+
disabled: [],
|
|
614
|
+
grandfathered: [],
|
|
615
|
+
exitCode: 0,
|
|
616
|
+
};
|
|
423
617
|
}
|
|
424
618
|
// ----------------------------------------------------------------------------
|
|
425
619
|
// Existing-project mode
|
|
@@ -433,9 +627,9 @@ async function readIfExists(path) {
|
|
|
433
627
|
}
|
|
434
628
|
}
|
|
435
629
|
// ----------------------------------------------------------------------------
|
|
436
|
-
// Publish-ready bundle
|
|
630
|
+
// Publish-ready bundle: the library path scaffolds the opt-in artifact
|
|
437
631
|
// checks into `checkride.config.json` so a buildable library orders itself out
|
|
438
|
-
// of the box (build → pack/smoke/attw/publint/snippets
|
|
632
|
+
// of the box (build → pack/smoke/attw/publint/snippets).
|
|
439
633
|
// ----------------------------------------------------------------------------
|
|
440
634
|
/**
|
|
441
635
|
* The publish-ready bundle, in wave order. Each slot's value is the adapter
|
|
@@ -446,7 +640,7 @@ async function readIfExists(path) {
|
|
|
446
640
|
const PUBLISH_BUNDLE_SLOTS = ['build', 'publint', 'attw', 'pack', 'smoke', 'snippets'];
|
|
447
641
|
/** `--add` values that expand to the whole bundle. */
|
|
448
642
|
const PUBLISH_BUNDLE_ALIASES = new Set(['publish', 'bundle']);
|
|
449
|
-
/** Printed (and recorded in `skipped`) when snippets is requested but no tagged fence exists
|
|
643
|
+
/** Printed (and recorded in `skipped`) when snippets is requested but no tagged fence exists. */
|
|
450
644
|
const SNIPPETS_POINTER = 'snippets-dist: no tagged fences found — add a `<!-- snippet: check -->` marker above a ' +
|
|
451
645
|
'ts/typescript fence in README.md or docs/*.md to enable the doc-snippet typecheck';
|
|
452
646
|
/**
|
|
@@ -465,9 +659,9 @@ function readInitManifest(cwd) {
|
|
|
465
659
|
}
|
|
466
660
|
/**
|
|
467
661
|
* True when README.md or a non-recursive docs/*.md carries at least one tagged
|
|
468
|
-
* snippet fence — the signal that gates scaffolding `snippets-dist
|
|
469
|
-
* Reuses
|
|
470
|
-
* the slot itself checks.
|
|
662
|
+
* snippet fence — the signal that gates scaffolding `snippets-dist`.
|
|
663
|
+
* Reuses the snippets module's pure discovery/plan primitives so the detection
|
|
664
|
+
* matches what the slot itself checks.
|
|
471
665
|
*/
|
|
472
666
|
async function hasTaggedSnippets(cwd) {
|
|
473
667
|
let docsEntries = [];
|
|
@@ -488,7 +682,7 @@ async function hasTaggedSnippets(cwd) {
|
|
|
488
682
|
}
|
|
489
683
|
/**
|
|
490
684
|
* Fold the requested publish slots into config entries, gating `build` on a
|
|
491
|
-
* build script and `snippets` (→ `snippets-dist`) on a tagged fence
|
|
685
|
+
* build script and `snippets` (→ `snippets-dist`) on a tagged fence — the
|
|
492
686
|
* two slots that would otherwise stand down as a skip (`build`) or hard-error on
|
|
493
687
|
* zero snippets (`snippets`).
|
|
494
688
|
*/
|
|
@@ -540,16 +734,80 @@ function splitAdd(add, manifest) {
|
|
|
540
734
|
}
|
|
541
735
|
return { publishRequested, fileAdds };
|
|
542
736
|
}
|
|
737
|
+
/**
|
|
738
|
+
* The stanza's active-check list: the selection a default `checkride` run
|
|
739
|
+
* makes, resolved config-aware from disk. `inventory()` is the wrong input
|
|
740
|
+
* here — it has detection semantics and never reads checkride.config.json, so
|
|
741
|
+
* it under-reports the gate (opt-in slots the config opts in, custom checks)
|
|
742
|
+
* to the exact audience the stanza exists to inform. Mirrors `doctor`'s
|
|
743
|
+
* `defaultActive` derivation.
|
|
744
|
+
*/
|
|
745
|
+
function activeCheckSlots(cwd, slots, adapters) {
|
|
746
|
+
const resolved = resolveChecks({ slots, adapters, config: loadConfig(cwd), cwd });
|
|
747
|
+
return selectChecks(resolved, {})
|
|
748
|
+
.filter((r) => r.adapter !== null)
|
|
749
|
+
.map((r) => r.slot);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* The voice-exemplars directory the repo's config names on its `prose` entry,
|
|
753
|
+
* or `null`. Read fresh at every stanza build rather than threaded from a
|
|
754
|
+
* caller, so the stanza and the config it describes cannot drift apart within
|
|
755
|
+
* one run — `initExisting` writes config between its stanza guard and its
|
|
756
|
+
* stanza write.
|
|
757
|
+
*/
|
|
758
|
+
function proseExemplarsDir(cwd) {
|
|
759
|
+
const entry = loadConfig(cwd)?.checks?.['prose'];
|
|
760
|
+
return entry && typeof entry === 'object' && 'use' in entry && typeof entry.exemplars === 'string'
|
|
761
|
+
? entry.exemplars
|
|
762
|
+
: null;
|
|
763
|
+
}
|
|
764
|
+
const STANZA_REFUSALS = {
|
|
765
|
+
edited: 'it has been edited since checkride wrote it',
|
|
766
|
+
unstamped: 'it predates the stanza stamp and matches no wording checkride has released, so it has been changed since it was written',
|
|
767
|
+
};
|
|
768
|
+
/**
|
|
769
|
+
* Refuse to refresh a stanza that is not checkride's own output, so a repo's
|
|
770
|
+
* edge-case additions survive the next `init`/`agent-setup` instead of being
|
|
771
|
+
* silently overwritten. `opts.force` overrides.
|
|
772
|
+
*
|
|
773
|
+
* Called before either entry point writes anything, so a refusal leaves the repo
|
|
774
|
+
* exactly as it found it (the same rule `assertNoCollisions` follows in new
|
|
775
|
+
* mode). In existing mode that puts it ahead of the config write, so `body` here
|
|
776
|
+
* reflects the config as it is on disk now rather than as this run will leave
|
|
777
|
+
* it — which no longer costs anything: a stamped stanza is checked against its
|
|
778
|
+
* own stamp, and an unstamped one against a registry of released wordings that
|
|
779
|
+
* ignores the active-checks line entirely. Neither reads `body`'s slot list.
|
|
780
|
+
*
|
|
781
|
+
* Hook management deliberately does not come through here — see the `hooks`
|
|
782
|
+
* command, which never touches AGENTS.md and so has nothing to refuse.
|
|
783
|
+
*/
|
|
784
|
+
async function assertStanzaUnedited(cwd, body, opts) {
|
|
785
|
+
if (opts.force === true)
|
|
786
|
+
return;
|
|
787
|
+
const existing = await readIfExists(join(cwd, 'AGENTS.md'));
|
|
788
|
+
if (existing === null)
|
|
789
|
+
return;
|
|
790
|
+
const state = inspectStanza(existing, body);
|
|
791
|
+
if (state === 'absent' || state === 'pristine')
|
|
792
|
+
return;
|
|
793
|
+
const remedy = state === 'edited'
|
|
794
|
+
? 'Move your additions outside the markers — checkride never rewrites what is outside them — or re-run with --force to discard them and refresh.'
|
|
795
|
+
: 'If you have not edited it, re-run with --force: the refreshed stanza carries a stamp, and later runs tell an edit from a refresh on their own.';
|
|
796
|
+
throw new Error(`refusing to overwrite the checkride stanza in AGENTS.md: ${STANZA_REFUSALS[state]}.\n ${remedy}`);
|
|
797
|
+
}
|
|
543
798
|
/**
|
|
544
799
|
* Write (or refresh) the AGENTS.md stanza for `slots`, idempotently: it writes
|
|
545
800
|
* only when the applied stanza differs from the current file, records the outcome
|
|
546
801
|
* on `w`/`skipped`, and honours dry-run. Shared by `initExisting` and
|
|
547
802
|
* `runAgentSetup` (both create-or-refresh the same stanza).
|
|
803
|
+
*
|
|
804
|
+
* It rewrites the marked region unconditionally; whether that is allowed is
|
|
805
|
+
* {@link assertStanzaUnedited}'s call, made before either caller writes anything.
|
|
548
806
|
*/
|
|
549
|
-
async function writeAgentsStanza(w, cwd, slots, skipped) {
|
|
807
|
+
async function writeAgentsStanza(w, cwd, slots, pm, skipped) {
|
|
550
808
|
const agentsPath = join(cwd, 'AGENTS.md');
|
|
551
809
|
const existing = await readIfExists(agentsPath);
|
|
552
|
-
const nextAgents = applyStanza(existing ?? '', buildStanza(slots));
|
|
810
|
+
const nextAgents = applyStanza(existing ?? '', buildStanza(slots, pm, proseExemplarsDir(cwd)));
|
|
553
811
|
if (nextAgents !== existing) {
|
|
554
812
|
if (!w.dryRun)
|
|
555
813
|
await writeFile(agentsPath, nextAgents);
|
|
@@ -566,12 +824,19 @@ const ADD_CONFIGS = {
|
|
|
566
824
|
types: [['shared/tsconfig.base.json', 'tsconfig.base.json'], ['flat/tsconfig.json', 'tsconfig.json']],
|
|
567
825
|
format: [['shared/prettierrc.json', '.prettierrc.json']],
|
|
568
826
|
lint: [['shared/oxlintrc.json', '.oxlintrc.json']],
|
|
827
|
+
// `struct` scaffolds the boundary rule and stops. New-mode `init` writes all
|
|
828
|
+
// four (`writeSharedStatic`), and that is a different bargain: there checkride
|
|
829
|
+
// is creating the package, so its house style has nobody to override. Here the
|
|
830
|
+
// repo already exists and has already decided. `no-class`, `no-default-export`
|
|
831
|
+
// and `require-js-extension` are style and module-system opinions — a repo
|
|
832
|
+
// with classes, default exports, or bundler resolution goes red on adoption
|
|
833
|
+
// for writing the code it always wrote, which reads as checkride being broken.
|
|
834
|
+
// `no-deep-sibling-import` is the one that makes `struct` the slot it is, and
|
|
835
|
+
// failing it means what it says. The other three stay one copy away, in
|
|
836
|
+
// `docs/deep-modules.md`.
|
|
569
837
|
struct: [
|
|
570
838
|
['shared/sgconfig.yml', 'sgconfig.yml'],
|
|
571
|
-
['shared/rules/no-class.yml', 'rules/no-class.yml'],
|
|
572
|
-
['shared/rules/no-default-export.yml', 'rules/no-default-export.yml'],
|
|
573
839
|
['shared/rules/no-deep-sibling-import.yml', 'rules/no-deep-sibling-import.yml'],
|
|
574
|
-
['shared/rules/require-js-extension.yml', 'rules/require-js-extension.yml'],
|
|
575
840
|
],
|
|
576
841
|
dead: [['flat/fallow.toml', 'fallow.toml']],
|
|
577
842
|
// dupes/health are the same fallow tool + config as `dead`, just other slots.
|
|
@@ -580,6 +845,23 @@ const ADD_CONFIGS = {
|
|
|
580
845
|
test: [['shared/vitest.config.ts.template', 'vitest.config.ts']],
|
|
581
846
|
docs: [['shared/markdownlint-cli2.jsonc', '.markdownlint-cli2.jsonc']],
|
|
582
847
|
spell: [['shared/cspell.json', 'cspell.json']],
|
|
848
|
+
// `prose` is the one slot whose config alone scaffolds nothing usable: vale's
|
|
849
|
+
// .vale.ini is a pointer at a StylesPath, and an empty StylesPath lints
|
|
850
|
+
// nothing. The rules are the deliverable, so they come with it. They are the
|
|
851
|
+
// repo's from the moment they land — plain YAML, no `vale sync`, no upstream
|
|
852
|
+
// to track — which is also why `struct`'s restraint does not apply here:
|
|
853
|
+
// ast-grep's extra rules assert what the repo's code already is, while these
|
|
854
|
+
// assert nothing about code at all, and a prose rule the author disagrees
|
|
855
|
+
// with is deleted in one line.
|
|
856
|
+
prose: [
|
|
857
|
+
['shared/vale.ini', '.vale.ini'],
|
|
858
|
+
['shared/styles/Repo/Drift.yml', '.vale/styles/Repo/Drift.yml'],
|
|
859
|
+
['shared/styles/Repo/Latin.yml', '.vale/styles/Repo/Latin.yml'],
|
|
860
|
+
['shared/styles/Repo/LyHyphen.yml', '.vale/styles/Repo/LyHyphen.yml'],
|
|
861
|
+
['shared/styles/Repo/Minted.yml', '.vale/styles/Repo/Minted.yml'],
|
|
862
|
+
['shared/styles/Repo/ThereIs.yml', '.vale/styles/Repo/ThereIs.yml'],
|
|
863
|
+
['shared/styles/Repo/Weasel.yml', '.vale/styles/Repo/Weasel.yml'],
|
|
864
|
+
],
|
|
583
865
|
};
|
|
584
866
|
/** `--add <slots>`: scaffold blessed-default configs for the named empty slots. */
|
|
585
867
|
async function addConfigs(w, add, skipped) {
|
|
@@ -625,7 +907,7 @@ async function addCheckAlias(w, skipped) {
|
|
|
625
907
|
w.written.push('package.json (added check script)');
|
|
626
908
|
}
|
|
627
909
|
/**
|
|
628
|
-
* Classify each failing adopted slot. `--baseline`
|
|
910
|
+
* Classify each failing adopted slot. `--baseline` grandfathers a
|
|
629
911
|
* failing *fingerprintable* slot (kept enabled; the baseline masks its debt and
|
|
630
912
|
* the ratchet carries the cleanup); a failing slot with no extractor can't be
|
|
631
913
|
* grandfathered, so it still falls back to a `false` disable. Without
|
|
@@ -652,7 +934,7 @@ async function captureBaselineIfNeeded(options, cwd, grandfathered, dryRun) {
|
|
|
652
934
|
}
|
|
653
935
|
/**
|
|
654
936
|
* Write checkride.config.json for the adopted tools (disabled failures as
|
|
655
|
-
* `false`) plus the publish-ready bundle
|
|
937
|
+
* `false`) plus the publish-ready bundle; never clobber an existing one.
|
|
656
938
|
*/
|
|
657
939
|
async function writeExistingConfig(w, cwd, adopted, disabledSet, bundleChecks, skipped) {
|
|
658
940
|
if (existsSync(join(cwd, 'checkride.config.json'))) {
|
|
@@ -667,7 +949,7 @@ async function writeExistingConfig(w, cwd, adopted, disabledSet, bundleChecks, s
|
|
|
667
949
|
checks[i.slot] = i.adapter;
|
|
668
950
|
}
|
|
669
951
|
// The opt-in publish bundle follows the adopted default slots; explicit
|
|
670
|
-
// entries opt these slots into the run (
|
|
952
|
+
// entries opt these slots into the run (the library orders itself).
|
|
671
953
|
for (const [slot, adapter] of Object.entries(bundleChecks))
|
|
672
954
|
checks[slot] = adapter;
|
|
673
955
|
const config = { $schema: configSchemaUrl(productVersion()), checks };
|
|
@@ -708,7 +990,7 @@ function reportExisting(stdout, adopted, w, grandfathered, disabled) {
|
|
|
708
990
|
}
|
|
709
991
|
}
|
|
710
992
|
/**
|
|
711
|
-
* Plan the publish-ready bundle
|
|
993
|
+
* Plan the publish-ready bundle for existing-repo init. Only meaningful
|
|
712
994
|
* when a config will actually be written: `writeExistingConfig` never clobbers an
|
|
713
995
|
* existing `checkride.config.json`, so an existing config means the bundle has
|
|
714
996
|
* nowhere to land. The bundle is scaffolded *enabled* — not probed/disabled like
|
|
@@ -733,8 +1015,12 @@ async function initExisting(options, cwd) {
|
|
|
733
1015
|
const adapters = options.adapters ?? ADAPTERS;
|
|
734
1016
|
const slots = options.slots ?? SLOTS;
|
|
735
1017
|
const manifest = readInitManifest(cwd);
|
|
736
|
-
const w = { cwd, dryRun: options.dryRun ?? false, written: [] };
|
|
1018
|
+
const w = { cwd, dryRun: options.dryRun ?? false, written: [], removed: [] };
|
|
737
1019
|
const skipped = [];
|
|
1020
|
+
const pm = detectPackageManager({ cwd });
|
|
1021
|
+
// Before the first write: a stanza this repo has customized stops the run
|
|
1022
|
+
// rather than being clobbered by the refresh below.
|
|
1023
|
+
await assertStanzaUnedited(cwd, buildStanza(activeCheckSlots(cwd, slots, adapters), pm, proseExemplarsDir(cwd)), options);
|
|
738
1024
|
// Route --add: publish slots (and the library path) opt into the bundle; the
|
|
739
1025
|
// rest scaffold blessed configs before inventory, so they're detected this run.
|
|
740
1026
|
const { publishRequested, fileAdds } = splitAdd(options.add ?? [], manifest);
|
|
@@ -746,24 +1032,35 @@ async function initExisting(options, cwd) {
|
|
|
746
1032
|
const failing = new Set(await probe(adopted.map((i) => i.slot), cwd));
|
|
747
1033
|
const { grandfathered, disabled } = resolveAdoptionPlan(adopted, failing, options.baseline ?? false);
|
|
748
1034
|
await captureBaselineIfNeeded(options, cwd, grandfathered, w.dryRun);
|
|
749
|
-
// Publish-ready bundle
|
|
1035
|
+
// Publish-ready bundle: planned only when a config will be written.
|
|
750
1036
|
const bundle = await planInitBundle(publishRequested, manifest, cwd);
|
|
751
1037
|
if (bundle.snippetsPointer)
|
|
752
1038
|
skipped.push(bundle.snippetsPointer);
|
|
753
1039
|
await writeExistingConfig(w, cwd, adopted, new Set(disabled), bundle.checks, skipped);
|
|
754
1040
|
await ensureGitignore(w, cwd, skipped);
|
|
755
|
-
// package.json: add the `check: checkride` alias
|
|
1041
|
+
// package.json: add the `check: checkride` alias if missing.
|
|
756
1042
|
await addCheckAlias(w, skipped);
|
|
757
|
-
// AGENTS.md stanza (create or refresh, idempotent)
|
|
758
|
-
|
|
1043
|
+
// AGENTS.md stanza (create or refresh, idempotent), derived from the config
|
|
1044
|
+
// written above so it reports the gate as configured, not as detected.
|
|
1045
|
+
await writeAgentsStanza(w, cwd, activeCheckSlots(cwd, slots, adapters), pm, skipped);
|
|
759
1046
|
await writeClaudePointer(w, cwd, skipped);
|
|
760
|
-
// Claude Code Stop hook (opt-out
|
|
761
|
-
await writeHook(w, options
|
|
1047
|
+
// Claude Code Stop hook (opt-out), using the repo's detected PM.
|
|
1048
|
+
await writeHook(w, options, skipped);
|
|
1049
|
+
await writeSkills(w, options, skipped);
|
|
762
1050
|
if (options.stdout) {
|
|
763
1051
|
reportExisting(options.stdout, adopted, w, grandfathered, disabled);
|
|
764
1052
|
reportInitBundle(options.stdout, bundle);
|
|
765
1053
|
}
|
|
766
|
-
return {
|
|
1054
|
+
return {
|
|
1055
|
+
mode: 'existing',
|
|
1056
|
+
shape: null,
|
|
1057
|
+
written: w.written,
|
|
1058
|
+
removed: w.removed,
|
|
1059
|
+
skipped,
|
|
1060
|
+
disabled,
|
|
1061
|
+
grandfathered,
|
|
1062
|
+
exitCode: 0,
|
|
1063
|
+
};
|
|
767
1064
|
}
|
|
768
1065
|
// ----------------------------------------------------------------------------
|
|
769
1066
|
// Entry
|
|
@@ -780,25 +1077,32 @@ export async function runInit(options) {
|
|
|
780
1077
|
/**
|
|
781
1078
|
* `checkride agent-setup` — wire the agent contract into an *existing* repo
|
|
782
1079
|
* without a full `init`: the `check` alias the hook resolves to, the AGENTS.md
|
|
783
|
-
* stanza (the human-readable contract),
|
|
784
|
-
* mechanical gate)
|
|
785
|
-
*
|
|
1080
|
+
* stanza (the human-readable contract), the stop-gate hooks for every detected
|
|
1081
|
+
* harness (the mechanical gate), and the skills a harness cannot get from the
|
|
1082
|
+
* bundled plugin. Every write is additive and idempotent — a second run is a
|
|
1083
|
+
* no-op — and the hooks are opt-out (`hook: false`).
|
|
786
1084
|
*/
|
|
787
1085
|
export async function runAgentSetup(options) {
|
|
788
1086
|
const cwd = options.cwd ?? process.cwd();
|
|
789
1087
|
const slots = options.slots ?? SLOTS;
|
|
790
1088
|
const adapters = options.adapters ?? ADAPTERS;
|
|
791
|
-
const w = { cwd, dryRun: options.dryRun ?? false, written: [] };
|
|
1089
|
+
const w = { cwd, dryRun: options.dryRun ?? false, written: [], removed: [] };
|
|
792
1090
|
const skipped = [];
|
|
1091
|
+
const stanzaSlots = activeCheckSlots(cwd, slots, adapters);
|
|
1092
|
+
const pm = detectPackageManager({ cwd });
|
|
1093
|
+
// Before the first write: a stanza this repo has customized stops the run
|
|
1094
|
+
// rather than being clobbered by the refresh below.
|
|
1095
|
+
await assertStanzaUnedited(cwd, buildStanza(stanzaSlots, pm, proseExemplarsDir(cwd)), options);
|
|
793
1096
|
// The `check` alias the hook's `<pm> run check` resolves to (never clobbers).
|
|
794
1097
|
await addCheckAlias(w, skipped);
|
|
795
|
-
// AGENTS.md stanza for the
|
|
796
|
-
|
|
797
|
-
await
|
|
798
|
-
await
|
|
1098
|
+
// AGENTS.md stanza for the checks the default run selects (create or refresh).
|
|
1099
|
+
await writeAgentsStanza(w, cwd, stanzaSlots, pm, skipped);
|
|
1100
|
+
await writeHook(w, options, skipped);
|
|
1101
|
+
await writeSkills(w, options, skipped);
|
|
799
1102
|
if (options.stdout) {
|
|
800
|
-
|
|
1103
|
+
const removed = w.removed.length > 0 ? `, removed ${w.removed.length}` : '';
|
|
1104
|
+
options.stdout.write(`checkride agent-setup: wrote ${w.written.length} file(s)${removed}${w.dryRun ? ' [dry run]' : ''}.\n`);
|
|
801
1105
|
}
|
|
802
|
-
return { written: w.written, skipped, exitCode: 0 };
|
|
1106
|
+
return { written: w.written, removed: w.removed, skipped, exitCode: 0 };
|
|
803
1107
|
}
|
|
804
1108
|
//# sourceMappingURL=init.js.map
|