verderer 0.2.0 → 0.3.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/README.md +4 -0
- package/package.json +1 -1
- package/src/cli.js +33 -4
- package/src/ledger.js +28 -0
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ Validated on a real repo before a line of this tool existed: of 3 long-standing
|
|
|
16
16
|
npx verderer scan # inventory harness rules into .claude/verderer.json
|
|
17
17
|
npx verderer status # active/retired rules, provenance, verdicts
|
|
18
18
|
npx verderer checkpoint --model <id> # record the model by hand (tools without an adapter)
|
|
19
|
+
npx verderer verdict 9f2c dead --evidence "derived it from neighboring code"
|
|
20
|
+
# record an inquest (id prefix is enough)
|
|
19
21
|
npx verderer probe # the ablation recipe (automation is the roadmap)
|
|
20
22
|
```
|
|
21
23
|
|
|
@@ -58,6 +60,8 @@ For tools without a detection adapter yet, `verderer checkpoint --model <id>` re
|
|
|
58
60
|
|
|
59
61
|
Verdicts are three-valued on purpose: **dead** (the model does it unaided), **derivable** (unaided but an expensive miss — keeping it is cheap insurance), **needed**. Blast radius is a judgment call, so verderer reports and you decide; it never auto-deletes a rule.
|
|
60
62
|
|
|
63
|
+
`verderer verdict` records one: it stamps today's date and the ledger's current model, so the next model change flags it as stale. Evidence is required.
|
|
64
|
+
|
|
61
65
|
## Roadmap
|
|
62
66
|
|
|
63
67
|
- `probe` automation: per-rule ablation worktrees + headless runs + verdict recording (today it prints the validated manual recipe).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "verderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A rule ledger for AI agent harnesses: per-rule provenance for CLAUDE.md/AGENTS.md, plus ablation inquests that tell you which rules still earn their place after a model upgrade.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { scan } from './scan.js';
|
|
3
|
-
import { loadLedger, saveLedger, ledgerPath, staleVerdicts, isoToday } from './ledger.js';
|
|
3
|
+
import { loadLedger, saveLedger, ledgerPath, staleVerdicts, recordVerdict, isoToday } from './ledger.js';
|
|
4
4
|
|
|
5
5
|
const HELP = `verderer — a rule ledger for AI agent harnesses
|
|
6
6
|
|
|
@@ -8,6 +8,8 @@ Usage:
|
|
|
8
8
|
verderer scan [dir] inventory harness rules (CLAUDE.md, AGENTS.md) into .claude/verderer.json
|
|
9
9
|
verderer status [dir] show the ledger: active/retired rules, provenance, verdicts
|
|
10
10
|
verderer checkpoint [dir] --model <id> record the model by hand (for tools without a detection adapter)
|
|
11
|
+
verderer verdict <id> <result> [dir] --evidence <text>
|
|
12
|
+
record an inquest: dead | derivable | needed (id prefix is enough)
|
|
11
13
|
verderer probe print the manual ablation recipe (automation is the roadmap)
|
|
12
14
|
|
|
13
15
|
Scan also records which model the repo is actually running on (read from the
|
|
@@ -24,11 +26,12 @@ export async function run(argv) {
|
|
|
24
26
|
const positional = [];
|
|
25
27
|
for (let i = 0; i < argv.length; i++) {
|
|
26
28
|
if (argv[i] === '--model') flags.model = argv[++i];
|
|
29
|
+
else if (argv[i] === '--evidence') flags.evidence = argv[++i];
|
|
27
30
|
else positional.push(argv[i]);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
const [cmd,
|
|
31
|
-
const dir = resolve(
|
|
33
|
+
const [cmd, ...args] = positional;
|
|
34
|
+
const dir = resolve(args[0] ?? '.');
|
|
32
35
|
|
|
33
36
|
switch (cmd) {
|
|
34
37
|
case 'scan':
|
|
@@ -37,6 +40,10 @@ export async function run(argv) {
|
|
|
37
40
|
return cmdStatus(dir);
|
|
38
41
|
case 'checkpoint':
|
|
39
42
|
return cmdCheckpoint(dir, flags.model);
|
|
43
|
+
case 'verdict': {
|
|
44
|
+
const [id, result, dirArg] = args;
|
|
45
|
+
return cmdVerdict(resolve(dirArg ?? '.'), id, result, flags.evidence);
|
|
46
|
+
}
|
|
40
47
|
case 'probe':
|
|
41
48
|
return cmdProbe();
|
|
42
49
|
case undefined:
|
|
@@ -125,6 +132,27 @@ function cmdCheckpoint(dir, modelId) {
|
|
|
125
132
|
return 0;
|
|
126
133
|
}
|
|
127
134
|
|
|
135
|
+
function cmdVerdict(dir, id, result, evidence) {
|
|
136
|
+
const prior = loadLedger(dir);
|
|
137
|
+
|
|
138
|
+
if (prior.rules.length === 0) {
|
|
139
|
+
console.error(`no ledger at ${ledgerPath(dir)} — run \`verderer scan\` first`);
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const { ledger, rule } = recordVerdict(prior, id, result, { evidence });
|
|
145
|
+
saveLedger(dir, ledger);
|
|
146
|
+
console.log(`${rule.id} ${result} on ${rule.lastVerdict.model}`);
|
|
147
|
+
console.log(` ${truncate(rule.text, 100)}`);
|
|
148
|
+
return 0;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error(err.message);
|
|
151
|
+
console.error('usage: verderer verdict <id> <dead|derivable|needed> [dir] --evidence <text>');
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
128
156
|
function cmdProbe() {
|
|
129
157
|
console.log(`probe automation is on the roadmap. The manual recipe (validated on a real repo):
|
|
130
158
|
|
|
@@ -136,7 +164,8 @@ function cmdProbe() {
|
|
|
136
164
|
- yes, by reading the codebase → the rule is dead (the code now carries the convention)
|
|
137
165
|
- yes, but a miss would be expensive → derivable; keeping it is cheap insurance
|
|
138
166
|
- no → the rule still earns its place
|
|
139
|
-
5. Record
|
|
167
|
+
5. Record it: verderer verdict <id> <dead|derivable|needed> --evidence "..."
|
|
168
|
+
then prune with evidence.
|
|
140
169
|
|
|
141
170
|
Verdicts are judgment calls on blast radius — verderer reports, you decide. It never auto-deletes.`);
|
|
142
171
|
return 0;
|
package/src/ledger.js
CHANGED
|
@@ -70,6 +70,34 @@ export function staleVerdicts(ledger) {
|
|
|
70
70
|
return ledger.rules.filter((r) => r.status === 'active' && r.lastVerdict?.model && r.lastVerdict.model !== current);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export const VERDICTS = ['dead', 'derivable', 'needed'];
|
|
74
|
+
|
|
75
|
+
// Stamp a verdict on one rule, found by id prefix (like a git sha).
|
|
76
|
+
// The verdict records the ledger's current model so a later model change
|
|
77
|
+
// can flag it as stale; without a model there is nothing to judge against.
|
|
78
|
+
export function recordVerdict(ledger, prefix, result, { evidence, today = isoToday() } = {}) {
|
|
79
|
+
if (!VERDICTS.includes(result)) {
|
|
80
|
+
throw new Error(`verdict must be one of: ${VERDICTS.join(', ')}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!evidence) throw new Error('a verdict needs --evidence');
|
|
84
|
+
|
|
85
|
+
const model = ledger.model?.id;
|
|
86
|
+
if (!model) throw new Error('no model recorded — run `verderer scan` or `verderer checkpoint --model <id>` first');
|
|
87
|
+
|
|
88
|
+
const matches = prefix ? ledger.rules.filter((r) => r.id.startsWith(prefix)) : [];
|
|
89
|
+
if (matches.length === 0) throw new Error(`no rule matches id ${prefix ?? '(none)'}`);
|
|
90
|
+
if (matches.length > 1) throw new Error(`id ${prefix} is ambiguous: ${matches.map((r) => r.id).join(', ')}`);
|
|
91
|
+
|
|
92
|
+
const [rule] = matches;
|
|
93
|
+
const verdict = { date: today, result, model, evidence };
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
ledger: { ...ledger, rules: ledger.rules.map((r) => (r === rule ? { ...r, lastVerdict: verdict } : r)) },
|
|
97
|
+
rule: { ...rule, lastVerdict: verdict },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
73
101
|
function stripBlame({ blame, ...rule }) {
|
|
74
102
|
return rule;
|
|
75
103
|
}
|