@event4u/agent-config 9.7.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.
Files changed (185) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +144 -0
  3. package/README.md +5 -5
  4. package/dist/agent-src/commands/commit/in-chunks.md +8 -0
  5. package/dist/agent-src/commands/commit.md +17 -0
  6. package/dist/agent-src/commands/pr/create.md +4 -0
  7. package/dist/agent-src/contexts/override-system.md +75 -5
  8. package/dist/agent-src/rules/context-hygiene.md +2 -0
  9. package/dist/agent-src/rules/doc-screenshot-hygiene.md +97 -0
  10. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  11. package/dist/agent-src/rules/external-code-graph-interop.md +13 -3
  12. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
  13. package/dist/agent-src/rules/git-history-discipline.md +2 -0
  14. package/dist/agent-src/rules/language-and-tone.md +2 -0
  15. package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
  16. package/dist/agent-src/rules/media-governance-routing.md +2 -0
  17. package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
  18. package/dist/agent-src/rules/no-roadmap-references.md +3 -0
  19. package/dist/agent-src/rules/non-destructive-by-default.md +20 -0
  20. package/dist/agent-src/rules/onboarding-gate.md +2 -0
  21. package/dist/agent-src/rules/output-discipline.md +2 -0
  22. package/dist/agent-src/rules/persona-governance.md +2 -0
  23. package/dist/agent-src/rules/preservation-guard.md +3 -0
  24. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  25. package/dist/agent-src/rules/secret-vcs-guard.md +106 -0
  26. package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
  27. package/dist/agent-src/rules/skill-quality.md +2 -0
  28. package/dist/agent-src/rules/source-confidentiality.md +2 -0
  29. package/dist/agent-src/rules/source-of-truth.md +2 -0
  30. package/dist/agent-src/rules/telegraph-speak.md +2 -0
  31. package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
  32. package/dist/agent-src/rules/tool-safety.md +1 -0
  33. package/dist/agent-src/rules/verify-before-complete.md +2 -0
  34. package/dist/agent-src/skills/adr-create/SKILL.md +17 -1
  35. package/dist/agent-src/skills/code-intelligence/SKILL.md +94 -0
  36. package/dist/agent-src/skills/gated-reach/SKILL.md +185 -0
  37. package/dist/agent-src/skills/gated-reach/evals/triggers.json +72 -0
  38. package/dist/agent-src/skills/image-analyser/SKILL.md +14 -12
  39. package/dist/agent-src/skills/image-editing/SKILL.md +1 -0
  40. package/dist/agent-src/skills/readme-writing/SKILL.md +6 -1
  41. package/dist/agent-src/skills/screenshot-hygiene/SKILL.md +171 -0
  42. package/dist/agent-src/skills/secrets-management/SKILL.md +44 -1
  43. package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +29 -0
  44. package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md +29 -0
  45. package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge.md +29 -0
  46. package/dist/agent-src/skills/subagent-orchestration/prompts/do-competitively.md +29 -0
  47. package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-parallel.md +29 -0
  48. package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-steps.md +29 -0
  49. package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-worktrees.md +29 -0
  50. package/dist/agent-src/skills/subagent-orchestration/prompts/judge-with-debate.md +29 -0
  51. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +4 -0
  52. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  53. package/dist/agent-src/templates/github-workflows/secret-scan.yml +65 -0
  54. package/dist/cli/configRoot.js +112 -0
  55. package/dist/cli/configRoot.js.map +1 -0
  56. package/dist/cli/configRoot.test.js +97 -0
  57. package/dist/cli/configRoot.test.js.map +1 -0
  58. package/dist/cli/help.js +4 -1
  59. package/dist/cli/help.js.map +1 -1
  60. package/dist/cli/main.js +19 -2
  61. package/dist/cli/main.js.map +1 -1
  62. package/dist/cli/registry.js +1 -0
  63. package/dist/cli/registry.js.map +1 -1
  64. package/dist/discovery/deprecation-report.md +1 -1
  65. package/dist/discovery/discovery-manifest.json +161 -44
  66. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  67. package/dist/discovery/discovery-manifest.summary.md +4 -4
  68. package/dist/discovery/orphan-report.md +1 -1
  69. package/dist/discovery/packs.json +13 -8
  70. package/dist/discovery/trust-report.md +11 -11
  71. package/dist/discovery/workspaces.json +11 -6
  72. package/dist/install/install.mjs +16 -0
  73. package/dist/mcp/registry-manifest.json +3 -3
  74. package/dist/router.json +1 -1
  75. package/dist/server/app.js +10 -0
  76. package/dist/server/app.js.map +1 -1
  77. package/dist/server/routes/ping.js +18 -0
  78. package/dist/server/routes/ping.js.map +1 -1
  79. package/dist/server/schemas/settings.js +8 -0
  80. package/dist/server/schemas/settings.js.map +1 -1
  81. package/dist/server/serverInfo.js +6 -2
  82. package/dist/server/serverInfo.js.map +1 -1
  83. package/dist/server/token.js +11 -4
  84. package/dist/server/token.js.map +1 -1
  85. package/dist/server/writeRoot.js +15 -1
  86. package/dist/server/writeRoot.js.map +1 -1
  87. package/dist/shared/capabilities.js +33 -0
  88. package/dist/shared/capabilities.js.map +1 -0
  89. package/dist/shared/capabilities.test.js +39 -0
  90. package/dist/shared/capabilities.test.js.map +1 -0
  91. package/dist/ui/assets/{index-BiEZiZ31.js → index-CrmZ34Rg.js} +16 -16
  92. package/dist/ui/assets/index-CrmZ34Rg.js.map +1 -0
  93. package/dist/ui/index.html +14 -5
  94. package/docs/CLAIMS.md +62 -37
  95. package/docs/architecture.md +2 -2
  96. package/docs/benchmark.md +137 -0
  97. package/docs/catalog.md +8 -3
  98. package/docs/comparison.yaml +44 -2
  99. package/docs/contracts/local-server-ports.md +53 -0
  100. package/docs/contracts/no-runtime-boundary.md +1 -1
  101. package/docs/contracts/subagent-boundary.md +15 -0
  102. package/docs/customization.md +14 -3
  103. package/docs/decisions/ADR-016-installer-architecture.md +2 -0
  104. package/docs/decisions/ADR-088-no-external-runtime-federation.md +1 -1
  105. package/docs/decisions/ADR-094-agent-memory-layer-removal.md +1 -1
  106. package/docs/decisions/ADR-109-subagent-v1-contract.md +2 -0
  107. package/docs/decisions/ADR-110-discipline-profile-resolution-locus.md +2 -0
  108. package/docs/decisions/ADR-113-ci-native-release-label-trigger.md +11 -3
  109. package/docs/decisions/ADR-124-embedded-engine-doctrine.md +255 -0
  110. package/docs/decisions/ADR-125-doc-screenshot-anonymization.md +131 -0
  111. package/docs/decisions/ADR-126-internet-reach-operator-tooling.md +254 -0
  112. package/docs/decisions/ADR-127-enforcement-claims-must-resolve.md +150 -0
  113. package/docs/decisions/ADR-128-executable-evidence-and-measured-escalation.md +201 -0
  114. package/docs/decisions/INDEX.md +21 -0
  115. package/docs/decisions/engine-reclassification-2026-07.md +104 -0
  116. package/docs/distribution/awesome-list-submission.md +1 -1
  117. package/docs/distribution/registries.md +36 -27
  118. package/docs/enforcement-by-host.md +7 -0
  119. package/docs/featured-skills.md +2 -2
  120. package/docs/getting-started-by-role.md +1 -1
  121. package/docs/getting-started.md +1 -1
  122. package/docs/governance-advantage.md +1 -1
  123. package/docs/guides/gated-platform-reads.md +221 -0
  124. package/docs/positioning-evidence.md +1 -1
  125. package/docs/proof.md +92 -16
  126. package/docs/threat-model.md +1 -0
  127. package/package.json +5 -2
  128. package/src/config/agent-settings.template.yml +43 -0
  129. package/src/config/reach-channels.yml +217 -0
  130. package/src/config/reach-prescriptions-intake.yml +200 -0
  131. package/src/scripts/_dispatch.bash +15 -0
  132. package/src/scripts/_lib/exec_evidence.ts +251 -0
  133. package/src/scripts/_lib/fixtures/secret-detector/negatives.txt +10 -0
  134. package/src/scripts/_lib/fixtures/secret-detector/positives.txt +11 -0
  135. package/src/scripts/_lib/kernel_rules.ts +46 -0
  136. package/src/scripts/_lib/reddit_thread_parse.ts +598 -0
  137. package/src/scripts/_lib/risk_paths.ts +160 -0
  138. package/src/scripts/_lib/secret_detector.test.ts +103 -0
  139. package/src/scripts/_lib/secret_detector.ts +319 -0
  140. package/src/scripts/_lib/tool_probe.ts +359 -0
  141. package/src/scripts/adr/regenerate_index.ts +81 -6
  142. package/src/scripts/audit_adr_coverage.ts +33 -0
  143. package/src/scripts/build_proof.ts +162 -0
  144. package/src/scripts/check_adr_frontmatter.ts +195 -0
  145. package/src/scripts/check_artefact_count_messaging.ts +4 -0
  146. package/src/scripts/check_backstop_debt.ts +200 -0
  147. package/src/scripts/check_claims.ts +188 -8
  148. package/src/scripts/check_comparison.ts +12 -0
  149. package/src/scripts/check_enforcement_coverage.ts +592 -0
  150. package/src/scripts/check_kernel_rule_bundle.ts +16 -12
  151. package/src/scripts/check_reach_channels.ts +407 -0
  152. package/src/scripts/check_reach_staleness.ts +364 -0
  153. package/src/scripts/check_secret_leak.test.ts +47 -0
  154. package/src/scripts/check_secret_leak.ts +290 -0
  155. package/src/scripts/code_graph/build.ts +0 -0
  156. package/src/scripts/code_graph/cli.ts +207 -0
  157. package/src/scripts/code_graph/detect.ts +134 -0
  158. package/src/scripts/code_graph/extract.ts +386 -0
  159. package/src/scripts/code_graph/loader.ts +123 -0
  160. package/src/scripts/code_graph/query.ts +176 -0
  161. package/src/scripts/code_graph/sanitize.ts +30 -0
  162. package/src/scripts/code_graph/types.ts +83 -0
  163. package/src/scripts/code_graph/validate.ts +63 -0
  164. package/src/scripts/external_sources_denylist.json +3 -1
  165. package/src/scripts/generate_subagent_floor.ts +194 -0
  166. package/src/scripts/hook_manifest.yaml +13 -3
  167. package/src/scripts/hooks/code_graph_nudge_hook.ts +184 -0
  168. package/src/scripts/iron_law_sha.ts +3 -12
  169. package/src/scripts/lint_override_kernel_guard.ts +219 -0
  170. package/src/scripts/measure_rule_budget.ts +3 -12
  171. package/src/scripts/postinstall_gui.ts +129 -0
  172. package/src/scripts/reach_doctor.ts +1651 -0
  173. package/src/scripts/release.ts +24 -3
  174. package/src/scripts/risk_escalation_shadow.ts +160 -0
  175. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  176. package/src/scripts/schemas/reach-channels.schema.json +134 -0
  177. package/src/scripts/schemas/reach-doctor-payload.schema.json +220 -0
  178. package/src/scripts/schemas/rule.schema.json +10 -0
  179. package/src/scripts/skill_linter.ts +55 -0
  180. package/src/scripts/validate_reach_prescriptions.ts +751 -0
  181. package/src/server/schemas/settings.ts +16 -0
  182. package/src/shared/capabilities.test.ts +43 -0
  183. package/src/shared/capabilities.ts +75 -0
  184. package/src/templates/minimal/agents-overrides-readme.md +20 -0
  185. package/dist/ui/assets/index-BiEZiZ31.js.map +0 -1
@@ -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 (v1):
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
- /** A percentage or multiplier reading as a measured capability figure. */
50
- const QUANTIFIED_CLAIM = /\b\d+(?:\.\d+)?\s*(?:%|[x×](?![A-Za-z0-9]))/;
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
- /** Does an evidence pointer resolve? Returns null on success, else a reason. */
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 (!QUANTIFIED_CLAIM.test(line)) continue;
258
- if (/<!--\s*claim:/.test(line) || /unverified/i.test(line)) continue;
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: `quantified claim without a claim marker or 'unverified' annotation — ${line.trim().slice(0, 70)}`,
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[] {