plumbbob 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +15 -13
- package/dist/cli-core.js +19 -2
- package/dist/lib/check.js +46 -13
- package/dist/lib/intent.js +1 -1
- package/dist/lib/latch.js +3 -3
- package/dist/lib/reentry.js +70 -0
- package/dist/verbs/agent.js +2 -2
- package/dist/verbs/doctor.js +4 -4
- package/dist/verbs/finish.js +1 -1
- package/dist/verbs/recover.js +297 -0
- package/dist/verbs/revert.js +2 -2
- package/dist/verbs/spike.js +8 -2
- package/hooks/post-edit.sh +4 -4
- package/hooks/pre-bash-commit.sh +3 -3
- package/node_modules/checkride/.claude-plugin/plugin.json +11 -0
- package/node_modules/checkride/CHANGELOG.md +1003 -0
- package/node_modules/checkride/LICENSE +201 -21
- package/node_modules/checkride/README.md +171 -30
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts +15 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.d.ts.map +1 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js +17 -0
- package/node_modules/checkride/dist/__tests__/fixtures/released-stanzas.js.map +1 -0
- package/node_modules/checkride/dist/adapters.d.ts +18 -6
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +42 -23
- package/node_modules/checkride/dist/adapters.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/claude.d.ts +88 -0
- package/node_modules/checkride/dist/agent-setup/claude.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/claude.js +305 -0
- package/node_modules/checkride/dist/agent-setup/claude.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/command.d.ts +43 -0
- package/node_modules/checkride/dist/agent-setup/command.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/command.js +77 -0
- package/node_modules/checkride/dist/agent-setup/command.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts +61 -0
- package/node_modules/checkride/dist/agent-setup/cursor.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js +189 -0
- package/node_modules/checkride/dist/agent-setup/cursor.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/files.d.ts +50 -0
- package/node_modules/checkride/dist/agent-setup/files.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/files.js +80 -0
- package/node_modules/checkride/dist/agent-setup/files.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/harness.d.ts +101 -0
- package/node_modules/checkride/dist/agent-setup/harness.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/harness.js +84 -0
- package/node_modules/checkride/dist/agent-setup/harness.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/hook.d.ts +45 -52
- package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/hook.js +64 -95
- package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.d.ts +10 -4
- package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.js +9 -4
- package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts +58 -0
- package/node_modules/checkride/dist/agent-setup/scripts.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js +299 -0
- package/node_modules/checkride/dist/agent-setup/scripts.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/skills.d.ts +31 -0
- package/node_modules/checkride/dist/agent-setup/skills.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/skills.js +56 -0
- package/node_modules/checkride/dist/agent-setup/skills.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/configured.d.ts +30 -0
- package/node_modules/checkride/dist/artifacts/configured.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/configured.js +45 -0
- package/node_modules/checkride/dist/artifacts/configured.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/format.d.ts +33 -0
- package/node_modules/checkride/dist/artifacts/format.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/format.js +56 -0
- package/node_modules/checkride/dist/artifacts/format.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/freshness.d.ts +32 -0
- package/node_modules/checkride/dist/artifacts/freshness.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/freshness.js +37 -0
- package/node_modules/checkride/dist/artifacts/freshness.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/index.d.ts +22 -0
- package/node_modules/checkride/dist/artifacts/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/index.js +19 -0
- package/node_modules/checkride/dist/artifacts/index.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/json.d.ts +31 -0
- package/node_modules/checkride/dist/artifacts/json.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/json.js +47 -0
- package/node_modules/checkride/dist/artifacts/json.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/raw.d.ts +60 -0
- package/node_modules/checkride/dist/artifacts/raw.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/raw.js +91 -0
- package/node_modules/checkride/dist/artifacts/raw.js.map +1 -0
- package/node_modules/checkride/dist/artifacts/summary.d.ts +69 -0
- package/node_modules/checkride/dist/artifacts/summary.d.ts.map +1 -0
- package/node_modules/checkride/dist/artifacts/summary.js +108 -0
- package/node_modules/checkride/dist/artifacts/summary.js.map +1 -0
- package/node_modules/checkride/dist/atomic.d.ts +16 -1
- package/node_modules/checkride/dist/atomic.d.ts.map +1 -1
- package/node_modules/checkride/dist/atomic.js +25 -3
- package/node_modules/checkride/dist/atomic.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.d.ts +9 -5
- package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.js +161 -58
- package/node_modules/checkride/dist/baseline/fallow.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +7 -7
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.js +16 -19
- package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
- package/node_modules/checkride/dist/baseline/index.d.ts +4 -4
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/index.js +4 -4
- package/node_modules/checkride/dist/baseline/index.js.map +1 -1
- package/node_modules/checkride/dist/baseline/store.d.ts +12 -4
- package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/store.js +16 -9
- package/node_modules/checkride/dist/baseline/store.js.map +1 -1
- package/node_modules/checkride/dist/baseline-command.d.ts +5 -5
- package/node_modules/checkride/dist/baseline-command.js +7 -7
- package/node_modules/checkride/dist/baseline-command.js.map +1 -1
- package/node_modules/checkride/dist/cli.d.ts +17 -4
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +259 -18
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +84 -8
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +122 -18
- package/node_modules/checkride/dist/config.js.map +1 -1
- package/node_modules/checkride/dist/digest/digest.d.ts +5 -5
- package/node_modules/checkride/dist/digest/digest.js +5 -5
- package/node_modules/checkride/dist/digest/index.d.ts +2 -2
- package/node_modules/checkride/dist/digest/index.js +2 -2
- package/node_modules/checkride/dist/doctor.d.ts +25 -6
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +193 -27
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/gate.d.ts +135 -0
- package/node_modules/checkride/dist/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/gate.js +490 -0
- package/node_modules/checkride/dist/gate.js.map +1 -0
- package/node_modules/checkride/dist/init.d.ts +72 -12
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +324 -81
- package/node_modules/checkride/dist/init.js.map +1 -1
- package/node_modules/checkride/dist/json.d.ts +21 -0
- package/node_modules/checkride/dist/json.d.ts.map +1 -0
- package/node_modules/checkride/dist/json.js +21 -0
- package/node_modules/checkride/dist/json.js.map +1 -0
- package/node_modules/checkride/dist/links.d.ts +31 -5
- package/node_modules/checkride/dist/links.d.ts.map +1 -1
- package/node_modules/checkride/dist/links.js +141 -32
- package/node_modules/checkride/dist/links.js.map +1 -1
- package/node_modules/checkride/dist/node-pin.d.ts +129 -0
- package/node_modules/checkride/dist/node-pin.d.ts.map +1 -0
- package/node_modules/checkride/dist/node-pin.js +211 -0
- package/node_modules/checkride/dist/node-pin.js.map +1 -0
- package/node_modules/checkride/dist/orchestrator.d.ts +40 -10
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +196 -102
- package/node_modules/checkride/dist/orchestrator.js.map +1 -1
- package/node_modules/checkride/dist/pack.d.ts +8 -9
- package/node_modules/checkride/dist/pack.d.ts.map +1 -1
- package/node_modules/checkride/dist/pack.js +53 -17
- package/node_modules/checkride/dist/pack.js.map +1 -1
- package/node_modules/checkride/dist/pm/detect.d.ts +6 -1
- package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/detect.js +6 -1
- package/node_modules/checkride/dist/pm/detect.js.map +1 -1
- package/node_modules/checkride/dist/pm/index.d.ts +6 -2
- package/node_modules/checkride/dist/pm/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/index.js +6 -2
- package/node_modules/checkride/dist/pm/index.js.map +1 -1
- package/node_modules/checkride/dist/pm/launch.d.ts +51 -0
- package/node_modules/checkride/dist/pm/launch.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/launch.js +92 -0
- package/node_modules/checkride/dist/pm/launch.js.map +1 -0
- package/node_modules/checkride/dist/pm/tools.d.ts +73 -0
- package/node_modules/checkride/dist/pm/tools.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/tools.js +124 -0
- package/node_modules/checkride/dist/pm/tools.js.map +1 -0
- package/node_modules/checkride/dist/pm/translate.d.ts +35 -9
- package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/translate.js +78 -18
- package/node_modules/checkride/dist/pm/translate.js.map +1 -1
- package/node_modules/checkride/dist/proc.d.ts +30 -0
- package/node_modules/checkride/dist/proc.d.ts.map +1 -0
- package/node_modules/checkride/dist/proc.js +42 -0
- package/node_modules/checkride/dist/proc.js.map +1 -0
- package/node_modules/checkride/dist/qa/cli.d.ts +20 -0
- package/node_modules/checkride/dist/qa/cli.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/cli.js +24 -0
- package/node_modules/checkride/dist/qa/cli.js.map +1 -0
- package/node_modules/checkride/dist/qa/dead.d.ts +39 -0
- package/node_modules/checkride/dist/qa/dead.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/dead.js +88 -0
- package/node_modules/checkride/dist/qa/dead.js.map +1 -0
- package/node_modules/checkride/dist/qa/dupes.d.ts +45 -0
- package/node_modules/checkride/dist/qa/dupes.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/dupes.js +62 -0
- package/node_modules/checkride/dist/qa/dupes.js.map +1 -0
- package/node_modules/checkride/dist/qa/health.d.ts +85 -0
- package/node_modules/checkride/dist/qa/health.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/health.js +109 -0
- package/node_modules/checkride/dist/qa/health.js.map +1 -0
- package/node_modules/checkride/dist/qa/index.d.ts +23 -0
- package/node_modules/checkride/dist/qa/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/index.js +22 -0
- package/node_modules/checkride/dist/qa/index.js.map +1 -0
- package/node_modules/checkride/dist/qa/mutation.d.ts +80 -0
- package/node_modules/checkride/dist/qa/mutation.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/mutation.js +195 -0
- package/node_modules/checkride/dist/qa/mutation.js.map +1 -0
- package/node_modules/checkride/dist/qa/qa.d.ts +71 -0
- package/node_modules/checkride/dist/qa/qa.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/qa.js +158 -0
- package/node_modules/checkride/dist/qa/qa.js.map +1 -0
- package/node_modules/checkride/dist/qa/read.d.ts +40 -0
- package/node_modules/checkride/dist/qa/read.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/read.js +47 -0
- package/node_modules/checkride/dist/qa/read.js.map +1 -0
- package/node_modules/checkride/dist/qa/render.d.ts +26 -0
- package/node_modules/checkride/dist/qa/render.d.ts.map +1 -0
- package/node_modules/checkride/dist/qa/render.js +310 -0
- package/node_modules/checkride/dist/qa/render.js.map +1 -0
- package/node_modules/checkride/dist/security.d.ts +45 -0
- package/node_modules/checkride/dist/security.d.ts.map +1 -0
- package/node_modules/checkride/dist/security.js +86 -0
- package/node_modules/checkride/dist/security.js.map +1 -0
- package/node_modules/checkride/dist/smoke.d.ts +5 -5
- package/node_modules/checkride/dist/smoke.js +5 -5
- package/node_modules/checkride/dist/snippets.d.ts +26 -29
- package/node_modules/checkride/dist/snippets.d.ts.map +1 -1
- package/node_modules/checkride/dist/snippets.js +26 -30
- package/node_modules/checkride/dist/snippets.js.map +1 -1
- package/node_modules/checkride/dist/tool-json.d.ts +33 -0
- package/node_modules/checkride/dist/tool-json.d.ts.map +1 -0
- package/node_modules/checkride/dist/tool-json.js +58 -0
- package/node_modules/checkride/dist/tool-json.js.map +1 -0
- package/node_modules/checkride/dist/triage/cli.d.ts +21 -0
- package/node_modules/checkride/dist/triage/cli.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/cli.js +26 -0
- package/node_modules/checkride/dist/triage/cli.js.map +1 -0
- package/node_modules/checkride/dist/triage/doctor-fold.d.ts +51 -0
- package/node_modules/checkride/dist/triage/doctor-fold.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/doctor-fold.js +88 -0
- package/node_modules/checkride/dist/triage/doctor-fold.js.map +1 -0
- package/node_modules/checkride/dist/triage/env.d.ts +54 -0
- package/node_modules/checkride/dist/triage/env.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/env.js +81 -0
- package/node_modules/checkride/dist/triage/env.js.map +1 -0
- package/node_modules/checkride/dist/triage/gate.d.ts +61 -0
- package/node_modules/checkride/dist/triage/gate.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/gate.js +121 -0
- package/node_modules/checkride/dist/triage/gate.js.map +1 -0
- package/node_modules/checkride/dist/triage/index.d.ts +22 -0
- package/node_modules/checkride/dist/triage/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/index.js +20 -0
- package/node_modules/checkride/dist/triage/index.js.map +1 -0
- package/node_modules/checkride/dist/triage/render.d.ts +19 -0
- package/node_modules/checkride/dist/triage/render.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/render.js +419 -0
- package/node_modules/checkride/dist/triage/render.js.map +1 -0
- package/node_modules/checkride/dist/triage/triage.d.ts +83 -0
- package/node_modules/checkride/dist/triage/triage.d.ts.map +1 -0
- package/node_modules/checkride/dist/triage/triage.js +131 -0
- package/node_modules/checkride/dist/triage/triage.js.map +1 -0
- package/node_modules/checkride/package.json +17 -15
- package/node_modules/checkride/schema/checkride.config.schema.json +62 -5
- package/node_modules/checkride/schema/checkride.summary.schema.json +1 -1
- package/node_modules/checkride/skills/check/SKILL.md +257 -0
- package/node_modules/checkride/skills/qa/SKILL.md +231 -0
- package/node_modules/checkride/templates/shared/cspell.json +1 -0
- package/node_modules/checkride/templates/shared/oxlintrc.json +1 -1
- package/package.json +2 -2
- package/skills/build/SKILL.md +23 -19
- package/skills/finish/SKILL.md +3 -1
- package/skills/harvest/SKILL.md +2 -1
- package/skills/plan/SKILL.md +8 -7
- package/skills/recover/SKILL.md +28 -0
- package/skills/refine/SKILL.md +5 -4
- package/skills/spike/SKILL.md +1 -1
- package/skills/step/SKILL.md +4 -4
- package/skills/verify/SKILL.md +19 -14
- package/templates/intent.md +8 -7
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The freshness window — which `.check/` files belong to the run that wrote the
|
|
3
|
+
* summary sitting beside them.
|
|
4
|
+
*
|
|
5
|
+
* The window is `[timestamp - total_duration_ms, ∞)`: the run's *start*, not
|
|
6
|
+
* its end, because `timestamp` is stamped once every check has finished (see
|
|
7
|
+
* `buildSummary` in `../orchestrator.ts`) and every artifact the run just wrote
|
|
8
|
+
* is therefore older than it. Anything outside the window is labelled with its
|
|
9
|
+
* age, never silently dropped.
|
|
10
|
+
*
|
|
11
|
+
* Why a window at all, and why this derivation is contract-legal:
|
|
12
|
+
* `docs/plugin.md` §How the readers decide an artifact is stale.
|
|
13
|
+
*
|
|
14
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
15
|
+
*/
|
|
16
|
+
const FRESH = { state: 'fresh', ageMs: null };
|
|
17
|
+
const UNKNOWN = { state: 'unknown', ageMs: null };
|
|
18
|
+
/**
|
|
19
|
+
* Epoch milliseconds at which the run began, or `null` when `timestamp` is not
|
|
20
|
+
* a parseable date. Callers treat `null` as "cannot judge freshness" rather
|
|
21
|
+
* than guessing in either direction.
|
|
22
|
+
*/
|
|
23
|
+
export function runWindowStart(summary) {
|
|
24
|
+
const stamped = Date.parse(summary.timestamp);
|
|
25
|
+
if (Number.isNaN(stamped))
|
|
26
|
+
return null;
|
|
27
|
+
return stamped - summary.total_duration_ms;
|
|
28
|
+
}
|
|
29
|
+
/** Classify one artifact's mtime against the window from {@link runWindowStart}. */
|
|
30
|
+
export function classifyFreshness(mtimeMs, windowStart) {
|
|
31
|
+
if (windowStart === null)
|
|
32
|
+
return UNKNOWN;
|
|
33
|
+
if (mtimeMs >= windowStart)
|
|
34
|
+
return FRESH;
|
|
35
|
+
return { state: 'stale', ageMs: windowStart - mtimeMs };
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=freshness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.js","sourceRoot":"","sources":["../../src/artifacts/freshness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH,MAAM,KAAK,GAAc,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzD,MAAM,OAAO,GAAc,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAE7D;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC;AAC7C,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,WAA0B;IAC3E,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IACzC,IAAI,OAAO,IAAI,WAAW;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,EAAE,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifacts module — the shared, bounded read of what a checkride run left in
|
|
3
|
+
* `.check/`. Parse the summary and pin its schema, judge each file against the
|
|
4
|
+
* run's freshness window, resolve a slot's raw output by the documented
|
|
5
|
+
* convention, and size what was found without opening it.
|
|
6
|
+
*
|
|
7
|
+
* Both bundled-plugin readers (`../triage` and `../qa`) consume the
|
|
8
|
+
* contract only through here — one copy of the summary parse,
|
|
9
|
+
* one freshness rule, one raw-output lookup. This barrel is the module's only
|
|
10
|
+
* public surface: siblings import from `../artifacts/index.js`, never from a
|
|
11
|
+
* file inside it.
|
|
12
|
+
*/
|
|
13
|
+
export { configuredSlots } from './configured.js';
|
|
14
|
+
export { formatBytes, formatDuration, tail } from './format.js';
|
|
15
|
+
export { classifyFreshness, runWindowStart } from './freshness.js';
|
|
16
|
+
export type { Freshness } from './freshness.js';
|
|
17
|
+
export { asNumberOrNull, asRecord, asRecordArray, asStringOrNull, isRecord, parseJson } from './json.js';
|
|
18
|
+
export { listArtifacts, resolveRawOutput, statArtifact } from './raw.js';
|
|
19
|
+
export type { ArtifactFile, RawOutput } from './raw.js';
|
|
20
|
+
export { CHECK_DIR, parseSummary, readSummary, SUPPORTED_SCHEMA_VERSION } from './summary.js';
|
|
21
|
+
export type { SummaryRead } from './summary.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/artifacts/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzG,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACzE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAExD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC9F,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifacts module — the shared, bounded read of what a checkride run left in
|
|
3
|
+
* `.check/`. Parse the summary and pin its schema, judge each file against the
|
|
4
|
+
* run's freshness window, resolve a slot's raw output by the documented
|
|
5
|
+
* convention, and size what was found without opening it.
|
|
6
|
+
*
|
|
7
|
+
* Both bundled-plugin readers (`../triage` and `../qa`) consume the
|
|
8
|
+
* contract only through here — one copy of the summary parse,
|
|
9
|
+
* one freshness rule, one raw-output lookup. This barrel is the module's only
|
|
10
|
+
* public surface: siblings import from `../artifacts/index.js`, never from a
|
|
11
|
+
* file inside it.
|
|
12
|
+
*/
|
|
13
|
+
export { configuredSlots } from './configured.js';
|
|
14
|
+
export { formatBytes, formatDuration, tail } from './format.js';
|
|
15
|
+
export { classifyFreshness, runWindowStart } from './freshness.js';
|
|
16
|
+
export { asNumberOrNull, asRecord, asRecordArray, asStringOrNull, isRecord, parseJson } from './json.js';
|
|
17
|
+
export { listArtifacts, resolveRawOutput, statArtifact } from './raw.js';
|
|
18
|
+
export { CHECK_DIR, parseSummary, readSummary, SUPPORTED_SCHEMA_VERSION } from './summary.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/artifacts/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGnE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzG,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGzE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defensive JSON narrowing for artifact reads.
|
|
3
|
+
*
|
|
4
|
+
* Both readers parse files a *different* checkride version may have written, so
|
|
5
|
+
* every field access goes through a guard rather than a cast: a reader that
|
|
6
|
+
* crashes on an unexpected shape is worse than one that says what it could not
|
|
7
|
+
* read. These are the smallest primitives that make a `JSON.parse` result
|
|
8
|
+
* usable without `any` and without asserting a type onto unvalidated bytes.
|
|
9
|
+
*
|
|
10
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
11
|
+
*/
|
|
12
|
+
import { isRecord } from '../json.js';
|
|
13
|
+
import type { JsonRecord } from '../json.js';
|
|
14
|
+
export { isRecord };
|
|
15
|
+
export type { JsonRecord };
|
|
16
|
+
/** A string field, or `null` for every other type — including a missing key. */
|
|
17
|
+
export declare function asStringOrNull(value: unknown): string | null;
|
|
18
|
+
/** A number field, or `null` for every other type — including a missing key. */
|
|
19
|
+
export declare function asNumberOrNull(value: unknown): number | null;
|
|
20
|
+
/**
|
|
21
|
+
* A nested object, or an empty one when the key is missing or holds something
|
|
22
|
+
* else. Reading a handful of fields off a block that may not exist is the
|
|
23
|
+
* common shape in these artifacts, and `{}` makes every such read a `null`
|
|
24
|
+
* rather than a branch at each call site.
|
|
25
|
+
*/
|
|
26
|
+
export declare function asRecord(value: unknown): JsonRecord;
|
|
27
|
+
/** The object entries of an array field; `[]` when it is not an array at all. */
|
|
28
|
+
export declare function asRecordArray(value: unknown): JsonRecord[];
|
|
29
|
+
/** Parse JSON without throwing: the parsed value, or `null` when it is not JSON. */
|
|
30
|
+
export declare function parseJson(raw: string): unknown;
|
|
31
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/artifacts/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK7C,OAAO,EAAE,QAAQ,EAAE,CAAC;AACpB,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAE5D;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAEnD;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,EAAE,CAE1D;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAM9C"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defensive JSON narrowing for artifact reads.
|
|
3
|
+
*
|
|
4
|
+
* Both readers parse files a *different* checkride version may have written, so
|
|
5
|
+
* every field access goes through a guard rather than a cast: a reader that
|
|
6
|
+
* crashes on an unexpected shape is worse than one that says what it could not
|
|
7
|
+
* read. These are the smallest primitives that make a `JSON.parse` result
|
|
8
|
+
* usable without `any` and without asserting a type onto unvalidated bytes.
|
|
9
|
+
*
|
|
10
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
11
|
+
*/
|
|
12
|
+
import { isRecord } from '../json.js';
|
|
13
|
+
// `isRecord`/`JsonRecord` are the package-wide primitives (`../json.ts`);
|
|
14
|
+
// re-exported here so the artifacts barrel keeps its existing surface and the
|
|
15
|
+
// readers' import sites are unchanged.
|
|
16
|
+
export { isRecord };
|
|
17
|
+
/** A string field, or `null` for every other type — including a missing key. */
|
|
18
|
+
export function asStringOrNull(value) {
|
|
19
|
+
return typeof value === 'string' ? value : null;
|
|
20
|
+
}
|
|
21
|
+
/** A number field, or `null` for every other type — including a missing key. */
|
|
22
|
+
export function asNumberOrNull(value) {
|
|
23
|
+
return typeof value === 'number' ? value : null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A nested object, or an empty one when the key is missing or holds something
|
|
27
|
+
* else. Reading a handful of fields off a block that may not exist is the
|
|
28
|
+
* common shape in these artifacts, and `{}` makes every such read a `null`
|
|
29
|
+
* rather than a branch at each call site.
|
|
30
|
+
*/
|
|
31
|
+
export function asRecord(value) {
|
|
32
|
+
return isRecord(value) ? value : {};
|
|
33
|
+
}
|
|
34
|
+
/** The object entries of an array field; `[]` when it is not an array at all. */
|
|
35
|
+
export function asRecordArray(value) {
|
|
36
|
+
return Array.isArray(value) ? value.filter(isRecord) : [];
|
|
37
|
+
}
|
|
38
|
+
/** Parse JSON without throwing: the parsed value, or `null` when it is not JSON. */
|
|
39
|
+
export function parseJson(raw) {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(raw);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/artifacts/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,0EAA0E;AAC1E,8EAA8E;AAC9E,uCAAuC;AACvC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAGpB,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACtC,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5D,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locating a slot's raw output — the bytes the summary indexes but usually does
|
|
3
|
+
* not name.
|
|
4
|
+
*
|
|
5
|
+
* `output_file` is populated only when a tool emits JSON on stdout, so most
|
|
6
|
+
* slots carry `null` there while their real output sits in `.check/` under the
|
|
7
|
+
* documented convention (`<slot>.json`, else `<slot>.stdout.txt` /
|
|
8
|
+
* `<slot>.stderr.txt`). On a full run of this repo, 8 of 17 checks name no file
|
|
9
|
+
* — `test` among them, the slot most likely to need triage. A reader that
|
|
10
|
+
* reported "no output" for those would be wrong about the one thing it exists
|
|
11
|
+
* to get right, so it owns the convention as a fallback.
|
|
12
|
+
*
|
|
13
|
+
* Everything found is measured, never opened: the readers report *sizes* and
|
|
14
|
+
* point at the file. `mutation.json` in this repo is 2.3 MB; `test.json` is
|
|
15
|
+
* 650 KB beside a 5 KB `test.stdout.txt` saying the same thing, which is why a
|
|
16
|
+
* smaller text sibling wins over a large JSON one.
|
|
17
|
+
*
|
|
18
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
19
|
+
*/
|
|
20
|
+
import type { Freshness } from './freshness.js';
|
|
21
|
+
/** One measured `.check/` file. Size and standing only — never contents. */
|
|
22
|
+
export type ArtifactFile = {
|
|
23
|
+
/** Name within `.check/`, as an agent would type it: `lint.json`. */
|
|
24
|
+
file: string;
|
|
25
|
+
/** Absolute path, for a caller that does choose to open it. */
|
|
26
|
+
path: string;
|
|
27
|
+
bytes: number;
|
|
28
|
+
mtimeMs: number;
|
|
29
|
+
freshness: Freshness;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A slot's raw output: the file worth reading first, plus every other candidate
|
|
33
|
+
* found — so a caller can say what else is there instead of implying the chosen
|
|
34
|
+
* one is all of it.
|
|
35
|
+
*/
|
|
36
|
+
export type RawOutput = {
|
|
37
|
+
chosen: ArtifactFile;
|
|
38
|
+
candidates: ArtifactFile[];
|
|
39
|
+
};
|
|
40
|
+
/** Measure one `.check/` file, or `null` when it does not exist. */
|
|
41
|
+
export declare function statArtifact(checkDir: string, file: string, windowStart: number | null): Promise<ArtifactFile | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Measure every file directly in `checkDir`, name-ordered so the render is
|
|
44
|
+
* byte-stable. Subdirectories and anything that vanishes mid-read drop out;
|
|
45
|
+
* an absent directory is `[]`, because "there is nothing there" is an answer.
|
|
46
|
+
*
|
|
47
|
+
* This is the fallback index. Normally the summary names what to look at and
|
|
48
|
+
* {@link resolveRawOutput} locates it, but when the summary cannot be parsed
|
|
49
|
+
* the directory itself is the only inventory that exists — and a reader that
|
|
50
|
+
* gave up there would leave an agent to run `ls` by hand, which is the
|
|
51
|
+
* unbounded read this module exists to prevent.
|
|
52
|
+
*/
|
|
53
|
+
export declare function listArtifacts(checkDir: string, windowStart: number | null): Promise<ArtifactFile[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve a slot's raw output under `checkDir`, freshness-gating whatever it
|
|
56
|
+
* finds — a stale candidate is labelled, not dropped. `null` only when the slot
|
|
57
|
+
* genuinely wrote nothing.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveRawOutput(checkDir: string, slot: string, outputFile: string | null, windowStart: number | null): Promise<RawOutput | null>;
|
|
60
|
+
//# sourceMappingURL=raw.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw.d.ts","sourceRoot":"","sources":["../../src/artifacts/raw.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,4EAA4E;AAC5E,MAAM,MAAM,YAAY,GAAG;IACzB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC;AAE7E,oEAAoE;AACpE,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAS9B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAWzG;AAwBD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAO3B"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locating a slot's raw output — the bytes the summary indexes but usually does
|
|
3
|
+
* not name.
|
|
4
|
+
*
|
|
5
|
+
* `output_file` is populated only when a tool emits JSON on stdout, so most
|
|
6
|
+
* slots carry `null` there while their real output sits in `.check/` under the
|
|
7
|
+
* documented convention (`<slot>.json`, else `<slot>.stdout.txt` /
|
|
8
|
+
* `<slot>.stderr.txt`). On a full run of this repo, 8 of 17 checks name no file
|
|
9
|
+
* — `test` among them, the slot most likely to need triage. A reader that
|
|
10
|
+
* reported "no output" for those would be wrong about the one thing it exists
|
|
11
|
+
* to get right, so it owns the convention as a fallback.
|
|
12
|
+
*
|
|
13
|
+
* Everything found is measured, never opened: the readers report *sizes* and
|
|
14
|
+
* point at the file. `mutation.json` in this repo is 2.3 MB; `test.json` is
|
|
15
|
+
* 650 KB beside a 5 KB `test.stdout.txt` saying the same thing, which is why a
|
|
16
|
+
* smaller text sibling wins over a large JSON one.
|
|
17
|
+
*
|
|
18
|
+
* The `../artifacts` barrel is this module's only public surface.
|
|
19
|
+
*/
|
|
20
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { classifyFreshness } from './freshness.js';
|
|
23
|
+
/** Measure one `.check/` file, or `null` when it does not exist. */
|
|
24
|
+
export async function statArtifact(checkDir, file, windowStart) {
|
|
25
|
+
const path = join(checkDir, file);
|
|
26
|
+
try {
|
|
27
|
+
const stats = await stat(path);
|
|
28
|
+
if (!stats.isFile())
|
|
29
|
+
return null;
|
|
30
|
+
return { file, path, bytes: stats.size, mtimeMs: stats.mtimeMs, freshness: classifyFreshness(stats.mtimeMs, windowStart) };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Measure every file directly in `checkDir`, name-ordered so the render is
|
|
38
|
+
* byte-stable. Subdirectories and anything that vanishes mid-read drop out;
|
|
39
|
+
* an absent directory is `[]`, because "there is nothing there" is an answer.
|
|
40
|
+
*
|
|
41
|
+
* This is the fallback index. Normally the summary names what to look at and
|
|
42
|
+
* {@link resolveRawOutput} locates it, but when the summary cannot be parsed
|
|
43
|
+
* the directory itself is the only inventory that exists — and a reader that
|
|
44
|
+
* gave up there would leave an agent to run `ls` by hand, which is the
|
|
45
|
+
* unbounded read this module exists to prevent.
|
|
46
|
+
*/
|
|
47
|
+
export async function listArtifacts(checkDir, windowStart) {
|
|
48
|
+
let names;
|
|
49
|
+
try {
|
|
50
|
+
names = await readdir(checkDir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const found = await Promise.all(names.toSorted().map((name) => statArtifact(checkDir, name, windowStart)));
|
|
56
|
+
return found.filter((file) => file !== null);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The convention, in priority order: what the summary named, then the JSON file,
|
|
60
|
+
* then the captured streams. Deduped, because `output_file` is usually
|
|
61
|
+
* `<slot>.json` when it is set at all.
|
|
62
|
+
*/
|
|
63
|
+
function candidateNames(slot, outputFile) {
|
|
64
|
+
const names = [outputFile, `${slot}.json`, `${slot}.stdout.txt`, `${slot}.stderr.txt`];
|
|
65
|
+
return [...new Set(names.filter((name) => name !== null))];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Pick what to read first. The convention order wins, except that a *smaller*
|
|
69
|
+
* text sibling beats a large JSON file: `test.stdout.txt` (5 KB) carries the
|
|
70
|
+
* same failure `test.json` (650 KB) does, and a reader that sends an agent to
|
|
71
|
+
* the big one has spent the context it was built to save. A text file that is
|
|
72
|
+
* already first stays first — stdout is the diagnostics, stderr the leftovers.
|
|
73
|
+
*/
|
|
74
|
+
function chooseRaw(first, rest) {
|
|
75
|
+
if (first.file.endsWith('.txt'))
|
|
76
|
+
return first;
|
|
77
|
+
return rest.find((c) => c.file.endsWith('.txt') && c.bytes < first.bytes) ?? first;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolve a slot's raw output under `checkDir`, freshness-gating whatever it
|
|
81
|
+
* finds — a stale candidate is labelled, not dropped. `null` only when the slot
|
|
82
|
+
* genuinely wrote nothing.
|
|
83
|
+
*/
|
|
84
|
+
export async function resolveRawOutput(checkDir, slot, outputFile, windowStart) {
|
|
85
|
+
const found = await Promise.all(candidateNames(slot, outputFile).map((name) => statArtifact(checkDir, name, windowStart)));
|
|
86
|
+
const [first, ...rest] = found.filter((c) => c !== null);
|
|
87
|
+
if (first === undefined)
|
|
88
|
+
return null;
|
|
89
|
+
return { chosen: chooseRaw(first, rest), candidates: [first, ...rest] };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=raw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw.js","sourceRoot":"","sources":["../../src/artifacts/raw.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|