plumbbob 0.9.3 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +92 -89
- package/dist/cli-core.js +25 -8
- package/dist/cli.js +1 -1
- package/dist/lib/agents.js +26 -26
- package/dist/lib/buildlog.js +11 -11
- package/dist/lib/buildlogsync.js +8 -8
- package/dist/lib/check.js +55 -22
- package/dist/lib/commitbody.js +72 -0
- package/dist/lib/commitmsg.js +7 -7
- package/dist/lib/git.js +44 -15
- package/dist/lib/intent.js +21 -20
- package/dist/lib/latch.js +19 -19
- package/dist/lib/orient.js +36 -17
- package/dist/lib/reentry.js +70 -0
- package/dist/lib/settings.js +16 -16
- package/dist/lib/sidecar.js +54 -54
- package/dist/lib/templates.js +1 -1
- package/dist/verbs/agent.js +27 -27
- package/dist/verbs/build.js +9 -9
- package/dist/verbs/check.js +6 -6
- package/dist/verbs/checkpoint.js +48 -47
- package/dist/verbs/doctor.js +31 -30
- package/dist/verbs/finish.js +28 -41
- package/dist/verbs/handoff.js +9 -8
- package/dist/verbs/init.js +9 -9
- package/dist/verbs/park.js +3 -3
- package/dist/verbs/recover.js +297 -0
- package/dist/verbs/revert.js +13 -13
- package/dist/verbs/spike.js +25 -19
- package/dist/verbs/start.js +49 -19
- package/dist/verbs/status.js +8 -8
- package/dist/verbs/turn.js +16 -16
- package/dist/verbs/use.js +4 -4
- package/hooks/post-edit.sh +4 -4
- package/hooks/pre-bash-commit.sh +3 -3
- package/node_modules/checkride/.claude-plugin/plugin.json +11 -0
- package/node_modules/checkride/CHANGELOG.md +1404 -0
- package/node_modules/checkride/LICENSE +201 -21
- package/node_modules/checkride/README.md +197 -38
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts +15 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts.map +1 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js +17 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js.map +1 -0
- package/node_modules/checkride/dist/adapters.d.ts +30 -10
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +62 -23
- package/node_modules/checkride/dist/adapters.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/claude.d.ts +88 -0
- package/node_modules/checkride/dist/agent-setup/claude.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/claude.js +305 -0
- package/node_modules/checkride/dist/agent-setup/claude.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/command.d.ts +43 -0
- package/node_modules/checkride/dist/agent-setup/command.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/command.js +77 -0
- package/node_modules/checkride/dist/agent-setup/command.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts +69 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js +247 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/files.d.ts +50 -0
- package/node_modules/checkride/dist/agent-setup/files.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/files.js +80 -0
- package/node_modules/checkride/dist/agent-setup/files.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/harness.d.ts +101 -0
- package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/harness.js +93 -0
- package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/hook.d.ts +45 -52
- package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/hook.js +64 -95
- package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.d.ts +10 -4
- package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.js +9 -4
- package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts +81 -0
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js +604 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/skills.d.ts +31 -0
- package/node_modules/checkride/dist/agent-setup/skills.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/skills.js +56 -0
- package/node_modules/checkride/dist/agent-setup/skills.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/configured.d.ts +30 -0
- package/node_modules/checkride/dist/artifacts/configured.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/configured.js +45 -0
- package/node_modules/checkride/dist/artifacts/configured.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/format.d.ts +33 -0
- package/node_modules/checkride/dist/artifacts/format.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/format.js +56 -0
- package/node_modules/checkride/dist/artifacts/format.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/freshness.d.ts +32 -0
- package/node_modules/checkride/dist/artifacts/freshness.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/freshness.js +37 -0
- package/node_modules/checkride/dist/artifacts/freshness.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/index.d.ts +22 -0
- package/node_modules/checkride/dist/artifacts/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/index.js +19 -0
- package/node_modules/checkride/dist/artifacts/index.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/json.d.ts +31 -0
- package/node_modules/checkride/dist/artifacts/json.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/json.js +47 -0
- package/node_modules/checkride/dist/artifacts/json.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/raw.d.ts +60 -0
- package/node_modules/checkride/dist/artifacts/raw.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/raw.js +91 -0
- package/node_modules/checkride/dist/artifacts/raw.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/summary.d.ts +69 -0
- package/node_modules/checkride/dist/artifacts/summary.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/summary.js +108 -0
- package/node_modules/checkride/dist/artifacts/summary.js.map +1 -0
- package/node_modules/checkride/dist/atomic.d.ts +16 -1
- package/node_modules/checkride/dist/atomic.d.ts.map +1 -1
- package/node_modules/checkride/dist/atomic.js +25 -3
- package/node_modules/checkride/dist/atomic.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.d.ts +9 -5
- package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.js +163 -60
- package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +20 -11
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.js +73 -23
- package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
- package/node_modules/checkride/dist/baseline/history.d.ts +98 -0
- package/node_modules/checkride/dist/baseline/history.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/history.js +232 -0
- package/node_modules/checkride/dist/baseline/history.js.map +1 -0
- package/node_modules/checkride/dist/baseline/index.d.ts +7 -5
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/index.js +5 -4
- package/node_modules/checkride/dist/baseline/index.js.map +1 -1
- package/node_modules/checkride/dist/baseline/store.d.ts +41 -10
- package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/store.js +65 -24
- package/node_modules/checkride/dist/baseline/store.js.map +1 -1
- package/node_modules/checkride/dist/baseline-command.d.ts +5 -5
- package/node_modules/checkride/dist/baseline-command.js +7 -7
- package/node_modules/checkride/dist/baseline-command.js.map +1 -1
- package/node_modules/checkride/dist/cli.d.ts +21 -4
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +350 -36
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +114 -9
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +142 -23
- package/node_modules/checkride/dist/config.js.map +1 -1
- package/node_modules/checkride/dist/digest/digest.d.ts +5 -5
- package/node_modules/checkride/dist/digest/digest.js +6 -6
- package/node_modules/checkride/dist/digest/index.d.ts +2 -2
- package/node_modules/checkride/dist/digest/index.js +2 -2
- package/node_modules/checkride/dist/doctor.d.ts +27 -6
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +231 -27
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/gate.d.ts +180 -0
- package/node_modules/checkride/dist/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/gate.js +617 -0
- package/node_modules/checkride/dist/gate.js.map +1 -0
- package/node_modules/checkride/dist/init.d.ts +79 -12
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +385 -81
- package/node_modules/checkride/dist/init.js.map +1 -1
- package/node_modules/checkride/dist/json.d.ts +21 -0
- package/node_modules/checkride/dist/json.d.ts.map +1 -0
- package/node_modules/checkride/dist/json.js +21 -0
- package/node_modules/checkride/dist/json.js.map +1 -0
- package/node_modules/checkride/dist/links.d.ts +31 -5
- package/node_modules/checkride/dist/links.d.ts.map +1 -1
- package/node_modules/checkride/dist/links.js +141 -32
- package/node_modules/checkride/dist/links.js.map +1 -1
- package/node_modules/checkride/dist/node-pin.d.ts +129 -0
- package/node_modules/checkride/dist/node-pin.d.ts.map +1 -0
- package/node_modules/checkride/dist/node-pin.js +211 -0
- package/node_modules/checkride/dist/node-pin.js.map +1 -0
- package/node_modules/checkride/dist/orchestrator.d.ts +53 -10
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +312 -113
- package/node_modules/checkride/dist/orchestrator.js.map +1 -1
- package/node_modules/checkride/dist/pack.d.ts +8 -9
- package/node_modules/checkride/dist/pack.d.ts.map +1 -1
- package/node_modules/checkride/dist/pack.js +53 -17
- package/node_modules/checkride/dist/pack.js.map +1 -1
- package/node_modules/checkride/dist/pm/detect.d.ts +6 -1
- package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/detect.js +6 -1
- package/node_modules/checkride/dist/pm/detect.js.map +1 -1
- package/node_modules/checkride/dist/pm/index.d.ts +7 -2
- package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/index.js +6 -2
- package/node_modules/checkride/dist/pm/index.js.map +1 -1
- package/node_modules/checkride/dist/pm/launch.d.ts +72 -0
- package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/launch.js +108 -0
- package/node_modules/checkride/dist/pm/launch.js.map +1 -0
- package/node_modules/checkride/dist/pm/tools.d.ts +73 -0
- package/node_modules/checkride/dist/pm/tools.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/tools.js +124 -0
- package/node_modules/checkride/dist/pm/tools.js.map +1 -0
- package/node_modules/checkride/dist/pm/translate.d.ts +35 -9
- package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/translate.js +78 -18
- package/node_modules/checkride/dist/pm/translate.js.map +1 -1
- package/node_modules/checkride/dist/proc.d.ts +30 -0
- package/node_modules/checkride/dist/proc.d.ts.map +1 -0
- package/node_modules/checkride/dist/proc.js +42 -0
- package/node_modules/checkride/dist/proc.js.map +1 -0
- package/node_modules/checkride/dist/qa/cli.d.ts +20 -0
- package/node_modules/checkride/dist/qa/cli.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/cli.js +24 -0
- package/node_modules/checkride/dist/qa/cli.js.map +1 -0
- package/node_modules/checkride/dist/qa/dead.d.ts +39 -0
- package/node_modules/checkride/dist/qa/dead.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/dead.js +88 -0
- package/node_modules/checkride/dist/qa/dead.js.map +1 -0
- package/node_modules/checkride/dist/qa/dupes.d.ts +45 -0
- package/node_modules/checkride/dist/qa/dupes.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/dupes.js +62 -0
- package/node_modules/checkride/dist/qa/dupes.js.map +1 -0
- package/node_modules/checkride/dist/qa/health.d.ts +85 -0
- package/node_modules/checkride/dist/qa/health.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/health.js +109 -0
- package/node_modules/checkride/dist/qa/health.js.map +1 -0
- package/node_modules/checkride/dist/qa/index.d.ts +23 -0
- package/node_modules/checkride/dist/qa/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/index.js +22 -0
- package/node_modules/checkride/dist/qa/index.js.map +1 -0
- package/node_modules/checkride/dist/qa/mutation.d.ts +80 -0
- package/node_modules/checkride/dist/qa/mutation.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/mutation.js +195 -0
- package/node_modules/checkride/dist/qa/mutation.js.map +1 -0
- package/node_modules/checkride/dist/qa/qa.d.ts +71 -0
- package/node_modules/checkride/dist/qa/qa.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/qa.js +158 -0
- package/node_modules/checkride/dist/qa/qa.js.map +1 -0
- package/node_modules/checkride/dist/qa/read.d.ts +40 -0
- package/node_modules/checkride/dist/qa/read.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/read.js +47 -0
- package/node_modules/checkride/dist/qa/read.js.map +1 -0
- package/node_modules/checkride/dist/qa/render.d.ts +26 -0
- package/node_modules/checkride/dist/qa/render.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/render.js +310 -0
- package/node_modules/checkride/dist/qa/render.js.map +1 -0
- package/node_modules/checkride/dist/recover.d.ts +48 -0
- package/node_modules/checkride/dist/recover.d.ts.map +1 -0
- package/node_modules/checkride/dist/recover.js +220 -0
- package/node_modules/checkride/dist/recover.js.map +1 -0
- package/node_modules/checkride/dist/security.d.ts +45 -0
- package/node_modules/checkride/dist/security.d.ts.map +1 -0
- package/node_modules/checkride/dist/security.js +86 -0
- package/node_modules/checkride/dist/security.js.map +1 -0
- package/node_modules/checkride/dist/smoke.d.ts +6 -6
- package/node_modules/checkride/dist/smoke.js +6 -6
- package/node_modules/checkride/dist/snippets.d.ts +27 -30
- package/node_modules/checkride/dist/snippets.d.ts.map +1 -1
- package/node_modules/checkride/dist/snippets.js +26 -30
- package/node_modules/checkride/dist/snippets.js.map +1 -1
- package/node_modules/checkride/dist/tool-json.d.ts +33 -0
- package/node_modules/checkride/dist/tool-json.d.ts.map +1 -0
- package/node_modules/checkride/dist/tool-json.js +58 -0
- package/node_modules/checkride/dist/tool-json.js.map +1 -0
- package/node_modules/checkride/dist/triage/cli.d.ts +21 -0
- package/node_modules/checkride/dist/triage/cli.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/cli.js +26 -0
- package/node_modules/checkride/dist/triage/cli.js.map +1 -0
- package/node_modules/checkride/dist/triage/doctor-fold.d.ts +51 -0
- package/node_modules/checkride/dist/triage/doctor-fold.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/doctor-fold.js +88 -0
- package/node_modules/checkride/dist/triage/doctor-fold.js.map +1 -0
- package/node_modules/checkride/dist/triage/env.d.ts +54 -0
- package/node_modules/checkride/dist/triage/env.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/env.js +81 -0
- package/node_modules/checkride/dist/triage/env.js.map +1 -0
- package/node_modules/checkride/dist/triage/gate.d.ts +61 -0
- package/node_modules/checkride/dist/triage/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/gate.js +121 -0
- package/node_modules/checkride/dist/triage/gate.js.map +1 -0
- package/node_modules/checkride/dist/triage/index.d.ts +22 -0
- package/node_modules/checkride/dist/triage/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/index.js +20 -0
- package/node_modules/checkride/dist/triage/index.js.map +1 -0
- package/node_modules/checkride/dist/triage/render.d.ts +19 -0
- package/node_modules/checkride/dist/triage/render.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/render.js +419 -0
- package/node_modules/checkride/dist/triage/render.js.map +1 -0
- package/node_modules/checkride/dist/triage/triage.d.ts +83 -0
- package/node_modules/checkride/dist/triage/triage.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/triage.js +131 -0
- package/node_modules/checkride/dist/triage/triage.js.map +1 -0
- package/node_modules/checkride/package.json +18 -15
- package/node_modules/checkride/schema/checkride.config.schema.json +70 -5
- package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
- package/node_modules/checkride/skills/check/SKILL.md +265 -0
- package/node_modules/checkride/skills/qa/SKILL.md +231 -0
- package/node_modules/checkride/templates/shared/cspell.json +1 -0
- package/node_modules/checkride/templates/shared/oxlintrc.json +1 -1
- package/node_modules/checkride/templates/shared/styles/Repo/Drift.yml +41 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Latin.yml +14 -0
- package/node_modules/checkride/templates/shared/styles/Repo/LyHyphen.yml +28 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Minted.yml +33 -0
- package/node_modules/checkride/templates/shared/styles/Repo/ThereIs.yml +16 -0
- package/node_modules/checkride/templates/shared/styles/Repo/Weasel.yml +26 -0
- package/node_modules/checkride/templates/shared/vale.ini +34 -0
- package/package.json +10 -9
- package/skills/build/SKILL.md +85 -76
- package/skills/doctor/SKILL.md +11 -11
- package/skills/finish/SKILL.md +21 -18
- package/skills/harvest/SKILL.md +13 -11
- package/skills/park/SKILL.md +10 -10
- package/skills/plan/SKILL.md +69 -59
- package/skills/recover/SKILL.md +28 -0
- package/skills/refine/SKILL.md +21 -18
- package/skills/revert/SKILL.md +6 -6
- package/skills/spike/SKILL.md +9 -9
- package/skills/status/SKILL.md +5 -5
- package/skills/step/SKILL.md +20 -15
- package/skills/verify/SKILL.md +56 -46
- package/templates/build-log.md +7 -7
- package/templates/intent.md +52 -41
- package/templates/spike-report.md +10 -10
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `.check/summary.json` read — the one place either reader parses it.
|
|
3
|
+
*
|
|
4
|
+
* The summary is checkride's promised index: `schema_version`-ed, additive-only,
|
|
5
|
+
* deterministically ordered and crash-consistent. This module turns it into a
|
|
6
|
+
* five-state result rather than a value-or-throw, because every failure mode is
|
|
7
|
+
* something a reader has to *report* — a missing summary means the gate never
|
|
8
|
+
* wrote one, and a `schema_version` this reader does not understand means stop,
|
|
9
|
+
* not guess.
|
|
10
|
+
*
|
|
11
|
+
* `foreign` and `schema-mismatch` are deliberately separate states even though
|
|
12
|
+
* both end in "this reader cannot use the file". An absent `schema_version` is
|
|
13
|
+
* evidence the file was not written by checkride at all — a repo whose gate is
|
|
14
|
+
* a homegrown script that happens to write `.check/` — and the honest report
|
|
15
|
+
* there is "your gate is not checkride", not "STOP, version boundary". A
|
|
16
|
+
* `schema_version` of 2 is the opposite case: checkride wrote it, and a newer
|
|
17
|
+
* one, so guessing across the boundary is what has to stop.
|
|
18
|
+
*
|
|
19
|
+
* Validation here is deliberately structural, not schematic: the full JSON
|
|
20
|
+
* Schema lives in `schema/checkride.summary.schema.json` and is the engine's
|
|
21
|
+
* job. All this needs is enough narrowing that the fields the readers touch are
|
|
22
|
+
* the types they claim to be, with no cast onto unvalidated bytes.
|
|
23
|
+
*
|
|
24
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
25
|
+
*/
|
|
26
|
+
import type { Summary } from '../orchestrator.js';
|
|
27
|
+
/** The gitignored directory every run writes its artifacts into. */
|
|
28
|
+
export declare const CHECK_DIR = ".check";
|
|
29
|
+
/**
|
|
30
|
+
* The only `schema_version` these readers understand.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately a literal, not the engine's `SCHEMA_VERSION` import: the
|
|
33
|
+
* additive-only guarantee holds *within* a version and promises nothing across
|
|
34
|
+
* one, so a bump has to stop the reader loudly rather than be silently
|
|
35
|
+
* followed. `artifacts.test.ts` asserts this equals the engine's constant, so a
|
|
36
|
+
* future bump turns a test red — a deliberate update, never a quiet change of
|
|
37
|
+
* behavior.
|
|
38
|
+
*/
|
|
39
|
+
export declare const SUPPORTED_SCHEMA_VERSION = 1;
|
|
40
|
+
/** The outcome of reading `.check/summary.json`. Every state is reportable. */
|
|
41
|
+
export type SummaryRead = {
|
|
42
|
+
state: 'ok';
|
|
43
|
+
path: string;
|
|
44
|
+
mtimeMs: number;
|
|
45
|
+
summary: Summary;
|
|
46
|
+
}
|
|
47
|
+
/** A JSON object with no `schema_version` at all — not checkride's file. */
|
|
48
|
+
| {
|
|
49
|
+
state: 'foreign';
|
|
50
|
+
path: string;
|
|
51
|
+
mtimeMs: number;
|
|
52
|
+
} | {
|
|
53
|
+
state: 'schema-mismatch';
|
|
54
|
+
path: string;
|
|
55
|
+
mtimeMs: number;
|
|
56
|
+
found: unknown;
|
|
57
|
+
} | {
|
|
58
|
+
state: 'unreadable';
|
|
59
|
+
path: string;
|
|
60
|
+
detail: string;
|
|
61
|
+
} | {
|
|
62
|
+
state: 'missing';
|
|
63
|
+
path: string;
|
|
64
|
+
};
|
|
65
|
+
/** Classify already-read bytes. Split out so tests can drive every state. */
|
|
66
|
+
export declare function parseSummary(raw: string, path: string, mtimeMs: number): SummaryRead;
|
|
67
|
+
/** Read and classify `<cwd>/.check/summary.json`. Never throws. */
|
|
68
|
+
export declare function readSummary(cwd: string): Promise<SummaryRead>;
|
|
69
|
+
//# sourceMappingURL=summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/artifacts/summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,oBAAoB,CAAC;AAIhE,oEAAoE;AACpE,eAAO,MAAM,SAAS,WAAW,CAAC;AAKlC;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE;AAClE,4EAA4E;GAC1E;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,KAAK,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC3E;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AA8CvC,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAcpF;AAED,mEAAmE;AACnE,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAQnE"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `.check/summary.json` read — the one place either reader parses it.
|
|
3
|
+
*
|
|
4
|
+
* The summary is checkride's promised index: `schema_version`-ed, additive-only,
|
|
5
|
+
* deterministically ordered and crash-consistent. This module turns it into a
|
|
6
|
+
* five-state result rather than a value-or-throw, because every failure mode is
|
|
7
|
+
* something a reader has to *report* — a missing summary means the gate never
|
|
8
|
+
* wrote one, and a `schema_version` this reader does not understand means stop,
|
|
9
|
+
* not guess.
|
|
10
|
+
*
|
|
11
|
+
* `foreign` and `schema-mismatch` are deliberately separate states even though
|
|
12
|
+
* both end in "this reader cannot use the file". An absent `schema_version` is
|
|
13
|
+
* evidence the file was not written by checkride at all — a repo whose gate is
|
|
14
|
+
* a homegrown script that happens to write `.check/` — and the honest report
|
|
15
|
+
* there is "your gate is not checkride", not "STOP, version boundary". A
|
|
16
|
+
* `schema_version` of 2 is the opposite case: checkride wrote it, and a newer
|
|
17
|
+
* one, so guessing across the boundary is what has to stop.
|
|
18
|
+
*
|
|
19
|
+
* Validation here is deliberately structural, not schematic: the full JSON
|
|
20
|
+
* Schema lives in `schema/checkride.summary.schema.json` and is the engine's
|
|
21
|
+
* job. All this needs is enough narrowing that the fields the readers touch are
|
|
22
|
+
* the types they claim to be, with no cast onto unvalidated bytes.
|
|
23
|
+
*
|
|
24
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
25
|
+
*/
|
|
26
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
import { isRecord, parseJson } from './json.js';
|
|
29
|
+
/** The gitignored directory every run writes its artifacts into. */
|
|
30
|
+
export const CHECK_DIR = '.check';
|
|
31
|
+
/** The aggregate report, beside the raw artifacts under {@link CHECK_DIR}. */
|
|
32
|
+
const SUMMARY_FILE = 'summary.json';
|
|
33
|
+
/**
|
|
34
|
+
* The only `schema_version` these readers understand.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately a literal, not the engine's `SCHEMA_VERSION` import: the
|
|
37
|
+
* additive-only guarantee holds *within* a version and promises nothing across
|
|
38
|
+
* one, so a bump has to stop the reader loudly rather than be silently
|
|
39
|
+
* followed. `artifacts.test.ts` asserts this equals the engine's constant, so a
|
|
40
|
+
* future bump turns a test red — a deliberate update, never a quiet change of
|
|
41
|
+
* behavior.
|
|
42
|
+
*/
|
|
43
|
+
export const SUPPORTED_SCHEMA_VERSION = 1;
|
|
44
|
+
/**
|
|
45
|
+
* Name, adapter and description are present and the right type. Bracket access
|
|
46
|
+
* throughout: these are unvalidated bytes, and reading them as a dynamic record
|
|
47
|
+
* is what they are.
|
|
48
|
+
*/
|
|
49
|
+
function hasCheckIdentity(value) {
|
|
50
|
+
return (typeof value['name'] === 'string' &&
|
|
51
|
+
typeof value['description'] === 'string' &&
|
|
52
|
+
(typeof value['adapter'] === 'string' || value['adapter'] === null));
|
|
53
|
+
}
|
|
54
|
+
/** The result fields the readers render are present and the right type. */
|
|
55
|
+
function hasCheckOutcome(value) {
|
|
56
|
+
return (typeof value['ok'] === 'boolean' &&
|
|
57
|
+
typeof value['duration_ms'] === 'number' &&
|
|
58
|
+
(typeof value['exit_code'] === 'number' || value['exit_code'] === null) &&
|
|
59
|
+
(typeof value['output_file'] === 'string' || value['output_file'] === null));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One `checks[]` entry. The optional additive fields (`skipped`, `reason`,
|
|
63
|
+
* `baselined`) are read through `??` at the point of use, so they need no guard.
|
|
64
|
+
*/
|
|
65
|
+
function isCheck(value) {
|
|
66
|
+
return isRecord(value) && hasCheckIdentity(value) && hasCheckOutcome(value);
|
|
67
|
+
}
|
|
68
|
+
/** Every required top-level field, and a well-formed `checks[]`. */
|
|
69
|
+
function isSummary(value) {
|
|
70
|
+
const checks = value['checks'];
|
|
71
|
+
return (typeof value['timestamp'] === 'string' &&
|
|
72
|
+
typeof value['ok'] === 'boolean' &&
|
|
73
|
+
typeof value['checks_run'] === 'number' &&
|
|
74
|
+
typeof value['total_duration_ms'] === 'number' &&
|
|
75
|
+
Array.isArray(checks) &&
|
|
76
|
+
checks.every(isCheck));
|
|
77
|
+
}
|
|
78
|
+
/** Classify already-read bytes. Split out so tests can drive every state. */
|
|
79
|
+
export function parseSummary(raw, path, mtimeMs) {
|
|
80
|
+
const value = parseJson(raw);
|
|
81
|
+
if (!isRecord(value))
|
|
82
|
+
return { state: 'unreadable', path, detail: 'not a JSON object' };
|
|
83
|
+
// Provenance first, then version: a summary this reader cannot read is a
|
|
84
|
+
// whose-file-is-this problem or a version problem, not a shape problem, even
|
|
85
|
+
// when its other fields also fail to narrow. Key presence rather than value,
|
|
86
|
+
// so an explicit `"schema_version": null` stays a mismatch — a malformed
|
|
87
|
+
// checkride summary — instead of being read as a stranger's file.
|
|
88
|
+
if (!('schema_version' in value))
|
|
89
|
+
return { state: 'foreign', path, mtimeMs };
|
|
90
|
+
if (value['schema_version'] !== SUPPORTED_SCHEMA_VERSION) {
|
|
91
|
+
return { state: 'schema-mismatch', path, mtimeMs, found: value['schema_version'] };
|
|
92
|
+
}
|
|
93
|
+
if (!isSummary(value))
|
|
94
|
+
return { state: 'unreadable', path, detail: 'missing or mistyped required field(s)' };
|
|
95
|
+
return { state: 'ok', path, mtimeMs, summary: value };
|
|
96
|
+
}
|
|
97
|
+
/** Read and classify `<cwd>/.check/summary.json`. Never throws. */
|
|
98
|
+
export async function readSummary(cwd) {
|
|
99
|
+
const path = join(cwd, CHECK_DIR, SUMMARY_FILE);
|
|
100
|
+
try {
|
|
101
|
+
const [stats, raw] = await Promise.all([stat(path), readFile(path, 'utf8')]);
|
|
102
|
+
return parseSummary(raw, path, stats.mtimeMs);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return { state: 'missing', path };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/artifacts/summary.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,21 @@
|
|
|
8
8
|
* version or the new complete version — `rename(2)` is atomic within a
|
|
9
9
|
* directory — never torn.
|
|
10
10
|
*/
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Write `data` to `path` atomically: temp sibling, flushed to disk, then
|
|
13
|
+
* renamed over.
|
|
14
|
+
*
|
|
15
|
+
* The `fsync` before the rename is what makes the promise above true rather
|
|
16
|
+
* than nearly true. `rename(2)` is atomic for the *name*, but a write that is
|
|
17
|
+
* still sitting in the page cache when the machine loses power can leave the
|
|
18
|
+
* renamed inode holding nothing — the reader then finds a file that exists and
|
|
19
|
+
* is empty, which is the torn read this exists to prevent, just relocated.
|
|
20
|
+
*
|
|
21
|
+
* The containing directory is deliberately not synced. Without that, the
|
|
22
|
+
* rename itself may not survive a crash — but the reader then sees the
|
|
23
|
+
* *previous complete version*, which is exactly what is promised. Paying a
|
|
24
|
+
* second fsync per artifact to also guarantee which of the two you get is not
|
|
25
|
+
* worth it for a report that the next run rewrites anyway.
|
|
26
|
+
*/
|
|
12
27
|
export declare function writeFileAtomic(path: string, data: string): Promise<void>;
|
|
13
28
|
//# sourceMappingURL=atomic.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH
|
|
1
|
+
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/E"}
|
|
@@ -8,12 +8,34 @@
|
|
|
8
8
|
* version or the new complete version — `rename(2)` is atomic within a
|
|
9
9
|
* directory — never torn.
|
|
10
10
|
*/
|
|
11
|
-
import { rename, rm
|
|
12
|
-
/**
|
|
11
|
+
import { open, rename, rm } from 'node:fs/promises';
|
|
12
|
+
/**
|
|
13
|
+
* Write `data` to `path` atomically: temp sibling, flushed to disk, then
|
|
14
|
+
* renamed over.
|
|
15
|
+
*
|
|
16
|
+
* The `fsync` before the rename is what makes the promise above true rather
|
|
17
|
+
* than nearly true. `rename(2)` is atomic for the *name*, but a write that is
|
|
18
|
+
* still sitting in the page cache when the machine loses power can leave the
|
|
19
|
+
* renamed inode holding nothing — the reader then finds a file that exists and
|
|
20
|
+
* is empty, which is the torn read this exists to prevent, just relocated.
|
|
21
|
+
*
|
|
22
|
+
* The containing directory is deliberately not synced. Without that, the
|
|
23
|
+
* rename itself may not survive a crash — but the reader then sees the
|
|
24
|
+
* *previous complete version*, which is exactly what is promised. Paying a
|
|
25
|
+
* second fsync per artifact to also guarantee which of the two you get is not
|
|
26
|
+
* worth it for a report that the next run rewrites anyway.
|
|
27
|
+
*/
|
|
13
28
|
export async function writeFileAtomic(path, data) {
|
|
14
29
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
15
30
|
try {
|
|
16
|
-
await
|
|
31
|
+
const handle = await open(tmp, 'w');
|
|
32
|
+
try {
|
|
33
|
+
await handle.writeFile(data);
|
|
34
|
+
await handle.sync();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await handle.close();
|
|
38
|
+
}
|
|
17
39
|
await rename(tmp, path);
|
|
18
40
|
}
|
|
19
41
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic.js","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"atomic.js","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,IAAY;IAC9D,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QACD,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import type { Fingerprint } from './fingerprint.js';
|
|
24
24
|
/**
|
|
25
|
-
* Fallow's fingerprint extractor
|
|
25
|
+
* Fallow's fingerprint extractor, for the extractor registry.
|
|
26
26
|
* Returns the finding keys for a recognized, readable report and an empty set
|
|
27
27
|
* for anything else — the "never throw, empty on malformed" contract every
|
|
28
28
|
* extractor honours. The *gating* verdict (`fallowVerdict`) is where an
|
|
@@ -55,10 +55,14 @@ export type FallowVerdict = {
|
|
|
55
55
|
* fallow found anything. With a baseline, findings are masked — the slot passes
|
|
56
56
|
* when every current finding is grandfathered and fails on any new one.
|
|
57
57
|
*
|
|
58
|
-
* A report
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
58
|
+
* A report whose keys don't map one-per-finding onto its issue count — an
|
|
59
|
+
* un-keyable finding, or a key produced for something the count doesn't include
|
|
60
|
+
* — has findings that can't be individually grandfathered; a baseline must never
|
|
61
|
+
* mask such a slot to green, so it stays at the raw findings-based verdict. A key
|
|
62
|
+
* *collision* (two findings sharing a key) is not un-keyable — both are keyed, so
|
|
63
|
+
* the baseline coarsens (grandfathering one grandfathers both) but stays active.
|
|
64
|
+
* Correctness over precision: better to keep re-surfacing a finding than to
|
|
65
|
+
* silently pass one.
|
|
62
66
|
*/
|
|
63
67
|
export declare function fallowVerdict(raw: string, baselineKeys: readonly string[] | null): FallowVerdict;
|
|
64
68
|
//# sourceMappingURL=fallow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fallow.d.ts","sourceRoot":"","sources":["../../src/baseline/fallow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;
|
|
1
|
+
{"version":3,"file":"fallow.d.ts","sourceRoot":"","sources":["../../src/baseline/fallow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAgTpD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAGvD;AAED,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG;IAC1B,2CAA2C;IAC3C,EAAE,EAAE,OAAO,CAAC;IACZ,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gEAAgE;IAChE,QAAQ,EAAE,WAAW,CAAC;IACtB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,aAAa,CA0BhG"}
|
|
@@ -20,12 +20,14 @@
|
|
|
20
20
|
* schema 4 with a different layout and is no longer supported). Each analysis
|
|
21
21
|
* kind has its own layout, so parsing dispatches on the top-level `kind`.
|
|
22
22
|
*/
|
|
23
|
+
import { parseToolJson } from '../tool-json.js';
|
|
23
24
|
import { applyBaseline } from './store.js';
|
|
24
25
|
/**
|
|
25
|
-
* Minimum fallow JSON schema version checkride understands. fallow
|
|
26
|
-
* 3.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* Minimum fallow JSON schema version checkride understands. Every fallow from
|
|
27
|
+
* 3.5.0 through 3.9.1 emits schema_version 7 (verified: dead-code, dupes, and
|
|
28
|
+
* health all report 7, with identical top-level layouts); 2.x emitted 4 with an
|
|
29
|
+
* incompatible layout. A report below this floor — or one whose kind/shape we
|
|
30
|
+
* can't read — is a hard failure, never a silent pass.
|
|
29
31
|
*/
|
|
30
32
|
const FALLOW_SCHEMA_MIN = 7;
|
|
31
33
|
/** Narrow to a plain object (arrays and null excluded). */
|
|
@@ -37,32 +39,74 @@ function str(value) {
|
|
|
37
39
|
return typeof value === 'string' ? value.trim() : '';
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* Fields naming the declaration that owns a member-scoped finding, in priority
|
|
43
|
+
* order. fallow reports an unused class/enum/store member as `parent_name` +
|
|
44
|
+
* `member_name`, a component binding as `component_name` + `prop_name` /
|
|
45
|
+
* `input_name` / `output_name` / `emit_name` / `event_name`, and a catalog entry
|
|
46
|
+
* as `catalog_name` + `entry_name`. Both halves are needed: the leaf alone is
|
|
47
|
+
* ambiguous (two classes in one file can each have a `render`), and the
|
|
48
|
+
* container alone collapses every member of one declaration onto one key.
|
|
46
49
|
*/
|
|
47
|
-
|
|
50
|
+
const CONTAINER_FIELDS = ['parent_name', 'component_name', 'catalog_name'];
|
|
51
|
+
/** Finding fields that carry a stable leaf symbol, in priority order. */
|
|
48
52
|
const SYMBOL_FIELDS = [
|
|
49
53
|
'export_name',
|
|
50
54
|
'name',
|
|
51
55
|
'type_name',
|
|
52
56
|
'member',
|
|
57
|
+
'member_name',
|
|
58
|
+
'prop_name',
|
|
59
|
+
'emit_name',
|
|
60
|
+
'input_name',
|
|
61
|
+
'output_name',
|
|
62
|
+
'event_name',
|
|
63
|
+
'entry_name',
|
|
64
|
+
'action_name',
|
|
65
|
+
'key_name',
|
|
53
66
|
'package_name',
|
|
54
67
|
'dependency',
|
|
55
68
|
'specifier',
|
|
56
69
|
];
|
|
57
|
-
/** The first non-empty
|
|
58
|
-
function
|
|
59
|
-
for (const field of
|
|
70
|
+
/** The first non-empty field of `fields` on a finding, or `''` when none is set. */
|
|
71
|
+
function firstField(item, fields) {
|
|
72
|
+
for (const field of fields) {
|
|
60
73
|
const value = str(item[field]);
|
|
61
74
|
if (value)
|
|
62
75
|
return value;
|
|
63
76
|
}
|
|
64
77
|
return '';
|
|
65
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* A finding's stable symbol identity: `<container>.<leaf>` when it carries both
|
|
81
|
+
* (`Svc.unusedOne`), otherwise whichever half it has, otherwise `''`. Composing
|
|
82
|
+
* the two is what keeps a member-scoped finding line-free — with neither half
|
|
83
|
+
* recognized such a finding has no symbol at all and falls back to
|
|
84
|
+
* `<path>:<line>:<col>`, a key that moves whenever code above it does, so a
|
|
85
|
+
* grandfathered finding resurfaces as new on the next unrelated edit.
|
|
86
|
+
*/
|
|
87
|
+
function symbolIdentity(item) {
|
|
88
|
+
return [firstField(item, CONTAINER_FIELDS), firstField(item, SYMBOL_FIELDS)].filter(Boolean).join('.');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A finding's `line[:col]` position, or `''` when it carries neither. Only used
|
|
92
|
+
* to disambiguate findings that share every stable identity field (see
|
|
93
|
+
* {@link withPosition}); a keyable finding never falls back to this.
|
|
94
|
+
*/
|
|
95
|
+
function position(item) {
|
|
96
|
+
return [item['line'], item['col']].filter((n) => typeof n === 'number').join(':');
|
|
97
|
+
}
|
|
98
|
+
/** Append `:<line>[:<col>]` to a key when the finding has a position, else leave it. */
|
|
99
|
+
function withPosition(key, item) {
|
|
100
|
+
const pos = position(item);
|
|
101
|
+
return pos ? `${key}:${pos}` : key;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A function name that is a real, stable identifier (kept line-free so its key
|
|
105
|
+
* survives edits above it). Fallow wraps names it can't resolve to an identity
|
|
106
|
+
* in angle brackets (`<arrow>`, `<anonymous>`); those fail this test and get a
|
|
107
|
+
* position appended, since every such placeholder in a file is otherwise equal.
|
|
108
|
+
*/
|
|
109
|
+
const STABLE_IDENTIFIER = /^[A-Za-z_$][\w$.]*$/;
|
|
66
110
|
/**
|
|
67
111
|
* Key a cross-file finding (circular_dependencies, re_export_cycles) on its
|
|
68
112
|
* sorted file membership — a list of files/segments rather than one location.
|
|
@@ -82,28 +126,78 @@ function cycleKey(category, item) {
|
|
|
82
126
|
}
|
|
83
127
|
return null;
|
|
84
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Fingerprint key for one dead-code finding: `dead-code:<category>:<identity>`,
|
|
131
|
+
* where identity is a file and symbol (never a line or column, so the key
|
|
132
|
+
* survives edits that only move the code). When a finding has a file but no
|
|
133
|
+
* symbol — `stale_suppressions`, `policy_violations` and the boundary
|
|
134
|
+
* categories carry only a path — the path alone would collide with its siblings
|
|
135
|
+
* in the same file, so the position is appended to keep them distinct, at the
|
|
136
|
+
* cost of a key that moves. Cross-file findings (cycles) key on the
|
|
137
|
+
* sorted set of files. Returns `null` when no stable identity can be read — the
|
|
138
|
+
* finding still counts toward `issueCount`, it just can't be individually
|
|
139
|
+
* grandfathered (which keeps the slot conservatively red, never silently green).
|
|
140
|
+
*/
|
|
85
141
|
function deadKey(category, item) {
|
|
86
142
|
if (!isPlainObject(item))
|
|
87
143
|
return null;
|
|
88
144
|
const file = str(item['path']) || str(item['file']);
|
|
89
145
|
const symbol = symbolIdentity(item);
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
146
|
+
if (symbol)
|
|
147
|
+
return `dead-code:${category}:${[file, symbol].filter(Boolean).join(':')}`;
|
|
148
|
+
if (file)
|
|
149
|
+
return withPosition(`dead-code:${category}:${file}`, item);
|
|
94
150
|
return cycleKey(category, item);
|
|
95
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Top-level arrays that are NOT counted in `total_issues`, so keying them would
|
|
154
|
+
* push the key count past the issue count and leave the two out of step: the
|
|
155
|
+
* advisory `next_steps`, the project-level `workspace_diagnostics`, and the
|
|
156
|
+
* three opt-in "health signal" rules that fallow's own `issue-registry.json`
|
|
157
|
+
* marks `counts_in_total: false`. They have to be skipped by name rather than
|
|
158
|
+
* left to {@link deadKey} — `workspace_diagnostics` carries a `path`, and
|
|
159
|
+
* `thin_wrappers` and `duplicate_prop_shapes` a `file`, so all three key
|
|
160
|
+
* readily. Verified against fallow 3.9.1's registry; every other array counts.
|
|
161
|
+
*/
|
|
162
|
+
const UNCOUNTED_ARRAYS = new Set([
|
|
163
|
+
'next_steps',
|
|
164
|
+
'workspace_diagnostics',
|
|
165
|
+
'prop_drilling_chains',
|
|
166
|
+
'thin_wrappers',
|
|
167
|
+
'duplicate_prop_shapes',
|
|
168
|
+
]);
|
|
169
|
+
/**
|
|
170
|
+
* Key every counted finding in a dead-code report. Returns the keys — duplicates
|
|
171
|
+
* intact, so a collision stays visible to the caller's tracking check — and how
|
|
172
|
+
* many items carried no stable identity to key on.
|
|
173
|
+
*/
|
|
174
|
+
function collectDeadKeys(j) {
|
|
175
|
+
const keys = [];
|
|
176
|
+
let unkeyed = 0;
|
|
177
|
+
for (const [category, value] of Object.entries(j)) {
|
|
178
|
+
if (!Array.isArray(value) || UNCOUNTED_ARRAYS.has(category))
|
|
179
|
+
continue;
|
|
180
|
+
for (const item of value) {
|
|
181
|
+
const key = deadKey(category, item);
|
|
182
|
+
if (key === null)
|
|
183
|
+
unkeyed += 1;
|
|
184
|
+
else
|
|
185
|
+
keys.push(key);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { keys, unkeyed };
|
|
189
|
+
}
|
|
96
190
|
/**
|
|
97
191
|
* Extract dead-code findings and the authoritative issue count (`total_issues`).
|
|
98
192
|
* Findings come from the top-level detail arrays (`unused_exports`,
|
|
99
193
|
* `unused_dev_dependencies`, `circular_dependencies`, …) rather than the summary
|
|
100
|
-
* keys: the summary aggregates a few categories under one name (
|
|
194
|
+
* keys: the summary aggregates a few categories under one name (for example dev and
|
|
101
195
|
* optional deps both count as `unused_dependencies`) whose items live in
|
|
102
|
-
* separately
|
|
103
|
-
* every array
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
196
|
+
* separately named arrays, so keying off the summary would miss them. Iterating
|
|
197
|
+
* every array except the {@link UNCOUNTED_ARRAYS} covers whatever categories
|
|
198
|
+
* fallow reports — including ones added in a future minor — without a
|
|
199
|
+
* hand-maintained list of the ones that do count. A category whose items carry
|
|
200
|
+
* no stable identity simply isn't fingerprinted; it still counts toward
|
|
107
201
|
* `total_issues`, so the slot stays red (never masked) until it's fixed.
|
|
108
202
|
*/
|
|
109
203
|
function parseDeadCode(j) {
|
|
@@ -112,17 +206,17 @@ function parseDeadCode(j) {
|
|
|
112
206
|
if (typeof total !== 'number') {
|
|
113
207
|
return { ok: false, reason: 'fallow dead-code JSON missing summary.total_issues' };
|
|
114
208
|
}
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return { ok: true, kind: 'dead-code', findings, issueCount: total };
|
|
209
|
+
const { keys, unkeyed } = collectDeadKeys(j);
|
|
210
|
+
// Two independent ways the keys can fail to cover the findings, both required
|
|
211
|
+
// because either alone has a blind spot. Counting the items we could not key
|
|
212
|
+
// is direct and exact. Comparing against the authoritative `total` catches
|
|
213
|
+
// findings the iteration never reached at all (nothing to count) — and cannot
|
|
214
|
+
// be substituted for the direct count, because a key produced for something
|
|
215
|
+
// `total` omits would otherwise offset a real un-keyable finding back to zero.
|
|
216
|
+
// `keys.length` is taken BEFORE the Set collapses collisions, so a collision
|
|
217
|
+
// still reads as fully tracked.
|
|
218
|
+
const fullyTracked = unkeyed === 0 && keys.length === total;
|
|
219
|
+
return { ok: true, kind: 'dead-code', findings: keys, issueCount: total, fullyTracked };
|
|
126
220
|
}
|
|
127
221
|
/**
|
|
128
222
|
* Extract duplication findings. Each clone group carries a content-hash
|
|
@@ -142,12 +236,14 @@ function parseDupes(j) {
|
|
|
142
236
|
if (fp)
|
|
143
237
|
findings.push(`dupes:${fp}`);
|
|
144
238
|
}
|
|
145
|
-
return { ok: true, kind: 'dupes', findings, issueCount: groups.length };
|
|
239
|
+
return { ok: true, kind: 'dupes', findings, issueCount: groups.length, fullyTracked: findings.length === groups.length };
|
|
146
240
|
}
|
|
147
241
|
/**
|
|
148
242
|
* Extract health (complexity) findings — one per function over threshold, keyed
|
|
149
|
-
* on file + function name
|
|
150
|
-
*
|
|
243
|
+
* on file + function name. A named function stays line-free so its key survives
|
|
244
|
+
* edits above it; an anonymous placeholder (`<arrow>`, `<anonymous>`) shares its
|
|
245
|
+
* name with every sibling in the file, so it gets a position appended to stay
|
|
246
|
+
* distinct. The issue count is the number of findings.
|
|
151
247
|
*/
|
|
152
248
|
function parseHealth(j) {
|
|
153
249
|
const findings = j['findings'];
|
|
@@ -158,16 +254,12 @@ function parseHealth(j) {
|
|
|
158
254
|
for (const f of findings) {
|
|
159
255
|
if (!isPlainObject(f))
|
|
160
256
|
continue;
|
|
161
|
-
|
|
257
|
+
const name = str(f['name']);
|
|
258
|
+
const base = `health:${str(f['path'])}:${name}`;
|
|
259
|
+
keys.push(STABLE_IDENTIFIER.test(name) ? base : withPosition(base, f));
|
|
162
260
|
}
|
|
163
|
-
return { ok: true, kind: 'health', findings: keys, issueCount: findings.length };
|
|
261
|
+
return { ok: true, kind: 'health', findings: keys, issueCount: findings.length, fullyTracked: keys.length === findings.length };
|
|
164
262
|
}
|
|
165
|
-
/**
|
|
166
|
-
* Parse a fallow report into findings + an issue count, or the reason it could
|
|
167
|
-
* not be trusted. Every failure path (bad JSON, missing/old schema, unknown
|
|
168
|
-
* kind, missing count field) is a *loud* failure the caller turns into a red
|
|
169
|
-
* slot — checkride never treats an unreadable fallow report as "clean".
|
|
170
|
-
*/
|
|
171
263
|
/**
|
|
172
264
|
* The reason a report's schema can't be trusted, or `null` when it is a
|
|
173
265
|
* supported version. Split out so {@link parseFallow} stays a thin dispatcher.
|
|
@@ -181,16 +273,20 @@ function fallowSchemaError(j) {
|
|
|
181
273
|
}
|
|
182
274
|
return null;
|
|
183
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Parse a fallow report into findings + an issue count, or the reason it could
|
|
278
|
+
* not be trusted. Every failure path (bad JSON, missing/old schema, unknown
|
|
279
|
+
* kind, missing count field) is a *loud* failure the caller turns into a red
|
|
280
|
+
* slot — checkride never treats an unreadable fallow report as "clean".
|
|
281
|
+
*/
|
|
184
282
|
function parseFallow(raw) {
|
|
185
283
|
if (raw.trim() === '')
|
|
186
284
|
return { ok: false, reason: 'fallow produced no JSON output' };
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
285
|
+
// Tolerant of a launcher preamble ahead of the report — see `parseToolJson`.
|
|
286
|
+
const parsed = parseToolJson(raw);
|
|
287
|
+
if (!parsed)
|
|
192
288
|
return { ok: false, reason: 'fallow did not emit valid JSON' };
|
|
193
|
-
|
|
289
|
+
const j = parsed.value;
|
|
194
290
|
if (!isPlainObject(j))
|
|
195
291
|
return { ok: false, reason: 'fallow output was not a JSON object' };
|
|
196
292
|
const schemaError = fallowSchemaError(j);
|
|
@@ -209,7 +305,7 @@ function parseFallow(raw) {
|
|
|
209
305
|
}
|
|
210
306
|
}
|
|
211
307
|
/**
|
|
212
|
-
* Fallow's fingerprint extractor
|
|
308
|
+
* Fallow's fingerprint extractor, for the extractor registry.
|
|
213
309
|
* Returns the finding keys for a recognized, readable report and an empty set
|
|
214
310
|
* for anything else — the "never throw, empty on malformed" contract every
|
|
215
311
|
* extractor honours. The *gating* verdict (`fallowVerdict`) is where an
|
|
@@ -226,10 +322,14 @@ export function fallowFindings(raw) {
|
|
|
226
322
|
* fallow found anything. With a baseline, findings are masked — the slot passes
|
|
227
323
|
* when every current finding is grandfathered and fails on any new one.
|
|
228
324
|
*
|
|
229
|
-
* A report
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
325
|
+
* A report whose keys don't map one-per-finding onto its issue count — an
|
|
326
|
+
* un-keyable finding, or a key produced for something the count doesn't include
|
|
327
|
+
* — has findings that can't be individually grandfathered; a baseline must never
|
|
328
|
+
* mask such a slot to green, so it stays at the raw findings-based verdict. A key
|
|
329
|
+
* *collision* (two findings sharing a key) is not un-keyable — both are keyed, so
|
|
330
|
+
* the baseline coarsens (grandfathering one grandfathers both) but stays active.
|
|
331
|
+
* Correctness over precision: better to keep re-surfacing a finding than to
|
|
332
|
+
* silently pass one.
|
|
233
333
|
*/
|
|
234
334
|
export function fallowVerdict(raw, baselineKeys) {
|
|
235
335
|
const parsed = parseFallow(raw);
|
|
@@ -243,9 +343,12 @@ export function fallowVerdict(raw, baselineKeys) {
|
|
|
243
343
|
return { ok: rawOk, baselined: 0, newKeys: [], findings, observed: true, reason: issueReason };
|
|
244
344
|
}
|
|
245
345
|
const adj = applyBaseline(findings, baselineKeys, rawOk);
|
|
246
|
-
|
|
247
|
-
//
|
|
248
|
-
|
|
346
|
+
// Findings the keys don't cover block masking: hold the slot at its raw
|
|
347
|
+
// verdict. A key collision does NOT (both findings are keyed, so the report
|
|
348
|
+
// stays fully tracked), so it coarsens the baseline instead of disabling it. A
|
|
349
|
+
// *deduplicated* size comparison could not tell the two apart, which is what
|
|
350
|
+
// disabled the whole slot on any collision.
|
|
351
|
+
if (!parsed.fullyTracked) {
|
|
249
352
|
return { ok: rawOk, baselined: adj.baselined, newKeys: adj.newKeys, findings, observed: true, reason: issueReason };
|
|
250
353
|
}
|
|
251
354
|
// Fully tracked: a masked slot (all findings grandfathered) is green; a slot
|