@event4u/agent-config 13.0.0 → 14.0.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/marketplace.json +1 -1
- package/CHANGELOG.md +160 -333
- package/README.md +1 -1
- package/dist/agent-src/commands/evals/roadmap.json +8 -0
- package/dist/agent-src/commands/fix/ci.md +39 -1
- package/dist/agent-src/commands/pr/create.md +65 -4
- package/dist/agent-src/commands/roadmap/next.md +28 -5
- package/dist/agent-src/commands/work.md +16 -0
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +47 -0
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +94 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +30 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +21 -8
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +55 -8
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +42 -0
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +29 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +33 -0
- package/dist/agent-src/rules/design-review-after-ui-write.md +25 -8
- package/dist/agent-src/rules/ui-audit-gate.md +25 -8
- package/dist/agent-src/scripts/gate_execute.ts +395 -0
- package/dist/agent-src/scripts/resume_probe.ts +422 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +138 -4
- package/dist/agent-src/scripts/update_roadmap_progress.ts +53 -2
- package/dist/agent-src/skills/ai-council/SKILL.md +61 -0
- package/dist/agent-src/skills/experiment-loop/SKILL.md +116 -0
- package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +27 -0
- package/dist/agent-src/skills/experiment-loop/evals/triggers.json +17 -0
- package/dist/agent-src/skills/experiment-loop/references/pivot-ladder.md +54 -0
- package/dist/agent-src/skills/experiment-loop/references/protocol.md +61 -0
- package/dist/agent-src/skills/experiment-loop/references/register-format.md +66 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +8 -3
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +43 -3
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-N7FEST6D.js → chunk-7PZ4FSZB.js} +351 -174
- package/dist/cli-delegate/{chunk-3B72LSZL.js → chunk-F3YUF7SA.js} +1 -1
- package/dist/cli-delegate/cmd_conformance.js +224 -104
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +40 -18
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +2018 -1320
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1652 -892
- package/dist/router.json +1 -1
- package/dist/scripts/condense.js +1 -1
- package/dist/scripts/condense.js.map +1 -1
- package/dist/ui/assets/{index-B0aMJnuz.js → index-C1pBRm1f.js} +2 -2
- package/dist/ui/assets/index-C1pBRm1f.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/gate_execute.ts +395 -0
- package/src/agent-src/scripts/resume_probe.ts +422 -0
- package/src/agent-src/scripts/roadmap_gates.ts +138 -4
- package/src/agent-src/scripts/update_roadmap_progress.ts +53 -2
- package/src/config/ci-local-parity.yml +13 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +111 -5
- package/src/config/gate-violation-baselines.json +2 -2
- package/src/config/rule-activation-census.json +41 -0
- package/src/config/rule-stub-ceilings.json +52 -0
- package/src/scripts/_dispatch.bash +4 -0
- package/src/scripts/_lib/evaluator_contract.ts +159 -0
- package/src/scripts/_lib/gh_transient.ts +80 -0
- package/src/scripts/_lib/release_material.ts +16 -5
- package/src/scripts/_lib/review_independence.ts +111 -0
- package/src/scripts/_lib/session_register.ts +14 -0
- package/src/scripts/_lib/skill_catalogue.ts +20 -0
- package/src/scripts/_lib/skill_catalogue_series.ts +411 -0
- package/src/scripts/_lib/turn_end_refusals.ts +482 -0
- package/src/scripts/ai_council/_py_json.ts +96 -0
- package/src/scripts/ai_council/cli_call_budget.ts +357 -0
- package/src/scripts/ai_council/clients.ts +80 -143
- package/src/scripts/ai_council/config.ts +25 -11
- package/src/scripts/ai_council/probe_store.ts +196 -0
- package/src/scripts/ai_council/qualification.ts +425 -0
- package/src/scripts/ai_council/qualification_wiring.ts +243 -0
- package/src/scripts/ai_council/quorum_wiring.ts +211 -0
- package/src/scripts/ai_team/team_dispatch.ts +40 -6
- package/src/scripts/capture_skill_catalogue.ts +264 -4
- package/src/scripts/check_evaluator_schema.ts +249 -0
- package/src/scripts/check_pr_ci_current.ts +409 -0
- package/src/scripts/check_review_schema.ts +195 -0
- package/src/scripts/check_rule_activation_census.ts +451 -0
- package/src/scripts/check_rule_stub_ceiling.ts +1128 -0
- package/src/scripts/condense.ts +1 -1
- package/src/scripts/council_cli.ts +141 -191
- package/src/scripts/dispatch_r2_reviewer.ts +8 -1
- package/src/scripts/hook_manifest.yaml +72 -5
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/dispatch_hook.ts +43 -1
- package/src/scripts/hooks/interruption_ledger_hook.ts +256 -0
- package/src/scripts/hooks/ship_diff_volume_hook.ts +180 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +119 -26
- package/src/scripts/hooks_doctor.ts +112 -0
- package/src/scripts/interruption_report.ts +443 -0
- package/src/scripts/lint_evidence_artifacts.ts +359 -0
- package/src/scripts/lint_roadmap_blockers.ts +65 -0
- package/src/scripts/lint_roadmap_complexity.ts +7 -1
- package/src/scripts/release.ts +38 -53
- package/src/scripts/rule_activation_census.ts +292 -0
- package/src/scripts/schemas/evaluator-output.schema.json +60 -0
- package/src/scripts/schemas/review-findings.schema.json +31 -0
- package/src/scripts/self_review_gate.ts +6 -0
- package/src/scripts/session_register_hook.ts +30 -1
- package/src/scripts/sync_pr_branch.ts +271 -0
- package/src/scripts/test_council_qualification.ts +213 -0
- package/dist/ui/assets/index-B0aMJnuz.js.map +0 -1
|
@@ -180,11 +180,11 @@ class RoadmapStats {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
get open_blockers(): Blocker[] {
|
|
183
|
-
return this.blockers.filter((b) => b
|
|
183
|
+
return this.blockers.filter((b) => !blocker_is_resolved(b));
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
get resolved_blockers(): Blocker[] {
|
|
187
|
-
return this.blockers.filter((b) => b
|
|
187
|
+
return this.blockers.filter((b) => blocker_is_resolved(b));
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
get done(): number {
|
|
@@ -365,6 +365,49 @@ interface Blocker {
|
|
|
365
365
|
* costs one enum entry and turns a silent swallow into a rendered field.
|
|
366
366
|
*/
|
|
367
367
|
question: string;
|
|
368
|
+
/**
|
|
369
|
+
* Gate taxonomy: `0` auto-run · `1` budget-preauthorized · `2`
|
|
370
|
+
* consent-once · `3` human-only. Empty string when the entry declares
|
|
371
|
+
* none, and an absent class READS AS 3 at every consumer.
|
|
372
|
+
*
|
|
373
|
+
* The default is the safe end on purpose. An agent that inferred the class
|
|
374
|
+
* at runtime could promote a Hard-Floor gate into something it executes;
|
|
375
|
+
* authoring it into the file means a misclassification is a reviewed edit,
|
|
376
|
+
* and `lint_roadmap_blockers` refuses a runnable class that names no
|
|
377
|
+
* command.
|
|
378
|
+
*/
|
|
379
|
+
blockerClass: string;
|
|
380
|
+
/** The command a class-0/1 entry is resolved by running. */
|
|
381
|
+
run: string;
|
|
382
|
+
/** Spend estimate for class 1 — recorded, never enforced by the lint. */
|
|
383
|
+
budget: string;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Is this blocker settled?
|
|
388
|
+
*
|
|
389
|
+
* A PREFIX test, not equality, and the difference was measured: authors write
|
|
390
|
+
* the resolution into the status line — `**Status:** RESOLVED 2026-08-17 —
|
|
391
|
+
* **option (b)**, narrowed to two rules` — and an equality check matches
|
|
392
|
+
* neither `open` nor `resolved`, so the entry falls through to open. Found
|
|
393
|
+
* 2026-08-17 in `road-to-mixed-trigger-activation-cost`: a decision taken two
|
|
394
|
+
* days earlier was still being rendered by `agent-config gates` as a live one
|
|
395
|
+
* the reader owed an answer to, and it inflated every blocker count in the
|
|
396
|
+
* dashboard by one.
|
|
397
|
+
*
|
|
398
|
+
* `lint_roadmap_blockers` already read the same field as a prefix
|
|
399
|
+
* (`/^-\s*\*\*Status:\*\*\s*resolved/i`), so the two surfaces disagreed about
|
|
400
|
+
* what "resolved" means — the lint exempted the entry from its decidability
|
|
401
|
+
* ratchet while the dashboard counted it open.
|
|
402
|
+
*/
|
|
403
|
+
function blocker_is_resolved(b: Blocker): boolean {
|
|
404
|
+
return /^resolved\b/.test(_strip(b.status).toLowerCase());
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Class of a blocker, with the absent-field default applied. */
|
|
408
|
+
function blocker_class(b: Blocker): '0' | '1' | '2' | '3' {
|
|
409
|
+
const c = _strip(b.blockerClass).split(/[\s—–-]/)[0] as string;
|
|
410
|
+
return c === '0' || c === '1' || c === '2' ? c : '3';
|
|
368
411
|
}
|
|
369
412
|
|
|
370
413
|
// Strip fenced code blocks before blocker detection — a roadmap that shows the
|
|
@@ -508,6 +551,9 @@ function parse_blockers(raw_text: string): Blocker[] {
|
|
|
508
551
|
recommendation: _blockerField(body, 'Recommendation') ?? '',
|
|
509
552
|
ifNothing: _blockerField(body, 'If you do nothing') ?? '',
|
|
510
553
|
question: _blockerField(body, 'Question') ?? '',
|
|
554
|
+
blockerClass: _blockerField(body, 'Class') ?? '',
|
|
555
|
+
run: _blockerField(body, 'Run') ?? '',
|
|
556
|
+
budget: _blockerField(body, 'Budget') ?? '',
|
|
511
557
|
});
|
|
512
558
|
}
|
|
513
559
|
}
|
|
@@ -542,6 +588,9 @@ function parse_blockers(raw_text: string): Blocker[] {
|
|
|
542
588
|
recommendation: '',
|
|
543
589
|
ifNothing: '',
|
|
544
590
|
question: '',
|
|
591
|
+
blockerClass: '',
|
|
592
|
+
run: '',
|
|
593
|
+
budget: '',
|
|
545
594
|
});
|
|
546
595
|
}
|
|
547
596
|
return blockers;
|
|
@@ -1187,6 +1236,8 @@ export {
|
|
|
1187
1236
|
is_roadmap_candidate,
|
|
1188
1237
|
count_checkboxes,
|
|
1189
1238
|
parse_blockers,
|
|
1239
|
+
blocker_class,
|
|
1240
|
+
blocker_is_resolved,
|
|
1190
1241
|
blocker_needs_user,
|
|
1191
1242
|
parse_roadmap,
|
|
1192
1243
|
bar,
|
|
@@ -184,6 +184,19 @@ ci_only:
|
|
|
184
184
|
# worth remote enforcement, OR it should simply be added to a workflow. Prefer
|
|
185
185
|
# adding it: a local-only gate is the direction that let real defects merge.
|
|
186
186
|
local_only:
|
|
187
|
+
- id: check_pr_ci_current
|
|
188
|
+
class: pre-push-scoped
|
|
189
|
+
reason: >
|
|
190
|
+
Compares the local branch tip against the open PR head and against the SHA
|
|
191
|
+
the PR's checks actually ran on. In CI that comparison is tautological by
|
|
192
|
+
construction — a workflow runs ON the PR head, so local, PR head and checks
|
|
193
|
+
head are the same commit and the gate can only ever pass. Its whole subject
|
|
194
|
+
is the window BEFORE a push (unpushed work, a green that ran against the
|
|
195
|
+
pre-fix commit, a PR head the checkout cannot reach because Update branch
|
|
196
|
+
was pressed), and that window does not exist inside a run. Declared rather
|
|
197
|
+
than wired: there is no remote equivalent to write, which is the distinction
|
|
198
|
+
this list exists to record. Sibling `check_branch_freshness` is a no-op in
|
|
199
|
+
CI for the same reason — the forge owns mergeability there.
|
|
187
200
|
- id: roadmap_progress_hook
|
|
188
201
|
class: git-hook-scoped
|
|
189
202
|
reason: >
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"deterministic": false
|
|
59
59
|
},
|
|
60
60
|
"cli_help_command_count": {
|
|
61
|
-
"max":
|
|
62
|
-
"last_measured":
|
|
63
|
-
"last_measured_at": "2026-08-
|
|
61
|
+
"max": 99,
|
|
62
|
+
"last_measured": 99,
|
|
63
|
+
"last_measured_at": "2026-08-17",
|
|
64
64
|
"method": "registry enumeration — count of `{ name: '...', disposition` entries in src/cli/registry.ts (NOT --help prose parsing; methods historically disagreed 74 vs 76 vs 79)",
|
|
65
|
-
"baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85. 85 → 86 on 2026-08-06: the `conformance:behavior` verb (road-to-agent-behavior-conformance Phase 5 — replays the local transcript store through the four mechanised conformance checks) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 86. 86 → 88 on 2026-08-07: the `sessions:list` + `sessions:claim` verbs (road-to-parallel-session-coordination — the shell surface of the shared session register, needed because /roadmap:next is command markdown and can only reach the register through a shell call) register in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 88. 88 → 89 on 2026-08-08: the `council:status` verb registers in the same PR that adds the surface, per this record's own contract. It exists because an agent in a consumer project announced \"kein Council konfiguriert (keine .agent-settings.yml)\" and substituted a weaker path while the council was configured user-globally the whole time — there was no verb that answered availability, so the only route was an inference from a filename, and the intuitive filename is the one location ADR-104 guarantees is never consulted. Re-measured via the pinned method: 89. 89 → 91 on 2026-08-08: the `self-repair:status` + `self-repair:release` verbs (the queued-defect reader and the single Hard-Floor-gated outward step of the self-repair loop — the loop is deliberately unable to publish unless a human runs the release command) register in the same PR that adds the surface, per this record's own contract. Landed alongside the +1 council:status move in the same branch. Re-measured via the pinned method: 91. 91 → 94 on 2026-08-08: the `packs:active`, `settings:get` and `mcp:available` verbs (road-to-capability-answerability Phase 2 — the three capability probes for the gaps that had none) register in the same PR that adds the surface, per this record's own contract. Each answers a question that was previously only inferable from a filename: which packs are active and from which file, what a setting resolves to and which file set it, and what is declared in mcp.json versus what can actually launch. 94 -> 95 in the same PR: `brand:status` (Phase 2.4 — brand-layer presence, which the rule depended on and no surface could answer). Re-measured twice via the pinned method on the unchanged tree: 95/95. 95 → 96 on 2026-08-10: the `session:recycle` verb (road-to-token-economy-recycling Phase 2.2 — the deterministic producer of the main-session recycle envelope; the recycle advisory names this exact verb, so it must exist as a shell surface) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 96. 96 -> 97 on 2026-08-10: the `gates` verb registers in the same PR that adds the surface, per this record's own contract. It exists because the blocker fields the dashboard already parses (Owner, What to do, Resolved when — CI-enforced by lint_roadmap_blockers) had no owner-filtered reader: the dashboard counts the open blockers across a 600-line file and links each to a per-roadmap anchor, so \"which of these are mine\" was a manual read of every anchor. The same inference-from-a-filename shape as the council:status entry above, one layer up. This branch and the session:recycle branch each measured 96 independently and landed on the same number for different verbs; the merge makes it 97, re-measured twice via the pinned method on the merged tree: 97/97. 97 -> 98 on 2026-08-15: the `workspace:doctor` verb (road-to-inbox-harvest-2026-08-c-workspace-identity Phase 3 — the read-only report over the new `workspaceIdentity()` resolver) registers in the same PR that adds the surface, per this record's own contract. It exists for the same inference-from-a-filename shape as the council:status and gates entries above, one layer down: the tree answered \"where am I\" in eight ad-hoc places across six files, and the 12.0.0 span shipped the same worktree misclassification twice, so no verb could be asked which checkout is the main one. Read-only by construction — the roadmap's Non-goals put worktree disposal out of scope and its Risk 3 names \"the pressure read becomes a disposal path by accident\" as the failure to design against, so the command carries no --fix and no --prune. Re-measured twice via the pinned method on the unchanged tree: 98/98."
|
|
65
|
+
"baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85. 85 → 86 on 2026-08-06: the `conformance:behavior` verb (road-to-agent-behavior-conformance Phase 5 — replays the local transcript store through the four mechanised conformance checks) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 86. 86 → 88 on 2026-08-07: the `sessions:list` + `sessions:claim` verbs (road-to-parallel-session-coordination — the shell surface of the shared session register, needed because /roadmap:next is command markdown and can only reach the register through a shell call) register in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 88. 88 → 89 on 2026-08-08: the `council:status` verb registers in the same PR that adds the surface, per this record's own contract. It exists because an agent in a consumer project announced \"kein Council konfiguriert (keine .agent-settings.yml)\" and substituted a weaker path while the council was configured user-globally the whole time — there was no verb that answered availability, so the only route was an inference from a filename, and the intuitive filename is the one location ADR-104 guarantees is never consulted. Re-measured via the pinned method: 89. 89 → 91 on 2026-08-08: the `self-repair:status` + `self-repair:release` verbs (the queued-defect reader and the single Hard-Floor-gated outward step of the self-repair loop — the loop is deliberately unable to publish unless a human runs the release command) register in the same PR that adds the surface, per this record's own contract. Landed alongside the +1 council:status move in the same branch. Re-measured via the pinned method: 91. 91 → 94 on 2026-08-08: the `packs:active`, `settings:get` and `mcp:available` verbs (road-to-capability-answerability Phase 2 — the three capability probes for the gaps that had none) register in the same PR that adds the surface, per this record's own contract. Each answers a question that was previously only inferable from a filename: which packs are active and from which file, what a setting resolves to and which file set it, and what is declared in mcp.json versus what can actually launch. 94 -> 95 in the same PR: `brand:status` (Phase 2.4 — brand-layer presence, which the rule depended on and no surface could answer). Re-measured twice via the pinned method on the unchanged tree: 95/95. 95 → 96 on 2026-08-10: the `session:recycle` verb (road-to-token-economy-recycling Phase 2.2 — the deterministic producer of the main-session recycle envelope; the recycle advisory names this exact verb, so it must exist as a shell surface) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 96. 96 -> 97 on 2026-08-10: the `gates` verb registers in the same PR that adds the surface, per this record's own contract. It exists because the blocker fields the dashboard already parses (Owner, What to do, Resolved when — CI-enforced by lint_roadmap_blockers) had no owner-filtered reader: the dashboard counts the open blockers across a 600-line file and links each to a per-roadmap anchor, so \"which of these are mine\" was a manual read of every anchor. The same inference-from-a-filename shape as the council:status entry above, one layer up. This branch and the session:recycle branch each measured 96 independently and landed on the same number for different verbs; the merge makes it 97, re-measured twice via the pinned method on the merged tree: 97/97. 97 -> 98 on 2026-08-15: the `workspace:doctor` verb (road-to-inbox-harvest-2026-08-c-workspace-identity Phase 3 — the read-only report over the new `workspaceIdentity()` resolver) registers in the same PR that adds the surface, per this record's own contract. It exists for the same inference-from-a-filename shape as the council:status and gates entries above, one layer down: the tree answered \"where am I\" in eight ad-hoc places across six files, and the 12.0.0 span shipped the same worktree misclassification twice, so no verb could be asked which checkout is the main one. Read-only by construction — the roadmap's Non-goals put worktree disposal out of scope and its Risk 3 names \"the pressure read becomes a disposal path by accident\" as the failure to design against, so the command carries no --fix and no --prune. Re-measured twice via the pinned method on the unchanged tree: 98/98. 98 -> 99 on 2026-08-17: the `council:quota` verb (road-to-council-quota-accounting-truth Phase 2) registers in the same PR that adds the surface, per this record's own contract. Exactly the inference-from-a-filename shape as the council:status entry above, one layer down: `cmd_quota` had existed in council_cli.ts and been reachable only via `scripts-run`, so the documented recovery path for an exhausted shared counter -- read the usage, reset one provider -- did not resolve through `agent-config`, and an operator debugging a quota failure met `unknown command`. The verb is read-only apart from the pre-existing `--reset <provider>`, which already required `--confirm`. Re-measured twice via the pinned method on the unchanged tree: 99/99."
|
|
66
66
|
},
|
|
67
67
|
"mcp_public_tool_count": {
|
|
68
68
|
"max": 20,
|
|
@@ -40,13 +40,26 @@
|
|
|
40
40
|
# visible, or it repeats the sin it exists to catch.
|
|
41
41
|
# canary optional mutation recipe (Phase 7) — see CANARY below
|
|
42
42
|
#
|
|
43
|
-
# COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-
|
|
43
|
+
# COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-17)
|
|
44
44
|
# ----------------------------------------------------------
|
|
45
|
-
#
|
|
45
|
+
# 266 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
|
|
46
46
|
# (the count is computed, not asserted — `check_gate_coverage.count_gate_scripts`).
|
|
47
|
-
# **
|
|
48
|
-
# CI-identical argv, and
|
|
49
|
-
# complete with respect to its own contract
|
|
47
|
+
# **41 of them emit the machine-readable `scanned: <N>` line under their
|
|
48
|
+
# CI-identical argv, and 40 of those 41 are listed below.** The manifest is
|
|
49
|
+
# therefore complete with respect to its own contract for every gate that can
|
|
50
|
+
# carry a floor, and covers ~15 % of the population.
|
|
51
|
+
#
|
|
52
|
+
# The one emitter deliberately NOT listed is `lint_evidence_artifacts`, for the
|
|
53
|
+
# same reason `check_rule_projection_integrity` is not (see the paragraph
|
|
54
|
+
# below): it is diff-scoped, so it legitimately emits `scanned: 0` on any
|
|
55
|
+
# branch that adds no evidence artifact, and `min_scanned: 0` is the
|
|
56
|
+
# false-count shape this file rejects. Its ledger half IS discharged — it
|
|
57
|
+
# adopts `_lib/gate_ledger.ts` and `_lib/scan_scope.ts`, so
|
|
58
|
+
# `check_gate_completeness` counts it as an adopter.
|
|
59
|
+
#
|
|
60
|
+
# Counting emitters rather than rows is the point of stating both numbers: the
|
|
61
|
+
# 2026-08-05 and 2026-08-10 readings below were miscounts precisely because one
|
|
62
|
+
# number was updated and the other was not.
|
|
50
63
|
# (The 2026-08-05 reading said "32 … and all 31 are listed" and the 2026-08-10
|
|
51
64
|
# reading said "33 … and all 33" while 34 entries were listed — those two are
|
|
52
65
|
# the MISCOUNTS this paragraph exists to warn about, and they are listed here
|
|
@@ -169,6 +182,99 @@ gates:
|
|
|
169
182
|
the Taskfile parse degrades and the population silently collapses —
|
|
170
183
|
cannot pass.
|
|
171
184
|
|
|
185
|
+
- id: check_rule_activation_census
|
|
186
|
+
argv: []
|
|
187
|
+
min_scanned: 100
|
|
188
|
+
corpus: "rule files under src/rules/ (117 at baseline)"
|
|
189
|
+
status: enforced
|
|
190
|
+
note: >
|
|
191
|
+
Floor set below the live count so a legitimate rule retirement does not red
|
|
192
|
+
the gate that watches activation. It is still far above a collapse: if the
|
|
193
|
+
rule root moved, this gate's own assertScanned fires first and the manifest
|
|
194
|
+
row is the second net. The gate exists because commit 33c7c20 moved nineteen
|
|
195
|
+
rules from path-scoped to always-on with nothing watching, so a version of
|
|
196
|
+
it that silently scanned zero would be the same failure twice.
|
|
197
|
+
|
|
198
|
+
- id: check_rule_stub_ceiling
|
|
199
|
+
argv: []
|
|
200
|
+
min_scanned: 100
|
|
201
|
+
corpus: "rule files under src/rules/ (117 at baseline; 44 carry a migration pointer)"
|
|
202
|
+
status: enforced
|
|
203
|
+
note: >
|
|
204
|
+
Scans the whole rule root and reports that count, not the 44 migrated rules
|
|
205
|
+
it ratchets — the denominator is what proves the root still exists, and a
|
|
206
|
+
floor on the migrated subset would fall as rules legitimately finish
|
|
207
|
+
migrating. Floor set below the live 117 so a rule retirement does not red
|
|
208
|
+
the gate. The gate's own assertScanned fires first if the root moves; this
|
|
209
|
+
row is the second net. It exists because the sibling census pins the corpus
|
|
210
|
+
total with a 2,000-token drift allowance, which is green while one rule
|
|
211
|
+
grows and another shrinks — measured at adoption, the corpus sat 411 tokens
|
|
212
|
+
above its pinned baseline with that gate green.
|
|
213
|
+
canary:
|
|
214
|
+
class: contract-violation
|
|
215
|
+
path: src/rules/zz-canary-stub-probe.md
|
|
216
|
+
content: |
|
|
217
|
+
---
|
|
218
|
+
type: "auto"
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
# ZZ canary probe
|
|
222
|
+
|
|
223
|
+
Body migrated to `guideline:zz-canary-target-that-does-not-exist`.
|
|
224
|
+
# A created rule is the create-only shape this gate is built for: it carries
|
|
225
|
+
# a migration pointer whose target resolves nowhere AND has no ceiling in the
|
|
226
|
+
# baseline, so it trips two independent axes. Without a recipe the gate would
|
|
227
|
+
# ship permanently UNPROVEN — a green run over a real corpus is not evidence
|
|
228
|
+
# that the reading changes the verdict, which is the whole point of the
|
|
229
|
+
# canary. The gate also carries a `--self-test` with seven rejecting cases;
|
|
230
|
+
# the two prove different things and neither replaces the other.
|
|
231
|
+
|
|
232
|
+
- id: check_review_schema
|
|
233
|
+
argv: []
|
|
234
|
+
min_scanned: 1
|
|
235
|
+
corpus: "committed review-findings ledgers under agents/evidence/release-findings/ (1 at baseline)"
|
|
236
|
+
status: enforced
|
|
237
|
+
canary:
|
|
238
|
+
class: contract-violation
|
|
239
|
+
path: agents/evidence/release-findings/zz-canary-probe.json
|
|
240
|
+
content: |
|
|
241
|
+
{
|
|
242
|
+
"schema_version": 1,
|
|
243
|
+
"release": "0.0.0-canary",
|
|
244
|
+
"review_independence": "same-family",
|
|
245
|
+
"acceptance_status": "accepted",
|
|
246
|
+
"assurance": "independent",
|
|
247
|
+
"reviewers": ["anthropic", "anthropic"],
|
|
248
|
+
"findings": []
|
|
249
|
+
}
|
|
250
|
+
note: >
|
|
251
|
+
Floor is 1 because the corpus genuinely holds one ledger today, and a
|
|
252
|
+
floor of 0 would let the directory empty out silently — the shape this
|
|
253
|
+
manifest exists to refuse. The canary plants the exact defect the gate was
|
|
254
|
+
built for: a same-family reviewer set claiming `accepted`. It must red.
|
|
255
|
+
|
|
256
|
+
- id: check_evaluator_schema
|
|
257
|
+
argv: []
|
|
258
|
+
min_scanned: 5
|
|
259
|
+
corpus: "registered evaluator x recorded verifier fixture pairs (6 at baseline)"
|
|
260
|
+
status: enforced
|
|
261
|
+
canary:
|
|
262
|
+
class: contract-violation
|
|
263
|
+
path: tests/fixtures/evaluator-output/lint_output_slop.degraded.stdout
|
|
264
|
+
content: |
|
|
265
|
+
planted canary: output with no clean sentinel and no file:line rows
|
|
266
|
+
note: >
|
|
267
|
+
The corpus is (evaluator x fixture) pairs, not evaluators, because the
|
|
268
|
+
mapping is what can be wrong and each flavour exercises a different branch
|
|
269
|
+
of it. The floor sits below the live count so retiring one fixture flavour
|
|
270
|
+
does not red the change that retires it, but above 3 so a corpus that
|
|
271
|
+
silently collapsed to green-only cannot pass — a green-only corpus cannot
|
|
272
|
+
distinguish a working adapter from one that hardcodes success, which is
|
|
273
|
+
the whole reason red and degraded fixtures are committed alongside.
|
|
274
|
+
The canary plants a `degraded` stdout with no matching `.exit`, so the
|
|
275
|
+
adapter reads exit 0 with an unparseable body: expected `unreadable`,
|
|
276
|
+
asserted `present` by the green expectation table -> the gate must fail.
|
|
277
|
+
|
|
172
278
|
- id: check_suppression_hygiene
|
|
173
279
|
argv: []
|
|
174
280
|
min_scanned: 8
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"falsifier": "./scripts-run src/scripts/lint_settings_classes"
|
|
58
58
|
},
|
|
59
59
|
"check_source_size_budget": {
|
|
60
|
-
"count":
|
|
60
|
+
"count": 19611,
|
|
61
61
|
"landed": "2026-08-17",
|
|
62
|
-
"note": "Lowered 19,
|
|
62
|
+
"note": "Lowered 19,626 → 19,611 on 2026-08-17 by the release-gh-transient branch, and it is a LOWERING through extraction, never a raise — the −15 is against the trunk value, reproducible with `git stash && npx tsx src/scripts/check_source_size_budget.ts`. That branch hardened `release.ts` against GitHub's 5xx responses, which is exactly the kind of change that grows an already over-ceiling file: release.ts sits at 2,819 and carries 1,319 of this total. The retry loop, the transient classifier and the argv elider therefore went into a NEW module (`_lib/gh_transient.ts`, 74 lines, under the ceiling and contributing nothing here), leaving release.ts a 20-line wrapper. The payback came from the five call sites the wrapper replaced: each was a `run(['gh', …], {check, capture})` block spread over up to 17 lines, and each collapses to one `gh([…])` call. The ordering is the lesson and it matches the two entries below: extract, then measure — a wrapper written inline would have added ~20 lines and turned a hardening commit into a ratchet regression. Second lesson, learned the hard way on this same branch: pin the baseline ONCE, at the end. Two intermediate pins (19,606 then 19,612) made subsequent legitimate edits read as `N new` against numbers that had never been on the trunk.",
|
|
63
63
|
"falsifier": "./scripts-run src/scripts/check_source_size_budget --quiet"
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Rule-activation ratchet baseline (road-to-mixed-trigger-activation-cost step 4.1). Two axes: the scoped/mixed ID SETS are pinned by identity, and the unconditional token total may only walk DOWN. Raising a number here to clear a failing check is the config-weakening move this repo blocks by construction \u2014 every raise needs a baseline_history entry whose reason is a real sentence. Regenerate with `check_rule_activation_census --write-baseline`; never hand-edit the counts.",
|
|
3
|
+
"measured_at_commit": "097ab6549",
|
|
4
|
+
"tokens_exact": true,
|
|
5
|
+
"unconditional_tokens": 103265,
|
|
6
|
+
"scoped_ids": [
|
|
7
|
+
"design-review-after-ui-write",
|
|
8
|
+
"no-roadmap-references",
|
|
9
|
+
"roadmap-progress-sync",
|
|
10
|
+
"rule-type-governance",
|
|
11
|
+
"skill-quality",
|
|
12
|
+
"source-confidentiality",
|
|
13
|
+
"source-of-truth",
|
|
14
|
+
"ui-audit-gate"
|
|
15
|
+
],
|
|
16
|
+
"mixed_ids": [
|
|
17
|
+
"augment-edit-discipline",
|
|
18
|
+
"design-fidelity",
|
|
19
|
+
"doc-screenshot-hygiene",
|
|
20
|
+
"domain-adoption-policy",
|
|
21
|
+
"framework-neutrality-in-generic-skills",
|
|
22
|
+
"image-likeness-and-rights",
|
|
23
|
+
"laravel-translations",
|
|
24
|
+
"lethal-trifecta-guard",
|
|
25
|
+
"linked-projects-onboarding-gate",
|
|
26
|
+
"low-impact-corpus-privacy-floor",
|
|
27
|
+
"markdown-safe-codeblocks",
|
|
28
|
+
"onboarding-gate",
|
|
29
|
+
"persona-governance",
|
|
30
|
+
"php-coding",
|
|
31
|
+
"provider-lifecycle-discipline",
|
|
32
|
+
"roadmap-ci-steps-policy",
|
|
33
|
+
"settings-ask-protocol"
|
|
34
|
+
],
|
|
35
|
+
"baseline_history": [
|
|
36
|
+
{
|
|
37
|
+
"date": "2026-08-17",
|
|
38
|
+
"reason": "Re-anchored WITH the authorized change, not to clear a red. road-to-mixed-trigger-activation-cost Phase 2, amendment option (b) narrowed to the two path-dominant per-edit rules: `design-review-after-ui-write` and `ui-audit-gate` had their `component` and `design token` keyword triggers removed as an authoring decision, so the emitter restores `paths:` for both. Scoped 6 -> 8, mixed 19 -> 17, unconditional corpus 107194 -> the value above. The direction is DOWN on the weight axis and the identity move is the authorized one; the maintainer asked for this entry in the same commit so the ratchet documents its own sanctioned change instead of reporting it as drift."
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Per-rule stub ceilings (road-to-rule-stub-projection Phase 2). One exact-BPE token ceiling per rule carrying a migration pointer. SHRINK-ONLY: a rule may fall below its ceiling freely, and may not rise above it. Raising a ceiling to clear a failing check is the config-weakening move this repo blocks by construction — every raise needs a `history` entry whose `reason` is a real sentence. The ceilings are a floor-plus-residue SNAPSHOT taken at adoption, not a target: a rule at its ceiling is not thereby correct, only not worse. Regenerate with `check_rule_stub_ceiling --write-baseline`; never hand-edit the counts.",
|
|
3
|
+
"measured_at_commit": "d8e2ee655",
|
|
4
|
+
"tokens_exact": true,
|
|
5
|
+
"ceilings": {
|
|
6
|
+
"active-remediation": 1601,
|
|
7
|
+
"analysis-skill-routing": 76,
|
|
8
|
+
"architecture": 955,
|
|
9
|
+
"artifact-drafting-protocol": 611,
|
|
10
|
+
"augment-edit-discipline": 137,
|
|
11
|
+
"autonomous-execution": 1633,
|
|
12
|
+
"brand-consistency": 133,
|
|
13
|
+
"broken-access-control": 743,
|
|
14
|
+
"cli-output-handling": 83,
|
|
15
|
+
"code-comment-discipline": 692,
|
|
16
|
+
"commit-conventions": 136,
|
|
17
|
+
"context-hygiene": 2447,
|
|
18
|
+
"copilot-routing": 128,
|
|
19
|
+
"decision-revisit-gate": 545,
|
|
20
|
+
"design-fidelity": 2245,
|
|
21
|
+
"devcontainer-routing": 128,
|
|
22
|
+
"docker-commands": 79,
|
|
23
|
+
"domain-adoption-policy": 497,
|
|
24
|
+
"framework-neutrality-in-generic-skills": 621,
|
|
25
|
+
"git-history-discipline": 1205,
|
|
26
|
+
"improve-before-implement": 845,
|
|
27
|
+
"laravel-routing": 126,
|
|
28
|
+
"laravel-translations": 82,
|
|
29
|
+
"legal-safety-floor": 1401,
|
|
30
|
+
"linked-projects-onboarding-gate": 172,
|
|
31
|
+
"minimal-safe-diff": 893,
|
|
32
|
+
"missing-tool-handling": 103,
|
|
33
|
+
"model-recommendation": 156,
|
|
34
|
+
"no-roadmap-references": 644,
|
|
35
|
+
"package-ci-checks": 74,
|
|
36
|
+
"persona-governance": 330,
|
|
37
|
+
"php-coding": 80,
|
|
38
|
+
"provider-lifecycle-discipline": 472,
|
|
39
|
+
"reviewer-awareness": 131,
|
|
40
|
+
"roadmap-ci-steps-policy": 564,
|
|
41
|
+
"roadmap-progress-sync": 1424,
|
|
42
|
+
"rule-type-governance": 87,
|
|
43
|
+
"skill-improvement-trigger": 81,
|
|
44
|
+
"skill-quality": 78,
|
|
45
|
+
"slash-command-routing-policy": 77,
|
|
46
|
+
"symfony-routing": 129,
|
|
47
|
+
"ui-audit-gate": 1280,
|
|
48
|
+
"untrusted-input-defense": 845,
|
|
49
|
+
"upstream-proposal": 76
|
|
50
|
+
},
|
|
51
|
+
"history": []
|
|
52
|
+
}
|
|
@@ -162,6 +162,9 @@ Tier 1 — power-user (release shape, audit, migration):
|
|
|
162
162
|
Usage: council:render <responses.json>
|
|
163
163
|
council:status Report whether an AI council is configured, and from where
|
|
164
164
|
(no API call, no spend)
|
|
165
|
+
council:quota Show today's per-provider CLI-call usage against the
|
|
166
|
+
enforced cap; --reset <provider> --confirm clears one
|
|
167
|
+
(no API call, no spend)
|
|
165
168
|
self-repair:status List queued agent-config defect records (read-only)
|
|
166
169
|
self-repair:release Publish one defect record — a PR when the fix can be
|
|
167
170
|
pushed, else an issue. Running it is your Hard-Floor
|
|
@@ -1443,6 +1446,7 @@ main() {
|
|
|
1443
1446
|
council:run) cmd_council run "$@" ;;
|
|
1444
1447
|
council:render) cmd_council render "$@" ;;
|
|
1445
1448
|
council:status) cmd_council status "$@" ;;
|
|
1449
|
+
council:quota) cmd_council quota "$@" ;;
|
|
1446
1450
|
self-repair:status) cmd_self_repair status "$@" ;;
|
|
1447
1451
|
self-repair:release) cmd_self_repair release "$@" ;;
|
|
1448
1452
|
update) cmd_update "$@" ;;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The evaluator-output contract: types, the registry of wrapped verifiers, and
|
|
3
|
+
* the validator both the gate and any future loop share.
|
|
4
|
+
*
|
|
5
|
+
* Contract: `docs/contracts/evaluator-output.md`.
|
|
6
|
+
* Schema: `src/scripts/schemas/evaluator-output.schema.json`.
|
|
7
|
+
*
|
|
8
|
+
* The adapters below are PURE — each maps a verifier's already-captured
|
|
9
|
+
* `{stdout, stderr, exitCode}` onto the contract shape. That is deliberate and
|
|
10
|
+
* it is what makes the gate cheap: the mapping is the part that can be wrong,
|
|
11
|
+
* and it can be tested against recorded fixtures without paying to re-run three
|
|
12
|
+
* verifiers CI already runs once. Spike s02 established that none of the three
|
|
13
|
+
* needs a change on its own side for this to work.
|
|
14
|
+
*/
|
|
15
|
+
import { load_schema, validate, type YamlValue } from '../validate_frontmatter.js';
|
|
16
|
+
|
|
17
|
+
export type MetricState = 'present' | 'absent' | 'unreadable';
|
|
18
|
+
export type Direction = 'maximize' | 'minimize';
|
|
19
|
+
|
|
20
|
+
export interface EvaluatorOutput {
|
|
21
|
+
readonly schema_version: 1;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly pass: boolean;
|
|
24
|
+
readonly score: number;
|
|
25
|
+
readonly metric?: number;
|
|
26
|
+
readonly metric_state?: MetricState;
|
|
27
|
+
readonly direction?: Direction;
|
|
28
|
+
readonly error?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** What a verifier invocation produced, before any interpretation. */
|
|
32
|
+
export interface RawRun {
|
|
33
|
+
readonly stdout: string;
|
|
34
|
+
readonly stderr: string;
|
|
35
|
+
readonly exitCode: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface EvaluatorAdapter {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
/** The verifier this adapter wraps, as its CI-identical script name. */
|
|
41
|
+
readonly wraps: string;
|
|
42
|
+
readonly direction: Direction;
|
|
43
|
+
readonly parse: (raw: RawRun) => EvaluatorOutput;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Read a count off either stream.
|
|
48
|
+
*
|
|
49
|
+
* Both streams on purpose: `check_references` writes its count to stderr
|
|
50
|
+
* (measured, spike s02), and a stdout-only reader returned a null metric beside
|
|
51
|
+
* `pass: true` — a degraded reading that looked clean. A wrapper reads both; it
|
|
52
|
+
* still emits its own verdict on stdout alone, per the contract.
|
|
53
|
+
*/
|
|
54
|
+
function readCount(raw: RawRun, re: RegExp): number | null {
|
|
55
|
+
const m = `${raw.stdout}\n${raw.stderr}`.match(re);
|
|
56
|
+
if (m === null || m[1] === undefined) return null;
|
|
57
|
+
const n = Number(m[1]);
|
|
58
|
+
return Number.isFinite(n) ? n : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fold a possibly-missing count into the three contract fields.
|
|
63
|
+
*
|
|
64
|
+
* `expected` says whether this verifier is supposed to have a number at all.
|
|
65
|
+
* When it is and the number is missing, the result is `unreadable` — never
|
|
66
|
+
* `absent`, and never a silent zero. That collapse is the exact defect the
|
|
67
|
+
* `metric_state` field exists to prevent.
|
|
68
|
+
*/
|
|
69
|
+
function withMetric(
|
|
70
|
+
base: Omit<EvaluatorOutput, 'metric' | 'metric_state' | 'score'>,
|
|
71
|
+
count: number | null,
|
|
72
|
+
expected: boolean,
|
|
73
|
+
): EvaluatorOutput {
|
|
74
|
+
if (count !== null) {
|
|
75
|
+
return { ...base, metric: count, metric_state: 'present', score: -count };
|
|
76
|
+
}
|
|
77
|
+
// `metric` is OMITTED rather than set to null: the subset validator this
|
|
78
|
+
// estate ships rejects an array-typed `type`, so the schema spells the field
|
|
79
|
+
// as an optional number. Absence is the null.
|
|
80
|
+
if (!expected) {
|
|
81
|
+
return { ...base, metric_state: 'absent', score: base.pass ? 0 : -1 };
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
...base,
|
|
85
|
+
metric_state: 'unreadable',
|
|
86
|
+
score: -1,
|
|
87
|
+
error: 'expected a count in the verifier output and found none',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const EVALUATORS: readonly EvaluatorAdapter[] = [
|
|
92
|
+
{
|
|
93
|
+
name: 'validate_frontmatter',
|
|
94
|
+
wraps: 'validate_frontmatter',
|
|
95
|
+
direction: 'minimize',
|
|
96
|
+
parse: (raw) =>
|
|
97
|
+
withMetric(
|
|
98
|
+
{ schema_version: 1, name: 'validate_frontmatter', pass: raw.exitCode === 0, direction: 'minimize' },
|
|
99
|
+
// Group 2 is the failing count; group 1 is the corpus size, which
|
|
100
|
+
// is the denominator and not the thing being minimized.
|
|
101
|
+
readCount(raw, /\d+\s+artefacts,\s+(\d+)\s+failing/),
|
|
102
|
+
true,
|
|
103
|
+
),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'lint_output_slop',
|
|
107
|
+
wraps: 'lint_output_slop',
|
|
108
|
+
direction: 'minimize',
|
|
109
|
+
parse: (raw) => {
|
|
110
|
+
const text = `${raw.stdout}\n${raw.stderr}`;
|
|
111
|
+
const clean = /clean\s+—\s+no placeholder-prose patterns found/.test(text);
|
|
112
|
+
const count = clean ? 0 : (text.match(/^\s*\S+:\d+/gm) ?? []).length;
|
|
113
|
+
return withMetric(
|
|
114
|
+
{ schema_version: 1, name: 'lint_output_slop', pass: raw.exitCode === 0, direction: 'minimize' },
|
|
115
|
+
count,
|
|
116
|
+
true,
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'check_references',
|
|
122
|
+
wraps: 'check_references',
|
|
123
|
+
direction: 'minimize',
|
|
124
|
+
parse: (raw) => {
|
|
125
|
+
const text = `${raw.stdout}\n${raw.stderr}`;
|
|
126
|
+
const scanned = readCount(raw, /scanned:\s*(\d+)/);
|
|
127
|
+
// A gate that scanned nothing has not measured a clean tree — it has
|
|
128
|
+
// not measured. Report that as unreadable rather than as zero broken
|
|
129
|
+
// references, which is the manufactured-green shape this estate names.
|
|
130
|
+
if (scanned === null || scanned === 0) {
|
|
131
|
+
return withMetric(
|
|
132
|
+
{ schema_version: 1, name: 'check_references', pass: raw.exitCode === 0, direction: 'minimize' },
|
|
133
|
+
null,
|
|
134
|
+
true,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const clean = /No broken references found/.test(text);
|
|
138
|
+
return withMetric(
|
|
139
|
+
{ schema_version: 1, name: 'check_references', pass: raw.exitCode === 0, direction: 'minimize' },
|
|
140
|
+
clean ? 0 : (text.match(/^\s*broken:/gim) ?? []).length || 1,
|
|
141
|
+
true,
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Schema violations for one evaluator output. Empty array = conformant.
|
|
149
|
+
*
|
|
150
|
+
* `SchemaError.format()` rather than `String(e)`: the class carries no
|
|
151
|
+
* `toString`, so stringifying it yields `[object Object]` — a violation list
|
|
152
|
+
* that names nothing, which is indistinguishable from a broken validator.
|
|
153
|
+
*/
|
|
154
|
+
export function validateEvaluatorOutput(value: unknown): string[] {
|
|
155
|
+
const schema = load_schema('evaluator-output');
|
|
156
|
+
return validate(value as YamlValue, schema)
|
|
157
|
+
.filter((e) => e.severity !== 'warning')
|
|
158
|
+
.map((e) => e.format());
|
|
159
|
+
}
|