@event4u/agent-config 9.6.0 → 9.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/marketplace.json +1 -1
- package/CHANGELOG.md +207 -942
- package/README.md +21 -21
- package/dist/agent-src/README.md +2 -2
- package/dist/agent-src/commands/commit/in-chunks.md +8 -0
- package/dist/agent-src/commands/commit.md +17 -0
- package/dist/agent-src/commands/pr/create.md +4 -0
- package/dist/agent-src/contexts/override-system.md +75 -5
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +97 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/downstream-changes.md +31 -1
- package/dist/agent-src/rules/external-code-graph-interop.md +13 -3
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
- package/dist/agent-src/rules/git-history-discipline.md +2 -0
- package/dist/agent-src/rules/language-and-tone.md +2 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
- package/dist/agent-src/rules/media-governance-routing.md +2 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
- package/dist/agent-src/rules/no-roadmap-references.md +3 -0
- package/dist/agent-src/rules/non-destructive-by-default.md +20 -0
- package/dist/agent-src/rules/onboarding-gate.md +2 -0
- package/dist/agent-src/rules/output-discipline.md +2 -0
- package/dist/agent-src/rules/persona-governance.md +2 -0
- package/dist/agent-src/rules/preservation-guard.md +3 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +106 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
- package/dist/agent-src/rules/skill-quality.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +2 -0
- package/dist/agent-src/rules/source-of-truth.md +2 -0
- package/dist/agent-src/rules/telegraph-speak.md +2 -0
- package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/verify-before-complete.md +2 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +17 -1
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +34 -16
- package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +9 -9
- package/dist/agent-src/skills/code-intelligence/SKILL.md +94 -0
- package/dist/agent-src/skills/gated-reach/SKILL.md +185 -0
- package/dist/agent-src/skills/gated-reach/evals/triggers.json +72 -0
- package/dist/agent-src/skills/image-analyser/SKILL.md +14 -12
- package/dist/agent-src/skills/image-editing/SKILL.md +1 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +6 -1
- package/dist/agent-src/skills/screenshot-hygiene/SKILL.md +171 -0
- package/dist/agent-src/skills/secrets-management/SKILL.md +44 -1
- package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-competitively.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-parallel.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-steps.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-worktrees.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/judge-with-debate.md +29 -0
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +4 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/github-workflows/doc-impact.yml +101 -0
- package/dist/agent-src/templates/github-workflows/secret-scan.yml +65 -0
- package/dist/cli/configRoot.js +112 -0
- package/dist/cli/configRoot.js.map +1 -0
- package/dist/cli/configRoot.test.js +97 -0
- package/dist/cli/configRoot.test.js.map +1 -0
- package/dist/cli/help.js +4 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +19 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +164 -47
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +13 -8
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +11 -6
- package/dist/install/install.mjs +16 -0
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1 -1
- package/dist/server/app.js +10 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/routes/ping.js +18 -0
- package/dist/server/routes/ping.js.map +1 -1
- package/dist/server/schemas/settings.js +8 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/server/serverInfo.js +6 -2
- package/dist/server/serverInfo.js.map +1 -1
- package/dist/server/token.js +11 -4
- package/dist/server/token.js.map +1 -1
- package/dist/server/writeRoot.js +15 -1
- package/dist/server/writeRoot.js.map +1 -1
- package/dist/shared/capabilities.js +33 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/capabilities.test.js +39 -0
- package/dist/shared/capabilities.test.js.map +1 -0
- package/dist/ui/assets/{index-BiEZiZ31.js → index-CrmZ34Rg.js} +16 -16
- package/dist/ui/assets/index-CrmZ34Rg.js.map +1 -0
- package/dist/ui/index.html +14 -5
- package/docs/CLAIMS.md +64 -38
- package/docs/architecture.md +2 -2
- package/docs/archive/CHANGELOG-6.0.0-overview.md +953 -0
- package/docs/benchmark.md +170 -9
- package/docs/catalog.md +8 -3
- package/docs/comparison.yaml +44 -2
- package/docs/contracts/local-server-ports.md +53 -0
- package/docs/contracts/no-runtime-boundary.md +1 -1
- package/docs/contracts/subagent-boundary.md +15 -0
- package/docs/customization.md +14 -3
- package/docs/decisions/ADR-016-installer-architecture.md +2 -0
- package/docs/decisions/ADR-088-no-external-runtime-federation.md +1 -1
- package/docs/decisions/ADR-094-agent-memory-layer-removal.md +1 -1
- package/docs/decisions/ADR-109-subagent-v1-contract.md +2 -0
- package/docs/decisions/ADR-110-discipline-profile-resolution-locus.md +2 -0
- package/docs/decisions/ADR-113-ci-native-release-label-trigger.md +11 -3
- package/docs/decisions/ADR-122-adversarial-verification-council.md +30 -0
- package/docs/decisions/ADR-123-runtime-security-scope-and-spawn-hardening.md +31 -0
- package/docs/decisions/ADR-124-embedded-engine-doctrine.md +255 -0
- package/docs/decisions/ADR-125-doc-screenshot-anonymization.md +131 -0
- package/docs/decisions/ADR-126-internet-reach-operator-tooling.md +254 -0
- package/docs/decisions/ADR-127-enforcement-claims-must-resolve.md +150 -0
- package/docs/decisions/ADR-128-executable-evidence-and-measured-escalation.md +201 -0
- package/docs/decisions/INDEX.md +21 -0
- package/docs/decisions/engine-reclassification-2026-07.md +104 -0
- package/docs/distribution/awesome-list-submission.md +1 -1
- package/docs/distribution/registries.md +36 -27
- package/docs/enforcement-by-host.md +7 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/governance-advantage.md +1 -1
- package/docs/guides/gated-platform-reads.md +221 -0
- package/docs/positioning-evidence.md +1 -1
- package/docs/proof.md +92 -16
- package/docs/spawn-site-policy.md +72 -0
- package/docs/threat-model.md +2 -1
- package/package.json +6 -3
- package/src/agent-src/README.md +2 -2
- package/src/config/agent-settings.template.yml +43 -0
- package/src/config/reach-channels.yml +217 -0
- package/src/config/reach-prescriptions-intake.yml +200 -0
- package/src/scripts/_dispatch.bash +15 -0
- package/src/scripts/_lib/adversarial_bench_score.ts +106 -0
- package/src/scripts/_lib/exec_evidence.ts +251 -0
- package/src/scripts/_lib/fixtures/secret-detector/negatives.txt +10 -0
- package/src/scripts/_lib/fixtures/secret-detector/positives.txt +11 -0
- package/src/scripts/_lib/kernel_rules.ts +46 -0
- package/src/scripts/_lib/reddit_thread_parse.ts +598 -0
- package/src/scripts/_lib/risk_paths.ts +160 -0
- package/src/scripts/_lib/secret_detector.test.ts +103 -0
- package/src/scripts/_lib/secret_detector.ts +319 -0
- package/src/scripts/_lib/spawn_env.ts +24 -4
- package/src/scripts/_lib/tool_probe.ts +359 -0
- package/src/scripts/adr/regenerate_index.ts +81 -6
- package/src/scripts/audit_adr_coverage.ts +33 -0
- package/src/scripts/bench_adversarial_council.ts +292 -0
- package/src/scripts/build_proof.ts +162 -0
- package/src/scripts/check_adr_frontmatter.ts +195 -0
- package/src/scripts/check_artefact_count_messaging.ts +4 -0
- package/src/scripts/check_backstop_debt.ts +200 -0
- package/src/scripts/check_claims.ts +188 -8
- package/src/scripts/check_comparison.ts +12 -0
- package/src/scripts/check_enforcement_coverage.ts +592 -0
- package/src/scripts/check_kernel_rule_bundle.ts +16 -12
- package/src/scripts/check_no_new_legacy_path.ts +2 -0
- package/src/scripts/check_reach_channels.ts +407 -0
- package/src/scripts/check_reach_staleness.ts +364 -0
- package/src/scripts/check_secret_leak.test.ts +47 -0
- package/src/scripts/check_secret_leak.ts +290 -0
- package/src/scripts/check_source_pointer_freshness.ts +165 -0
- package/src/scripts/code_graph/build.ts +0 -0
- package/src/scripts/code_graph/cli.ts +207 -0
- package/src/scripts/code_graph/detect.ts +134 -0
- package/src/scripts/code_graph/extract.ts +386 -0
- package/src/scripts/code_graph/loader.ts +123 -0
- package/src/scripts/code_graph/query.ts +176 -0
- package/src/scripts/code_graph/sanitize.ts +30 -0
- package/src/scripts/code_graph/types.ts +83 -0
- package/src/scripts/code_graph/validate.ts +63 -0
- package/src/scripts/external_sources_denylist.json +3 -1
- package/src/scripts/generate_subagent_floor.ts +194 -0
- package/src/scripts/hook_manifest.yaml +13 -3
- package/src/scripts/hooks/code_graph_nudge_hook.ts +184 -0
- package/src/scripts/hooks/dispatch_hook.ts +5 -1
- package/src/scripts/iron_law_sha.ts +3 -12
- package/src/scripts/lint_override_kernel_guard.ts +219 -0
- package/src/scripts/lint_readme_serial_comma.ts +169 -0
- package/src/scripts/measure_rule_budget.ts +3 -12
- package/src/scripts/postinstall_gui.ts +129 -0
- package/src/scripts/reach_doctor.ts +1651 -0
- package/src/scripts/release.ts +24 -3
- package/src/scripts/risk_escalation_shadow.ts +160 -0
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/reach-channels.schema.json +134 -0
- package/src/scripts/schemas/reach-doctor-payload.schema.json +220 -0
- package/src/scripts/schemas/rule.schema.json +10 -0
- package/src/scripts/skill_linter.ts +55 -0
- package/src/scripts/validate_reach_prescriptions.ts +751 -0
- package/src/server/schemas/settings.ts +16 -0
- package/src/shared/capabilities.test.ts +43 -0
- package/src/shared/capabilities.ts +75 -0
- package/src/templates/minimal/agents-overrides-readme.md +20 -0
- package/dist/ui/assets/index-BiEZiZ31.js.map +0 -1
|
@@ -53,6 +53,10 @@ export const SURFACES: readonly string[] = [
|
|
|
53
53
|
'docs/featured-skills.md',
|
|
54
54
|
'docs/featured-commands.md',
|
|
55
55
|
'docs/proof.md',
|
|
56
|
+
// Public submission text: the block here described version 3.2.0 with a
|
|
57
|
+
// "4929 tests" figure while the package reached 9.7.0, because nothing
|
|
58
|
+
// watched this file. Added 2026-07-25 so a hand-typed count here fails CI.
|
|
59
|
+
'docs/distribution/registries.md',
|
|
56
60
|
'site/src/content/docs/index.mdx',
|
|
57
61
|
'site/src/content/docs/claims.md',
|
|
58
62
|
];
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Debt ratchet for rule backstops that are red on arrival.
|
|
4
|
+
*
|
|
5
|
+
* Wiring the `enforced_by:` validators into CI (`rule-backstops.yml`) surfaced
|
|
6
|
+
* something the coverage number could not: **five of them were already failing**,
|
|
7
|
+
* and had been, invisibly, because no workflow ran them. A rule declared a
|
|
8
|
+
* backstop; the backstop existed; the backstop was red; nothing said so.
|
|
9
|
+
*
|
|
10
|
+
* Those failures are pre-existing debt, not regressions from the wiring change,
|
|
11
|
+
* and fixing eleven framework-leakage spots plus eighteen source-confidentiality
|
|
12
|
+
* hits is a different piece of work with a different reviewer. Folding them in
|
|
13
|
+
* would make one PR unreviewable and would mix "make the gate run" with "clean up
|
|
14
|
+
* what the gate found".
|
|
15
|
+
*
|
|
16
|
+
* So the established pattern applies — the one the decision-homing work already
|
|
17
|
+
* uses and the council endorsed: **baseline the legacy, enforce the boundary.**
|
|
18
|
+
* Each gate's current finding count is committed. The count may fall freely; it
|
|
19
|
+
* may not rise. New violations fail, existing debt is visible and counted, and
|
|
20
|
+
* nothing is silently tolerated.
|
|
21
|
+
*
|
|
22
|
+
* What this deliberately is NOT: a `continue-on-error` step. That is a WARN
|
|
23
|
+
* wearing a gate's clothes, and removing exactly that confusion is why this
|
|
24
|
+
* workflow exists.
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* ./scripts-run src/scripts/check_backstop_debt # ratchet (CI)
|
|
28
|
+
* ./scripts-run src/scripts/check_backstop_debt --write-baseline
|
|
29
|
+
* ./scripts-run src/scripts/check_backstop_debt --json
|
|
30
|
+
*
|
|
31
|
+
* Exit codes: 0 held or improved · 1 a count rose · 2 usage/env error.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { spawnSync } from 'node:child_process';
|
|
35
|
+
import * as fs from 'node:fs';
|
|
36
|
+
import * as path from 'node:path';
|
|
37
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
38
|
+
|
|
39
|
+
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
41
|
+
const BASELINE = path.join(REPO_ROOT, 'internal', 'reports', 'rule-backstop-debt.json');
|
|
42
|
+
|
|
43
|
+
export interface Gate {
|
|
44
|
+
/** Script stem under `src/scripts/`. */
|
|
45
|
+
script: string;
|
|
46
|
+
/** The rule whose `enforced_by:` names it — so a reader knows what is unguarded. */
|
|
47
|
+
rule: string;
|
|
48
|
+
/**
|
|
49
|
+
* Extracts the finding count from the gate's own output.
|
|
50
|
+
*
|
|
51
|
+
* Explicit per gate rather than a shared heuristic: these five print their
|
|
52
|
+
* totals in five different shapes, and a regex that guesses would silently
|
|
53
|
+
* read 0 the day a gate changes its wording — turning a rising count into a
|
|
54
|
+
* passing build, which is the failure mode this file exists to prevent.
|
|
55
|
+
*/
|
|
56
|
+
count: RegExp;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const GATES: readonly Gate[] = [
|
|
60
|
+
{ script: 'lint_framework_leakage', rule: 'framework-neutrality-in-generic-skills', count: /^(\d+) hits across \d+ files/m },
|
|
61
|
+
{ script: 'check_no_roadmap_refs', rule: 'no-roadmap-references', count: /Found (\d+) roadmap reference\(s\)/ },
|
|
62
|
+
{ script: 'check_council_references', rule: 'no-roadmap-references', count: /(\d+) forbidden council reference\(s\)/ },
|
|
63
|
+
{ script: 'check_no_external_sources', rule: 'source-confidentiality', count: /(\d+) external-source reference\(s\)/ },
|
|
64
|
+
{ script: 'check_token_optimizer_freshness', rule: 'token-optimizer-maintenance', count: /(\d+) drift signal\(s\)/ },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export interface Measured {
|
|
68
|
+
script: string;
|
|
69
|
+
rule: string;
|
|
70
|
+
findings: number;
|
|
71
|
+
exit_code: number;
|
|
72
|
+
/** True when the gate ran but its count pattern did not match — never read as 0. */
|
|
73
|
+
unparsed: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function measure(gate: Gate, repo: string = REPO_ROOT): Measured {
|
|
77
|
+
const r = spawnSync('./scripts-run', [`src/scripts/${gate.script}`], {
|
|
78
|
+
cwd: repo,
|
|
79
|
+
encoding: 'utf-8',
|
|
80
|
+
env: { ...process.env, NO_COLOR: '1' },
|
|
81
|
+
});
|
|
82
|
+
const out = `${r.stdout ?? ''}\n${r.stderr ?? ''}`;
|
|
83
|
+
const m = gate.count.exec(out);
|
|
84
|
+
// A clean gate legitimately prints no total. Only treat a MISSING count as
|
|
85
|
+
// unparsed when the gate actually failed — otherwise a green run reads as
|
|
86
|
+
// broken instrumentation.
|
|
87
|
+
const failed = (r.status ?? 0) !== 0;
|
|
88
|
+
return {
|
|
89
|
+
script: gate.script,
|
|
90
|
+
rule: gate.rule,
|
|
91
|
+
findings: m ? Number(m[1]) : 0,
|
|
92
|
+
exit_code: r.status ?? 0,
|
|
93
|
+
unparsed: failed && m === null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function main(argv: string[]): number {
|
|
98
|
+
const as_json = argv.includes('--json');
|
|
99
|
+
const write = argv.includes('--write-baseline');
|
|
100
|
+
|
|
101
|
+
const measured = GATES.map((g) => measure(g));
|
|
102
|
+
|
|
103
|
+
if (as_json) {
|
|
104
|
+
process.stdout.write(JSON.stringify({ gates: measured }, null, 2) + '\n');
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (write) {
|
|
109
|
+
fs.mkdirSync(path.dirname(BASELINE), { recursive: true });
|
|
110
|
+
fs.writeFileSync(
|
|
111
|
+
BASELINE,
|
|
112
|
+
JSON.stringify(
|
|
113
|
+
{
|
|
114
|
+
_doc:
|
|
115
|
+
'Pre-existing findings in rule backstops that were red when they were first ' +
|
|
116
|
+
'wired into CI — they had been failing invisibly because no workflow ran them. ' +
|
|
117
|
+
'Counts may FALL freely; a rise fails the build. This is debt made visible, ' +
|
|
118
|
+
'not debt tolerated: every number here is a rule whose declared backstop is ' +
|
|
119
|
+
'currently not clean. Regenerate with --write-baseline only when the change ' +
|
|
120
|
+
'is deliberate.',
|
|
121
|
+
gates: Object.fromEntries(measured.map((m) => [m.script, { rule: m.rule, findings: m.findings }])),
|
|
122
|
+
total: measured.reduce((a, m) => a + m.findings, 0),
|
|
123
|
+
},
|
|
124
|
+
null,
|
|
125
|
+
2,
|
|
126
|
+
) + '\n',
|
|
127
|
+
);
|
|
128
|
+
process.stdout.write(`✅ wrote baseline → ${path.relative(REPO_ROOT, BASELINE)}\n`);
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(BASELINE)) {
|
|
133
|
+
process.stderr.write(
|
|
134
|
+
`❌ check_backstop_debt: no baseline at ${path.relative(REPO_ROOT, BASELINE)}; run --write-baseline\n`,
|
|
135
|
+
);
|
|
136
|
+
return 2;
|
|
137
|
+
}
|
|
138
|
+
const base = JSON.parse(fs.readFileSync(BASELINE, 'utf-8')).gates as Record<string, { findings: number }>;
|
|
139
|
+
|
|
140
|
+
const regressions: string[] = [];
|
|
141
|
+
const improvements: string[] = [];
|
|
142
|
+
for (const m of measured) {
|
|
143
|
+
if (m.unparsed) {
|
|
144
|
+
regressions.push(
|
|
145
|
+
`${m.script}: failed (exit ${m.exit_code}) but its finding count could not be read — ` +
|
|
146
|
+
`the count pattern no longer matches its output. Fix the pattern; do not assume 0.`,
|
|
147
|
+
);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const was = base[m.script]?.findings;
|
|
151
|
+
if (was === undefined) {
|
|
152
|
+
regressions.push(`${m.script}: not in the baseline — add it with --write-baseline`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (m.findings > was) {
|
|
156
|
+
regressions.push(`${m.script} (${m.rule}): findings rose ${was} → ${m.findings}`);
|
|
157
|
+
} else if (m.findings < was) {
|
|
158
|
+
improvements.push(`${m.script} (${m.rule}): ${was} → ${m.findings}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const total = measured.reduce((a, m) => a + m.findings, 0);
|
|
163
|
+
process.stdout.write(
|
|
164
|
+
`rule-backstop debt · ${total} finding(s) across ${measured.filter((m) => m.findings > 0).length} red gate(s)\n`,
|
|
165
|
+
);
|
|
166
|
+
for (const m of measured) {
|
|
167
|
+
if (m.findings > 0) process.stdout.write(` · ${m.script} (${m.rule}): ${m.findings}\n`);
|
|
168
|
+
}
|
|
169
|
+
for (const i of improvements) process.stdout.write(` ✅ improved — ${i}\n`);
|
|
170
|
+
|
|
171
|
+
if (regressions.length > 0) {
|
|
172
|
+
process.stderr.write('❌ rule-backstop debt ratchet:\n');
|
|
173
|
+
for (const r of regressions) process.stderr.write(` · ${r}\n`);
|
|
174
|
+
process.stderr.write(
|
|
175
|
+
' These gates are already failing on pre-existing debt; the ratchet only forbids ADDING to it.\n',
|
|
176
|
+
);
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
if (improvements.length > 0) {
|
|
180
|
+
process.stdout.write(' Baseline is now loose — regenerate with --write-baseline to lock the improvement.\n');
|
|
181
|
+
}
|
|
182
|
+
process.stdout.write('✅ rule-backstop debt ratchet holds\n');
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Main-guard (realpath-compared, mirrors the repo convention).
|
|
187
|
+
if (process.argv[1] !== undefined) {
|
|
188
|
+
try {
|
|
189
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
190
|
+
const argv1 = fs.realpathSync(path.resolve(process.argv[1]));
|
|
191
|
+
if (here === argv1) {
|
|
192
|
+
process.exit(main(process.argv.slice(2)));
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
196
|
+
if (import.meta.url === argvUrl) {
|
|
197
|
+
process.exit(main(process.argv.slice(2)));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -15,10 +15,17 @@
|
|
|
15
15
|
* Ledger entries with `status: unbacked` are inventory (documented debt) and do
|
|
16
16
|
* NOT fail the build — but marking such an entry's claim in prose does.
|
|
17
17
|
*
|
|
18
|
-
* Evidence-pointer grammar (
|
|
18
|
+
* Evidence-pointer grammar (v2):
|
|
19
19
|
* <repo-path>[:line] → the repo file exists (line advisory).
|
|
20
20
|
* <repo-path>#<substring> → the file exists AND contains <substring>.
|
|
21
21
|
* https://… (YYYY-MM-DD) → external cite with a dated stamp (not fetched).
|
|
22
|
+
* exec:<command> -> <code> → the command RE-RUNS and its exit code matches.
|
|
23
|
+
*
|
|
24
|
+
* The first three are existence checks and cannot tell a live claim from a
|
|
25
|
+
* stale one: a pointer at a report nobody regenerated resolves forever. The
|
|
26
|
+
* fourth re-derives the claim. It runs in CI only — locally it reports
|
|
27
|
+
* UNVERIFIED rather than executing anything, because a consumer's checkout has
|
|
28
|
+
* no business re-running this package's evidence commands.
|
|
22
29
|
*
|
|
23
30
|
* Exit codes: 0 = clean · 2 = unbacked/dangling/missing-entry finding OR usage
|
|
24
31
|
* error. Success prints to stdout; findings print to stderr.
|
|
@@ -26,10 +33,17 @@
|
|
|
26
33
|
* Usage:
|
|
27
34
|
* ./scripts-run src/scripts/check_claims
|
|
28
35
|
* ./scripts-run src/scripts/check_claims --quiet
|
|
36
|
+
* CI=true ./scripts-run src/scripts/check_claims # re-runs exec: claims
|
|
29
37
|
*/
|
|
30
38
|
import * as fs from 'node:fs';
|
|
31
39
|
import * as path from 'node:path';
|
|
32
40
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
41
|
+
import {
|
|
42
|
+
exec_allowed_here,
|
|
43
|
+
exec_static_error,
|
|
44
|
+
parse_exec_pointer,
|
|
45
|
+
run_exec_evidence,
|
|
46
|
+
} from './_lib/exec_evidence.js';
|
|
33
47
|
|
|
34
48
|
const _FILE = fileURLToPath(import.meta.url);
|
|
35
49
|
const _HERE = path.dirname(_FILE);
|
|
@@ -46,8 +60,47 @@ const URL_DATED = /^https?:\/\/\S+\(\d{4}-\d{2}-\d{2}\)\s*$/;
|
|
|
46
60
|
* not appear as a capability claim. docs/proof.md and docs/comparison.yaml
|
|
47
61
|
* are structurally witnessed (pointer columns + their own linters). */
|
|
48
62
|
const WITNESS_SURFACES = ['README.md', 'CAPABILITIES.yaml'];
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Shapes that read as a measured capability figure.
|
|
66
|
+
*
|
|
67
|
+
* The ratio pattern was written against the "84.8%-style headline" anti-lesson
|
|
68
|
+
* and catches exactly that. It missed the shape that actually bit us: the README
|
|
69
|
+
* shipped "compiled into 7+ host agents" while the real, test-pinned number was
|
|
70
|
+
* 23 detected / 20 emitted — understating coverage by 3x, for months, on the one
|
|
71
|
+
* surface this sweep already watched. A bare integer carries no `%` and no `x`,
|
|
72
|
+
* so no ratio pattern could see it and no unit allowlist would either.
|
|
73
|
+
*
|
|
74
|
+
* Three narrow classes:
|
|
75
|
+
* - RATIO — percentages and multipliers (the original).
|
|
76
|
+
* - MAGNITUDE — a number carrying a measurement unit. Unit-gated, not
|
|
77
|
+
* digit-gated: a thousands-separator heuristic would catch
|
|
78
|
+
* `22,077` and also every year and large ordinal.
|
|
79
|
+
* - SELF_COUNT — a count-shaped assertion about the package's own reach.
|
|
80
|
+
* This is the class that shipped wrong.
|
|
81
|
+
*
|
|
82
|
+
* Excluded from MAGNITUDE in v1: time units. "wait 30 seconds" is an
|
|
83
|
+
* instruction, not a claim, and a gate that false-positives is a gate that gets
|
|
84
|
+
* bypassed (`narrow > recall`, as with the credential floor).
|
|
85
|
+
*
|
|
86
|
+
* Deliberately NOT widened to more surfaces, and this was measured rather than
|
|
87
|
+
* assumed: `docs/benchmark.md` alone matches the ratio pattern on 54 lines — it
|
|
88
|
+
* is a methodology document whose job is to be full of statistics, and sweeping
|
|
89
|
+
* it would produce exactly the flood that teaches a maintainer to bypass the
|
|
90
|
+
* gate. `docs/proof.md` and `docs/comparison.yaml` are pointer-enforced by their
|
|
91
|
+
* own linters, so an unmarkered figure cannot originate there. The gap was never
|
|
92
|
+
* the surface list; it was the pattern.
|
|
93
|
+
*/
|
|
94
|
+
const RATIO = /\b\d+(?:\.\d+)?\s*(?:%|[x×](?![A-Za-z0-9]))/;
|
|
95
|
+
// The optional `<word>-` allows a qualified unit: `13,881 GPT-tokens` is the
|
|
96
|
+
// same claim shape as `13,881 tokens` and was missed without it.
|
|
97
|
+
const MAGNITUDE = /\b\d[\d,._]*\s*(?:[A-Za-z]+-)?(?:tokens?|ms|USD|KB|MB|GB|chars?)\b/i;
|
|
98
|
+
const SELF_COUNT = /\b\d+\+?\s+(?:host agents?|hosts|supported (?:agents?|hosts))\b/i;
|
|
99
|
+
|
|
100
|
+
/** True when a line carries any figure shape that must bind to a claim. */
|
|
101
|
+
export function is_quantified_claim(line: string): boolean {
|
|
102
|
+
return RATIO.test(line) || MAGNITUDE.test(line) || SELF_COUNT.test(line);
|
|
103
|
+
}
|
|
51
104
|
|
|
52
105
|
class ExitCode extends Error {
|
|
53
106
|
code: number;
|
|
@@ -57,7 +110,7 @@ class ExitCode extends Error {
|
|
|
57
110
|
}
|
|
58
111
|
}
|
|
59
112
|
|
|
60
|
-
interface LedgerEntry {
|
|
113
|
+
export interface LedgerEntry {
|
|
61
114
|
id: string;
|
|
62
115
|
claim: string;
|
|
63
116
|
kind: string;
|
|
@@ -170,10 +223,24 @@ function scan_markers(): { id: string; file: string }[] {
|
|
|
170
223
|
return found;
|
|
171
224
|
}
|
|
172
225
|
|
|
173
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* Does an evidence pointer resolve? Returns null on success, else a reason.
|
|
228
|
+
*
|
|
229
|
+
* For `exec:` this is the STATIC half only — is the pointer well-formed and is
|
|
230
|
+
* the command allowlisted. A malformed or non-allowlisted command fails
|
|
231
|
+
* everywhere, including locally, because that is a defect in the ledger rather
|
|
232
|
+
* than a property of the environment. Re-execution is the separate CI-gated
|
|
233
|
+
* pass in `main`, so a laptop never runs an evidence command.
|
|
234
|
+
*/
|
|
174
235
|
function pointer_unresolved(evidence: string): string | null {
|
|
175
236
|
const ev = evidence.trim();
|
|
176
237
|
if (!ev) return 'empty evidence pointer';
|
|
238
|
+
|
|
239
|
+
const exec = parse_exec_pointer(ev);
|
|
240
|
+
if (exec !== null) {
|
|
241
|
+
return 'error' in exec ? exec.error : exec_static_error(exec);
|
|
242
|
+
}
|
|
243
|
+
|
|
177
244
|
if (/^https?:\/\//.test(ev)) {
|
|
178
245
|
return URL_DATED.test(ev) ? null : 'external URL missing a (YYYY-MM-DD) stamp';
|
|
179
246
|
}
|
|
@@ -254,16 +321,100 @@ function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
254
321
|
continue;
|
|
255
322
|
}
|
|
256
323
|
if (inFence) continue;
|
|
257
|
-
if (!
|
|
258
|
-
if (
|
|
324
|
+
if (!is_quantified_claim(line)) continue;
|
|
325
|
+
if (/unverified/i.test(line)) continue;
|
|
326
|
+
|
|
327
|
+
// A marker on the line is not enough — it must be a marker that can
|
|
328
|
+
// LICENSE A NUMBER. This is how "7+ host agents" survived: the line
|
|
329
|
+
// already carried `claim:no-runtime-daemon`, a `kind: qual` claim
|
|
330
|
+
// about having no daemon, and any-marker-exempts-the-line let the
|
|
331
|
+
// unrelated figure ride along on it. A qualitative claim says nothing
|
|
332
|
+
// about a quantity, so only a `kind: quant` entry clears a figure.
|
|
333
|
+
const markers = [...line.matchAll(/<!--\s*claim:([A-Za-z0-9._-]+)\s*-->/g)].map((m) => m[1] as string);
|
|
334
|
+
const licensed = markers.some((id) => ledger.get(id)?.kind === 'quant');
|
|
335
|
+
if (licensed) continue;
|
|
336
|
+
|
|
337
|
+
const why =
|
|
338
|
+
markers.length > 0
|
|
339
|
+
? `figure carried only by non-quantitative claim marker(s) [${markers.join(', ')}] — a \`kind: qual\` claim cannot license a number`
|
|
340
|
+
: 'quantified claim without a claim marker or \'unverified\' annotation';
|
|
259
341
|
findings.push({
|
|
260
342
|
id: '(unmarkered)',
|
|
261
343
|
file: `${rel}:${i + 1}`,
|
|
262
|
-
reason:
|
|
344
|
+
reason: `${why} — ${line.trim().slice(0, 70)}`,
|
|
263
345
|
});
|
|
264
346
|
}
|
|
265
347
|
}
|
|
266
348
|
|
|
349
|
+
// A published denominator must match the live ledger.
|
|
350
|
+
//
|
|
351
|
+
// `exec-evidence-feasibility.json` records how many backed claims the
|
|
352
|
+
// feasibility measurement was taken over. That number was hand-written and
|
|
353
|
+
// drifted twice inside two days — 25 when the ledger held 26, then 26 when it
|
|
354
|
+
// held 27 — and CI stayed green both times, because the claim's evidence
|
|
355
|
+
// pointer resolved. Pointer-resolution is not truth: that is the very thing
|
|
356
|
+
// `ledger-exec-verifiability` says about the ledger, demonstrated by its own
|
|
357
|
+
// entry. The classification in that file is a human judgment and stays one;
|
|
358
|
+
// the denominator is mechanical, so it gets checked.
|
|
359
|
+
{
|
|
360
|
+
const rel = 'internal/reports/exec-evidence-feasibility.json';
|
|
361
|
+
const abs = path.join(REPO, rel);
|
|
362
|
+
if (fs.existsSync(abs)) {
|
|
363
|
+
try {
|
|
364
|
+
const stored = JSON.parse(fs.readFileSync(abs, 'utf8')) as { backed_claims?: number };
|
|
365
|
+
const live = [...ledger.values()].filter((e) => e.status === 'backed').length;
|
|
366
|
+
if (typeof stored.backed_claims === 'number' && stored.backed_claims !== live) {
|
|
367
|
+
findings.push({
|
|
368
|
+
id: '(derived-count)',
|
|
369
|
+
file: rel,
|
|
370
|
+
reason:
|
|
371
|
+
`backed_claims is ${stored.backed_claims} but the ledger holds ${live} — ` +
|
|
372
|
+
`a published denominator drifted from its source. Re-measure and update the report.`,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
} catch {
|
|
376
|
+
findings.push({
|
|
377
|
+
id: '(derived-count)',
|
|
378
|
+
file: rel,
|
|
379
|
+
reason: 'unparseable JSON — the derived-count check cannot verify the published denominator',
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 4. Re-execution pass (`exec:` evidence). CI only — locally every exec
|
|
386
|
+
// claim reports UNVERIFIED and nothing runs. A mismatch is a finding: the
|
|
387
|
+
// command ran and disagreed with the claim. A skip is NOT a finding —
|
|
388
|
+
// collapsing "could not run" into "failed" is how a verifier degrades
|
|
389
|
+
// into a rubber stamp in the other direction.
|
|
390
|
+
const execEntries = [...ledger.values()]
|
|
391
|
+
.filter((e) => e.status === 'backed')
|
|
392
|
+
.map((e) => ({ entry: e, ptr: parse_exec_pointer(e.evidence) }))
|
|
393
|
+
.filter((x): x is { entry: LedgerEntry; ptr: { command: string; expected: number } } =>
|
|
394
|
+
x.ptr !== null && !('error' in x.ptr),
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const execSkipped: string[] = [];
|
|
398
|
+
const execVerified: string[] = [];
|
|
399
|
+
|
|
400
|
+
if (execEntries.length > 0) {
|
|
401
|
+
const allowed = exec_allowed_here();
|
|
402
|
+
for (const { entry, ptr } of execEntries) {
|
|
403
|
+
if (!allowed) {
|
|
404
|
+
execSkipped.push(entry.id);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const outcome = run_exec_evidence(ptr, REPO);
|
|
408
|
+
if (outcome.mismatch) {
|
|
409
|
+
findings.push({ id: entry.id, file: LEDGER_REL, reason: outcome.reason });
|
|
410
|
+
} else if (outcome.verified) {
|
|
411
|
+
execVerified.push(entry.id);
|
|
412
|
+
} else {
|
|
413
|
+
execSkipped.push(entry.id);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
267
418
|
if (findings.length > 0) {
|
|
268
419
|
process.stderr.write(`❌ check_claims: ${findings.length} unbacked/dangling claim(s):\n`);
|
|
269
420
|
for (const f of findings) {
|
|
@@ -279,6 +430,14 @@ function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
279
430
|
process.stdout.write(
|
|
280
431
|
`✅ check_claims: ${markers.length} markered claim(s) bound · ledger ${ledger.size} entries (${backed} backed, ${unbacked} unbacked inventory)\n`,
|
|
281
432
|
);
|
|
433
|
+
if (execEntries.length > 0) {
|
|
434
|
+
const detail = execVerified.length > 0 ? ` (${execVerified.length} re-verified)` : '';
|
|
435
|
+
process.stdout.write(
|
|
436
|
+
execSkipped.length === execEntries.length
|
|
437
|
+
? ` exec: ${execEntries.length} claim(s) UNVERIFIED — re-execution is CI-only, skipped locally\n`
|
|
438
|
+
: ` exec: ${execEntries.length} claim(s)${detail}, ${execSkipped.length} skipped\n`,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
282
441
|
}
|
|
283
442
|
return 0;
|
|
284
443
|
}
|
|
@@ -308,3 +467,24 @@ if (_isCliEntry()) {
|
|
|
308
467
|
}
|
|
309
468
|
|
|
310
469
|
export { REPO, LEDGER_REL, main, parse_args, load_ledger, scan_markers, pointer_unresolved, ExitCode };
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Live count of `status: backed` ledger entries.
|
|
473
|
+
*
|
|
474
|
+
* Exists so a published denominator can be DERIVED instead of typed. The
|
|
475
|
+
* `ledger-exec-verifiability` entry hard-coded this number twice and drifted
|
|
476
|
+
* within a day both times — first 25 when the ledger held 26, then 26 when it
|
|
477
|
+
* held 27 — while `check_claims` stayed green because its evidence pointer
|
|
478
|
+
* resolved. A number a human retypes on every ledger edit will drift; the only
|
|
479
|
+
* fix is to stop retyping it.
|
|
480
|
+
*
|
|
481
|
+
* Note this is not the same as `grep -c '^- status: backed'`, which also counts
|
|
482
|
+
* the entry-schema template in the document header.
|
|
483
|
+
*/
|
|
484
|
+
export function count_backed(): number {
|
|
485
|
+
let n = 0;
|
|
486
|
+
for (const e of load_ledger().values()) {
|
|
487
|
+
if (e.status === 'backed') n += 1;
|
|
488
|
+
}
|
|
489
|
+
return n;
|
|
490
|
+
}
|
|
@@ -29,6 +29,18 @@ export interface Row {
|
|
|
29
29
|
our_evidence: string;
|
|
30
30
|
their_evidence: string;
|
|
31
31
|
checkable: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Optional — the failure mode this row's claim prevents, in adopter-facing
|
|
34
|
+
* voice. Populated rows additionally render as a second, non-comparative
|
|
35
|
+
* view; an empty field is the honest signal that a row is comparative only.
|
|
36
|
+
*
|
|
37
|
+
* The field exists so that "which rows appear where" is a **data property**
|
|
38
|
+
* rather than a curatorial decision. Hand-picking rows for a second surface
|
|
39
|
+
* would be manual curation with weaker enforcement than the projection this
|
|
40
|
+
* repo already runs — and either way the row's `our_evidence` pointer keeps
|
|
41
|
+
* being resolved, so no cell escapes the gate by appearing in the new view.
|
|
42
|
+
*/
|
|
43
|
+
failure_mode?: string;
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
export function loadRows(repo: string = REPO): Row[] {
|