@event4u/agent-config 9.31.0 → 9.33.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 (107) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +146 -0
  3. package/dist/agent-src/commands/review/routing.md +3 -4
  4. package/dist/agent-src/contexts/communication/rules-auto/reply-close-mechanics.md +65 -0
  5. package/dist/agent-src/contexts/execution/host-capability-manifest.md +32 -1
  6. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +3 -0
  7. package/dist/agent-src/rules/delegation-policy.md +17 -1
  8. package/dist/agent-src/scripts/roadmap_gates.ts +107 -3
  9. package/dist/agent-src/skills/docker/SKILL.md +11 -0
  10. package/dist/agent-src/skills/laravel/SKILL.md +11 -0
  11. package/dist/agent-src/skills/review-routing/SKILL.md +23 -6
  12. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  13. package/dist/agent-src/templates/scripts/ownership-map.example.yml +1 -1
  14. package/dist/agent-src/templates/scripts/work_engine/delivery_state.ts +6 -0
  15. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +332 -0
  16. package/dist/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +106 -1
  17. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +50 -15
  18. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +49 -15
  19. package/dist/agent-src/templates/scripts/work_engine/state.ts +74 -0
  20. package/dist/agent-src/templates/scripts/work_engine/state_io.ts +6 -0
  21. package/dist/cli/registry.js +4 -4
  22. package/dist/cli/registry.js.map +1 -1
  23. package/dist/cli-delegate/chunk-2WELHO3P.js +582 -0
  24. package/dist/cli-delegate/chunk-HTIU3W56.js +66 -0
  25. package/dist/cli-delegate/{chunk-ITIQSGOQ.js → chunk-MA4GFWDY.js} +7 -4
  26. package/dist/cli-delegate/{chunk-2WCLVDWC.js → chunk-PMQ6Z3T3.js} +1642 -165
  27. package/dist/cli-delegate/chunk-PZWRPRWP.js +38 -0
  28. package/dist/cli-delegate/{chunk-PFBV6XRT.js → chunk-Q26HT4QT.js} +1 -63
  29. package/dist/cli-delegate/{chunk-LVRYBKTI.js → chunk-YPAWXXXR.js} +158 -0
  30. package/dist/cli-delegate/cmd_conformance.js +308 -227
  31. package/dist/cli-delegate/cmd_doctor.js +4 -2
  32. package/dist/cli-delegate/cmd_export.js +1 -1
  33. package/dist/cli-delegate/cmd_handoff.js +160 -58
  34. package/dist/cli-delegate/cmd_prune.js +1 -1
  35. package/dist/cli-delegate/cmd_session_recycle.js +30 -5
  36. package/dist/cli-delegate/cmd_settings_get.js +4 -2
  37. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  38. package/dist/cli-delegate/cmd_settings_set.js +5 -3
  39. package/dist/cli-delegate/cmd_sync.js +1 -1
  40. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  41. package/dist/cli-delegate/cmd_validate.js +1 -1
  42. package/dist/discovery/deprecation-report.md +1 -1
  43. package/dist/discovery/discovery-manifest.json +8 -8
  44. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  45. package/dist/discovery/discovery-manifest.summary.md +1 -1
  46. package/dist/discovery/orphan-report.md +1 -1
  47. package/dist/discovery/packs.json +2 -2
  48. package/dist/discovery/trust-report.md +1 -1
  49. package/dist/discovery/workspaces.json +2 -2
  50. package/dist/hooks/dispatch.js +1136 -690
  51. package/dist/install/install.mjs +7 -4
  52. package/dist/mcp/registry-manifest.json +2 -2
  53. package/dist/mcp/server.json +2 -2
  54. package/dist/mcp/server.mjs +3688 -1159
  55. package/package.json +1 -1
  56. package/src/agent-src/scripts/roadmap_gates.ts +107 -3
  57. package/src/agent-src/templates/scripts/ownership-map.example.yml +1 -1
  58. package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +6 -0
  59. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +332 -0
  60. package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +106 -1
  61. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +50 -15
  62. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +49 -15
  63. package/src/agent-src/templates/scripts/work_engine/state.ts +74 -0
  64. package/src/agent-src/templates/scripts/work_engine/state_io.ts +6 -0
  65. package/src/config/cost-parity-budget.json +167 -0
  66. package/src/config/gate-coverage.yml +44 -5
  67. package/src/config/quorum-attendance-budget.json +39 -0
  68. package/src/scripts/_cli/cmd_doctor.ts +69 -1
  69. package/src/scripts/_cli/cmd_session_recycle.ts +44 -3
  70. package/src/scripts/_cli/handoff_generate.ts +36 -1
  71. package/src/scripts/_cli/handoff_sessions.ts +140 -9
  72. package/src/scripts/_dispatch.bash +5 -3
  73. package/src/scripts/_lib/carrier_divergence.ts +98 -27
  74. package/src/scripts/_lib/envelope_grounding.ts +206 -0
  75. package/src/scripts/_lib/host_capability.ts +79 -0
  76. package/src/scripts/_lib/orchestration_record.ts +45 -0
  77. package/src/scripts/_lib/session_eol.ts +85 -0
  78. package/src/scripts/_lib/subagent_capsule.ts +256 -1
  79. package/src/scripts/ai_council/clients.ts +31 -0
  80. package/src/scripts/ai_council/events_log.ts +128 -6
  81. package/src/scripts/ai_council/orchestrator.ts +12 -3
  82. package/src/scripts/ai_council/pricing.ts +48 -1
  83. package/src/scripts/ai_council/quorum.ts +20 -0
  84. package/src/scripts/ai_council/session.ts +11 -0
  85. package/src/scripts/ask_transport.ts +20 -1
  86. package/src/scripts/cache_realization_report.ts +38 -0
  87. package/src/scripts/check_release_highlights.ts +7 -1
  88. package/src/scripts/check_secret_leak.test.ts +43 -0
  89. package/src/scripts/check_secret_leak.ts +12 -0
  90. package/src/scripts/conformance_scan.ts +105 -0
  91. package/src/scripts/cost/track.mjs +39 -0
  92. package/src/scripts/cost_summary.ts +104 -1
  93. package/src/scripts/council_cli.ts +131 -13
  94. package/src/scripts/dispatch_economy_report.ts +5 -2
  95. package/src/scripts/handoff_context_hook.ts +66 -18
  96. package/src/scripts/hooks/session_eol_hook.ts +12 -12
  97. package/src/scripts/install.ts +20 -6
  98. package/src/scripts/lint_budget_ownership.ts +81 -0
  99. package/src/scripts/lint_skill_frontmatter_safety.ts +55 -1
  100. package/src/scripts/lint_workflow_paths.ts +384 -0
  101. package/src/scripts/preamble_byte_census.ts +9 -1
  102. package/src/scripts/report_carrier_divergence.ts +104 -29
  103. package/src/scripts/report_conformance_funnel.ts +51 -4
  104. package/src/scripts/routing_doctor.ts +25 -2
  105. package/src/scripts/schemas/dod.schema.json +45 -0
  106. package/src/scripts/schemas/subagent.schema.json +3 -2
  107. package/dist/cli-delegate/chunk-RVGBH4XK.js +0 -142
@@ -17,10 +17,12 @@
17
17
  * - Must be a dict.
18
18
  * - Must carry a `verdict` key — one of `success`, `failed`,
19
19
  * or `mixed` (targeted vs full-suite split). Anything else blocks.
20
- * - `failed` or `mixed` verdicts halt with the verdict as part of
21
- * the surfaced message so the user decides whether to continue or
22
- * stop. This follows the `verify-before-complete` rule: a bad test
23
- * outcome must never be silently skipped.
20
+ * - `failed` or `mixed` verdicts enter the bounded self-fix loop
21
+ * (`./_self_fix.ts`): the first attempts are delegated by directive, and an
22
+ * exhausted or non-progressing loop exits `PARTIAL` with the verdict still
23
+ * on the surface so the user decides whether to continue or stop. This
24
+ * follows the `verify-before-complete` rule: a bad test outcome must never
25
+ * be silently skipped, and the loop cannot report one as success.
24
26
  * - Optional keys (`targeted`, `full`, `duration_ms`,
25
27
  * `followups`) feed the delivery report.
26
28
  */
@@ -28,6 +30,7 @@
28
30
  import type { DeliveryState} from '../../delivery_state.js';
29
31
  import { type Any, Outcome, StepResult, agent_directive } from '../../delivery_state.js';
30
32
  import { resolve_policy } from '../../persona_policy.js';
33
+ import { decide, partial_exit, record_attempt, retry_halt, unmet_dod, verdict_signature } from './_self_fix.js';
31
34
 
32
35
  const _ALLOWED_VERDICTS = ['success', 'failed', 'mixed'] as const;
33
36
 
@@ -53,7 +56,18 @@ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
53
56
  {
54
57
  code: 'bad_test_verdict',
55
58
  trigger: '`state.tests[\'verdict\']` is `failed` or `mixed`',
56
- resolution: 'fix failures and re-run, or abort',
59
+ resolution:
60
+ 'bounded self-fix loop: agent directive `fix-failing-checks` ' +
61
+ 'while attempts remain and the verdict signature keeps changing',
62
+ },
63
+ {
64
+ code: 'self_fix_exhausted',
65
+ trigger:
66
+ 'the self-fix budget for `test` is spent, or two consecutive ' +
67
+ 'attempts produced an identical verdict signature',
68
+ resolution:
69
+ 'PARTIAL honest exit — the red verdict stays on the surface, ' +
70
+ 'the user decides whether to take over, override, or abort',
57
71
  },
58
72
  ];
59
73
 
@@ -156,18 +170,39 @@ function _blocked_on_shape(state: DeliveryState, issues: string[]): StepResult {
156
170
  });
157
171
  }
158
172
 
173
+ /**
174
+ * Route a red test verdict through the bounded self-fix loop.
175
+ *
176
+ * Before this loop existed the function below returned a directive-free
177
+ * BLOCKED — a user question block — so every red cost a user round-trip even
178
+ * when the failure was one the agent could fix unaided. Now the first attempts
179
+ * are delegated and only an exhausted or non-progressing loop reaches the
180
+ * user, as a PARTIAL that still carries the red verdict.
181
+ */
159
182
  function _blocked_on_bad_verdict(state: DeliveryState, verdict: Any): StepResult {
160
183
  const ticketId = _ticketId(state);
161
- return new StepResult({
162
- outcome: Outcome.BLOCKED,
163
- questions: [
164
- `> Ticket ${ticketId} — tests reported \`${String(verdict)}\`. ` +
165
- 'Verification cannot proceed on a non-success verdict.',
166
- '> 1. Fix the failing tests and re-run `run-tests`',
167
- '> 2. Continue anyway — override (NOT recommended)',
168
- '> 3. Abort',
169
- ],
170
- message: `Ticket ${ticketId} test verdict was \`${String(verdict)}\`, not success.`,
184
+ const signature = verdict_signature('test', state.tests);
185
+ const decision = decide(state, 'test', signature);
186
+
187
+ if (decision.kind === 'retry') {
188
+ record_attempt(state, 'test', signature);
189
+ return retry_halt({
190
+ lane: 'test',
191
+ ticket_id: ticketId,
192
+ verdict,
193
+ decision,
194
+ fix_hint: 'read the failing assertions, fix the cause, re-run the same filter.',
195
+ rerun_directive: 'run-tests',
196
+ });
197
+ }
198
+
199
+ return partial_exit({
200
+ lane: 'test',
201
+ ticket_id: ticketId,
202
+ verdict,
203
+ decision,
204
+ unmet: unmet_dod(state),
205
+ rerun_directive: 'run-tests',
171
206
  });
172
207
  }
173
208
 
@@ -18,9 +18,12 @@
18
18
  * `partial`. Matches the `Outcome` vocabulary used everywhere
19
19
  * else in the flow.
20
20
  * - A `success` verdict advances the flow to `report`.
21
- * - A `blocked` or `partial` verdict halts with numbered options
22
- * so the user decides whether to address the findings, override
23
- * (rarely appropriate), or abort.
21
+ * - A `blocked` or `partial` verdict enters the bounded self-fix loop
22
+ * (`./_self_fix.ts`): while attempts remain and the verdict signature
23
+ * keeps changing, the fix is delegated by directive. An exhausted or
24
+ * non-progressing loop exits `PARTIAL` with numbered options so the user
25
+ * decides whether to take over, override (rarely appropriate), or abort.
26
+ * The loop never turns a non-success verdict into `SUCCESS`.
24
27
  * - Optional keys (`confidence`, `findings`, `followups`) feed
25
28
  * the delivery report.
26
29
  */
@@ -28,6 +31,7 @@
28
31
  import type { DeliveryState} from '../../delivery_state.js';
29
32
  import { type Any, Outcome, StepResult, agent_directive } from '../../delivery_state.js';
30
33
  import { resolve_policy } from '../../persona_policy.js';
34
+ import { decide, partial_exit, record_attempt, retry_halt, unmet_dod, verdict_signature } from './_self_fix.js';
31
35
 
32
36
  const _ALLOWED_VERDICTS = ['success', 'blocked', 'partial'] as const;
33
37
 
@@ -52,7 +56,17 @@ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
52
56
  code: 'bad_verify_verdict',
53
57
  trigger: '`state.verify[\'verdict\']` is `blocked` or `partial`',
54
58
  resolution:
55
- 'address findings and re-run `/review-changes` — never bypass ' + '(see `verify-before-complete`)',
59
+ 'bounded self-fix loop: agent directive `fix-failing-checks` ' +
60
+ 'while attempts remain — never bypass ' + '(see `verify-before-complete`)',
61
+ },
62
+ {
63
+ code: 'self_fix_exhausted',
64
+ trigger:
65
+ 'the self-fix budget for `verify` is spent, or two consecutive ' +
66
+ 'attempts produced an identical verdict signature',
67
+ resolution:
68
+ 'PARTIAL honest exit — the non-success verdict stays on the ' +
69
+ 'surface; the user takes over, overrides, or aborts',
56
70
  },
57
71
  ];
58
72
 
@@ -145,19 +159,39 @@ function _blocked_on_shape(state: DeliveryState, issues: string[]): StepResult {
145
159
  });
146
160
  }
147
161
 
162
+ /**
163
+ * Route a red review verdict through the bounded self-fix loop.
164
+ *
165
+ * Same shape as the `test` lane, separate counter: `autonomous-execution`
166
+ * resets its N=3 budget on a different validation target, and a review finding
167
+ * is not a failing test. `verify-before-complete` is unaffected — the loop
168
+ * cannot report completion, and its exhausted exit is PARTIAL with the
169
+ * non-success verdict still on the surface.
170
+ */
148
171
  function _blocked_on_bad_verdict(state: DeliveryState, verdict: Any): StepResult {
149
172
  const ticketId = _ticketId(state);
150
- return new StepResult({
151
- outcome: Outcome.BLOCKED,
152
- questions: [
153
- `> Ticket ${ticketId} — \`review-changes\` reported ` +
154
- `\`${String(verdict)}\`. The delivery report cannot claim completion on a ` +
155
- 'non-success verdict (see `verify-before-complete`).',
156
- '> 1. Address the findings and re-run `review-changes`',
157
- '> 2. Continue anyway — override (NOT recommended)',
158
- '> 3. Abort',
159
- ],
160
- message: `Ticket ${ticketId} verify verdict was \`${String(verdict)}\`, not success.`,
173
+ const signature = verdict_signature('verify', state.verify);
174
+ const decision = decide(state, 'verify', signature);
175
+
176
+ if (decision.kind === 'retry') {
177
+ record_attempt(state, 'verify', signature);
178
+ return retry_halt({
179
+ lane: 'verify',
180
+ ticket_id: ticketId,
181
+ verdict,
182
+ decision,
183
+ fix_hint: 'address the recorded findings, then re-run the four-judge review.',
184
+ rerun_directive: 'review-changes',
185
+ });
186
+ }
187
+
188
+ return partial_exit({
189
+ lane: 'verify',
190
+ ticket_id: ticketId,
191
+ verdict,
192
+ decision,
193
+ unmet: unmet_dod(state),
194
+ rerun_directive: 'review-changes',
161
195
  });
162
196
  }
163
197
 
@@ -220,6 +220,7 @@ export class WorkState {
220
220
  ui_polish: Dict | null;
221
221
  contract: Dict | null;
222
222
  stitch: Dict | null;
223
+ self_fix: Dict | null;
223
224
  halts: Dict[];
224
225
  version: number;
225
226
  persona: string;
@@ -245,6 +246,7 @@ export class WorkState {
245
246
  ui_polish?: Dict | null;
246
247
  contract?: Dict | null;
247
248
  stitch?: Dict | null;
249
+ self_fix?: Dict | null;
248
250
  halts?: Dict[];
249
251
  version?: number;
250
252
  persona?: string;
@@ -269,6 +271,7 @@ export class WorkState {
269
271
  this.ui_polish = opts.ui_polish ?? null;
270
272
  this.contract = opts.contract ?? null;
271
273
  this.stitch = opts.stitch ?? null;
274
+ this.self_fix = opts.self_fix ?? null;
272
275
  this.halts = opts.halts ?? [];
273
276
  this.version = opts.version ?? SCHEMA_VERSION;
274
277
  this.persona = opts.persona ?? 'senior-engineer';
@@ -309,6 +312,7 @@ export function to_dict(state: WorkState): Dict {
309
312
  _validate_ui_polish(state.ui_polish);
310
313
  _validate_contract(state.contract);
311
314
  _validate_stitch(state.stitch);
315
+ _validate_self_fix(state.self_fix);
312
316
  _validate_halts(state.halts);
313
317
  return {
314
318
  version: state.version,
@@ -324,6 +328,7 @@ export function to_dict(state: WorkState): Dict {
324
328
  ui_polish: state.ui_polish,
325
329
  contract: state.contract,
326
330
  stitch: state.stitch,
331
+ self_fix: state.self_fix,
327
332
  halts: [...state.halts],
328
333
  persona: state.persona,
329
334
  memory: state.memory,
@@ -406,6 +411,9 @@ export function from_dict(payload: JsonValue): WorkState {
406
411
  const stitch = payload['stitch'] ?? null;
407
412
  _validate_stitch(stitch);
408
413
 
414
+ const self_fix = payload['self_fix'] ?? null;
415
+ _validate_self_fix(self_fix);
416
+
409
417
  const halts = 'halts' in payload ? payload['halts'] : [];
410
418
  _validate_halts(halts);
411
419
 
@@ -422,6 +430,7 @@ export function from_dict(payload: JsonValue): WorkState {
422
430
  ui_polish: _isDict(ui_polish) ? { ...ui_polish } : null,
423
431
  contract: _isDict(contract) ? { ...contract } : null,
424
432
  stitch: _isDict(stitch) ? { ...stitch } : null,
433
+ self_fix: _isDict(self_fix) ? { ...self_fix } : null,
425
434
  halts: Array.isArray(halts) ? ([...halts] as Dict[]) : [],
426
435
  version: version as number,
427
436
  persona: _get(payload, 'persona', 'senior-engineer') as string,
@@ -866,6 +875,71 @@ function _validate_ui_review(ui_review: JsonValue): void {
866
875
  }
867
876
  }
868
877
 
878
+ /**
879
+ * Reject malformed `self_fix` envelopes; tolerate `null` and `{}`.
880
+ *
881
+ * `null` means no red check has been seen. Once populated, each key is a lane
882
+ * name (`test`, `verify`) whose record may carry `attempts` (a non-negative
883
+ * int) and `signatures` (a list of strings). As with `ui_polish`, the schema
884
+ * enforces only shape: the ceiling and the no-progress floor are the handler's
885
+ * business (`directives/backend/_self_fix.ts`), so a state file cannot decide
886
+ * how many attempts are allowed.
887
+ *
888
+ * `attempts` is validated but NOT bounded here on purpose. A file carrying
889
+ * `attempts: 99` is well-formed and simply exhausted — rejecting it would turn
890
+ * a spent budget into a crash, and the loop's whole point is a clean exit.
891
+ */
892
+ function _validate_self_fix(self_fix: JsonValue): void {
893
+ if (self_fix === null) {
894
+ return;
895
+ }
896
+ if (!_isDict(self_fix)) {
897
+ throw new SchemaError(
898
+ `state.self_fix must be a JSON object or null; ` +
899
+ `got ${pyTypeName(self_fix)}`,
900
+ );
901
+ }
902
+ for (const [lane, record] of Object.entries(self_fix)) {
903
+ if (!_isDict(record)) {
904
+ throw new SchemaError(
905
+ `state.self_fix.${lane} must be a JSON object; ` +
906
+ `got ${pyTypeName(record)}`,
907
+ );
908
+ }
909
+ if ('attempts' in record) {
910
+ const attempts = record['attempts'];
911
+ if (
912
+ typeof attempts !== 'number' ||
913
+ typeof attempts === 'boolean' ||
914
+ !Number.isInteger(attempts) ||
915
+ attempts < 0
916
+ ) {
917
+ throw new SchemaError(
918
+ `state.self_fix.${lane}.attempts must be a non-negative ` +
919
+ `integer when present; got ${pyRepr(attempts)}`,
920
+ );
921
+ }
922
+ }
923
+ if ('signatures' in record) {
924
+ const signatures = record['signatures'];
925
+ if (!Array.isArray(signatures)) {
926
+ throw new SchemaError(
927
+ `state.self_fix.${lane}.signatures must be a list when ` +
928
+ `present; got ${pyTypeName(signatures)}`,
929
+ );
930
+ }
931
+ for (const entry of signatures) {
932
+ if (typeof entry !== 'string') {
933
+ throw new SchemaError(
934
+ `state.self_fix.${lane}.signatures entries must be ` +
935
+ `strings; got ${pyTypeName(entry)}`,
936
+ );
937
+ }
938
+ }
939
+ }
940
+ }
941
+ }
942
+
869
943
  /**
870
944
  * Reject malformed `ui_polish` envelopes; tolerate `null` and `{}`.
871
945
  *
@@ -150,6 +150,7 @@ export function _to_delivery(work: WorkState): DeliveryState {
150
150
  contract: work.contract as Record<string, DeliveryAny> | null,
151
151
  stitch: work.stitch as Record<string, DeliveryAny> | null,
152
152
  stack: work.stack as Record<string, DeliveryAny> | null,
153
+ self_fix: work.self_fix as Record<string, DeliveryAny> | null,
153
154
  });
154
155
  }
155
156
 
@@ -182,6 +183,11 @@ export function _sync_back(work: WorkState, delivery: DeliveryState): void {
182
183
  work.contract = delivery.contract as Dict | null;
183
184
  work.stitch = delivery.stitch as Dict | null;
184
185
  work.stack = delivery.stack as Dict | null;
186
+ // Reassigned (not mutated) by `record_attempt`, so the defensive mirror
187
+ // above is load-bearing here rather than a no-op: without this line the
188
+ // attempt counter would reset on every dispatch and the loop would never
189
+ // reach either floor.
190
+ work.self_fix = delivery.self_fix as Dict | null;
185
191
  }
186
192
 
187
193
  /**
@@ -0,0 +1,167 @@
1
+ {
2
+ "_comment": "Committed target table for the road-to-cost-parity-* family (part 0, Phase 1). Registers what a session costs today, what the family is steering toward, and what the rule payload actually is PER HOST — the correction that matters most, because every diet target in the source drafts was set against one aggregate number that describes no host. Nothing here changes behaviour: no carrier ships for these thresholds (part 0 Phase 4.5), and no gate reads the target columns. What IS gated is the file's own shape — lint_budget_ownership rejects a row without a `source`, and rejects a `revised_from` without `revision_evidence`, so a number cannot enter or move without a traceable origin.",
3
+ "schema_version": 1,
4
+ "registered_at": "2026-08-10",
5
+ "owner": "maintainer",
6
+ "review_by": "2026-11-10",
7
+ "row_schema": {
8
+ "_comment": "Reading of part 0 § 1.2b: `schema_version` / `registered_at` / `owner` / `review_by` are FILE-level (as in every sibling budget file — a per-row copy would be four constants repeated N times), and every ROW carries `source` plus the three honest-null fields from § 1.3. The validator enforces exactly that split.",
9
+ "required_row_fields": ["id", "unit", "baseline", "source", "revisable", "revision_evidence", "revised_from"],
10
+ "honest_null_contract": "A target bends to a measurement, never to a preference. `revisable: true` says the number is revisable at all; `revised_from` records what it used to be once it moves; `revision_evidence` must then point at the evidence note that forced the move. The validator fails the file when `revised_from` is set and `revision_evidence` is empty — a revision without published evidence is the one form of drift this schema exists to refuse."
11
+ },
12
+ "measurement": {
13
+ "_comment": "Both measurement rules from part 0's Context are fields here, not prose: a rule that lives only in a paragraph is a rule the next author does not inherit.",
14
+ "measurement_date": "2026-08-10",
15
+ "commit": "c073d5732fb890bbf8b41ec879f62f651c7ba293",
16
+ "projection_regenerated": true,
17
+ "regeneration_command": "task sync && task generate-tools — verified no-op afterwards (git status clean), so the payload rows below measure the commit and not when someone last ran a generator",
18
+ "emitter_generation": "real, frontmatter-less projection files (25 of 110 .claude/rules entries carry a `paths:` block; the rest carry no frontmatter). NOT the older emitter that symlinked .claude/rules/* into dist/agent-src/rules/ and so inherited dist's full frontmatter. This is the THIRD variable behind the 92-vs-110 file-count spread: a projection count is a fact about which emitter last wrote the tree, on top of how stale that tree is.",
19
+ "carrier_divergence_admitted": false,
20
+ "carrier_divergence_reason": "report_carrier_divergence compares a globally installed carrier against the project projection on disk; both can be stale independently, so it measures relative cache staleness rather than any property of the source. It may diagnose a behind-the-times install; it may never gate a phase, and no row here is derived from it."
21
+ },
22
+ "session_cost_targets": [
23
+ {
24
+ "id": "median_final_context_tokens",
25
+ "unit": "billable input tokens of the last main-chain assistant record (per cc_transcript.ts)",
26
+ "baseline": 519349,
27
+ "interim": 300000,
28
+ "target": 200000,
29
+ "direction": "lower_is_better",
30
+ "source": "agents/evidence/analysis/token-economy-recycling-phase1.md:45 — median over 201 sessions with parseable final usage (:44) out of 205 non-empty (:10)",
31
+ "revisable": true,
32
+ "revision_evidence": "",
33
+ "revised_from": null
34
+ },
35
+ {
36
+ "id": "p90_final_context_tokens",
37
+ "unit": "billable input tokens of the last main-chain assistant record",
38
+ "baseline": 807937,
39
+ "interim": 550000,
40
+ "target": 400000,
41
+ "direction": "lower_is_better",
42
+ "source": "agents/evidence/analysis/token-economy-recycling-phase1.md:45 — same 201-session corpus",
43
+ "revisable": true,
44
+ "revision_evidence": "",
45
+ "revised_from": null
46
+ },
47
+ {
48
+ "id": "auto_compact_incidence",
49
+ "unit": "share of sessions with >= 1 auto-compaction event",
50
+ "baseline": 0.112,
51
+ "interim": 0.05,
52
+ "target": 0.0,
53
+ "direction": "lower_is_better",
54
+ "source": "agents/evidence/analysis/token-economy-recycling-phase1.md:59 — 23 of 205 sessions, 31 events (:60), all trigger:\"auto\"",
55
+ "revisable": true,
56
+ "revision_evidence": "",
57
+ "revised_from": null
58
+ },
59
+ {
60
+ "id": "late_early_per_call_cost_ratio",
61
+ "unit": "median billable input per call over the last 20 calls / the first 20 calls of a session",
62
+ "baseline": 2.1,
63
+ "interim": 1.6,
64
+ "target": 1.3,
65
+ "direction": "lower_is_better",
66
+ "source": "agents/evidence/analysis/token-economy-recycling-phase1.md:69-75 — 145 sessions with >= 200 main-chain calls; median early 279,859 vs late 594,985; p90 ratio 3.3x",
67
+ "revisable": true,
68
+ "revision_evidence": "",
69
+ "revised_from": null
70
+ }
71
+ ],
72
+ "payload_rows": [
73
+ {
74
+ "_comment": "Per host, never one number. The aggregate the source drafts dieted against (\"419 KB always-loaded layer\") describes no host that exists: it is the whole rule tree, and only 9 of 116 rules are type: always.",
75
+ "id": "src_rules_maintained",
76
+ "unit": "bytes across src/rules/*.md",
77
+ "baseline": 424896,
78
+ "files": 116,
79
+ "target_bytes": null,
80
+ "target_owner": "none — this is the maintained source, not a delivered payload; it shrinks only as a consequence of a diet elsewhere",
81
+ "source": "measured 2026-08-10 at commit c073d5732, post-regeneration (see `measurement`)",
82
+ "revisable": true,
83
+ "revision_evidence": "",
84
+ "revised_from": null
85
+ },
86
+ {
87
+ "id": "dist_agent_src_rules",
88
+ "unit": "bytes across dist/agent-src/rules/*.md",
89
+ "baseline": 420051,
90
+ "files": 115,
91
+ "target_bytes": null,
92
+ "target_owner": "none — the tracked projection source. It is the ONLY rule-payload figure that is stable across checkouts, because it is committed; every other row below is regenerated and therefore machine-dependent.",
93
+ "source": "measured 2026-08-10 at commit c073d5732, post-regeneration",
94
+ "revisable": true,
95
+ "revision_evidence": "",
96
+ "revised_from": null
97
+ },
98
+ {
99
+ "id": "augment_rules_payload",
100
+ "unit": "bytes across .augment/rules/*.md",
101
+ "baseline": 420051,
102
+ "files": 115,
103
+ "target_bytes": null,
104
+ "target_owner": "src/config/budgets.yml § standing_rule_delivery (total_cap_tokens 110000) already owns the delivered-rule ceiling. This family does not open a second, byte-denominated target for the same surface — a metric with two owners is a metric with none.",
105
+ "source": "measured 2026-08-10 at commit c073d5732, post-regeneration; mirrors dist/ by symlink",
106
+ "revisable": true,
107
+ "revision_evidence": "",
108
+ "revised_from": null
109
+ },
110
+ {
111
+ "id": "claude_rules_payload",
112
+ "unit": "bytes across .claude/rules/*.md",
113
+ "baseline": 339035,
114
+ "files": 110,
115
+ "target_bytes": null,
116
+ "target_owner": "src/config/budgets.yml § standing_rule_delivery — same ceiling, same reason as the augment row",
117
+ "source": "measured 2026-08-10 at commit c073d5732, post-regeneration; 25 of 110 files carry a `paths:` block, the rest none",
118
+ "revisable": true,
119
+ "revision_evidence": "",
120
+ "revised_from": null
121
+ },
122
+ {
123
+ "_comment": "CORRECTION to part 0's triage table, which recorded these two as \"~3 KB / ~0 KB\" and therefore as already-lean hosts needing no target. Re-measured post-regeneration they are 408 KB and 330 KB — a full payload each. The earlier reading is a stale-projection artefact of exactly the class measurement rule 1 warns about: both carriers are UNTRACKED and written only by `task generate-tools`, so a checkout that has not run it reads them as near-empty. Risk 3's mitigation (\"the lean hosts need no target\") does not survive the re-measure and is not encoded here.",
124
+ "id": "clinerules_payload",
125
+ "unit": "bytes across .clinerules/*.md",
126
+ "baseline": 417703,
127
+ "files": 111,
128
+ "target_bytes": null,
129
+ "target_owner": "unowned — a diet for this host belongs to the parked rule-payload part of this family, not to part 0",
130
+ "source": "measured 2026-08-10 at commit c073d5732, post-regeneration; untracked, generated by task generate-tools",
131
+ "revisable": true,
132
+ "revision_evidence": "",
133
+ "revised_from": null
134
+ },
135
+ {
136
+ "id": "windsurfrules_payload",
137
+ "unit": "bytes of the single .windsurfrules file",
138
+ "baseline": 338160,
139
+ "files": 1,
140
+ "target_bytes": null,
141
+ "target_owner": "unowned — see the clinerules row; same correction, same parked owner",
142
+ "source": "measured 2026-08-10 at commit c073d5732, post-regeneration; untracked, generated by task generate-tools",
143
+ "revisable": true,
144
+ "revision_evidence": "",
145
+ "revised_from": null
146
+ },
147
+ {
148
+ "id": "gemini_md_payload",
149
+ "unit": "bytes of GEMINI.md",
150
+ "baseline": 2982,
151
+ "files": 1,
152
+ "target_bytes": null,
153
+ "target_owner": "none — genuinely lean, and the only host in this table for which that is true. It is a pointer file, not a rule payload.",
154
+ "source": "measured 2026-08-10 at commit c073d5732, post-regeneration",
155
+ "revisable": true,
156
+ "revision_evidence": "",
157
+ "revised_from": null
158
+ }
159
+ ],
160
+ "anchors": {
161
+ "premium_band_tokens": 200000,
162
+ "rationale": "Above ~200k a session sits in the premium pricing band, which is why the median target is set there rather than at a round fraction of the baseline.",
163
+ "known_noise_problem": "187 of 205 observed sessions end at or above 200k, so a session-end hint at this threshold would fire on ~91% of sessions — a constant line, not an advisory. The 200k figure is a TARGET here and nothing else.",
164
+ "no_carrier_ships": "Part 0 § 4.5: no session-end advisory ships for this anchor. The existing 800k recycle threshold (src/config/recycle-threshold-budget.json) stays the only strong line, and any carrier for 200k is a separate, separately-argued change.",
165
+ "source": "agents/evidence/analysis/token-economy-recycling-phase1.md — session-final distribution (:45) over the 205-session corpus (:10)"
166
+ }
167
+ }
@@ -40,13 +40,16 @@
40
40
  # visible, or it repeats the sin it exists to catch.
41
41
  # canary optional mutation recipe (Phase 7) — see CANARY below
42
42
  #
43
- # COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-05)
43
+ # COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-10)
44
44
  # ----------------------------------------------------------
45
- # 240 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
45
+ # 251 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
46
46
  # (the count is computed, not asserted — `check_gate_coverage.count_gate_scripts`).
47
- # **32 of them emit the machine-readable `scanned: <N>` line under their
48
- # CI-identical argv, and all 31 are listed below.** The manifest is therefore
47
+ # **33 of them emit the machine-readable `scanned: <N>` line under their
48
+ # CI-identical argv, and all 33 are listed below.** The manifest is therefore
49
49
  # complete with respect to its own contract, and covers ~13 % of the population.
50
+ # (The 2026-08-05 reading said "32 … and all 31 are listed": the two halves of
51
+ # one sentence disagreed by one, which is the drift this paragraph exists to
52
+ # make visible. Both are recounted above, not adjusted toward each other.)
50
53
  #
51
54
  # Two revisions of this header have now gone stale the same way. It claimed
52
55
  # "8 of 211", then "22 of 223" while 26 entries were listed. Both halves drift
@@ -98,7 +101,7 @@
98
101
  # which is the shape this repo tells a gate author to use — writes the line from
99
102
  # inside the library, so its own source never matches and the test cannot see it.
100
103
  # Live split: **1 unregistered gate emits the inline shape (`check_gate_coverage`
101
- # itself), and 175 of 244 gate scripts emit through the helper.** So for the
104
+ # itself), and 205 of 251 gate scripts emit through the helper.** So for the
102
105
  # recommended shape the sentence above is false, and the list CAN fall behind.
103
106
  #
104
107
  # Not widened here, deliberately. Widening the regex to include the helper turns
@@ -557,6 +560,42 @@ gates:
557
560
  untouched original stayed clean. Floor at 450 against a live 503 leaves
558
561
  room for skill retirement without redding the change that retires one.
559
562
 
563
+ - id: lint_workflow_paths
564
+ argv: []
565
+ min_scanned: 20
566
+ corpus: "every workflow under .github/workflows (28 at baseline); the unit is the file, and each file's `paths:` / `paths-ignore:` entries are resolved against `git ls-files`"
567
+ status: enforced
568
+ canary:
569
+ class: undetected-antipattern
570
+ path: .github/workflows/zz-canary-workflow-paths.yml
571
+ content: |
572
+ name: Canary
573
+ on:
574
+ pull_request:
575
+ paths:
576
+ - "retired/tree/that/never/existed/**"
577
+ jobs:
578
+ noop:
579
+ runs-on: ubuntu-latest
580
+ steps:
581
+ - run: echo canary
582
+ note: >
583
+ Registered STRICT on day one for the same reason `lint_example_fences`
584
+ was, and on the same evidence standard: the classification pass ran
585
+ BEFORE the gate existed and returned **20 dead entries across 6 workflow
586
+ files**, each classified by hand into one of four causes — a tree retired
587
+ by ADR-051, a gitignored projection that can never appear in a diff, two
588
+ paths moved by the Python-to-TS migration, and bare root paths whose real
589
+ files sit under `src/`. All 20 were repaired in the change that added the
590
+ gate, so the corpus is verified empty rather than unexamined, and an
591
+ advisory gate over it could only ever be ignored. Floor at 20 against a
592
+ live 28 so retiring a workflow does not red the change that retires it.
593
+ Worth recording that the highest-severity hit was invisible to the hand
594
+ census and only the gate found it: `release-validation.yml` filtered on a
595
+ root `marketplace.json` that has never existed, so a version drift in the
596
+ real manifest did not re-trigger the `version-consistency` job whose
597
+ entire purpose is catching that drift.
598
+
560
599
  - id: check_depth_budget
561
600
  argv: []
562
601
  min_scanned: 140
@@ -0,0 +1,39 @@
1
+ {
2
+ "_comment": "Pre-registered council ATTENDANCE metrics (road-to-inbox-harvest-2026-08-b Phase 1.4). Registered here rather than in roadmap prose because a roadmap is archived when it closes and a metric outlives it — the sibling budget files (hook-token-budget.json, recycle-threshold-budget.json) already carry the owner / review_by / honest-gap discipline this reuses. The instrument is the quorum_result line appendQuorumEvent writes to agents/runtime/council/events.log (schema v2); nothing here is measured yet, and no threshold is committed before data exists.",
3
+ "schema_version": 1,
4
+ "registered_at": "2026-08-10",
5
+ "owner": "maintainer",
6
+ "review_by": "2026-11-10",
7
+ "unit": "quorum_result event lines in agents/runtime/council/events.log",
8
+ "privacy": "a quorum_result line carries {phase, command, dispatch, verdict, threshold, configured_total, total, present, solo, absent[{member, reason}]} — counts, closed vocabularies, and member ids only. The type has no field able to hold free-form content, so the CLI's own `detail` string (provider error text, which can embed paths and prompt fragments) is dropped at the boundary rather than scrubbed.",
9
+ "phase_split": "A pass emits `pre_run` (build_members — who could be constructed) and, on the `run` path, `post_run` (_postRunQuorum — who actually returned a usable response). They disagree whenever a member constructs and then fails mid-flight, so a rate computed over both double-counts. A `pre_run` line is NOT evidence a pass ran: `command: estimate` is a spend-free preview, and a `run` / `debate` line is written before the `--confirm` gate (which defaults to false and returns before any provider call) and before the necessity and size-fit gates. A `post_run` line is the only line that proves providers were called — which is why every rate below is post_run-scoped, and why `command` exists at all.",
10
+ "attendance_rate": {
11
+ "definition": "of the members that entered the pass, how many answered: present / total per post_run pass. Bounded by 1 by construction (evaluateQuorum clamps present into [0, total]).",
12
+ "instrument": "quorum_result lines, phase=post_run, command=run, fields present + total",
13
+ "threshold": "none committed before data — baseline first, threshold at review date"
14
+ },
15
+ "roster_shortfall_rate": {
16
+ "definition": "how often the council shrank BEFORE the pass: share of post_run passes with total < configured_total, plus the size of the gap. This is the half attendance_rate cannot see — a pass with 3 configured, 2 failing to construct and 1 answering reads 1/1 = 100 % attendance and a shortfall of 2. Both numbers are needed and neither substitutes for the other; a single ratio over mixed rosters is the error this pair replaces.",
17
+ "instrument": "quorum_result lines, phase=post_run, command=run, configured_total vs total",
18
+ "threshold": "none committed before data — baseline first, threshold at review date",
19
+ "not_an_error": "total > configured_total is legitimate and not a shortfall: `--siblings` fans ONE enabled config entry into N clients, so the client roster can exceed the entry count. configured_total counts enabled config ENTRIES; total counts CLIENTS. Compare them for direction, never divide one by the other."
20
+ },
21
+ "solo_conclusion_rate": {
22
+ "definition": "share of post_run passes with `solo: true` — written by the isSoloConcluded predicate in quorum.ts, never re-derived from the numbers here, so one change to the rule cannot leave this definition stale. ceil(n/2) makes 1-of-2 a legitimate conclusion; this measures how often that legitimacy is used, not whether it should be. THREE cases carry solo: true and they are not the same finding — a degraded multi-member council (configured_total > 1), a council configured with one member (configured_total = 1, dispatch = full), and a deliberate --single dispatch (dispatch = single). Split on dispatch and configured_total before reading the rate.",
23
+ "instrument": "quorum_result lines, phase=post_run, command=run, field solo, split by dispatch + configured_total",
24
+ "threshold": "none committed before data. The quorum-solo-floor blocker pre-registers the three outcomes this rate decides between — a third CLI member, a gate-scoped min_present: 2, or a published null under 5 % — and that choice is made against real data, never ahead of it. It is decided on the degraded case only; a --single pass is the operator asking for one voice, not the council failing to assemble."
25
+ },
26
+ "absent_reason_distribution": {
27
+ "definition": "count per reason over absent[] entries — no_binary / no_auth / timeout / quota (AbsentReason) plus the two runtime fallbacks the CLI writes directly, `unavailable` (transport resolved to nothing) and `binary_missing` (a CLI client refused to construct). Distinguishes a council degraded by configuration from one degraded by provider behaviour, which have opposite fixes.",
28
+ "instrument": "quorum_result lines, absent[].reason, split by phase (a pre_run absence is configuration; a post_run absence is mid-flight). Excluding `command: estimate` is NOT sufficient: `run` and `debate` also reach construction and return at the `--confirm` gate, which defaults to false. The reliable filter is a pre_run line whose pass produced a matching post_run line; a pre_run line with no post_run partner reached construction and stopped there, whatever its command says.",
29
+ "threshold": "none committed before data — the distribution IS the finding at review date",
30
+ "merge_before_reading": "`no_binary` and `binary_missing` are two spellings of one condition — the CLI binary is unusable — written by different code paths (transport_resolver's static classification vs the CLI client's construction failure). They are recorded separately because that is what the tree emits, not because they are separate findings; merge them before reading the distribution, or the largest bucket is split in two."
31
+ },
32
+ "honest_null_consequence": "If the log accumulates and the solo-conclusion rate sits near zero, that is the published answer to the quorum-solo-floor blocker and 1.5 is cancelled against it — not quietly dropped. If attendance data never accumulates at all (nobody runs a metered council pass in the window), that is reported as an unmeasured metric at the review date, never as a passing one.",
33
+ "honest_gaps": [
34
+ "`lens` and `invocation` are empty on every pre_run line — neither is known at member-construction time — so any per-lens breakdown is post_run-only.",
35
+ "`command: debate` carries the pre_run half ONLY. There is no _postRunQuorum on the debate path, and defining `usable response` for a multi-round debate is a separate question this registration does not answer. So debate contributes to absent_reason_distribution and to nothing else: attendance_rate and solo_conclusion_rate cover the `run` path, and a reader must not read them as covering every metered pass.",
36
+ "A pre_run line proves construction happened, never that a pass followed — the `--confirm` gate (default false), the necessity gate and the size-fit gate all abort afterwards and nothing on the line records it. Any count over pre_run lines is an upper bound; pair it with a post_run line before treating it as a pass.",
37
+ "The CLI emit wiring itself has no test. The two modules are covered directly, but nothing exercises build_members / cmd_run / cmd_debate end-to-end against a captured log — and `_emitQuorumEvent` forwards no AppendEventOptions, so there is no target to capture into. Two defects in this file's first version (a `dispatch` written from the flag rather than the outcome, and a rate mixing config entries with clients) were found by review rather than by a test, which is what that gap costs."
38
+ ]
39
+ }