plumbbob 0.6.5 → 0.8.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 +2 -2
- package/README.md +24 -7
- package/dist/cli-core.js +7 -2
- package/dist/lib/buildlog.js +5 -2
- package/dist/lib/check.js +33 -1
- package/dist/lib/git.js +17 -0
- package/dist/lib/intent.js +3 -2
- package/dist/lib/latch.js +97 -0
- package/dist/lib/orient.js +31 -1
- package/dist/lib/sidecar.js +116 -0
- package/dist/verbs/build.js +5 -1
- package/dist/verbs/checkpoint.js +61 -7
- package/dist/verbs/doctor.js +33 -2
- package/dist/verbs/finish.js +57 -1
- package/dist/verbs/revert.js +19 -1
- package/dist/verbs/start.js +22 -2
- package/dist/verbs/status.js +10 -3
- package/dist/verbs/turn.js +106 -0
- package/hooks/hooks.json +15 -0
- package/hooks/pre-bash-commit.sh +69 -0
- package/node_modules/checkride/CHANGELOG.md +111 -0
- package/node_modules/checkride/README.md +185 -20
- package/node_modules/checkride/dist/adapters.d.ts +9 -4
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +62 -2
- package/node_modules/checkride/dist/adapters.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/hook.d.ts +63 -0
- package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/hook.js +92 -0
- package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -0
- package/node_modules/checkride/dist/agent-setup/index.d.ts +8 -0
- package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/agent-setup/index.js +7 -0
- package/node_modules/checkride/dist/agent-setup/index.js.map +1 -0
- package/node_modules/checkride/dist/atomic.d.ts +13 -0
- package/node_modules/checkride/dist/atomic.d.ts.map +1 -0
- package/node_modules/checkride/dist/atomic.js +24 -0
- package/node_modules/checkride/dist/atomic.js.map +1 -0
- package/node_modules/checkride/dist/baseline/command.d.ts +42 -0
- package/node_modules/checkride/dist/baseline/command.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/command.js +58 -0
- package/node_modules/checkride/dist/baseline/command.js.map +1 -0
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +41 -0
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/fingerprint.js +119 -0
- package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -0
- package/node_modules/checkride/dist/baseline/index.d.ts +13 -0
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/index.js +10 -0
- package/node_modules/checkride/dist/baseline/index.js.map +1 -0
- package/node_modules/checkride/dist/baseline/store.d.ts +67 -0
- package/node_modules/checkride/dist/baseline/store.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/store.js +110 -0
- package/node_modules/checkride/dist/baseline/store.js.map +1 -0
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +37 -3
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +43 -3
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +95 -22
- package/node_modules/checkride/dist/config.js.map +1 -1
- package/node_modules/checkride/dist/digest/digest.d.ts +52 -0
- package/node_modules/checkride/dist/digest/digest.d.ts.map +1 -0
- package/node_modules/checkride/dist/digest/digest.js +147 -0
- package/node_modules/checkride/dist/digest/digest.js.map +1 -0
- package/node_modules/checkride/dist/digest/index.d.ts +8 -0
- package/node_modules/checkride/dist/digest/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/digest/index.js +7 -0
- package/node_modules/checkride/dist/digest/index.js.map +1 -0
- package/node_modules/checkride/dist/doctor.d.ts +4 -0
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +33 -10
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/index.d.ts +1 -1
- package/node_modules/checkride/dist/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/index.js +1 -1
- package/node_modules/checkride/dist/index.js.map +1 -1
- package/node_modules/checkride/dist/init.d.ts +38 -0
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +97 -8
- package/node_modules/checkride/dist/init.js.map +1 -1
- package/node_modules/checkride/dist/orchestrator.d.ts +58 -1
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +155 -27
- package/node_modules/checkride/dist/orchestrator.js.map +1 -1
- package/node_modules/checkride/dist/pm/detect.d.ts +21 -0
- package/node_modules/checkride/dist/pm/detect.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/detect.js +60 -0
- package/node_modules/checkride/dist/pm/detect.js.map +1 -0
- package/node_modules/checkride/dist/pm/index.d.ts +10 -0
- package/node_modules/checkride/dist/pm/index.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/index.js +9 -0
- package/node_modules/checkride/dist/pm/index.js.map +1 -0
- package/node_modules/checkride/dist/pm/translate.d.ts +28 -0
- package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -0
- package/node_modules/checkride/dist/pm/translate.js +40 -0
- package/node_modules/checkride/dist/pm/translate.js.map +1 -0
- package/node_modules/checkride/package.json +20 -17
- package/node_modules/checkride/schema/checkride.config.schema.json +97 -0
- package/node_modules/checkride/schema/checkride.summary.schema.json +57 -0
- package/node_modules/checkride/templates/shared/cspell.json +1 -0
- package/node_modules/checkride/templates/shared/prettierrc.json +6 -0
- package/package.json +8 -4
- package/skills/pb-build/SKILL.md +32 -14
- package/skills/pb-finish/SKILL.md +5 -6
- package/skills/pb-harvest/SKILL.md +1 -2
- package/skills/pb-park/SKILL.md +1 -1
- package/skills/pb-plan/SKILL.md +37 -5
- package/skills/pb-refine/SKILL.md +0 -1
- package/skills/pb-status/SKILL.md +5 -1
- package/skills/pb-step/SKILL.md +5 -4
- package/skills/pb-verify/SKILL.md +26 -9
- package/templates/intent.md +4 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Stop hook — the mechanical half of the agent contract.
|
|
3
|
+
*
|
|
4
|
+
* `init` and `checkride agent-setup` write an idempotent Stop hook into
|
|
5
|
+
* `.claude/settings.json`. It runs the project's `check` script as a hard gate:
|
|
6
|
+
* exit 2 blocks the agent from finishing while the pipeline is red, so "exit 0 =
|
|
7
|
+
* done" becomes enforcement, not just advice. The command uses the *detected*
|
|
8
|
+
* package manager's run form (`pnpm run check`, `npm run check`, `yarn run
|
|
9
|
+
* check`, `bun run check`) so the hook works in any repo, not only pnpm ones
|
|
10
|
+
* (b7). Merging is surgical: unrelated hooks, other Stop groups, and every other
|
|
11
|
+
* settings key are preserved, and re-applying with the same PM is a no-op.
|
|
12
|
+
*
|
|
13
|
+
* This module owns only the hook; the AGENTS.md stanza and the higher-level
|
|
14
|
+
* `agent-setup` command live in `init.ts`, which imports the writer here — a
|
|
15
|
+
* single direction, so there is no cycle.
|
|
16
|
+
*/
|
|
17
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
19
|
+
import { detectPackageManager } from '../pm/index.js';
|
|
20
|
+
/** Project-shared Claude Code settings (committed), relative to the repo root. */
|
|
21
|
+
export const CLAUDE_SETTINGS_FILE = '.claude/settings.json';
|
|
22
|
+
/**
|
|
23
|
+
* Stable, package-manager-independent marker identifying checkride's Stop hook
|
|
24
|
+
* inside a settings file. The run prefix varies by PM, so identity keys on the
|
|
25
|
+
* guidance message, which does not.
|
|
26
|
+
*/
|
|
27
|
+
const HOOK_SENTINEL = 'checkride: the gate is red';
|
|
28
|
+
/** True when a hook entry is checkride's (matched by the PM-independent sentinel). */
|
|
29
|
+
function isCheckrideHook(hook) {
|
|
30
|
+
return hook.type === 'command' && typeof hook.command === 'string' && hook.command.includes(HOOK_SENTINEL);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The Stop-hook command for `pm`: run the gate, and on red print guidance and
|
|
34
|
+
* exit 2 (the blocking code — a plain exit 1 lets the agent stop anyway).
|
|
35
|
+
*/
|
|
36
|
+
export function stopHookCommand(pm) {
|
|
37
|
+
const run = `${pm} run check`;
|
|
38
|
+
const guidance = `${HOOK_SENTINEL} — read .check/summary.json, fix the failing slot, then finish (do not stop while checkride is red).`;
|
|
39
|
+
return `${run} || { echo '${guidance}' >&2; exit 2; }`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Merge checkride's Stop hook into parsed settings, idempotently. An existing
|
|
43
|
+
* checkride hook (found by its sentinel) has its command refreshed in place;
|
|
44
|
+
* otherwise a new Stop group is appended. Sibling hooks in the same group, other
|
|
45
|
+
* Stop groups, and every unrelated settings key are left untouched, so applying
|
|
46
|
+
* twice with the same command yields deep-equal settings.
|
|
47
|
+
*/
|
|
48
|
+
export function applyStopHook(settings, command) {
|
|
49
|
+
const hooks = { ...settings.hooks };
|
|
50
|
+
const stop = Array.isArray(hooks.Stop) ? hooks.Stop.map((g) => ({ ...g })) : [];
|
|
51
|
+
const idx = stop.findIndex((g) => (g.hooks ?? []).some(isCheckrideHook));
|
|
52
|
+
const existing = idx >= 0 ? stop[idx] : undefined;
|
|
53
|
+
if (existing) {
|
|
54
|
+
stop[idx] = {
|
|
55
|
+
...existing,
|
|
56
|
+
hooks: (existing.hooks ?? []).map((h) => (isCheckrideHook(h) ? { ...h, command } : h)),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
stop.push({ hooks: [{ type: 'command', command }] });
|
|
61
|
+
}
|
|
62
|
+
return { ...settings, hooks: { ...hooks, Stop: stop } };
|
|
63
|
+
}
|
|
64
|
+
async function readIfExists(path) {
|
|
65
|
+
try {
|
|
66
|
+
return await readFile(path, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Write or refresh the Stop hook in `cwd/.claude/settings.json` for the detected
|
|
74
|
+
* (or provided) package manager, preserving any other settings. Returns whether
|
|
75
|
+
* the file changed: a second run with the same PM leaves it byte-identical, so
|
|
76
|
+
* `changed` is `false`. `dryRun` computes the result without writing.
|
|
77
|
+
*/
|
|
78
|
+
export async function writeStopHook(cwd, opts = {}) {
|
|
79
|
+
const path = join(cwd, CLAUDE_SETTINGS_FILE);
|
|
80
|
+
const pm = opts.pm ?? detectPackageManager({ cwd });
|
|
81
|
+
const raw = await readIfExists(path);
|
|
82
|
+
const settings = raw ? JSON.parse(raw) : {};
|
|
83
|
+
const next = applyStopHook(settings, stopHookCommand(pm));
|
|
84
|
+
const nextRaw = `${JSON.stringify(next, null, 2)}\n`;
|
|
85
|
+
const changed = raw !== nextRaw;
|
|
86
|
+
if (changed && !opts.dryRun) {
|
|
87
|
+
await mkdir(dirname(path), { recursive: true });
|
|
88
|
+
await writeFile(path, nextRaw);
|
|
89
|
+
}
|
|
90
|
+
return { path: CLAUDE_SETTINGS_FILE, changed };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../../src/agent-setup/hook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-setup module — the Claude Code Stop hook (step 12). This barrel is the
|
|
3
|
+
* module's only public surface (C2): siblings import from `../agent-setup`,
|
|
4
|
+
* never from `./hook.js` directly.
|
|
5
|
+
*/
|
|
6
|
+
export type { StopHookResult } from './hook.js';
|
|
7
|
+
export { applyStopHook, CLAUDE_SETTINGS_FILE, stopHookCommand, writeStopHook } from './hook.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent-setup/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-setup module — the Claude Code Stop hook (step 12). This barrel is the
|
|
3
|
+
* module's only public surface (C2): siblings import from `../agent-setup`,
|
|
4
|
+
* never from `./hook.js` directly.
|
|
5
|
+
*/
|
|
6
|
+
export { applyStopHook, CLAUDE_SETTINGS_FILE, stopHookCommand, writeStopHook } from './hook.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent-setup/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crash-consistent artifact writes.
|
|
3
|
+
*
|
|
4
|
+
* Consumers parse `.check/summary.json` (and the digest, the raw slot files,
|
|
5
|
+
* and the committed baseline) as machine input; a run killed mid-write must
|
|
6
|
+
* never leave them a half-written file to read. Writing to a temp sibling
|
|
7
|
+
* and renaming into place makes each artifact either the previous complete
|
|
8
|
+
* version or the new complete version — `rename(2)` is atomic within a
|
|
9
|
+
* directory — never torn.
|
|
10
|
+
*/
|
|
11
|
+
/** Write `data` to `path` atomically: temp sibling first, then rename over. */
|
|
12
|
+
export declare function writeFileAtomic(path: string, data: string): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=atomic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,+EAA+E;AAC/E,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS/E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crash-consistent artifact writes.
|
|
3
|
+
*
|
|
4
|
+
* Consumers parse `.check/summary.json` (and the digest, the raw slot files,
|
|
5
|
+
* and the committed baseline) as machine input; a run killed mid-write must
|
|
6
|
+
* never leave them a half-written file to read. Writing to a temp sibling
|
|
7
|
+
* and renaming into place makes each artifact either the previous complete
|
|
8
|
+
* version or the new complete version — `rename(2)` is atomic within a
|
|
9
|
+
* directory — never torn.
|
|
10
|
+
*/
|
|
11
|
+
import { rename, rm, writeFile } from 'node:fs/promises';
|
|
12
|
+
/** Write `data` to `path` atomically: temp sibling first, then rename over. */
|
|
13
|
+
export async function writeFileAtomic(path, data) {
|
|
14
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
15
|
+
try {
|
|
16
|
+
await writeFile(tmp, data);
|
|
17
|
+
await rename(tmp, path);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
await rm(tmp, { force: true });
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=atomic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic.js","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEzD,+EAA+E;AAC/E,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,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3B,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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `checkride baseline` command (baseline part 2).
|
|
3
|
+
*
|
|
4
|
+
* Runs the normal pipeline, fingerprints every fingerprintable slot's raw output
|
|
5
|
+
* (step 4's per-adapter extractors), and writes the result to
|
|
6
|
+
* `checkride.baseline.json` at repo root — beside `checkride.config.json`,
|
|
7
|
+
* committed, NOT under the gitignored `.check/` (D9). The file grandfathers a
|
|
8
|
+
* legacy repo's existing diagnostics so a later baseline-aware run (step 6) can
|
|
9
|
+
* subtract them and fail only on genuinely new findings.
|
|
10
|
+
*
|
|
11
|
+
* Only slots whose adapter has an extractor participate: a supported-but-clean
|
|
12
|
+
* slot records an empty array (it opted in and had nothing to grandfather),
|
|
13
|
+
* while a slot with no extractor (fallow, tsc, links, …) simply never appears —
|
|
14
|
+
* exactly the "supported vs. unsupported" distinction the fingerprint contract
|
|
15
|
+
* draws. Running each check under the resolved package manager and capturing raw
|
|
16
|
+
* output is delegated to {@link runChecks}, so baseline fingerprints precisely
|
|
17
|
+
* the bytes a normal run would.
|
|
18
|
+
*/
|
|
19
|
+
import type { Adapter, Slot } from '../adapters.js';
|
|
20
|
+
import type { CheckrideConfig } from '../config.js';
|
|
21
|
+
import type { CheckRunner, Out } from '../orchestrator.js';
|
|
22
|
+
import type { PackageManager } from '../pm/index.js';
|
|
23
|
+
import type { Baseline } from './store.js';
|
|
24
|
+
export type BaselineOptions = {
|
|
25
|
+
cwd?: string;
|
|
26
|
+
slots?: readonly Slot[];
|
|
27
|
+
adapters?: readonly Adapter[];
|
|
28
|
+
config?: CheckrideConfig | null;
|
|
29
|
+
stdout?: Out;
|
|
30
|
+
stderr?: Out;
|
|
31
|
+
runner?: CheckRunner;
|
|
32
|
+
/** Package manager to run under; detected from `cwd` when omitted. */
|
|
33
|
+
pm?: PackageManager;
|
|
34
|
+
};
|
|
35
|
+
export type BaselineResult = {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
exitCode: number;
|
|
38
|
+
baseline: Baseline;
|
|
39
|
+
};
|
|
40
|
+
/** Run the pipeline and write the per-adapter fingerprint baseline to `cwd`. */
|
|
41
|
+
export declare function runBaseline(options: BaselineOptions): Promise<BaselineResult>;
|
|
42
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/baseline/command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,GAAG,EAAc,MAAM,oBAAoB,CAAC;AAEvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC9B,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,sEAAsE;IACtE,EAAE,CAAC,EAAE,cAAc,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEnF,gFAAgF;AAChF,wBAAsB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAsCnF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `checkride baseline` command (baseline part 2).
|
|
3
|
+
*
|
|
4
|
+
* Runs the normal pipeline, fingerprints every fingerprintable slot's raw output
|
|
5
|
+
* (step 4's per-adapter extractors), and writes the result to
|
|
6
|
+
* `checkride.baseline.json` at repo root — beside `checkride.config.json`,
|
|
7
|
+
* committed, NOT under the gitignored `.check/` (D9). The file grandfathers a
|
|
8
|
+
* legacy repo's existing diagnostics so a later baseline-aware run (step 6) can
|
|
9
|
+
* subtract them and fail only on genuinely new findings.
|
|
10
|
+
*
|
|
11
|
+
* Only slots whose adapter has an extractor participate: a supported-but-clean
|
|
12
|
+
* slot records an empty array (it opted in and had nothing to grandfather),
|
|
13
|
+
* while a slot with no extractor (fallow, tsc, links, …) simply never appears —
|
|
14
|
+
* exactly the "supported vs. unsupported" distinction the fingerprint contract
|
|
15
|
+
* draws. Running each check under the resolved package manager and capturing raw
|
|
16
|
+
* output is delegated to {@link runChecks}, so baseline fingerprints precisely
|
|
17
|
+
* the bytes a normal run would.
|
|
18
|
+
*/
|
|
19
|
+
import { runChecks } from '../orchestrator.js';
|
|
20
|
+
import { fingerprint } from './fingerprint.js';
|
|
21
|
+
import { BASELINE_FILE, BASELINE_SCHEMA_VERSION, writeBaseline } from './store.js';
|
|
22
|
+
/** Run the pipeline and write the per-adapter fingerprint baseline to `cwd`. */
|
|
23
|
+
export async function runBaseline(options) {
|
|
24
|
+
const cwd = options.cwd ?? process.cwd();
|
|
25
|
+
const stderr = options.stderr ?? process.stderr;
|
|
26
|
+
// json:false so the run reports progress on stderr and nothing on stdout; the
|
|
27
|
+
// baseline artifact is the file, so stdout stays clean (C5). baseline:null so a
|
|
28
|
+
// re-capture records the raw current diagnostics — never masked or pruned by an
|
|
29
|
+
// existing baseline the run would otherwise pick up (step 6).
|
|
30
|
+
const runOptions = {
|
|
31
|
+
cwd,
|
|
32
|
+
json: false,
|
|
33
|
+
stderr,
|
|
34
|
+
baseline: null,
|
|
35
|
+
...(options.slots !== undefined ? { slots: options.slots } : {}),
|
|
36
|
+
...(options.adapters !== undefined ? { adapters: options.adapters } : {}),
|
|
37
|
+
...(options.config !== undefined ? { config: options.config } : {}),
|
|
38
|
+
...(options.runner !== undefined ? { runner: options.runner } : {}),
|
|
39
|
+
...(options.pm !== undefined ? { pm: options.pm } : {}),
|
|
40
|
+
...(options.stdout !== undefined ? { stdout: options.stdout } : {}),
|
|
41
|
+
};
|
|
42
|
+
const result = await runChecks(runOptions);
|
|
43
|
+
const slots = {};
|
|
44
|
+
for (const run of result.runs) {
|
|
45
|
+
const keys = fingerprint(run.adapter.name, run.outcome.stdout);
|
|
46
|
+
if (keys === null)
|
|
47
|
+
continue;
|
|
48
|
+
slots[run.slot] = [...keys].toSorted();
|
|
49
|
+
}
|
|
50
|
+
const baseline = { schema_version: BASELINE_SCHEMA_VERSION, slots };
|
|
51
|
+
await writeBaseline(cwd, baseline);
|
|
52
|
+
const slotNames = Object.keys(slots);
|
|
53
|
+
const total = Object.values(slots).reduce((n, keys) => n + keys.length, 0);
|
|
54
|
+
const where = slotNames.length > 0 ? slotNames.join(', ') : '(no fingerprintable slots)';
|
|
55
|
+
stderr.write(`\nbaseline: wrote ${BASELINE_FILE} — ${total} diagnostic(s) across ${where}\n`);
|
|
56
|
+
return { ok: true, exitCode: 0, baseline };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/baseline/command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-adapter diagnostic fingerprints (baseline part 1).
|
|
3
|
+
*
|
|
4
|
+
* A *fingerprint* is a stable, order-independent set of key strings extracted
|
|
5
|
+
* from one adapter's raw output. Each key names a single finding by what it is
|
|
6
|
+
* (`<file>:<rule>:<message>`) rather than where it sits, so a finding keeps its
|
|
7
|
+
* key when unrelated edits shift its line and column — that is what later lets
|
|
8
|
+
* the baseline (steps 5–6) subtract already-known findings without re-flagging
|
|
9
|
+
* them every time surrounding code moves.
|
|
10
|
+
*
|
|
11
|
+
* This is deliberately NOT a normalized diagnostic schema (D3): each adapter
|
|
12
|
+
* owns its own key string, the raw `.check/<slot>.json` stays authoritative, and
|
|
13
|
+
* an adapter whose output isn't a stable diagnostic set simply has no extractor
|
|
14
|
+
* and returns `null` — baseline is unsupported for that slot, decided
|
|
15
|
+
* per-adapter against real fixtures (D12), not from an up-front list. Step 4
|
|
16
|
+
* ships extractors for the blessed lint/struct/spell adapters (oxlint, ast-grep,
|
|
17
|
+
* cspell); fallow's cross-file findings (cycles, duplication) sit out for now
|
|
18
|
+
* (a4) and can gain a composite-key extractor later without touching this seam.
|
|
19
|
+
*/
|
|
20
|
+
/** A stable, order-independent set of diagnostic keys for one adapter's output. */
|
|
21
|
+
export type Fingerprint = ReadonlySet<string>;
|
|
22
|
+
/** Turns one adapter's raw output (JSON or text) into a fingerprint. */
|
|
23
|
+
export type Extractor = (raw: string) => Fingerprint;
|
|
24
|
+
/**
|
|
25
|
+
* Fingerprint one adapter's raw output, or `null` when the adapter has no
|
|
26
|
+
* extractor (baseline unsupported for that slot). Keyed on adapter name, so an
|
|
27
|
+
* alternate tool that shares a slot with a supported default (biome/eslint for
|
|
28
|
+
* `lint`, knip for `dead`) opts out until it grows its own extractor. A
|
|
29
|
+
* supported adapter with zero findings returns an empty set, never `null` —
|
|
30
|
+
* "supported but clean" and "unsupported" are distinct, and steps 5–6 depend on
|
|
31
|
+
* the difference.
|
|
32
|
+
*/
|
|
33
|
+
export declare function fingerprint(adapter: string, raw: string): Fingerprint | null;
|
|
34
|
+
/**
|
|
35
|
+
* Whether an adapter can be fingerprinted (has an extractor), decided by name
|
|
36
|
+
* without needing its output. `init --baseline` uses this to tell which failing
|
|
37
|
+
* slots the baseline can grandfather from those that must still fall back to a
|
|
38
|
+
* `false` disable.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isFingerprintable(adapter: string): boolean;
|
|
41
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/baseline/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;AAE9C,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,CAAC;AAmFrD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAG5E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE1D"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-adapter diagnostic fingerprints (baseline part 1).
|
|
3
|
+
*
|
|
4
|
+
* A *fingerprint* is a stable, order-independent set of key strings extracted
|
|
5
|
+
* from one adapter's raw output. Each key names a single finding by what it is
|
|
6
|
+
* (`<file>:<rule>:<message>`) rather than where it sits, so a finding keeps its
|
|
7
|
+
* key when unrelated edits shift its line and column — that is what later lets
|
|
8
|
+
* the baseline (steps 5–6) subtract already-known findings without re-flagging
|
|
9
|
+
* them every time surrounding code moves.
|
|
10
|
+
*
|
|
11
|
+
* This is deliberately NOT a normalized diagnostic schema (D3): each adapter
|
|
12
|
+
* owns its own key string, the raw `.check/<slot>.json` stays authoritative, and
|
|
13
|
+
* an adapter whose output isn't a stable diagnostic set simply has no extractor
|
|
14
|
+
* and returns `null` — baseline is unsupported for that slot, decided
|
|
15
|
+
* per-adapter against real fixtures (D12), not from an up-front list. Step 4
|
|
16
|
+
* ships extractors for the blessed lint/struct/spell adapters (oxlint, ast-grep,
|
|
17
|
+
* cspell); fallow's cross-file findings (cycles, duplication) sit out for now
|
|
18
|
+
* (a4) and can gain a composite-key extractor later without touching this seam.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Build a finding's key from its identity. Line and column are intentionally
|
|
22
|
+
* excluded so the key survives edits that only move the code; the message is
|
|
23
|
+
* whitespace-collapsed so cosmetic wrapping differences don't split one finding
|
|
24
|
+
* into two. `rule` is empty for tools (cspell) that don't name a rule.
|
|
25
|
+
*/
|
|
26
|
+
function key(file, rule, message) {
|
|
27
|
+
return `${file}:${rule}:${message.replace(/\s+/g, ' ').trim()}`;
|
|
28
|
+
}
|
|
29
|
+
/** Parse JSON output defensively; malformed input yields `null`, never a throw. */
|
|
30
|
+
function parseJson(raw) {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Narrow to a plain object so string fields can be read without assertions. */
|
|
40
|
+
function isRecord(value) {
|
|
41
|
+
return typeof value === 'object' && value !== null;
|
|
42
|
+
}
|
|
43
|
+
/** Read a field as a string, or `''` when it is missing or not a string. */
|
|
44
|
+
function str(value) {
|
|
45
|
+
return typeof value === 'string' ? value : '';
|
|
46
|
+
}
|
|
47
|
+
/** Coerce an unknown to an array of unknowns (non-arrays become empty). */
|
|
48
|
+
function asArray(value) {
|
|
49
|
+
return Array.isArray(value) ? value : [];
|
|
50
|
+
}
|
|
51
|
+
/** oxlint `--format=json`: `{ diagnostics: [{ filename, code, message }] }`. */
|
|
52
|
+
const extractOxlint = (raw) => {
|
|
53
|
+
const keys = new Set();
|
|
54
|
+
const payload = parseJson(raw);
|
|
55
|
+
const diagnostics = isRecord(payload) ? asArray(payload['diagnostics']) : [];
|
|
56
|
+
for (const d of diagnostics) {
|
|
57
|
+
if (!isRecord(d))
|
|
58
|
+
continue;
|
|
59
|
+
const file = str(d['filename']);
|
|
60
|
+
const message = str(d['message']);
|
|
61
|
+
if (file && message)
|
|
62
|
+
keys.add(key(file, str(d['code']), message));
|
|
63
|
+
}
|
|
64
|
+
return keys;
|
|
65
|
+
};
|
|
66
|
+
/** ast-grep `--json=compact`: an array of `{ file, ruleId, message }`. */
|
|
67
|
+
const extractAstGrep = (raw) => {
|
|
68
|
+
const keys = new Set();
|
|
69
|
+
for (const m of asArray(parseJson(raw))) {
|
|
70
|
+
if (!isRecord(m))
|
|
71
|
+
continue;
|
|
72
|
+
const file = str(m['file']);
|
|
73
|
+
const message = str(m['message']);
|
|
74
|
+
if (file && message)
|
|
75
|
+
keys.add(key(file, str(m['ruleId']), message));
|
|
76
|
+
}
|
|
77
|
+
return keys;
|
|
78
|
+
};
|
|
79
|
+
/** cspell default reporter line: `<file>:<line>:<col> - <message>`. */
|
|
80
|
+
const CSPELL_LINE = /^(.+?):\d+:\d+ - (.+)$/;
|
|
81
|
+
/** cspell has no rule id, so its key is `<file>::<message>` (empty rule slot). */
|
|
82
|
+
const extractCspell = (raw) => {
|
|
83
|
+
const keys = new Set();
|
|
84
|
+
for (const line of raw.split('\n')) {
|
|
85
|
+
const m = CSPELL_LINE.exec(line.trim());
|
|
86
|
+
if (m)
|
|
87
|
+
keys.add(key(m[1] ?? '', '', m[2] ?? ''));
|
|
88
|
+
}
|
|
89
|
+
return keys;
|
|
90
|
+
};
|
|
91
|
+
/** Extractors keyed by adapter name (not slot); everything else sits out. */
|
|
92
|
+
const EXTRACTORS = {
|
|
93
|
+
oxlint: extractOxlint,
|
|
94
|
+
'ast-grep': extractAstGrep,
|
|
95
|
+
cspell: extractCspell,
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Fingerprint one adapter's raw output, or `null` when the adapter has no
|
|
99
|
+
* extractor (baseline unsupported for that slot). Keyed on adapter name, so an
|
|
100
|
+
* alternate tool that shares a slot with a supported default (biome/eslint for
|
|
101
|
+
* `lint`, knip for `dead`) opts out until it grows its own extractor. A
|
|
102
|
+
* supported adapter with zero findings returns an empty set, never `null` —
|
|
103
|
+
* "supported but clean" and "unsupported" are distinct, and steps 5–6 depend on
|
|
104
|
+
* the difference.
|
|
105
|
+
*/
|
|
106
|
+
export function fingerprint(adapter, raw) {
|
|
107
|
+
const extractor = EXTRACTORS[adapter];
|
|
108
|
+
return extractor ? extractor(raw) : null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Whether an adapter can be fingerprinted (has an extractor), decided by name
|
|
112
|
+
* without needing its output. `init --baseline` uses this to tell which failing
|
|
113
|
+
* slots the baseline can grandfather from those that must still fall back to a
|
|
114
|
+
* `false` disable.
|
|
115
|
+
*/
|
|
116
|
+
export function isFingerprintable(adapter) {
|
|
117
|
+
return EXTRACTORS[adapter] !== undefined;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/baseline/fingerprint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline module — per-adapter diagnostic fingerprints (step 4) and, in later
|
|
3
|
+
* steps, the baseline read/write/ratchet built on top of them. This barrel is
|
|
4
|
+
* the module's only public surface (C2): siblings import from `../baseline`,
|
|
5
|
+
* never from `./fingerprint.js` directly.
|
|
6
|
+
*/
|
|
7
|
+
export type { Extractor, Fingerprint } from './fingerprint.js';
|
|
8
|
+
export { fingerprint, isFingerprintable } from './fingerprint.js';
|
|
9
|
+
export type { Baseline, BaselineAdjustment } from './store.js';
|
|
10
|
+
export { applyBaseline, BASELINE_FILE, BASELINE_SCHEMA_VERSION, baselinesEqual, countBaselineKeys, loadBaseline, ratchet, writeBaseline, } from './store.js';
|
|
11
|
+
export type { BaselineOptions, BaselineResult } from './command.js';
|
|
12
|
+
export { runBaseline } from './command.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/baseline/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAClE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EACL,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,OAAO,EACP,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline module — per-adapter diagnostic fingerprints (step 4) and, in later
|
|
3
|
+
* steps, the baseline read/write/ratchet built on top of them. This barrel is
|
|
4
|
+
* the module's only public surface (C2): siblings import from `../baseline`,
|
|
5
|
+
* never from `./fingerprint.js` directly.
|
|
6
|
+
*/
|
|
7
|
+
export { fingerprint, isFingerprintable } from './fingerprint.js';
|
|
8
|
+
export { applyBaseline, BASELINE_FILE, BASELINE_SCHEMA_VERSION, baselinesEqual, countBaselineKeys, loadBaseline, ratchet, writeBaseline, } from './store.js';
|
|
9
|
+
export { runBaseline } from './command.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/baseline/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,EACL,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,OAAO,EACP,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The baseline artifact: its on-disk shape and the pure read / write / apply /
|
|
3
|
+
* ratchet operations a baseline-aware run (step 6) is built from.
|
|
4
|
+
*
|
|
5
|
+
* This file imports nothing from the orchestrator on purpose: the `baseline`
|
|
6
|
+
* command (command.ts) bridges to the run pipeline, but a *run* only needs to
|
|
7
|
+
* mask and ratchet an already-captured baseline, so the orchestrator can depend
|
|
8
|
+
* on these helpers without a load-order cycle. Keeping the file I/O and the
|
|
9
|
+
* set algebra here (not in the orchestrator) also preserves the orchestrator's
|
|
10
|
+
* "never parse diagnostics" thesis — fingerprinting stays inside this module.
|
|
11
|
+
*/
|
|
12
|
+
import type { Fingerprint } from './fingerprint.js';
|
|
13
|
+
/** The committed baseline artifact, at repo root beside `checkride.config.json`. */
|
|
14
|
+
export declare const BASELINE_FILE = "checkride.baseline.json";
|
|
15
|
+
/** Schema version of the baseline file; independent of the summary schema (D8). */
|
|
16
|
+
export declare const BASELINE_SCHEMA_VERSION = 1;
|
|
17
|
+
/** Shape of `checkride.baseline.json`: per-slot sets of grandfathered keys. */
|
|
18
|
+
export type Baseline = {
|
|
19
|
+
schema_version: number;
|
|
20
|
+
slots: Record<string, string[]>;
|
|
21
|
+
};
|
|
22
|
+
/** The result of masking one slot's current fingerprint with its baseline. */
|
|
23
|
+
export type BaselineAdjustment = {
|
|
24
|
+
/** Grandfathered-adjusted pass: the failure is fully explained by known debt. */
|
|
25
|
+
ok: boolean;
|
|
26
|
+
/** How many current diagnostics the baseline suppressed (the `baselined` count). */
|
|
27
|
+
baselined: number;
|
|
28
|
+
/** Sorted diagnostics present now that the baseline does NOT grandfather. */
|
|
29
|
+
newKeys: string[];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Read `checkride.baseline.json` from `cwd`; `null` when absent or unusable. The
|
|
33
|
+
* baseline is a committed file a run reads on every invocation, so a malformed
|
|
34
|
+
* one is coerced into a clean shape (or dropped) rather than thrown — a corrupt
|
|
35
|
+
* baseline must never break `checkride`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadBaseline(cwd: string): Baseline | null;
|
|
38
|
+
/** Serialize and write the baseline to `cwd` (pretty-printed, trailing newline, atomic). */
|
|
39
|
+
export declare function writeBaseline(cwd: string, baseline: Baseline): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Mask one slot's current fingerprint with its grandfathered keys. Masking is
|
|
42
|
+
* pure set subtraction: `newKeys` are the current diagnostics the baseline does
|
|
43
|
+
* not cover, `baselined` counts the ones it does.
|
|
44
|
+
*
|
|
45
|
+
* The failure flips to a pass *only* when there are current diagnostics and none
|
|
46
|
+
* of them are new — a tool that produced no parseable diagnostics (a crash, empty
|
|
47
|
+
* output that fingerprints to the empty set) is never masked, so a genuine
|
|
48
|
+
* breakage still fails even while a baseline is active. Correctness beats
|
|
49
|
+
* precision: better to re-surface a masked finding than to silently green a crash.
|
|
50
|
+
*/
|
|
51
|
+
export declare function applyBaseline(current: Fingerprint, baselinedKeys: readonly string[], rawOk: boolean): BaselineAdjustment;
|
|
52
|
+
/**
|
|
53
|
+
* Ratchet the baseline against a fully-observed run: for every slot observed this
|
|
54
|
+
* run, keep only the grandfathered keys still present (a fixed diagnostic is
|
|
55
|
+
* dropped); a slot absent from `observed` — disabled, without an extractor, or
|
|
56
|
+
* not run — is preserved untouched. The result never grows: genuinely new findings
|
|
57
|
+
* are the run's failures, not new debt, so only a `checkride baseline` capture
|
|
58
|
+
* ever adds keys. Callers gate this on a full run (no `--only`/`--skip`/
|
|
59
|
+
* `--changed`, no early `--bail` break) so a partial run can't prune keys it
|
|
60
|
+
* simply didn't observe (a1).
|
|
61
|
+
*/
|
|
62
|
+
export declare function ratchet(baseline: Baseline, observed: ReadonlyMap<string, Fingerprint>): Baseline;
|
|
63
|
+
/** Total grandfathered diagnostics across every slot. */
|
|
64
|
+
export declare function countBaselineKeys(baseline: Baseline): number;
|
|
65
|
+
/** Order-independent structural equality, so an unchanged ratchet skips its write. */
|
|
66
|
+
export declare function baselinesEqual(a: Baseline, b: Baseline): boolean;
|
|
67
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/baseline/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,oFAAoF;AACpF,eAAO,MAAM,aAAa,4BAA4B,CAAC;AAEvD,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACjC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iFAAiF;IACjF,EAAE,EAAE,OAAO,CAAC;IACZ,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAOF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAezD;AAED,4FAA4F;AAC5F,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,KAAK,EAAE,OAAO,GACb,kBAAkB,CAUpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,QAAQ,CAOhG;AAED,yDAAyD;AACzD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAE5D;AASD,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAEhE"}
|