@event4u/agent-config 14.6.0 → 14.7.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 (188) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +260 -7
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/analyze/inbox.md +264 -10
  5. package/dist/agent-src/commands/analyze.md +1 -1
  6. package/dist/agent-src/commands/roadmap/process-full.md +116 -30
  7. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +31 -0
  8. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +135 -104
  9. package/dist/agent-src/rules/context-hygiene.md +2 -0
  10. package/dist/agent-src/rules/decision-revisit-gate.md +3 -0
  11. package/dist/agent-src/rules/fix-what-you-see.md +108 -0
  12. package/dist/agent-src/rules/no-roadmap-references.md +0 -5
  13. package/dist/agent-src/rules/recurring-criticism.md +110 -0
  14. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  15. package/dist/agent-src/rules/rule-type-governance.md +0 -2
  16. package/dist/agent-src/rules/self-repair-loop.md +17 -0
  17. package/dist/agent-src/rules/skill-quality.md +0 -2
  18. package/dist/agent-src/rules/source-confidentiality.md +37 -7
  19. package/dist/agent-src/scripts/archival_sweep.ts +117 -0
  20. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
  21. package/dist/agent-src/scripts/gate_budget.ts +282 -0
  22. package/dist/agent-src/scripts/gate_execute.ts +72 -31
  23. package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
  24. package/dist/agent-src/scripts/update_roadmap_progress.ts +251 -7
  25. package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
  26. package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
  27. package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
  28. package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
  29. package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
  30. package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
  31. package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
  32. package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
  33. package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
  34. package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
  35. package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
  36. package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
  37. package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
  38. package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
  39. package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
  40. package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
  41. package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
  42. package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
  43. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  44. package/dist/agent-src/templates/roadmaps.md +45 -2
  45. package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  46. package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  47. package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  48. package/dist/cli/registry.js +1 -1
  49. package/dist/cli/registry.js.map +1 -1
  50. package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
  51. package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-4VR4ORQ6.js} +10 -7
  52. package/dist/cli-delegate/chunk-6EOCNQAM.js +402 -0
  53. package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-74E3TKWV.js} +381 -280
  54. package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-7F3VYSM2.js} +4 -4
  55. package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
  56. package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
  57. package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-GJ5BVHXM.js} +72 -165
  58. package/dist/cli-delegate/{chunk-P6LQDIWK.js → chunk-JZ4NR2TW.js} +338 -0
  59. package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
  60. package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
  61. package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
  62. package/dist/cli-delegate/cmd_conformance.js +459 -709
  63. package/dist/cli-delegate/cmd_doctor.js +6 -6
  64. package/dist/cli-delegate/cmd_explain.js +1 -1
  65. package/dist/cli-delegate/cmd_export.js +7 -6
  66. package/dist/cli-delegate/cmd_handoff.js +6 -6
  67. package/dist/cli-delegate/cmd_packs_active.js +1 -1
  68. package/dist/cli-delegate/cmd_preflight.js +2 -2
  69. package/dist/cli-delegate/cmd_prune.js +7 -6
  70. package/dist/cli-delegate/cmd_session_recycle.js +840 -30
  71. package/dist/cli-delegate/cmd_settings_migrate.js +7 -6
  72. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  73. package/dist/cli-delegate/cmd_sync.js +7 -6
  74. package/dist/cli-delegate/cmd_uninstall.js +9 -7
  75. package/dist/cli-delegate/cmd_update.js +1 -1
  76. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  77. package/dist/cli-delegate/cmd_validate.js +7 -6
  78. package/dist/discovery/deprecation-report.md +1 -1
  79. package/dist/discovery/discovery-manifest.json +68 -25
  80. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  81. package/dist/discovery/discovery-manifest.summary.md +4 -4
  82. package/dist/discovery/dormancy-report.md +1 -1
  83. package/dist/discovery/orphan-report.md +1 -1
  84. package/dist/discovery/packs.json +10 -8
  85. package/dist/discovery/trust-report.md +3 -3
  86. package/dist/discovery/workspaces.json +8 -6
  87. package/dist/hooks/dispatch.js +2901 -2053
  88. package/dist/install/claudePathsPlan.js +187 -0
  89. package/dist/install/claudePathsPlan.js.map +1 -0
  90. package/dist/install/hostLayerFingerprint.js +136 -0
  91. package/dist/install/hostLayerFingerprint.js.map +1 -0
  92. package/dist/install/install.mjs +553 -425
  93. package/dist/install/partitionEligibility.js +276 -0
  94. package/dist/install/partitionEligibility.js.map +1 -0
  95. package/dist/mcp/registry-manifest.json +3 -3
  96. package/dist/mcp/server.json +2 -2
  97. package/dist/mcp/server.mjs +1744 -1191
  98. package/dist/router.json +1 -1
  99. package/dist/scripts/_lib/agent_src.js +41 -0
  100. package/dist/scripts/_lib/agent_src.js.map +1 -1
  101. package/dist/scripts/_lib/install_layout.js +83 -0
  102. package/dist/scripts/_lib/install_layout.js.map +1 -0
  103. package/dist/scripts/_lib/installed_lock.js +444 -0
  104. package/dist/scripts/_lib/installed_lock.js.map +1 -0
  105. package/dist/scripts/condense.js +13 -164
  106. package/dist/scripts/condense.js.map +1 -1
  107. package/dist/server/schemas/settings.js +4 -0
  108. package/dist/server/schemas/settings.js.map +1 -1
  109. package/docs/contracts/settings-classes.md +12 -6
  110. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
  111. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
  112. package/docs/guidelines/agent-infra/memory-access.md +17 -0
  113. package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
  114. package/package.json +1 -1
  115. package/src/agent-src/scripts/archival_sweep.ts +117 -0
  116. package/src/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
  117. package/src/agent-src/scripts/gate_budget.ts +282 -0
  118. package/src/agent-src/scripts/gate_execute.ts +72 -31
  119. package/src/agent-src/scripts/roadmap_gates.ts +74 -3
  120. package/src/agent-src/scripts/update_roadmap_progress.ts +251 -7
  121. package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  122. package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  123. package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  124. package/src/config/agent-settings.template.yml +27 -2
  125. package/src/config/ci-local-parity.yml +18 -0
  126. package/src/config/estate-count-budget.json +339 -3
  127. package/src/config/gate-violation-baselines.json +41 -41
  128. package/src/config/rule-activation-census.json +14 -6
  129. package/src/config/rule-stub-ceilings.json +19 -6
  130. package/src/install/claudePathsPlan.ts +196 -0
  131. package/src/install/hostLayerFingerprint.ts +145 -0
  132. package/src/install/partitionEligibility.ts +325 -0
  133. package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
  134. package/src/scripts/_dispatch.bash +6 -1
  135. package/src/scripts/_lib/ac_heading.ts +30 -0
  136. package/src/scripts/_lib/agent_src.ts +42 -0
  137. package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
  138. package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
  139. package/src/scripts/_lib/envelope_grounding.ts +38 -8
  140. package/src/scripts/_lib/installed_lock.ts +50 -5
  141. package/src/scripts/_lib/md_section.ts +129 -0
  142. package/src/scripts/_lib/scope_guard.sh +26 -3
  143. package/src/scripts/_lib/self_repair.ts +19 -0
  144. package/src/scripts/_lib/self_repair_class_b.ts +171 -0
  145. package/src/scripts/ai_council/cli_help.ts +55 -0
  146. package/src/scripts/before_complete_hook.ts +153 -28
  147. package/src/scripts/bench_ab_v2_complexity.ts +27 -7
  148. package/src/scripts/check_references.ts +60 -0
  149. package/src/scripts/check_release_published.ts +32 -11
  150. package/src/scripts/check_single_delivery.ts +59 -15
  151. package/src/scripts/ci_settle.ts +170 -0
  152. package/src/scripts/condense.ts +20 -170
  153. package/src/scripts/council_cli.ts +3 -3
  154. package/src/scripts/dispatch_r2_reviewer.ts +6 -14
  155. package/src/scripts/hook_manifest.json +1 -1
  156. package/src/scripts/hook_manifest.yaml +77 -14
  157. package/src/scripts/hooks/block_no_verify.ts +61 -10
  158. package/src/scripts/hooks/block_unauthorized_git.ts +1 -1
  159. package/src/scripts/hooks/concern_registry.ts +4 -0
  160. package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
  161. package/src/scripts/hooks/run_continuation_hook.ts +8 -5
  162. package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
  163. package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
  164. package/src/scripts/hooks/state_io.ts +782 -20
  165. package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
  166. package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
  167. package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
  168. package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
  169. package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
  170. package/src/scripts/install.ts +16 -11
  171. package/src/scripts/language_mirror_hook.ts +561 -25
  172. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  173. package/src/scripts/lint_plan_risk_register.ts +4 -3
  174. package/src/scripts/lint_skill_router_head.ts +4 -4
  175. package/src/scripts/mcp_server/tools.ts +2 -2
  176. package/src/scripts/new_roadmap.ts +136 -0
  177. package/src/scripts/pack_mcp_content.ts +20 -5
  178. package/src/scripts/release.ts +37 -38
  179. package/src/scripts/report_layer_overlap.ts +6 -0
  180. package/src/scripts/self_repair_hook.ts +37 -5
  181. package/src/scripts/session_register_hook.ts +11 -0
  182. package/src/scripts/skill_usage_collect.ts +11 -1
  183. package/src/scripts/skill_usage_report.ts +278 -8
  184. package/src/scripts/sync_pr_branch.ts +7 -0
  185. package/src/server/schemas/settings.ts +8 -0
  186. package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
  187. package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
  188. /package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
@@ -29,13 +29,36 @@
29
29
  * correctness fact rather than a token fact, and it needs a different
30
30
  * remedy, so it is never folded into the overlap number.
31
31
  *
32
- * REPORT BY DEFAULT, ENFORCE ON REQUEST — and that is not timidity. The
33
- * invariant is not true yet: `road-to-single-delivery` Phase 2, which makes the
34
- * producers write disjoint layers, is HALTED on blocker
35
- * `partition-requires-global-layer`. A blocking default would therefore red every
36
- * run on a defect nobody can currently fix, which teaches readers to ignore it —
37
- * the exact failure this estate has recorded twice already. Pass `--enforce` once
38
- * Phase 2 lands.
32
+ * REPORT BY DEFAULT, ENFORCE ON REQUEST — and that is not timidity. A blocking
33
+ * default would red every run on a defect a given machine may not be able to
34
+ * fix, which teaches readers to ignore it — the exact failure this estate has
35
+ * recorded twice already.
36
+ *
37
+ * The reason used to be that `road-to-single-delivery` Phase 2 was HALTED on
38
+ * blocker `partition-current-layer-undecidable`. That is no longer true and the
39
+ * sentence is corrected rather than left standing: the blocker reads
40
+ * `**Status:** resolved`, its roadmap is archived, and the partition shipped
41
+ * under ADR-236 Phase 2. What replaced the halt is a per-machine activation —
42
+ * the partition withholds artefacts only where the host layer is verified
43
+ * against `installed.lock`, so a machine whose install predates the fingerprint
44
+ * keeps the full projection BY DESIGN and the overlap this gate reports there is
45
+ * the fail-safe working, not a regression.
46
+ *
47
+ * WHERE THIS CHECK IS MEANINGFUL — read this before quoting one of its numbers
48
+ * (road-to-session-closeout 3.3). It is a DEVELOPER-MACHINE check. It reads two
49
+ * host layers off the filesystem, and in CI both are absent: a fresh checkout
50
+ * carries no `<project>/.claude/**` (zero tracked files under `.claude/`) and no
51
+ * `~/.claude/**` because no CI leg installs at user scope. So in CI this gate
52
+ * finds zero layers and has nothing to compare — which is reported as `absent`
53
+ * rather than as a pass, per the paragraph below, but is still not a statement
54
+ * about any developer's machine.
55
+ *
56
+ * How large that gap is, measured on ONE tree in ONE session on 2026-08-20:
57
+ * `check_standing_rule_delivery` read the same repository twice and reported
58
+ * 115 781 tok with one layer installed and 209 767 tok with two — a 1.8x
59
+ * difference produced entirely by which layers existed where. Any figure from
60
+ * this gate or that one describes the machine it ran on. Cite it with the layer
61
+ * counts attached, never as a repository property.
39
62
  *
40
63
  * A LAYER THAT DOES NOT EXIST IS NOT A PASS. Where a layer is absent there is no
41
64
  * overlap to find, and saying "invariant holds" would be a gate reading nothing
@@ -51,6 +74,17 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
51
74
 
52
75
  import { reportScanned } from './_lib/scan_scope.js';
53
76
 
77
+ // ledger-exempt: this gate has no skip semantics to account for. `_lib/gate_ledger`
78
+ // exists so a gate that PASSES OVER targets says which and why; here the target set
79
+ // is four fixed artefact types, every one of them appears in the output, and a type
80
+ // whose layer is missing is printed as `absent` rather than quietly dropped — see
81
+ // the `absent` field and the `types_compared=N of 4` ledger line, which together
82
+ // already carry what a per-target ledger would. Adding one would restate the render
83
+ // in a second format, and the gate's own docstring warns that the degenerate pass
84
+ // here is a marker with no argument behind it. If a future version gains a real skip
85
+ // path — a type it declines to compare for a reason — this exemption is void and the
86
+ // ledger is the right answer.
87
+
54
88
  const _HERE = fileURLToPath(import.meta.url);
55
89
  const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
56
90
 
@@ -336,6 +370,7 @@ export function main(argv?: readonly string[]): number {
336
370
  let globalRoot = path.join(os.homedir(), '.claude');
337
371
  let projectRoot = path.join(REPO_ROOT, '.claude');
338
372
  let enforce = false;
373
+ let quiet = false;
339
374
 
340
375
  const value = (i: number): string | null => {
341
376
  const v = args[i + 1];
@@ -354,13 +389,20 @@ export function main(argv?: readonly string[]): number {
354
389
  i += 1;
355
390
  } else if (a === '--enforce') {
356
391
  enforce = true;
392
+ } else if (a === '--quiet') {
393
+ // R2 finding: the full per-type table is ~30 lines, and preflight is a
394
+ // `silent: true` task. A gate that floods a quiet chain is a gate people
395
+ // learn to scroll past, which is the same end state as an unbound one.
396
+ quiet = true;
357
397
  } else if (a === '--help' || a === '-h') {
358
398
  process.stdout.write(
359
- 'usage: check_single_delivery [--global DIR] [--project DIR] [--enforce]\n' +
399
+ 'usage: check_single_delivery [--global DIR] [--project DIR] [--enforce] [--quiet]\n' +
360
400
  '\n' +
361
- 'Reports by default and exits 0: the invariant is not true yet, because\n' +
362
- 'road-to-single-delivery Phase 2 is halted on partition-requires-global-layer.\n' +
363
- '--enforce exits 1 on any overlap, and is what to register once Phase 2 lands.\n',
401
+ 'Reports by default and exits 0: the partition SHIPPED (ADR-236, Phase 2)\n' +
402
+ 'but activates per machine — only where a verified host layer exists, so a\n' +
403
+ 'checkout without one keeps the full projection BY DESIGN and reports overlap.\n' +
404
+ '--enforce exits 1 on any overlap; registering it would fail every machine\n' +
405
+ 'that has not re-run `agent-config install`, which is why it stays opt-in.\n',
364
406
  );
365
407
  return 0;
366
408
  } else if (a !== undefined) {
@@ -379,7 +421,7 @@ export function main(argv?: readonly string[]): number {
379
421
  }
380
422
 
381
423
  const v = evaluate(globalRoot, projectRoot);
382
- process.stdout.write(`${render(v, globalRoot, projectRoot)}\n`);
424
+ if (!quiet) process.stdout.write(`${render(v, globalRoot, projectRoot)}\n`);
383
425
  // Dead-scope assertion via the shared primitive rather than a hand-rolled
384
426
  // `scanned:` line: `check_gate_coverage`'s hardening ratchet accepts an
385
427
  // emitted line only from a gate registered in the enforced manifest, and this
@@ -451,9 +493,11 @@ export function main(argv?: readonly string[]): number {
451
493
  }
452
494
  process.stdout.write(
453
495
  `⚠️ check_single_delivery: ${detail}. Reported, not enforced — ` +
454
- 'road-to-single-delivery Phase 2 is halted on `partition-requires-global-layer`, ' +
455
- 'so this is a known-open defect rather than a regression. Re-run with --enforce ' +
456
- 'once the partition ships.\n',
496
+ 'the partition shipped (ADR-236 Phase 2) but activates per machine: it withholds ' +
497
+ 'artefacts only where the host layer is verified against `installed.lock`, so a ' +
498
+ 'machine whose install predates the fingerprint keeps the full projection BY DESIGN ' +
499
+ 'and this overlap is the fail-safe working. Run `agent-config install` to enable it ' +
500
+ 'here; use --enforce where every machine is known to be verified.\n',
457
501
  );
458
502
  return 0;
459
503
  }
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Wait for a PR's checks to settle, and never report a verdict it did not read.
4
+ *
5
+ * WHY THIS EXISTS. Agents write this loop by hand, and the exit condition is
6
+ * the part that is easy to get wrong. Measured 2026-08-20:
7
+ *
8
+ * until ! gh pr checks 1459 | grep -q "pending"; do sleep 60; done
9
+ *
10
+ * The API then failed. Its error text — `error connecting to api.github.com` —
11
+ * contains no `pending`, so the loop exited, the run reported a settle that had
12
+ * not happened, and the claim had to be retracted to the user in the next
13
+ * reply. The bug is not the sleep or the grep: it is that **absence of the word
14
+ * `pending` was treated as evidence of completion**, and an error is neither
15
+ * settled nor pending.
16
+ *
17
+ * The rule this encodes, and the reason it belongs in one place rather than in
18
+ * every agent's head: `not settled` and `settled` are different from `could not
19
+ * read`, and only the first two may end a wait.
20
+ *
21
+ * Exit codes: 0 = settled, every check succeeded (or was skipped) ·
22
+ * 1 = settled, at least one check failed ·
23
+ * 2 = did not settle within the budget, or the API could not be
24
+ * read — NEVER a verdict, and the reason is printed.
25
+ *
26
+ * Usage: ./scripts-run src/scripts/ci_settle <pr> [--timeout-min N] [--interval-sec N]
27
+ */
28
+
29
+ import { spawnSync } from 'node:child_process';
30
+
31
+ /** What one poll told us. `unreadable` is deliberately not a verdict. */
32
+ export type PollState =
33
+ | { kind: 'unreadable'; reason: string }
34
+ | { kind: 'pending'; total: number; done: number }
35
+ | { kind: 'settled'; failing: string[]; total: number };
36
+
37
+ /** Markers that mean the API did not answer — not that nothing is pending. */
38
+ const _UNREADABLE_MARKERS = [
39
+ 'error connecting to',
40
+ 'could not resolve host',
41
+ 'connection refused',
42
+ 'timeout',
43
+ 'timed out',
44
+ 'bad gateway',
45
+ 'service unavailable',
46
+ 'rate limit',
47
+ 'gh auth login',
48
+ 'not logged into',
49
+ 'HTTP 5',
50
+ ];
51
+
52
+ /**
53
+ * Classify one `gh pr view --json statusCheckRollup` result.
54
+ *
55
+ * Pure, so the failure this file exists to prevent is testable without a
56
+ * network: feed it an API error and it must answer `unreadable`, never
57
+ * `settled`.
58
+ */
59
+ export function classifyPoll(stdout: string, stderr: string, status: number | null): PollState {
60
+ const combined = `${stdout}\n${stderr}`.toLowerCase();
61
+ for (const marker of _UNREADABLE_MARKERS) {
62
+ if (combined.includes(marker.toLowerCase())) {
63
+ return { kind: 'unreadable', reason: marker };
64
+ }
65
+ }
66
+ if (status !== 0) {
67
+ return { kind: 'unreadable', reason: `gh exited ${String(status)}` };
68
+ }
69
+ let rows: { name?: string; conclusion?: string | null; status?: string | null }[];
70
+ try {
71
+ const parsed = JSON.parse(stdout) as { statusCheckRollup?: unknown };
72
+ const roll = parsed.statusCheckRollup;
73
+ if (!Array.isArray(roll)) {
74
+ return { kind: 'unreadable', reason: 'no statusCheckRollup in response' };
75
+ }
76
+ rows = roll as typeof rows;
77
+ } catch {
78
+ return { kind: 'unreadable', reason: 'unparseable JSON' };
79
+ }
80
+ if (rows.length === 0) {
81
+ // Zero checks is not "settled green" — a run that has not registered
82
+ // its checks yet looks exactly like this.
83
+ return { kind: 'pending', total: 0, done: 0 };
84
+ }
85
+ const terminal = (r: (typeof rows)[number]): boolean =>
86
+ typeof r.conclusion === 'string' && r.conclusion.length > 0;
87
+ const done = rows.filter(terminal).length;
88
+ if (done < rows.length) {
89
+ return { kind: 'pending', total: rows.length, done };
90
+ }
91
+ const bad = new Set(['FAILURE', 'TIMED_OUT', 'CANCELLED', 'ACTION_REQUIRED', 'STARTUP_FAILURE']);
92
+ const failing = rows
93
+ .filter((r) => bad.has(String(r.conclusion).toUpperCase()))
94
+ .map((r) => String(r.name ?? '?'));
95
+ return { kind: 'settled', failing, total: rows.length };
96
+ }
97
+
98
+ function poll(pr: string): PollState {
99
+ const r = spawnSync('gh', ['pr', 'view', pr, '--json', 'statusCheckRollup'], {
100
+ encoding: 'utf-8',
101
+ timeout: 60_000,
102
+ });
103
+ return classifyPoll(r.stdout ?? '', r.stderr ?? '', r.status);
104
+ }
105
+
106
+ function sleepSync(seconds: number): void {
107
+ // Deliberately synchronous: this is a CLI whose whole job is to block.
108
+ spawnSync(process.execPath, ['-e', `setTimeout(()=>{}, ${String(seconds * 1000)})`], {
109
+ timeout: (seconds + 5) * 1000,
110
+ });
111
+ }
112
+
113
+ export function main(argv: readonly string[]): number {
114
+ const positional = argv.filter((a) => !a.startsWith('--'));
115
+ const pr = positional[0];
116
+ if (pr === undefined) {
117
+ process.stderr.write('usage: ci_settle <pr> [--timeout-min N] [--interval-sec N]\n');
118
+ return 2;
119
+ }
120
+ const num = (flag: string, dflt: number): number => {
121
+ const i = argv.indexOf(flag);
122
+ if (i === -1) return dflt;
123
+ const v = argv[i + 1];
124
+ const n = v === undefined ? NaN : parseInt(v, 10);
125
+ return Number.isFinite(n) && n > 0 ? n : dflt;
126
+ };
127
+ const timeoutMin = num('--timeout-min', 45);
128
+ const intervalSec = num('--interval-sec', 60);
129
+
130
+ const deadline = Date.now() + timeoutMin * 60_000;
131
+ let unreadableStreak = 0;
132
+
133
+ for (;;) {
134
+ const state = poll(pr);
135
+ if (state.kind === 'settled') {
136
+ if (state.failing.length === 0) {
137
+ process.stdout.write(`ci_settle: SETTLED GREEN — ${String(state.total)} check(s)\n`);
138
+ return 0;
139
+ }
140
+ process.stdout.write(
141
+ `ci_settle: SETTLED RED — ${String(state.failing.length)} of ${String(state.total)} failing:\n` +
142
+ state.failing.map((f) => ` ${f}\n`).join(''),
143
+ );
144
+ return 1;
145
+ }
146
+ if (state.kind === 'unreadable') {
147
+ unreadableStreak += 1;
148
+ process.stdout.write(
149
+ `ci_settle: could not read the API (${state.reason}) — NOT a verdict, still waiting ` +
150
+ `[${String(unreadableStreak)}]\n`,
151
+ );
152
+ } else {
153
+ unreadableStreak = 0;
154
+ process.stdout.write(
155
+ `ci_settle: pending — ${String(state.done)}/${String(state.total)} settled\n`,
156
+ );
157
+ }
158
+ if (Date.now() >= deadline) {
159
+ process.stdout.write(
160
+ `ci_settle: DID NOT SETTLE within ${String(timeoutMin)} min — no verdict is claimed.\n`,
161
+ );
162
+ return 2;
163
+ }
164
+ sleepSync(intervalSec);
165
+ }
166
+ }
167
+
168
+ if (process.argv[1] !== undefined && process.argv[1].endsWith('ci_settle.ts')) {
169
+ process.exitCode = main(process.argv.slice(2));
170
+ }
@@ -251,7 +251,20 @@ function _stripChars(s: string, chars: string): string {
251
251
  // module-level CLI self-invoke into its bundle. Re-exported here because four
252
252
  // in-repo consumers import it from `condense.js`.
253
253
  export { rule_in_scope } from '../install/ruleInScope.js';
254
+ // Re-exported for in-repo consumers; definitions moved beside emit_host_rules_cli.ts (Phase 5).
255
+ export {
256
+ CLAUDE_PATHS_PATTERN_BUDGET,
257
+ _claude_paths_plan,
258
+ _escape_claude_bracket,
259
+ _expanded_pattern_count,
260
+ _has_non_path_trigger,
261
+ _is_unresolved_placeholder,
262
+ derive_trigger_globs,
263
+ type ClaudePathsPlan,
264
+ } from '../install/claudePathsPlan.js';
254
265
  import { rule_in_scope } from '../install/ruleInScope.js';
266
+ import { isExclusivelyPackageOnly, partitionActive, setPartitionAnnounce } from '../install/partitionEligibility.js'; // ADR-236
267
+ import { _claude_paths_plan, derive_trigger_globs } from '../install/claudePathsPlan.js';
255
268
 
256
269
  const _HERE = path.dirname(fileURLToPath(import.meta.url)); // <repo>/src/scripts
257
270
  const _DEFAULT_PROJECT_ROOT = path.resolve(_HERE, '..', '..');
@@ -304,6 +317,7 @@ function _deriveState(root: string): ModuleState {
304
317
  }
305
318
 
306
319
  export const MODULE_STATE: ModuleState = _deriveState(_DEFAULT_PROJECT_ROOT);
320
+ setPartitionAnnounce(success); // ADR-236: the mode line must be visible at the default level
307
321
 
308
322
  /**
309
323
  * Test seam — reassign one or more module-state fields, mirroring the pytest
@@ -1118,6 +1132,7 @@ function _scoped_rule_basenames(): string[] {
1118
1132
  // is its fourth consumer, after the router compiler, the dist writer, and
1119
1133
  // check_sync. Same predicate in all four; that is the point.
1120
1134
  .filter((p) => !skip_compile_disabled_rule(`rules/${path.basename(p)}`))
1135
+ .filter((p) => !partitionActive(MODULE_STATE.PROJECT_ROOT) || isExclusivelyPackageOnly(p))
1121
1136
  .map((p) => path.basename(p))
1122
1137
  .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
1123
1138
  }
@@ -1322,32 +1337,6 @@ function _yaml_scalar(value: string): string {
1322
1337
  return JSON.stringify(value);
1323
1338
  }
1324
1339
 
1325
- /**
1326
- * Derive host-native activation globs from a rule's `triggers:` frontmatter
1327
- * (road-to-request-scoped-rule-load Phase 2). `file_pattern` triggers map
1328
- * verbatim; `path_prefix` triggers map to `<prefix>**`. Keyword / phrase /
1329
- * command triggers produce no glob — those rules stay
1330
- * description-activated (Cursor Agent-Requested / Windsurf model_decision).
1331
- */
1332
- export function derive_trigger_globs(meta: Record<string, unknown>): string[] {
1333
- const triggers = meta['triggers'];
1334
- if (!Array.isArray(triggers)) {
1335
- return [];
1336
- }
1337
- const globs: string[] = [];
1338
- for (const t of triggers) {
1339
- if (t === null || typeof t !== 'object' || Array.isArray(t)) continue;
1340
- const obj = t as Record<string, unknown>;
1341
- if (typeof obj['file_pattern'] === 'string' && obj['file_pattern']) {
1342
- globs.push(obj['file_pattern']);
1343
- } else if (typeof obj['path_prefix'] === 'string' && obj['path_prefix']) {
1344
- const prefix = obj['path_prefix'] as string;
1345
- globs.push(prefix.endsWith('/') ? `${prefix}**` : `${prefix}**`);
1346
- }
1347
- }
1348
- // De-dup, keep declaration order (deterministic output).
1349
- return [...new Set(globs)];
1350
- }
1351
1340
 
1352
1341
  export function _emit_cursor_mdc(source: string, target: string): void {
1353
1342
  const [meta, body] = _parse_frontmatter(_readText(source));
@@ -1390,151 +1379,8 @@ export function _emit_windsurf_rule(source: string, target: string): void {
1390
1379
  _writeText(target, lines.join('\n'));
1391
1380
  }
1392
1381
 
1393
- /**
1394
- * A brace group the host expands, versus an agent-config placeholder that only
1395
- * LOOKS like one.
1396
- *
1397
- * `path_prefix: "{module_root}/"` in `roadmap-ci-steps-policy` is resolved by
1398
- * this suite from `modules.root_paths`. Emitted verbatim into a host `paths:`
1399
- * list it is a brace group with no comma, which the host expands to the literal
1400
- * string `{module_root}` — a pattern that matches no file. The rule would then
1401
- * reach the model **never** instead of on-demand, and nothing would say so: the
1402
- * probed contract records this exact degradation as silent
1403
- * (`claude-code-rules-dir-contract.md` § constraints).
1404
- *
1405
- * Dropping the pattern is fail-safe in the one direction that matters. A rule
1406
- * left with no pattern at all carries no `paths:` key and loads
1407
- * unconditionally — the status quo, not a regression. A rule carrying a
1408
- * no-op pattern is invisible.
1409
- *
1410
- * The discriminator is the comma, not a placeholder allowlist: `{ts,tsx}` is a
1411
- * real alternation, `{module_root}` cannot be one.
1412
- */
1413
- export function _is_unresolved_placeholder(pattern: string): boolean {
1414
- for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
1415
- if (!(m[1] as string).includes(',')) {
1416
- return true;
1417
- }
1418
- }
1419
- return false;
1420
- }
1421
-
1422
- /**
1423
- * `[` opens a bracket expression for the host. An unparseable one makes that
1424
- * pattern match nothing while the rule's other patterns keep working — so a
1425
- * literal `[` in a trigger is escaped rather than trusted.
1426
- */
1427
- export function _escape_claude_bracket(pattern: string): string {
1428
- return pattern.replace(/\[/g, '\\[');
1429
- }
1430
-
1431
- /**
1432
- * Expanded-pattern cost of one glob: the product of its brace alternations.
1433
- * The host budgets a rule's whole `paths:` list at 1,000 expanded patterns and
1434
- * 4 MiB, and a list over budget is used UNEXPANDED — braces then match
1435
- * literally, i.e. nothing. Counting keeps the emitter under the cliff instead
1436
- * of discovering it as a silent no-op.
1437
- */
1438
- export function _expanded_pattern_count(pattern: string): number {
1439
- let n = 1;
1440
- for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
1441
- n *= (m[1] as string).split(',').length;
1442
- }
1443
- return n;
1444
- }
1445
-
1446
- /** Host-documented budget for one rule's whole `paths:` list. */
1447
- export const CLAUDE_PATHS_PATTERN_BUDGET = 1000;
1448
-
1449
- export interface ClaudePathsPlan {
1450
- /** Patterns to emit under `paths:`, in declaration order. */
1451
- globs: string[];
1452
- /** Patterns deliberately not emitted, with the reason a reader can act on. */
1453
- dropped: { pattern: string; reason: 'unresolved-placeholder' | 'over-budget' | 'mixed-triggers' }[];
1454
- }
1455
1382
 
1456
- /**
1457
- * Does this rule declare any trigger that is NOT path-shaped?
1458
- *
1459
- * Load-bearing for `_claude_paths_plan` below, because one `triggers:` list
1460
- * means two different things on two hosts. Cursor and Windsurf treat globs as
1461
- * ADDITIVE — a rule with empty globs still reaches the agent through its
1462
- * description (see `_emit_cursor_mdc`'s own comment). Claude Code treats
1463
- * `paths:` as EXCLUSIVE: with it the rule loads on a path match; without it the
1464
- * rule loads unconditionally. The emitter fed both semantics from one list, so
1465
- * a rule that declared keywords *and* one path glob silently became
1466
- * path-gated-only on Claude.
1467
- */
1468
- export function _has_non_path_trigger(meta: Record<string, unknown>): boolean {
1469
- const triggers = meta['triggers'];
1470
- if (!Array.isArray(triggers)) return false;
1471
- for (const t of triggers) {
1472
- if (t === null || typeof t !== 'object' || Array.isArray(t)) continue;
1473
- const obj = t as Record<string, unknown>;
1474
- if (typeof obj['keyword'] === 'string' && obj['keyword']) return true;
1475
- if (typeof obj['phrase'] === 'string' && obj['phrase']) return true;
1476
- }
1477
- return false;
1478
- }
1479
1383
 
1480
- /**
1481
- * Derive the host's `paths:` list for one rule, with every drop recorded.
1482
- *
1483
- * Kernel / always-apply rules get an EMPTY list on purpose: absent `paths:` is
1484
- * how this host says "load unconditionally", which is exactly what an Iron-Law
1485
- * rule needs. Scoping one would be a correctness regression dressed as a byte
1486
- * saving — and the same contract notes path-scoped rules are not re-injected
1487
- * after `/compact`, so an obligation that must survive compaction cannot be
1488
- * path-scoped at all.
1489
- */
1490
- export function _claude_paths_plan(meta: Record<string, unknown>): ClaudePathsPlan {
1491
- const always_apply = Boolean(meta['alwaysApply'] || meta['type'] === 'always');
1492
- if (always_apply) {
1493
- return { globs: [], dropped: [] };
1494
- }
1495
- const globs: string[] = [];
1496
- const dropped: ClaudePathsPlan['dropped'] = [];
1497
-
1498
- // A rule that ALSO declares keyword / phrase triggers gets no `paths:` at
1499
- // all, and therefore keeps loading unconditionally on Claude Code.
1500
- //
1501
- // Emitting `paths:` here would narrow the rule to a path match and discard
1502
- // every keyword the author wrote, because this host reads the list as the
1503
- // whole gate rather than as one way in. Measured before this guard: 25 of
1504
- // 110 projected rules carried `paths:` and 19 of them also carried keyword
1505
- // triggers — `design-fidelity` lost 21 of them, in exactly the pasted-
1506
- // screenshot and capability-URL handovers its own routing section names as
1507
- // the classes it exists to catch. Nothing in the tree recorded a decision
1508
- // to make that trade, which is why it is treated as a defect.
1509
- //
1510
- // The drop is REPORTED rather than silent, so a rule whose obligation
1511
- // really is path-bound is visible as a candidate for removing its keyword
1512
- // triggers — the narrowing then becomes an authoring decision instead of an
1513
- // emitter side effect. Cursor and Windsurf are untouched: globs are
1514
- // additive there, so the same rule keeps both routes.
1515
- if (_has_non_path_trigger(meta)) {
1516
- for (const raw of derive_trigger_globs(meta)) {
1517
- dropped.push({ pattern: raw, reason: 'mixed-triggers' });
1518
- }
1519
- return { globs, dropped };
1520
- }
1521
-
1522
- let spent = 0;
1523
- for (const raw of derive_trigger_globs(meta)) {
1524
- if (_is_unresolved_placeholder(raw)) {
1525
- dropped.push({ pattern: raw, reason: 'unresolved-placeholder' });
1526
- continue;
1527
- }
1528
- const cost = _expanded_pattern_count(raw);
1529
- if (spent + cost > CLAUDE_PATHS_PATTERN_BUDGET) {
1530
- dropped.push({ pattern: raw, reason: 'over-budget' });
1531
- continue;
1532
- }
1533
- spent += cost;
1534
- globs.push(_escape_claude_bracket(raw));
1535
- }
1536
- return { globs, dropped };
1537
- }
1538
1384
 
1539
1385
  /**
1540
1386
  * Emit a Claude Code rule carrying the host's OWN activation key.
@@ -1823,6 +1669,7 @@ function _render_native_model_md(src_md: string, tier: string): string {
1823
1669
  }
1824
1670
 
1825
1671
  export function generate_claude_skills(active_skill_names: ReadonlySet<string> | null = null): number {
1672
+ if (partitionActive(MODULE_STATE.PROJECT_ROOT)) active_skill_names = new Set<string>();
1826
1673
  if (!_exists(MODULE_STATE.SKILLS_SOURCE)) {
1827
1674
  process.stderr.write(' ⚠️ dist/agent-src/skills/ not found — skipping skills\n');
1828
1675
  return 0;
@@ -1836,7 +1683,9 @@ export function generate_claude_skills(active_skill_names: ReadonlySet<string> |
1836
1683
  skills = skills.filter((s) => active_skill_names.has(s));
1837
1684
  }
1838
1685
  const skill_set = new Set(skills);
1839
- const command_slugs = new Set([...iterCommands()].map(([, slug]) => slug));
1686
+ const command_slugs = partitionActive(MODULE_STATE.PROJECT_ROOT)
1687
+ ? new Set<string>()
1688
+ : new Set([...iterCommands()].map(([, slug]) => slug));
1840
1689
 
1841
1690
  _mkdirp(MODULE_STATE.CLAUDE_SKILLS_DIR);
1842
1691
  const auto = _read_model_auto_switch() === 'auto';
@@ -1997,6 +1846,7 @@ export function generate_claude_project_commands(
1997
1846
  }
1998
1847
 
1999
1848
  export function generate_claude_commands(active_command_slugs: ReadonlySet<string> | null = null): number {
1849
+ if (partitionActive(MODULE_STATE.PROJECT_ROOT)) active_command_slugs = new Set<string>();
2000
1850
  if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
2001
1851
  process.stderr.write(' ⚠️ src/domains/ not found — skipping commands\n');
2002
1852
  return 0;
@@ -63,6 +63,7 @@ import {
63
63
  plan_advisor_swap,
64
64
  } from './ai_council/advisors.js';
65
65
  import { format_install_hints } from './ai_council/cli_hints.js';
66
+ import { renderSubHelp } from './ai_council/cli_help.js';
66
67
  import {
67
68
  type AdvisorConfig,
68
69
  type CouncilConfig,
@@ -2507,6 +2508,7 @@ function cmd_run(
2507
2508
  const [extra_calls, extra_usd] = _consensus_cost_delta(ai_cfg, question.mode, estimates, billable.length);
2508
2509
  const [ch_calls, ch_usd] = _chairman_cost_delta(ai_cfg, estimates);
2509
2510
  const [pr_extra_calls, pr_extra_usd] = _peer_review_cost_delta(ai_cfg, args, estimates, billable.length);
2511
+ if (!args.confirm) _stdout('council:run · DRY PASS — estimate only, no seat is contacted. Add --confirm to run.\n');
2510
2512
  _stdout(
2511
2513
  `council:run · mode=${question.mode} · members=${members.length} ` +
2512
2514
  `(billable=${billable.length})\n`,
@@ -3728,10 +3730,8 @@ function _parseArgs(argv: string[]): Args {
3728
3730
  const rest = argv.slice(1);
3729
3731
  const usage = _usageFor(first);
3730
3732
  const { positionals, opts, requiredOpts } = _specsFor(first);
3731
-
3732
- // Sub-help.
3733
3733
  if (rest.includes('-h') || rest.includes('--help')) {
3734
- _stdout(usage);
3734
+ _stdout(renderSubHelp(first, usage, { positionals, opts, requiredOpts }));
3735
3735
  process.exitCode = 0;
3736
3736
  throw new _ArgExit();
3737
3737
  }
@@ -51,6 +51,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
51
51
  // (validator → dispatcher for the scope hash, dispatcher → validator for
52
52
  // relevance) is safe: neither module calls the other at module-evaluation
53
53
  // time, and each CLI entry guard only fires for its own argv[1].
54
+ import { isAcceptanceCriteriaHeading } from './_lib/ac_heading.js';
54
55
  import { gitEnv } from './_lib/git_env.js';
55
56
  import { completionReviewDisabled } from './_lib/planning_settings.js';
56
57
  import { artifactRelevance } from './check_completion_review.js';
@@ -492,20 +493,11 @@ export function extractAcceptanceCriteria(roadmapText: string): string {
492
493
  const lines = roadmapText.split('\n');
493
494
  let start = -1;
494
495
  for (let i = 0; i < lines.length; i++) {
495
- // Case-insensitive, and NOT anchored at the end of the line.
496
- //
497
- // Case first: the tree carries both `## Acceptance Criteria` and
498
- // `## Acceptance criteria`; a case-sensitive match silently extracted
499
- // nothing for the latter (found by the zcs-close R2 review, 2026-08-09).
500
- //
501
- // The end-anchor was the same defect wearing a different mask, and it was
502
- // LIVE: `\s*$` rejected a heading carrying a qualifier, so
503
- // `## Acceptance criteria (per phase, on promotion to ready)` and
504
- // `## Acceptance criteria (anti-dump — the review-s own rule)` extracted
505
- // nothing. Two roadmaps, measured 2026-08-18 — and they had been counted
506
- // as "declares no criteria" by the very census that motivated this fix,
507
- // which is how a matcher tuned to its own measurement hides its misses.
508
- if (/^##\s+acceptance criteria\b/i.test(lines[i] as string)) {
496
+ // Case-insensitive and NOT end-anchored — both of those were live misses
497
+ // here (case: zcs-close R2 review 2026-08-09; the `(per phase, …)` qualifier:
498
+ // measured 2026-08-18). The predicate now lives in `_lib/ac_heading`, which
499
+ // records all three instances of the defect and why it is shared.
500
+ if (isAcceptanceCriteriaHeading(lines[i] as string)) {
509
501
  start = i;
510
502
  break;
511
503
  }