@event4u/agent-config 14.8.0 → 14.10.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 (170) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +214 -237
  3. package/README.md +1 -1
  4. package/agents/templates/.ai-council.yml.example +34 -4
  5. package/dist/agent-src/commands/pr/merge.md +9 -5
  6. package/dist/agent-src/commands/review/changes.md +95 -14
  7. package/dist/agent-src/commands/roadmap/process-full.md +2 -2
  8. package/dist/agent-src/contexts/execution/host-capability-manifest.md +77 -0
  9. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +36 -0
  10. package/dist/agent-src/contexts/execution/subagent-response-contract.md +80 -0
  11. package/dist/agent-src/rules/untrusted-input-defense.md +9 -3
  12. package/dist/agent-src/skills/async-python-patterns/SKILL.md +1 -1
  13. package/dist/agent-src/skills/code-refactoring/SKILL.md +1 -1
  14. package/dist/agent-src/skills/code-review/evals/evals.json +87 -46
  15. package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
  16. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +1 -1
  17. package/dist/agent-src/skills/design-review/SKILL.md +1 -1
  18. package/dist/agent-src/skills/discovery-interview/SKILL.md +1 -1
  19. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
  20. package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -1
  21. package/dist/agent-src/skills/editorial-calendar/SKILL.md +1 -1
  22. package/dist/agent-src/skills/github-ci/SKILL.md +1 -1
  23. package/dist/agent-src/skills/humanizer/SKILL.md +39 -0
  24. package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +57 -0
  25. package/dist/agent-src/skills/humanizer/references/fixtures.md +54 -0
  26. package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
  27. package/dist/agent-src/skills/judge-injection-defense/SKILL.md +1 -1
  28. package/dist/agent-src/skills/judge-spec-compliance/SKILL.md +154 -0
  29. package/dist/agent-src/skills/judge-synthesis/SKILL.md +45 -0
  30. package/dist/agent-src/skills/laravel-validation/SKILL.md +1 -1
  31. package/dist/agent-src/skills/legal-intake-triage/SKILL.md +1 -1
  32. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +1 -1
  33. package/dist/agent-src/skills/messaging-architecture/SKILL.md +1 -1
  34. package/dist/agent-src/skills/migration-architect/SKILL.md +1 -1
  35. package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +1 -1
  36. package/dist/agent-src/skills/module-management/SKILL.md +1 -1
  37. package/dist/agent-src/skills/motion-choreographer/SKILL.md +1 -1
  38. package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
  39. package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
  40. package/dist/agent-src/skills/prompt-optimizer/SKILL.md +1 -1
  41. package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
  42. package/dist/agent-src/skills/roadmap-writing/SKILL.md +18 -18
  43. package/dist/agent-src/skills/scene-expander/SKILL.md +1 -1
  44. package/dist/agent-src/skills/secrets-management/SKILL.md +1 -1
  45. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
  46. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  47. package/dist/agent-src/templates/roadmaps.md +8 -0
  48. package/dist/cli/registry.js +2 -0
  49. package/dist/cli/registry.js.map +1 -1
  50. package/dist/cli-delegate/{chunk-MMSGVZKV.js → chunk-77GR3VD4.js} +65 -40
  51. package/dist/cli-delegate/{chunk-Q26HT4QT.js → chunk-E46KPN6K.js} +251 -150
  52. package/dist/cli-delegate/{chunk-EEJUMCDZ.js → chunk-PLSG6UL5.js} +1 -1
  53. package/dist/cli-delegate/cmd_conformance.js +89 -13
  54. package/dist/cli-delegate/cmd_doctor.js +3 -3
  55. package/dist/cli-delegate/cmd_handoff.js +2 -2
  56. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  57. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  58. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  59. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  60. package/dist/discovery/deprecation-report.md +1 -1
  61. package/dist/discovery/discovery-manifest.json +64 -42
  62. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  63. package/dist/discovery/discovery-manifest.summary.md +3 -3
  64. package/dist/discovery/dormancy-report.md +1 -1
  65. package/dist/discovery/orphan-report.md +1 -1
  66. package/dist/discovery/packs.json +6 -5
  67. package/dist/discovery/trust-report.md +2 -2
  68. package/dist/discovery/workspaces.json +5 -4
  69. package/dist/hooks/dispatch.js +562 -204
  70. package/dist/mcp/registry-manifest.json +3 -3
  71. package/dist/mcp/server.json +2 -2
  72. package/dist/mcp/server.mjs +808 -331
  73. package/dist/scripts/_lib/claude_plugin.js +162 -0
  74. package/dist/scripts/_lib/claude_plugin.js.map +1 -0
  75. package/dist/scripts/_lib/environment_detector.js +397 -0
  76. package/dist/scripts/_lib/environment_detector.js.map +1 -0
  77. package/dist/scripts/_lib/spawn_env.js +129 -0
  78. package/dist/scripts/_lib/spawn_env.js.map +1 -0
  79. package/dist/scripts/ai_council/modes.js +199 -0
  80. package/dist/scripts/ai_council/modes.js.map +1 -0
  81. package/dist/scripts/ai_council/transport_resolver.js +418 -0
  82. package/dist/scripts/ai_council/transport_resolver.js.map +1 -0
  83. package/dist/server/routes/wizard.js +3 -2
  84. package/dist/server/routes/wizard.js.map +1 -1
  85. package/dist/ui/assets/index-Cxgy63kz.js +26 -0
  86. package/dist/ui/assets/index-Cxgy63kz.js.map +1 -0
  87. package/dist/ui/index.html +1 -1
  88. package/docs/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
  89. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +17 -0
  90. package/package.json +7 -3
  91. package/src/config/estate-count-budget.json +13 -744
  92. package/src/config/evaluator-budgets.json +4 -4
  93. package/src/config/gate-coverage.yml +267 -9
  94. package/src/config/gate-violation-baselines.json +72 -72
  95. package/src/config/preamble-payload-budget.json +71 -67
  96. package/src/config/publish-surface.json +60 -0
  97. package/src/config/requirements-trace-budget.json +32 -0
  98. package/src/config/rule-stub-ceilings.json +8 -1
  99. package/src/scripts/_dispatch.bash +7 -0
  100. package/src/scripts/_lib/base_tree.ts +175 -0
  101. package/src/scripts/_lib/billing_grant.ts +165 -0
  102. package/src/scripts/_lib/billing_grant_cli.ts +123 -0
  103. package/src/scripts/_lib/council_fallback_posture.ts +53 -6
  104. package/src/scripts/_lib/council_fallback_wiring.ts +61 -1
  105. package/src/scripts/_lib/host_capability.ts +59 -5
  106. package/src/scripts/_lib/quota_parked.ts +133 -0
  107. package/src/scripts/_lib/retrieval_sanitize.ts +62 -0
  108. package/src/scripts/_lib/review_telemetry.ts +196 -0
  109. package/src/scripts/_lib/subagent_response.ts +92 -0
  110. package/src/scripts/adr/regenerate_index.ts +14 -7
  111. package/src/scripts/ai_council/clients.ts +12 -1
  112. package/src/scripts/ai_council/config.ts +72 -115
  113. package/src/scripts/ai_council/fallback_config.ts +19 -7
  114. package/src/scripts/ai_council/mid_flight_fallback.ts +88 -7
  115. package/src/scripts/ai_council/orchestrator.ts +2 -2
  116. package/src/scripts/ai_council/py_format.ts +124 -0
  117. package/src/scripts/ai_council/seating.ts +211 -0
  118. package/src/scripts/ai_council/transport_resolver.ts +65 -2
  119. package/src/scripts/audit_adr_coverage.ts +33 -6
  120. package/src/scripts/check_council_pin_staleness.ts +300 -0
  121. package/src/scripts/check_estate_count.ts +420 -293
  122. package/src/scripts/check_gate_coverage.ts +91 -0
  123. package/src/scripts/check_no_stub_inventory_table.ts +292 -0
  124. package/src/scripts/check_pack_passport_reconciliation.ts +274 -0
  125. package/src/scripts/check_pack_size.ts +234 -1
  126. package/src/scripts/check_publish_surface.ts +297 -0
  127. package/src/scripts/check_requirements_trace.ts +378 -0
  128. package/src/scripts/check_secret_leak.ts +166 -12
  129. package/src/scripts/code_graph/cli.ts +44 -5
  130. package/src/scripts/code_graph/extract.ts +87 -0
  131. package/src/scripts/council_cli.ts +12 -16
  132. package/src/scripts/detect_ai_tells.ts +87 -0
  133. package/src/scripts/dispatch_r2_reviewer.ts +42 -0
  134. package/src/scripts/generate_capabilities_index.ts +41 -0
  135. package/src/scripts/generate_pack_manifests.ts +78 -0
  136. package/src/scripts/git_authorization_hook.ts +54 -4
  137. package/src/scripts/hooks/block_unauthorized_git.ts +19 -1
  138. package/src/scripts/hooks/run_continuation_hook.ts +3 -1
  139. package/src/scripts/hooks/subagent_ledger_hook.ts +76 -11
  140. package/src/scripts/injection_scan_hook.ts +120 -10
  141. package/src/scripts/lint_adr_sweep_routing.ts +365 -0
  142. package/src/scripts/lint_workflow_security.ts +53 -10
  143. package/src/scripts/lint_workflow_security_allowlist.json +2 -18
  144. package/src/scripts/report_envelope_rate.ts +166 -0
  145. package/src/scripts/review_axis_report.ts +218 -0
  146. package/src/scripts/run_supervise.ts +46 -1
  147. package/src/scripts/schemas/evals.schema.json +5 -0
  148. package/src/scripts/schemas/pack.schema.json +62 -4
  149. package/src/scripts/schemas/skill.schema.json +2 -2
  150. package/src/scripts/sync_pr_branch.ts +14 -7
  151. package/src/scripts/tighten_skill_descriptions.ts +264 -0
  152. package/dist/cli/commands/commands.candidates.test.js +0 -257
  153. package/dist/cli/commands/commands.candidates.test.js.map +0 -1
  154. package/dist/cli/commands/profiles.test.js +0 -88
  155. package/dist/cli/commands/profiles.test.js.map +0 -1
  156. package/dist/cli/commands/recordTriggerEval.test.js +0 -113
  157. package/dist/cli/commands/recordTriggerEval.test.js.map +0 -1
  158. package/dist/cli/configRoot.test.js +0 -97
  159. package/dist/cli/configRoot.test.js.map +0 -1
  160. package/dist/cli/mcp/content.test.js +0 -54
  161. package/dist/cli/mcp/content.test.js.map +0 -1
  162. package/dist/cli/mcp/dispatch.test.js +0 -108
  163. package/dist/cli/mcp/dispatch.test.js.map +0 -1
  164. package/dist/server/writeRoot.test.js +0 -91
  165. package/dist/server/writeRoot.test.js.map +0 -1
  166. package/dist/shared/capabilities.test.js +0 -39
  167. package/dist/shared/capabilities.test.js.map +0 -1
  168. package/dist/ui/assets/index-C1pBRm1f.js +0 -26
  169. package/dist/ui/assets/index-C1pBRm1f.js.map +0 -1
  170. /package/dist/cli-delegate/{chunk-N7CDDGI3.js → chunk-CYH5SP6M.js} +0 -0
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Report the spec axis's measured effect on the review surface.
4
+ *
5
+ * The question this answers, and the only one: over the observation window,
6
+ * how often did the spec axis change the recommendation the craft judges would
7
+ * have produced alone? The honest answers include **zero**, and **"nothing was
8
+ * recorded"** — which is a third answer, not a variant of zero. A report that
9
+ * printed `0 changed` over an empty ledger would read as evidence the axis is
10
+ * useless, when it is evidence that nothing has been observed yet; those two
11
+ * states are separated everywhere below.
12
+ *
13
+ * Three populations, never collapsed (the same model `check_pack_size` and
14
+ * `check_requirements_trace` use):
15
+ * observed — lines in the window
16
+ * comparable — lines where the axis ran, so a counterfactual exists
17
+ * changed — comparable lines where it flipped the recommendation
18
+ *
19
+ * `comparable` is the denominator, never `observed`: a run where the axis was
20
+ * unreachable carries `spec_axis_effect: null` and contributes to neither
21
+ * numerator nor denominator. Dividing by `observed` would report a low rate for
22
+ * an axis that was never given the chance to act, which is the mis-attribution
23
+ * this split exists to prevent.
24
+ *
25
+ * Exit code is 0 on every outcome including the empty one. This is a REPORT,
26
+ * not a gate — there is no threshold it could honestly enforce with the window
27
+ * this repo has, and a gate over an unmeasured surface fails for the wrong
28
+ * reason. `--self-test` exercises the pure summariser and exits non-zero on a
29
+ * mismatch.
30
+ */
31
+ import * as fs from 'node:fs';
32
+ import * as path from 'node:path';
33
+
34
+ import type { ReviewLine } from './_lib/review_telemetry.js';
35
+
36
+ export interface ReviewAxisSummary {
37
+ observed: number;
38
+ /** Lines where the axis ran, i.e. a counterfactual exists. */
39
+ comparable: number;
40
+ changed: number;
41
+ unchanged: number;
42
+ /** Lines where the axis could not run at all. */
43
+ unreachable: number;
44
+ /** Reviews that ran with no criteria supplied — the axis reached, nothing to measure against. */
45
+ no_criteria: number;
46
+ /** Criteria handed over and unreadable — an error state, never folded into no_criteria. */
47
+ unparseable: number;
48
+ /** Earliest / latest `at` seen, or null over an empty window. */
49
+ window_start: string | null;
50
+ window_end: string | null;
51
+ }
52
+
53
+ /** Pure summariser — the unit under `--self-test`. */
54
+ export function summarise(lines: readonly ReviewLine[]): ReviewAxisSummary {
55
+ const s: ReviewAxisSummary = {
56
+ observed: 0,
57
+ comparable: 0,
58
+ changed: 0,
59
+ unchanged: 0,
60
+ unreachable: 0,
61
+ no_criteria: 0,
62
+ unparseable: 0,
63
+ window_start: null,
64
+ window_end: null,
65
+ };
66
+ for (const l of lines) {
67
+ s.observed += 1;
68
+ if (s.window_start === null || l.at < s.window_start) s.window_start = l.at;
69
+ if (s.window_end === null || l.at > s.window_end) s.window_end = l.at;
70
+ if (l.spec_axis_reach === 'unreachable') s.unreachable += 1;
71
+ if (l.criteria_source === 'not_provided') s.no_criteria += 1;
72
+ if (l.criteria_source === 'supplied_unparseable') s.unparseable += 1;
73
+ if (l.spec_axis_effect === 'changed') {
74
+ s.comparable += 1;
75
+ s.changed += 1;
76
+ } else if (l.spec_axis_effect === 'unchanged') {
77
+ s.comparable += 1;
78
+ s.unchanged += 1;
79
+ }
80
+ }
81
+ return s;
82
+ }
83
+
84
+ /** Render the summary as the prose a human reads. Empty window says so in words. */
85
+ export function render(s: ReviewAxisSummary): string {
86
+ const out: string[] = [];
87
+ if (s.observed === 0) {
88
+ out.push('NO WINDOW — 0 reviews recorded, so the spec axis has no measured effect in either direction.');
89
+ out.push('This is not "the axis changed nothing": nothing has been observed yet. Rerun once reviews have run.');
90
+ return out.join('\n');
91
+ }
92
+ out.push(`window: ${String(s.window_start)} … ${String(s.window_end)}`);
93
+ out.push(`observed: ${s.observed} review(s)`);
94
+ if (s.comparable === 0) {
95
+ out.push(
96
+ 'comparable: 0 — the axis ran in none of them, so it changed nothing BY CONSTRUCTION, ' +
97
+ 'which is not evidence about the axis.',
98
+ );
99
+ } else {
100
+ const pct = ((s.changed / s.comparable) * 100).toFixed(1);
101
+ out.push(`comparable: ${s.comparable} (the axis ran; a counterfactual exists)`);
102
+ out.push(`changed the recommendation: ${s.changed} of ${s.comparable} (${pct}%)`);
103
+ if (s.changed === 0) {
104
+ out.push('The honest reading of 0: the axis ran and never flipped a verdict over this window.');
105
+ }
106
+ }
107
+ out.push(
108
+ `not comparable: ${s.unreachable} unreachable · ${s.no_criteria} ran with no criteria · ` +
109
+ `${s.unparseable} with unreadable criteria (an error state, counted apart)`,
110
+ );
111
+ return out.join('\n');
112
+ }
113
+
114
+ function readLedger(dir: string): ReviewLine[] {
115
+ if (!fs.existsSync(dir)) return [];
116
+ const lines: ReviewLine[] = [];
117
+ for (const f of fs.readdirSync(dir).sort()) {
118
+ if (!f.endsWith('.jsonl')) continue;
119
+ for (const raw of fs.readFileSync(path.join(dir, f), 'utf8').split('\n')) {
120
+ const t = raw.trim();
121
+ if (!t) continue;
122
+ try {
123
+ const o = JSON.parse(t) as ReviewLine;
124
+ if (o && o.schema === 'review-axis-v1') lines.push(o);
125
+ } catch {
126
+ // A malformed line is skipped, never guessed at.
127
+ }
128
+ }
129
+ }
130
+ return lines;
131
+ }
132
+
133
+ function selfTest(): number {
134
+ const at = '2026-08-22T00:00:00Z';
135
+ const base = {
136
+ schema: 'review-axis-v1' as const,
137
+ at,
138
+ judges_declared: 6,
139
+ judges_ran: 6,
140
+ criteria_count: 2,
141
+ spec_missing: 0,
142
+ spec_partial: 0,
143
+ recommendation: 'proceed' as const,
144
+ };
145
+ const cases: { name: string; lines: ReviewLine[]; want: Partial<ReviewAxisSummary>; wantText?: string }[] = [
146
+ { name: 'empty window is not zero-effect', lines: [], want: { observed: 0, comparable: 0 }, wantText: 'NO WINDOW' },
147
+ {
148
+ name: 'unreachable contributes to neither population',
149
+ lines: [
150
+ {
151
+ ...base,
152
+ spec_axis_reach: 'unreachable',
153
+ criteria_source: 'not_provided',
154
+ criteria_count: 0,
155
+ spec_axis_effect: null,
156
+ },
157
+ ],
158
+ want: { observed: 1, comparable: 0, changed: 0, unreachable: 1 },
159
+ wantText: 'BY CONSTRUCTION',
160
+ },
161
+ {
162
+ name: 'changed and unchanged both land in comparable',
163
+ lines: [
164
+ { ...base, spec_axis_reach: 'reachable_with_criteria', criteria_source: 'supplied', spec_axis_effect: 'changed' },
165
+ { ...base, spec_axis_reach: 'reachable_with_criteria', criteria_source: 'supplied', spec_axis_effect: 'unchanged' },
166
+ ],
167
+ want: { observed: 2, comparable: 2, changed: 1, unchanged: 1 },
168
+ },
169
+ {
170
+ name: 'unparseable is counted apart from no_criteria',
171
+ lines: [
172
+ {
173
+ ...base,
174
+ spec_axis_reach: 'reachable_no_criteria',
175
+ criteria_source: 'supplied_unparseable',
176
+ criteria_count: 0,
177
+ spec_axis_effect: 'unchanged',
178
+ },
179
+ ],
180
+ want: { observed: 1, no_criteria: 0, unparseable: 1, comparable: 1 },
181
+ },
182
+ ];
183
+ let pass = 0;
184
+ let fail = 0;
185
+ for (const c of cases) {
186
+ const got = summarise(c.lines);
187
+ const bad = Object.entries(c.want).filter(([k, v]) => got[k as keyof ReviewAxisSummary] !== v);
188
+ const text = render(got);
189
+ const textBad = c.wantText !== undefined && !text.includes(c.wantText);
190
+ if (bad.length === 0 && !textBad) {
191
+ pass += 1;
192
+ } else {
193
+ fail += 1;
194
+ console.error(`❌ ${c.name}`);
195
+ for (const [k, v] of bad) console.error(` ${k}: want ${String(v)}, got ${String(got[k as keyof ReviewAxisSummary])}`);
196
+ if (textBad) console.error(` prose missing: ${String(c.wantText)}`);
197
+ }
198
+ }
199
+ process.stdout.write(`review_axis_report --self-test: ${pass} pass, ${fail} fail\n`);
200
+ return fail === 0 ? 0 : 1;
201
+ }
202
+
203
+ function main(argv: string[]): number {
204
+ if (argv.includes('--self-test')) return selfTest();
205
+ const dirIdx = argv.indexOf('--dir');
206
+ const dir =
207
+ dirIdx >= 0 && argv[dirIdx + 1] !== undefined
208
+ ? String(argv[dirIdx + 1])
209
+ : path.join(process.cwd(), 'agents', 'runtime', 'state', 'review-axis');
210
+ const lines = readLedger(dir);
211
+ process.stdout.write(render(summarise(lines)) + '\n');
212
+ process.stdout.write(`scanned: ${lines.length}\n`);
213
+ return 0;
214
+ }
215
+
216
+ if (process.argv[1] !== undefined && process.argv[1].includes('review_axis_report')) {
217
+ process.exit(main(process.argv.slice(2)));
218
+ }
@@ -51,6 +51,7 @@ import {
51
51
  roadmapPath,
52
52
  } from './_lib/run_checkpoint.js';
53
53
  import { readBudget } from './_lib/unattended_guard.js';
54
+ import { findQuotaParked, type QuotaParkedMarker } from './_lib/quota_parked.js';
54
55
  import {
55
56
  LIVE_SPAWN_REFUSAL,
56
57
  planResume,
@@ -80,6 +81,7 @@ export type Disposition =
80
81
  | 'no-roadmap'
81
82
  | 'roadmap-unreadable'
82
83
  | 'complete'
84
+ | 'quota-parked'
83
85
  | 'budget-exhausted'
84
86
  | 'relaunchable';
85
87
 
@@ -246,6 +248,21 @@ export function classify(
246
248
  reason: 'no open steps remain — the run finished, the session just never said so',
247
249
  };
248
250
  }
251
+ // Before the relaunch cap, and the order is load-bearing in one direction
252
+ // only: a parked run that has also spent its relaunches should still read
253
+ // as parked, because "waiting for a reset" is the cause and "spent three
254
+ // relaunches" is a consequence of it. Reported the other way round, an
255
+ // operator sees a run that failed repeatedly rather than one that keeps
256
+ // meeting the same wall.
257
+ const parked = findQuotaParked(repoRoot, rec.session_id);
258
+ if (parked !== null) {
259
+ return {
260
+ ...base,
261
+ disposition: 'quota-parked',
262
+ open_steps: counts.open,
263
+ reason: quotaParkedReason(parked, counts.open),
264
+ };
265
+ }
249
266
  if (base.relaunches >= MAX_RELAUNCHES_PER_RUN) {
250
267
  return {
251
268
  ...base,
@@ -264,6 +281,23 @@ export function classify(
264
281
  };
265
282
  }
266
283
 
284
+ /**
285
+ * The one sentence a human acts on for a parked run.
286
+ *
287
+ * It states the reset time is unknown rather than omitting it, and rather than
288
+ * guessing an interval. There is no reset-time parser and there will not be one
289
+ * until a verified error string exists to pin a fixture against — see
290
+ * `later/road-to-billing-cliff-detection.md`. An omitted unknown reads as "no
291
+ * wait needed"; a guessed interval reads as a fact.
292
+ */
293
+ export function quotaParkedReason(m: QuotaParkedMarker, open: number): string {
294
+ return (
295
+ `plan quota exhausted on ${m.providers.join(', ')} since ${m.parked_at} — ` +
296
+ `${open} open step(s) remain; the run is waiting, not broken. ` +
297
+ `Reset time unknown (no parser: no verified error string yet).`
298
+ );
299
+ }
300
+
267
301
  export interface ScanOptions {
268
302
  readonly now?: Date;
269
303
  }
@@ -434,10 +468,21 @@ export function digest(repoRoot: string, candidates: readonly Candidate[], now:
434
468
 
435
469
  const dead = candidates.filter((c) => c.disposition !== 'alive');
436
470
  const relaunchable = candidates.filter((c) => c.disposition === 'relaunchable');
471
+ const quotaParked = candidates.filter((c) => c.disposition === 'quota-parked');
437
472
  lines.push(
438
473
  ` sessions: ${candidates.length} registered · ${candidates.length - dead.length} alive · ` +
439
- `${relaunchable.length} relaunchable`,
474
+ `${relaunchable.length} relaunchable` +
475
+ (quotaParked.length > 0 ? ` · ${quotaParked.length} quota-parked` : ''),
440
476
  );
477
+ // Named on its own line, not folded into the relaunchable count, because
478
+ // the two call for opposite responses: a relaunchable run wants a session,
479
+ // a parked one wants time. Suppressed at zero so the ordinary digest does
480
+ // not carry a line about a state nothing is in.
481
+ for (const c of quotaParked) {
482
+ lines.push(
483
+ ` parked: ${c.roadmap ?? '-'} — ${c.reason}`,
484
+ );
485
+ }
441
486
 
442
487
  const budget = readBudget(repoRoot, now);
443
488
  lines.push(
@@ -48,6 +48,11 @@
48
48
  "minLength": 1,
49
49
  "description": "The input fixture handed to the skill under test."
50
50
  },
51
+ "_why": {
52
+ "type": "string",
53
+ "minLength": 1,
54
+ "description": "Optional rationale: why this fixture exists at all. Annotation, never input — nothing reads it at run time, and it carries the same underscore prefix as the top-level `_calibration` to say so. Added because the alternative, on scenarios whose rationale IS the negative-control argument for the pair above them, was to delete the argument to satisfy additionalProperties."
55
+ },
51
56
  "environment": {
52
57
  "type": "string",
53
58
  "minLength": 1,
@@ -6,7 +6,10 @@
6
6
  "$comment": "Generated by scripts/generate_pack_manifests.py and validated on load by scripts/validate_pack_yaml.py. Schema introduced in road-to-6.0.0-D Phase 0 Step 2. `dependencies` is the artefact-level include graph (skills/rules the pack's commands reference); `requires` is the pack-level hard-dependency graph (must be acyclic — enforced by scripts/lint_pack_dependencies.py).",
7
7
  "type": "object",
8
8
  "additionalProperties": false,
9
- "required": ["id", "artefact_count"],
9
+ "required": [
10
+ "id",
11
+ "artefact_count"
12
+ ],
10
13
  "properties": {
11
14
  "id": {
12
15
  "type": "string",
@@ -27,7 +30,9 @@
27
30
  },
28
31
  "owner": {
29
32
  "type": "array",
30
- "items": { "type": "string" }
33
+ "items": {
34
+ "type": "string"
35
+ }
31
36
  },
32
37
  "requires": {
33
38
  "type": "array",
@@ -47,12 +52,21 @@
47
52
  },
48
53
  "size_class": {
49
54
  "type": "string",
50
- "enum": ["core", "small", "medium", "large", "platform"],
55
+ "enum": [
56
+ "core",
57
+ "small",
58
+ "medium",
59
+ "large",
60
+ "platform"
61
+ ],
51
62
  "description": "Per-pack visible-command budget input."
52
63
  },
53
64
  "surface_tier": {
54
65
  "type": "string",
55
- "enum": ["core", "lab"],
66
+ "enum": [
67
+ "core",
68
+ "lab"
69
+ ],
56
70
  "description": "Install-layout surface tier (road-to-install-contract-stability Phase 2). `core` = the lean stable multi-host engine that users install (rules + skills + install + condensation); `lab` = experimental / pilot domain tooling that must not destabilise the core install. Default `core`. The core-only install path excludes `lab` packs."
57
71
  },
58
72
  "trust_level_default": {
@@ -92,6 +106,50 @@
92
106
  }
93
107
  }
94
108
  }
109
+ },
110
+ "token_passport": {
111
+ "type": "object",
112
+ "additionalProperties": false,
113
+ "required": [
114
+ "basis",
115
+ "rules_tokens",
116
+ "catalog_tokens",
117
+ "commands_tokens",
118
+ "other_tokens",
119
+ "total_tokens"
120
+ ],
121
+ "description": "GENERATED — what this pack contributes to the STANDING session payload. Derivation, tokenizer and the reconciliation target are fixed by docs/contracts/pack-token-passport.md, written before the first passport existed. Standing cost only: a skill body loaded on invocation is a runtime cost and is not here, and the residual bucket (tool definitions + dispatch prompt) is not attributable to a pack at all. It is a floor on a pack's session cost, never the whole of it.",
122
+ "properties": {
123
+ "basis": {
124
+ "type": "string",
125
+ "enum": [
126
+ "chars/4"
127
+ ],
128
+ "description": "Pinned to an enum on purpose. chars/4 is the basis preamble-payload-budget.json declares for itself; a passport measured with an exact tokenizer could not be compared against a census measured with a proxy without the gap between two METHODS reading as a discrepancy in the packs."
129
+ },
130
+ "rules_tokens": {
131
+ "type": "integer",
132
+ "minimum": 0
133
+ },
134
+ "catalog_tokens": {
135
+ "type": "integer",
136
+ "minimum": 0,
137
+ "description": "Sum of `- <name>: <description>\\n` over the pack's skill artefacts — the SAME construction censusSkillsCatalog uses, not an approximation of it."
138
+ },
139
+ "commands_tokens": {
140
+ "type": "integer",
141
+ "minimum": 0
142
+ },
143
+ "other_tokens": {
144
+ "type": "integer",
145
+ "minimum": 0,
146
+ "description": "Every other claimed artefact — contexts, personas, presets, profiles, user-types. Exists because the three named buckets produced a misleading zero: `core` claimed 47 artefacts and reported 0 tokens, which reads as 'this pack is free'."
147
+ },
148
+ "total_tokens": {
149
+ "type": "integer",
150
+ "minimum": 0
151
+ }
152
+ }
95
153
  }
96
154
  }
97
155
  }
@@ -25,8 +25,8 @@
25
25
  "description": {
26
26
  "type": "string",
27
27
  "minLength": 1,
28
- "maxLength": 220,
29
- "description": "Trigger phrase; ≤ 200 chars recommended, 220 is the ceiling (lean-initial-context: descriptions load eagerly via progressive disclosure). Over-cap is a soft warning, not a hard fail — a warning window so authors adapt."
28
+ "maxLength": 200,
29
+ "description": "Trigger phrase; the ceiling is 200 chars, measured. Lowered from 220 on 2026-08-22 because 220 described no artefact that existed: across all 290 skills the distribution was median 181 / p75 189 / max 200, with ZERO over 200, so the 20 chars above the recommended line were dead headroom a future author could fill with no gate objecting. p75 = 189 is the recorded soft target; 200 is the hard line. Descriptions load eagerly via progressive disclosure, so every char here is standing cost on every spawn."
30
30
  },
31
31
  "compatibility": {
32
32
  "type": "string",
@@ -105,14 +105,21 @@ const GENERATED = [
105
105
  * because the result looks measured either way. So this class NAMES the
106
106
  * resolution and stops. The human runs it, with the result in front of them.
107
107
  *
108
- * Both members conflict in 7 of the 7 open PRs measured for
109
- * road-to-merge-hotspot-drawdown. Neither is gitignorable: an untracked baseline
110
- * is a baseline no PR diff can be compared against, which deletes the ratchet.
108
+ * `gate-violation-baselines.json` conflicted in 7 of the 7 open PRs measured for
109
+ * road-to-merge-hotspot-drawdown, and it is not gitignorable: its counts are not
110
+ * a function of the tree, so the committed number is the only "before" side that
111
+ * exists.
112
+ *
113
+ * `estate-count-budget.json` USED to be the other member and is deliberately not
114
+ * listed any more (ADR-243). Its metrics ARE a function of the tree, so
115
+ * `check_estate_count` measures the floor at the base ref instead of storing it,
116
+ * and the file now carries policy only. A conflict in it is an ordinary AUTHORED
117
+ * one — two humans editing the same policy sentence — and telling the reader to
118
+ * "re-run the measurement" for that would name a resolution the file no longer
119
+ * has. Removing the row is the point of the change, not an oversight: the row
120
+ * described a conflict that no longer occurs.
111
121
  */
112
- const REMEASURED = [
113
- 'src/config/estate-count-budget.json',
114
- 'src/config/gate-violation-baselines.json',
115
- ];
122
+ const REMEASURED = ['src/config/gate-violation-baselines.json'];
116
123
 
117
124
  function sh(cmd: string, args: readonly string[], cwd: string): { ok: boolean; out: string; err: string } {
118
125
  const r = spawnSync(cmd, [...args], {