@event4u/agent-config 9.26.0 → 9.28.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 (252) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +12 -11
  3. package/CHANGELOG.md +134 -207
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/analyze/conformance.md +43 -0
  6. package/dist/agent-src/commands/roadmap/next.md +65 -3
  7. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +10 -3
  8. package/dist/agent-src/rules/active-remediation.md +2 -0
  9. package/dist/agent-src/rules/analysis-skill-routing.md +2 -0
  10. package/dist/agent-src/rules/architecture.md +2 -0
  11. package/dist/agent-src/rules/artifact-drafting-protocol.md +2 -0
  12. package/dist/agent-src/rules/artifact-engagement-recording.md +6 -1
  13. package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
  14. package/dist/agent-src/rules/autonomous-execution.md +3 -1
  15. package/dist/agent-src/rules/brand-consistency.md +2 -0
  16. package/dist/agent-src/rules/brand-source-of-truth.md +35 -6
  17. package/dist/agent-src/rules/broken-access-control.md +2 -0
  18. package/dist/agent-src/rules/cli-output-handling.md +2 -0
  19. package/dist/agent-src/rules/code-comment-discipline.md +2 -0
  20. package/dist/agent-src/rules/code-provenance.md +2 -0
  21. package/dist/agent-src/rules/command-suggestion-policy.md +2 -0
  22. package/dist/agent-src/rules/commit-conventions.md +2 -0
  23. package/dist/agent-src/rules/communication-through-line.md +2 -0
  24. package/dist/agent-src/rules/content-quoting-floor.md +2 -0
  25. package/dist/agent-src/rules/context-hygiene.md +13 -1
  26. package/dist/agent-src/rules/copilot-routing.md +2 -0
  27. package/dist/agent-src/rules/council-availability.md +93 -0
  28. package/dist/agent-src/rules/cross-source-consistency.md +5 -0
  29. package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
  30. package/dist/agent-src/rules/delegation-policy.md +31 -5
  31. package/dist/agent-src/rules/design-fidelity.md +5 -0
  32. package/dist/agent-src/rules/design-review-after-ui-write.md +4 -0
  33. package/dist/agent-src/rules/devcontainer-routing.md +2 -0
  34. package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
  35. package/dist/agent-src/rules/docker-commands.md +2 -0
  36. package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
  37. package/dist/agent-src/rules/domain-safety-disclaimer.md +2 -0
  38. package/dist/agent-src/rules/domain-safety-pii.md +2 -0
  39. package/dist/agent-src/rules/domain-safety-retention.md +2 -0
  40. package/dist/agent-src/rules/downstream-changes.md +2 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +14 -3
  43. package/dist/agent-src/rules/external-code-graph-interop.md +4 -1
  44. package/dist/agent-src/rules/external-reference-deep-dive.md +2 -0
  45. package/dist/agent-src/rules/fast-path-marker-visibility.md +2 -0
  46. package/dist/agent-src/rules/finance-safety-floor.md +3 -1
  47. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
  48. package/dist/agent-src/rules/git-history-discipline.md +3 -1
  49. package/dist/agent-src/rules/guidelines.md +2 -0
  50. package/dist/agent-src/rules/history-discipline.md +5 -1
  51. package/dist/agent-src/rules/icon-consistency.md +2 -0
  52. package/dist/agent-src/rules/image-likeness-and-rights.md +2 -0
  53. package/dist/agent-src/rules/improve-before-implement.md +2 -0
  54. package/dist/agent-src/rules/invite-challenge.md +2 -0
  55. package/dist/agent-src/rules/laravel-routing.md +2 -0
  56. package/dist/agent-src/rules/laravel-translations.md +2 -0
  57. package/dist/agent-src/rules/legal-safety-floor.md +3 -1
  58. package/dist/agent-src/rules/lethal-trifecta-guard.md +2 -0
  59. package/dist/agent-src/rules/linked-projects-onboarding-gate.md +2 -0
  60. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +2 -0
  61. package/dist/agent-src/rules/markdown-safe-codeblocks.md +2 -0
  62. package/dist/agent-src/rules/media-governance-routing.md +2 -0
  63. package/dist/agent-src/rules/media-sync-ground-truth.md +2 -0
  64. package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
  65. package/dist/agent-src/rules/missing-tool-handling.md +2 -0
  66. package/dist/agent-src/rules/model-recommendation.md +2 -0
  67. package/dist/agent-src/rules/no-attribution-footers.md +2 -0
  68. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +2 -0
  69. package/dist/agent-src/rules/no-pr-progress-comments.md +5 -1
  70. package/dist/agent-src/rules/no-roadmap-references.md +2 -0
  71. package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
  72. package/dist/agent-src/rules/onboarding-gate.md +5 -1
  73. package/dist/agent-src/rules/output-discipline.md +2 -0
  74. package/dist/agent-src/rules/package-ci-checks.md +2 -0
  75. package/dist/agent-src/rules/persona-governance.md +2 -0
  76. package/dist/agent-src/rules/php-coding.md +2 -0
  77. package/dist/agent-src/rules/prefer-enums-over-literals.md +2 -0
  78. package/dist/agent-src/rules/preservation-guard.md +2 -0
  79. package/dist/agent-src/rules/provider-lifecycle-discipline.md +2 -0
  80. package/dist/agent-src/rules/question-not-instruction.md +2 -0
  81. package/dist/agent-src/rules/reviewer-awareness.md +2 -0
  82. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +6 -1
  83. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  84. package/dist/agent-src/rules/role-mode-adherence.md +3 -1
  85. package/dist/agent-src/rules/rule-type-governance.md +2 -0
  86. package/dist/agent-src/rules/runtime-safety.md +2 -0
  87. package/dist/agent-src/rules/scale-discipline.md +5 -1
  88. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  89. package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
  90. package/dist/agent-src/rules/self-repair-loop.md +103 -0
  91. package/dist/agent-src/rules/senior-engineering-discipline.md +2 -0
  92. package/dist/agent-src/rules/session-canary.md +62 -19
  93. package/dist/agent-src/rules/settings-ask-protocol.md +4 -0
  94. package/dist/agent-src/rules/size-enforcement.md +2 -0
  95. package/dist/agent-src/rules/skill-improvement-trigger.md +2 -0
  96. package/dist/agent-src/rules/skill-quality.md +2 -0
  97. package/dist/agent-src/rules/slash-command-routing-policy.md +2 -0
  98. package/dist/agent-src/rules/source-confidentiality.md +2 -0
  99. package/dist/agent-src/rules/source-discovery-gate.md +2 -0
  100. package/dist/agent-src/rules/source-of-truth.md +2 -0
  101. package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -0
  102. package/dist/agent-src/rules/strategy-safety-floor.md +3 -1
  103. package/dist/agent-src/rules/symfony-routing.md +2 -0
  104. package/dist/agent-src/rules/think-before-action.md +2 -0
  105. package/dist/agent-src/rules/token-budget-discipline.md +5 -1
  106. package/dist/agent-src/rules/token-efficiency.md +2 -0
  107. package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
  108. package/dist/agent-src/rules/tool-safety.md +7 -1
  109. package/dist/agent-src/rules/ui-audit-gate.md +4 -0
  110. package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
  111. package/dist/agent-src/rules/upstream-proposal.md +2 -0
  112. package/dist/agent-src/rules/user-interaction.md +2 -0
  113. package/dist/agent-src/rules/user-interrupt-priority.md +2 -0
  114. package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
  115. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  116. package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
  117. package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
  118. package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
  119. package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
  120. package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
  121. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  122. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  123. package/dist/agent-src/templates/AGENTS.md +6 -10
  124. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  125. package/dist/cli/registry.js +9 -0
  126. package/dist/cli/registry.js.map +1 -1
  127. package/dist/cli-delegate/chunk-22GCIJ2O.js +270 -0
  128. package/dist/cli-delegate/{chunk-BSLUHV2F.js → chunk-2BE5I2MN.js} +156 -141
  129. package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-7OMQCH4V.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-GB2FSUEE.js} +57 -7
  131. package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-JXARCGVN.js} +3 -3
  132. package/dist/cli-delegate/{chunk-SJQMJI7M.js → chunk-PQSAQKHW.js} +467 -305
  133. package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
  134. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  135. package/dist/cli-delegate/cmd_brand_status.js +134 -0
  136. package/dist/cli-delegate/cmd_conformance.js +7 -7
  137. package/dist/cli-delegate/cmd_converge.js +3 -3
  138. package/dist/cli-delegate/cmd_doctor.js +4 -4
  139. package/dist/cli-delegate/cmd_explain.js +80 -330
  140. package/dist/cli-delegate/cmd_export.js +5 -5
  141. package/dist/cli-delegate/cmd_fleet.js +3 -3
  142. package/dist/cli-delegate/cmd_handoff.js +2 -2
  143. package/dist/cli-delegate/cmd_mcp_available.js +433 -0
  144. package/dist/cli-delegate/cmd_migrate.js +3 -3
  145. package/dist/cli-delegate/cmd_packs_active.js +153 -0
  146. package/dist/cli-delegate/cmd_preflight.js +1 -1
  147. package/dist/cli-delegate/cmd_prune.js +5 -5
  148. package/dist/cli-delegate/cmd_route_audit.js +8 -8
  149. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  150. package/dist/cli-delegate/cmd_settings_get.js +272 -0
  151. package/dist/cli-delegate/cmd_settings_migrate.js +5 -5
  152. package/dist/cli-delegate/cmd_settings_set.js +7 -2967
  153. package/dist/cli-delegate/cmd_sync.js +5 -5
  154. package/dist/cli-delegate/cmd_uninstall.js +5 -5
  155. package/dist/cli-delegate/cmd_update.js +4 -4
  156. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  157. package/dist/cli-delegate/cmd_validate.js +5 -5
  158. package/dist/cli-delegate/cmd_versions.js +4 -4
  159. package/dist/discovery/deprecation-report.md +1 -1
  160. package/dist/discovery/discovery-manifest.json +184 -124
  161. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  162. package/dist/discovery/discovery-manifest.summary.md +3 -3
  163. package/dist/discovery/orphan-report.md +1 -1
  164. package/dist/discovery/packs.json +7 -5
  165. package/dist/discovery/trust-report.md +11 -11
  166. package/dist/discovery/workspaces.json +6 -4
  167. package/dist/hooks/dispatch.js +9080 -5701
  168. package/dist/install/install.mjs +698 -508
  169. package/dist/mcp/registry-manifest.json +3 -3
  170. package/dist/mcp/server.json +2 -2
  171. package/dist/mcp/server.mjs +698 -437
  172. package/dist/router.json +1 -1
  173. package/dist/scripts/_lib/agent_settings.js +80 -15
  174. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  175. package/dist/scripts/condense.js +33 -2
  176. package/dist/scripts/condense.js.map +1 -1
  177. package/dist/server/io/yamlIO.js +85 -0
  178. package/dist/server/io/yamlIO.js.map +1 -1
  179. package/dist/server/routes/settings.js +1 -12
  180. package/dist/server/routes/settings.js.map +1 -1
  181. package/dist/server/routes/wizard.js +52 -2
  182. package/dist/server/routes/wizard.js.map +1 -1
  183. package/dist/shared/settingsCarveOut.js +118 -0
  184. package/dist/shared/settingsCarveOut.js.map +1 -0
  185. package/dist/shared/settingsSurface.js +7 -1
  186. package/dist/shared/settingsSurface.js.map +1 -1
  187. package/dist/ui/assets/index-DmR0mbk4.js.map +1 -1
  188. package/docs/contracts/settings-classes.md +29 -8
  189. package/docs/guidelines/agent-infra/memory-access.md +14 -3
  190. package/package.json +1 -1
  191. package/src/agent-src/templates/AGENTS.md +6 -10
  192. package/src/config/budgets.yml +29 -0
  193. package/src/config/evaluator-budgets.json +4 -4
  194. package/src/config/gate-coverage.yml +26 -0
  195. package/src/scripts/_cli/cmd_brand_status.ts +174 -0
  196. package/src/scripts/_cli/cmd_doctor.ts +6 -21
  197. package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
  198. package/src/scripts/_cli/cmd_packs_active.ts +225 -0
  199. package/src/scripts/_cli/cmd_settings_get.ts +324 -0
  200. package/src/scripts/_dispatch.bash +75 -0
  201. package/src/scripts/_lib/agent_settings.ts +82 -16
  202. package/src/scripts/_lib/carrier_divergence.ts +135 -0
  203. package/src/scripts/_lib/git_common_dir.ts +128 -0
  204. package/src/scripts/_lib/host_capability.ts +84 -1
  205. package/src/scripts/_lib/obligation_frequency.ts +391 -0
  206. package/src/scripts/_lib/prompt_shape.ts +88 -0
  207. package/src/scripts/_lib/review_skipped_record.ts +102 -0
  208. package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
  209. package/src/scripts/_lib/self_repair.ts +342 -0
  210. package/src/scripts/_lib/self_repair_store.ts +100 -0
  211. package/src/scripts/_lib/session_register.ts +361 -0
  212. package/src/scripts/_lib/transcript_entry.ts +80 -0
  213. package/src/scripts/check_branch_freshness.ts +255 -0
  214. package/src/scripts/check_council_config_location.ts +125 -1
  215. package/src/scripts/check_enforcement_coverage.ts +192 -7
  216. package/src/scripts/check_rule_projection_integrity.ts +364 -0
  217. package/src/scripts/check_standing_rule_delivery.ts +314 -0
  218. package/src/scripts/condense.ts +36 -2
  219. package/src/scripts/config/profiles.ts +13 -3
  220. package/src/scripts/conformance_scan.ts +307 -24
  221. package/src/scripts/council_availability_hook.ts +188 -0
  222. package/src/scripts/council_cli.ts +126 -7
  223. package/src/scripts/generate_settings_reference.ts +232 -0
  224. package/src/scripts/hook_manifest.yaml +155 -33
  225. package/src/scripts/hooks/block_no_verify.ts +95 -3
  226. package/src/scripts/hooks/block_unauthorized_git.ts +217 -2
  227. package/src/scripts/hooks/concern_registry.ts +12 -0
  228. package/src/scripts/hooks/delegation_nudge_hook.ts +450 -0
  229. package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
  230. package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
  231. package/src/scripts/install.ts +151 -0
  232. package/src/scripts/language_mirror_hook.ts +49 -6
  233. package/src/scripts/measure_scope_dedup.ts +19 -13
  234. package/src/scripts/onboarding_gate_hook.ts +40 -1
  235. package/src/scripts/orchestration_backfill.ts +354 -0
  236. package/src/scripts/orchestration_record.ts +4 -1
  237. package/src/scripts/preamble_byte_census.ts +9 -20
  238. package/src/scripts/release.ts +142 -36
  239. package/src/scripts/report_carrier_divergence.ts +364 -0
  240. package/src/scripts/report_skill_activation.ts +257 -0
  241. package/src/scripts/report_skill_obligation_violations.ts +579 -0
  242. package/src/scripts/routing_doctor.ts +77 -6
  243. package/src/scripts/schemas/rule.schema.json +5 -0
  244. package/src/scripts/self_repair_cli.ts +240 -0
  245. package/src/scripts/self_repair_hook.ts +241 -0
  246. package/src/scripts/session_canary_hook.ts +35 -3
  247. package/src/scripts/session_register_hook.ts +288 -0
  248. package/src/scripts/sessions_cli.ts +140 -0
  249. package/src/scripts/validate_frontmatter.ts +68 -0
  250. package/src/server/io/yamlIO.ts +87 -0
  251. package/src/shared/settingsCarveOut.ts +143 -0
  252. package/src/shared/settingsSurface.ts +7 -1
@@ -700,13 +700,84 @@ export function _failed_checks_report(names: readonly string[]): string {
700
700
  return lines.join('\n');
701
701
  }
702
702
 
703
+ /** Rounds of re-watching before an absent required check is called absent. */
704
+ const _NO_CHECKS_ROUNDS = 10;
705
+ /** Delay between those rounds — 10 × 60 s ≈ 10 minutes of tolerance. */
706
+ const _NO_CHECKS_DELAY_MS = 60_000;
707
+
703
708
  /**
704
- * Watch PR checks and tolerate the 'no checks' case.
709
+ * Required status-check contexts for a branch, parsed from
710
+ * `gh api repos/{owner}/{repo}/rules/branches/{branch}`.
711
+ *
712
+ * That endpoint is the one that answers the question here: this repo protects
713
+ * `main` with a RULESET, and the classic `/branches/main/protection` API
714
+ * answers "Branch not protected" for it — so a check built on the classic
715
+ * endpoint would conclude "no required checks" and wave everything through.
716
+ *
717
+ * Any parse or shape surprise yields [] — the caller then behaves exactly as
718
+ * it did before this function existed.
719
+ */
720
+ export function _required_contexts_from_rules(json: string): string[] {
721
+ let parsed: unknown;
722
+ try {
723
+ parsed = JSON.parse(json);
724
+ } catch {
725
+ return [];
726
+ }
727
+ if (!Array.isArray(parsed)) return [];
728
+ const out: string[] = [];
729
+ for (const rule of parsed) {
730
+ const r = rule as { type?: unknown; parameters?: { required_status_checks?: unknown } };
731
+ if (r.type !== 'required_status_checks') continue;
732
+ const checks = r.parameters?.required_status_checks;
733
+ if (!Array.isArray(checks)) continue;
734
+ for (const c of checks) {
735
+ const ctx = (c as { context?: unknown }).context;
736
+ if (typeof ctx === 'string' && ctx !== '') out.push(ctx);
737
+ }
738
+ }
739
+ return out;
740
+ }
741
+
742
+ export type NoChecksAction = 'retry' | 'accept' | 'die';
743
+
744
+ /**
745
+ * What to do when `gh pr checks` reports no checks at all.
746
+ *
747
+ * The old code had one answer — accept — for two situations it could not tell
748
+ * apart: a repo that configures no required checks (nothing will ever arrive,
749
+ * accepting is correct) and a repo whose checks have simply not been
750
+ * dispatched yet (accepting merges, tags and publishes unvalidated).
751
+ *
752
+ * The branch's own protection rules separate them. Required contexts declared
753
+ * ⇒ something IS expected, so absence means not-yet, and the answer is to wait
754
+ * and finally to refuse. None declared ⇒ the pre-existing tolerance stands.
755
+ */
756
+ export function _no_checks_action(
757
+ required: readonly string[],
758
+ round: number,
759
+ maxRounds: number = _NO_CHECKS_ROUNDS,
760
+ ): NoChecksAction {
761
+ if (required.length === 0) return 'accept';
762
+ return round < maxRounds ? 'retry' : 'die';
763
+ }
764
+
765
+ /**
766
+ * Watch PR checks and tolerate the 'no checks' case ONLY where it is tolerable.
705
767
  *
706
768
  * `gh pr checks --watch` exits 1 both on real failures and when no checks are
707
- * reported at all (no workflow triggered, no required checks configured in
708
- * branch protection). The latter must not block the release — we warn and
709
- * continue. Real failures still die.
769
+ * reported at all. Real failures still die.
770
+ *
771
+ * Measured 2026-08-06/07: during a critical GitHub Actions incident
772
+ * (15:22–02:04 UTC, webhook triggers throttled from 20:34) `pull_request`
773
+ * events were delivered 15–30 minutes late. The five-second grace period below
774
+ * expires long before that, so the old unconditional accept would have tagged
775
+ * `main` and published to npm without a single check having run. It did not
776
+ * only because `main` happened to be independently green.
777
+ *
778
+ * So the tolerance is now conditional on the branch declaring no required
779
+ * checks. Where it declares some, absence is treated as latency: re-watch, and
780
+ * refuse rather than release blind.
710
781
  *
711
782
  * A short grace period gives GitHub time to register workflow runs on a
712
783
  * freshly-pushed branch.
@@ -721,41 +792,76 @@ function watch_pr_checks(branch: string): void {
721
792
  // busy-wait stand-in for time.sleep(5) without an event-loop yield.
722
793
  }
723
794
  }
724
- // The branch is passed explicitly — `gh` inferring the PR from HEAD
725
- // resolved to `main` mid-run once (2026-08-03, 9.16.0 resume) and the
726
- // whole release died on "no pull requests found for branch main".
727
- const proc = run(['gh', 'pr', 'checks', branch, '--watch'], {
728
- check: false,
729
- capture: true,
730
- });
731
- const output = ((proc.stdout || '') + (proc.stderr || '')).trim();
732
- const returncode = proc.returncode;
733
- if (returncode === 0) {
795
+ // Rounds exist only for the no-checks-yet case below; every other outcome
796
+ // returns or dies on the first pass, exactly as before.
797
+ for (let round = 0; ; round++) {
798
+ // The branch is passed explicitly — `gh` inferring the PR from HEAD
799
+ // resolved to `main` mid-run once (2026-08-03, 9.16.0 resume) and the
800
+ // whole release died on "no pull requests found for branch main".
801
+ const proc = run(['gh', 'pr', 'checks', branch, '--watch'], {
802
+ check: false,
803
+ capture: true,
804
+ });
805
+ const output = ((proc.stdout || '') + (proc.stderr || '')).trim();
806
+ const returncode = proc.returncode;
807
+ if (returncode === 0) {
808
+ if (output) {
809
+ process.stdout.write(output + '\n');
810
+ }
811
+ return;
812
+ }
813
+ if (output.toLowerCase().includes('no checks reported')) {
814
+ const rules = run(
815
+ ['gh', 'api', `repos/{owner}/{repo}/rules/branches/${MAIN_BRANCH}`],
816
+ { check: false, capture: true },
817
+ );
818
+ const required = _required_contexts_from_rules(rules.stdout ?? '');
819
+ const action = _no_checks_action(required, round, _NO_CHECKS_ROUNDS);
820
+
821
+ if (action === 'accept') {
822
+ process.stdout.write(`⚠️ ${output}\n`);
823
+ process.stdout.write(
824
+ ' Continuing without check validation — configure required ' +
825
+ 'checks in branch protection to enforce this gate.\n',
826
+ );
827
+ return;
828
+ }
829
+ if (action === 'retry') {
830
+ process.stdout.write(
831
+ `⏳ no checks reported yet, but ${MAIN_BRANCH} requires ` +
832
+ `${String(required.length)} (${required.join(', ')}) — ` +
833
+ `waiting (${String(round + 1)}/${String(_NO_CHECKS_ROUNDS)})\n`,
834
+ );
835
+ if (_exec_override === null) {
836
+ const until = Date.now() + _NO_CHECKS_DELAY_MS;
837
+ while (Date.now() < until) {
838
+ // blocking wait, same shape as the grace period above
839
+ }
840
+ }
841
+ continue;
842
+ }
843
+ die(
844
+ `no checks reported for ${branch} after ` +
845
+ `${String(_NO_CHECKS_ROUNDS)} rounds, but ${MAIN_BRANCH} requires ` +
846
+ `${required.join(', ')}. Refusing to merge, tag and publish ` +
847
+ 'unvalidated — GitHub may be delaying event delivery (check ' +
848
+ 'githubstatus.com). Re-run with `task release -- --resume --yes` ' +
849
+ 'once the checks appear.',
850
+ );
851
+ }
734
852
  if (output) {
735
- process.stdout.write(output + '\n');
853
+ process.stderr.write(output + '\n');
736
854
  }
737
- return;
738
- }
739
- if (output.toLowerCase().includes('no checks reported')) {
740
- process.stdout.write(`⚠️ ${output}\n`);
741
- process.stdout.write(
742
- ' Continuing without check validation — configure required ' +
743
- 'checks in branch protection to enforce this gate.\n',
744
- );
745
- return;
746
- }
747
- if (output) {
748
- process.stderr.write(output + '\n');
749
- }
750
- const summary = run(['gh', 'pr', 'checks', branch, '--json', 'name,bucket'], {
751
- check: false,
752
- capture: true,
753
- });
754
- const report = _failed_checks_report(_failed_check_names(summary.stdout ?? ''));
755
- if (report) {
756
- process.stderr.write(report + '\n');
855
+ const summary = run(['gh', 'pr', 'checks', branch, '--json', 'name,bucket'], {
856
+ check: false,
857
+ capture: true,
858
+ });
859
+ const report = _failed_checks_report(_failed_check_names(summary.stdout ?? ''));
860
+ if (report) {
861
+ process.stderr.write(report + '\n');
862
+ }
863
+ die(`PR checks failed (exit ${returncode})`);
757
864
  }
758
- die(`PR checks failed (exit ${returncode})`);
759
865
  }
760
866
 
761
867
  /**
@@ -0,0 +1,364 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * report_carrier_divergence.ts — which rules the two carriers deliver
4
+ * differently, BY NAME.
5
+ *
6
+ * Executes round-5 Phase 1.3, landed under round-6 Phase 4.1. Advisory: **it
7
+ * gates on nothing** and must not acquire a threshold, for the reason
8
+ * `report_skill_activation` and `report_imperative_density` must not — plus one
9
+ * specific to this surface, below.
10
+ *
11
+ * WHY THIS EXISTS
12
+ * ---------------
13
+ * The suite installs machine-globally (`~/.claude/rules/`) and the package's own
14
+ * checkout also projects per-project (`dist/agent-src/rules/`, which every
15
+ * project-scope tree symlinks). A machine that has both loads the shared rules
16
+ * twice, and nothing said whether the two copies agree. Round 5 measured the
17
+ * condition once and named the dangerous case: its global copy of
18
+ * `git-history-discipline` asserted an unqualified "deterministically blocked by
19
+ * the `block-no-verify` PreToolUse guard" while the shipped copy carried the
20
+ * host-scoped retraction of exactly that claim. The agent held both, with no
21
+ * precedence marker.
22
+ *
23
+ * A one-off measurement cannot own that condition, because the condition is
24
+ * **transient and maintainer-dependent**: it appears when the checkout moves
25
+ * ahead of the installed release and disappears the next time the maintainer
26
+ * reinstalls. Verified both directions — see § THE 2026-08-08 RE-MEASUREMENT.
27
+ * So the deliverable is a report anyone can re-run, not a number in a roadmap.
28
+ *
29
+ * WHY IT MUST NOT BECOME A GATE
30
+ * -----------------------------
31
+ * A maintainer developing the package is *legitimately* ahead of their own
32
+ * global install — `condense.ts` says so where it explains why the byte-keyed
33
+ * dedup is inert in that state. A gate here would fail on the normal
34
+ * development condition, and a gate that fires on sanctioned behaviour gets
35
+ * ignored, which is the failure `check-rule-invariants` already paid for once.
36
+ * The point is that the condition becomes VISIBLE, not that it becomes illegal.
37
+ *
38
+ * THE THREE CLASSES, AND WHICH ONE A READER MUST ACT ON
39
+ * ----------------------------------------------------
40
+ * body-diff the two carriers deliver different TEXT. This is the class
41
+ * round 5's contradiction lived in, and the only one that
42
+ * needs a decision. Precedence when it appears: the project
43
+ * projection is generated from `src/` at the current commit,
44
+ * so it is the newer copy and it wins; the global install is
45
+ * a release snapshot. Printed with the report so the
46
+ * precedence is read at the moment it matters rather than
47
+ * looked up.
48
+ * provenance-only identical after removing the two installer ownership keys.
49
+ * Structural, expected, and decided against closing —
50
+ * `agents/settings/contexts/dedup-reachability-refusal.md`.
51
+ * Reported as a count with that pointer, never as a finding,
52
+ * or the real class drowns in it.
53
+ * one-carrier-only a rule present at exactly one scope. Not a contradiction:
54
+ * project-only is a rule newer than the installed release,
55
+ * global-only is a rule the release still carries. Named
56
+ * because asymmetric reach is what makes a project-scope
57
+ * measurement non-transferable to another machine.
58
+ *
59
+ * NOT IN SCOPE. Skills, commands and contexts have the same two-carrier shape
60
+ * and are not compared here; this executes the rule-scoped step that was
61
+ * specified. Nor does it compare `~/.claude/CLAUDE.md` against the project's
62
+ * `CLAUDE.md` — a different mechanism with a different precedence story.
63
+ *
64
+ * Exit: 0 always, except a usage error (1). Deliberate — see above.
65
+ */
66
+ import * as fs from 'node:fs';
67
+ import * as os from 'node:os';
68
+ import * as path from 'node:path';
69
+ import process from 'node:process';
70
+ import { fileURLToPath, pathToFileURL } from 'node:url';
71
+
72
+ import { comparePair, type PairVerdict } from './_lib/carrier_divergence.js';
73
+
74
+ const _HERE = fileURLToPath(import.meta.url);
75
+ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
76
+
77
+ /**
78
+ * The project carrier — the tree the host actually reads at project scope, NOT
79
+ * `dist/agent-src/rules/`.
80
+ *
81
+ * The distinction is load-bearing and was found by measuring: the global
82
+ * installer delivers the five ADR-004 `type: manual` rules while the project
83
+ * projection deliberately omits them, so anchoring on `dist` (which holds all
84
+ * 115) reports those five as *shared* when in fact only one carrier delivers
85
+ * them. Two writers, different filters — exactly the asymmetry this report
86
+ * exists to name. `dist` is still read, as the reference for what COULD be
87
+ * projected, which is what makes the manual class attributable rather than a
88
+ * bare absence.
89
+ */
90
+ export const PROJECT_RULES = path.join('.claude', 'rules');
91
+ /** What the project projection is generated from — the reference set. */
92
+ export const PROJECTION_SOURCE = path.join('dist', 'agent-src', 'rules');
93
+ /** The machine-global carrier, relative to the user's home. */
94
+ export const GLOBAL_RULES = path.join('.claude', 'rules');
95
+
96
+ export interface CarrierDivergence {
97
+ projectDir: string;
98
+ globalDir: string;
99
+ /** `false` when the global carrier is absent — every count is then 0. */
100
+ globalPresent: boolean;
101
+ /** `false` when the project tree has not been generated in this checkout. */
102
+ projectPresent: boolean;
103
+ shared: number;
104
+ identical: string[];
105
+ provenanceOnly: string[];
106
+ bodyDiff: string[];
107
+ projectOnly: string[];
108
+ globalOnly: string[];
109
+ /**
110
+ * Shared names whose copies could not both be read (a dangling symlink from a
111
+ * half-finished install). Counted rather than dropped: a pair that silently
112
+ * leaves `shared` makes every other number in the report unaccounted for.
113
+ */
114
+ unreadable: string[];
115
+ /**
116
+ * Rules the projection source carries, the global carrier delivers, and the
117
+ * project projection omits by design (`type: manual`). A subset of
118
+ * `globalOnly`, separated so the expected half does not read as drift.
119
+ */
120
+ manualOnlyGlobal: string[];
121
+ }
122
+
123
+ function _listRules(dir: string): string[] {
124
+ try {
125
+ return fs
126
+ .readdirSync(dir)
127
+ .filter((f) => f.endsWith('.md'))
128
+ .sort();
129
+ } catch {
130
+ return [];
131
+ }
132
+ }
133
+
134
+ /** `type:` from a rule's frontmatter, or `''` when unreadable / absent. */
135
+ function _ruleType(file: string): string {
136
+ let text: string;
137
+ try {
138
+ text = fs.readFileSync(file, 'utf-8');
139
+ } catch {
140
+ return '';
141
+ }
142
+ if (!text.startsWith('---')) return '';
143
+ const end = text.indexOf('\n---', 3);
144
+ const fm = end === -1 ? text : text.slice(0, end);
145
+ const m = /^type:\s*"?([a-z]+)"?/m.exec(fm);
146
+ return m === null ? '' : (m[1] as string);
147
+ }
148
+
149
+ /**
150
+ * Compare the two carriers over the UNION of their rule names.
151
+ *
152
+ * The union is the point: anchoring on either side alone hides one direction of
153
+ * asymmetric reach, and it was the hidden direction — rules the global install
154
+ * carries and the project does not — that made round 5's cross-project
155
+ * comparison hard to interpret.
156
+ *
157
+ * `sourceDir` is the projection source, read only to attribute a global-only
158
+ * rule to the ADR-004 manual filter rather than to drift. Absent → the manual
159
+ * split is simply empty, never a guess.
160
+ */
161
+ export function compareCarriers(
162
+ projectDir: string,
163
+ globalDir: string,
164
+ sourceDir?: string,
165
+ ): CarrierDivergence {
166
+ const out: CarrierDivergence = {
167
+ projectDir,
168
+ globalDir,
169
+ globalPresent: fs.existsSync(globalDir),
170
+ projectPresent: fs.existsSync(projectDir),
171
+ shared: 0,
172
+ identical: [],
173
+ provenanceOnly: [],
174
+ bodyDiff: [],
175
+ projectOnly: [],
176
+ globalOnly: [],
177
+ unreadable: [],
178
+ manualOnlyGlobal: [],
179
+ };
180
+ const project = new Set(_listRules(projectDir));
181
+ const global = new Set(_listRules(globalDir));
182
+
183
+ for (const rule of [...new Set([...project, ...global])].sort()) {
184
+ const inP = project.has(rule);
185
+ const inG = global.has(rule);
186
+ if (inP && !inG) {
187
+ out.projectOnly.push(rule);
188
+ continue;
189
+ }
190
+ if (!inP && inG) {
191
+ out.globalOnly.push(rule);
192
+ if (sourceDir !== undefined && _ruleType(path.join(sourceDir, rule)) === 'manual') {
193
+ out.manualOnlyGlobal.push(rule);
194
+ }
195
+ continue;
196
+ }
197
+ out.shared += 1;
198
+ let verdict: PairVerdict;
199
+ try {
200
+ verdict = comparePair(
201
+ fs.readFileSync(path.join(projectDir, rule)),
202
+ fs.readFileSync(path.join(globalDir, rule)),
203
+ );
204
+ } catch {
205
+ // An unreadable copy is not a body difference and must not be
206
+ // reported as one — a permission error would otherwise manufacture
207
+ // the only class this report asks a reader to act on. It is still
208
+ // NAMED: a pair that just disappears from `shared` leaves the ledger
209
+ // short with nothing saying why.
210
+ out.shared -= 1;
211
+ out.unreadable.push(rule);
212
+ continue;
213
+ }
214
+ if (verdict === 'identical') out.identical.push(rule);
215
+ else if (verdict === 'provenance-only') out.provenanceOnly.push(rule);
216
+ else out.bodyDiff.push(rule);
217
+ }
218
+ return out;
219
+ }
220
+
221
+ export function render(d: CarrierDivergence): string {
222
+ const lines: string[] = [];
223
+ lines.push('cross-carrier rule divergence — advisory, gates on nothing');
224
+ lines.push(` project carrier: ${d.projectDir}`);
225
+ lines.push(` global carrier: ${d.globalDir}`);
226
+ lines.push('');
227
+
228
+ if (!d.globalPresent) {
229
+ lines.push(' The global carrier is absent — this machine loads the project projection');
230
+ lines.push(' only, so there is no second copy to diverge from. Nothing to report, and');
231
+ lines.push(' that is a real answer rather than a skipped check.');
232
+ return lines.join('\n');
233
+ }
234
+ if (!d.projectPresent) {
235
+ lines.push(' The project rule tree does not exist in this checkout — it is generated and');
236
+ lines.push(' gitignored, so a fresh clone has none, and `agents/.agent-tools.yml` may');
237
+ lines.push(' select zero tools deliberately. Run `task generate-tools` and re-run this');
238
+ lines.push(' report. Reported rather than silently substituted with the projection');
239
+ lines.push(' source: dist/ holds rules the project tree omits by design, so reading it');
240
+ lines.push(' as the project carrier would answer a different question than the one asked.');
241
+ return lines.join('\n');
242
+ }
243
+
244
+ lines.push(` shared rule names ${d.shared}`);
245
+ lines.push(` byte-identical ${d.identical.length}`);
246
+ lines.push(` differ ONLY in the install stamp ${d.provenanceOnly.length}`);
247
+ lines.push(` differ in BODY ${d.bodyDiff.length}`);
248
+ if (d.unreadable.length > 0) {
249
+ lines.push(` unreadable on one side ${d.unreadable.length} (${d.unreadable.join(', ')})`);
250
+ lines.push(' Not counted as shared and NOT a body difference — a dangling entry is');
251
+ lines.push(' a broken install, not a disagreement about content.');
252
+ }
253
+ lines.push(` present at project scope only ${d.projectOnly.length}`);
254
+ lines.push(` present at global scope only ${d.globalOnly.length}`);
255
+ lines.push('');
256
+
257
+ if (d.bodyDiff.length > 0) {
258
+ lines.push(' BODY DIVERGENCE — the two carriers deliver different text for these rules.');
259
+ lines.push(' Both copies reach the model, with no precedence marker between them, so a');
260
+ lines.push(' claim one copy retracts can be re-asserted by the other. PRECEDENCE: the');
261
+ lines.push(' project projection is generated from src/ at this commit and wins; the');
262
+ lines.push(' global install is a release snapshot. Close it by reinstalling the global');
263
+ lines.push(' copy, or by reading the project copy as authoritative.');
264
+ for (const r of d.bodyDiff) lines.push(` - ${r}`);
265
+ lines.push('');
266
+ }
267
+
268
+ if (d.provenanceOnly.length > 0) {
269
+ lines.push(` ${String(d.provenanceOnly.length)} pair(s) differ only by the installer's package:/source_path: stamp.`);
270
+ lines.push(' Not a content difference and NOT a defect: install.ts stamps every installed');
271
+ lines.push(' rule, the in-repo projection stamps nothing. Closing it was decided against');
272
+ lines.push(' on 2026-07-31 — agents/settings/contexts/dedup-reachability-refusal.md holds');
273
+ lines.push(' the analysis and the five reopen conditions. Reported as a count on purpose:');
274
+ lines.push(' naming every expected pair would bury the class above.');
275
+ lines.push('');
276
+ }
277
+
278
+ if (d.projectOnly.length > 0) {
279
+ lines.push(` project-scope only (${String(d.projectOnly.length)}) — newer than the installed release:`);
280
+ lines.push(` ${d.projectOnly.join(', ')}`);
281
+ }
282
+ const driftGlobalOnly = d.globalOnly.filter((r) => !d.manualOnlyGlobal.includes(r));
283
+ if (driftGlobalOnly.length > 0) {
284
+ lines.push(` global-scope only (${String(driftGlobalOnly.length)}) — the release carries these, this checkout does not project them:`);
285
+ lines.push(` ${driftGlobalOnly.join(', ')}`);
286
+ }
287
+ if (d.manualOnlyGlobal.length > 0) {
288
+ lines.push(
289
+ ` delivered by the global carrier ONLY, by design (${String(d.manualOnlyGlobal.length)}, ADR-004 \`type: manual\`):`,
290
+ );
291
+ lines.push(` ${d.manualOnlyGlobal.join(', ')}`);
292
+ lines.push(' The project projection filters these out and the global installer does not.');
293
+ lines.push(' Two writers, different filters — so on a machine with both carriers these');
294
+ lines.push(' rules reach the model, and on a project-only machine they do not. Expected,');
295
+ lines.push(' and worth knowing before citing any project-scope reach figure.');
296
+ }
297
+ if (d.projectOnly.length > 0 || d.globalOnly.length > 0) {
298
+ lines.push('');
299
+ lines.push(' Asymmetric reach is why a project-scope conformance figure does not transfer');
300
+ lines.push(' to another machine: the rule set the model was told differs by carrier.');
301
+ }
302
+
303
+ if (d.bodyDiff.length === 0) {
304
+ lines.push('');
305
+ lines.push(' No body divergence. The condition is transient — it returns whenever the');
306
+ lines.push(' checkout moves ahead of the installed release — so this is a reading of');
307
+ lines.push(' right now, not a property of the repo. Re-run it, do not cite it.');
308
+ }
309
+ return lines.join('\n');
310
+ }
311
+
312
+ export function main(argv?: readonly string[]): number {
313
+ const args = argv ?? process.argv.slice(2);
314
+ let projectDir = path.join(REPO_ROOT, PROJECT_RULES);
315
+ let globalDir = path.join(os.homedir(), GLOBAL_RULES);
316
+ let sourceDir = path.join(REPO_ROOT, PROJECTION_SOURCE);
317
+ // A value must not itself be a flag. `--project --global /x` otherwise
318
+ // resolves a directory literally named `--global` and silently drops `/x`,
319
+ // reporting "tree does not exist" instead of a usage error — the flag-VALUE
320
+ // half of the hole this file's own test closes on the flag-NAME half.
321
+ const value = (i: number): string | null => {
322
+ const v = args[i + 1];
323
+ return v === undefined || v.startsWith('-') ? null : v;
324
+ };
325
+ for (let i = 0; i < args.length; i += 1) {
326
+ const a = args[i];
327
+ if (a === '--project' || a === '--global' || a === '--source') {
328
+ const v = value(i);
329
+ if (v === null) {
330
+ process.stderr.write(`report_carrier_divergence: ${a} needs a directory\n`);
331
+ return 1;
332
+ }
333
+ const resolved = path.resolve(v);
334
+ if (a === '--project') projectDir = resolved;
335
+ else if (a === '--global') globalDir = resolved;
336
+ else sourceDir = resolved;
337
+ i += 1;
338
+ } else if (a === '--help' || a === '-h') {
339
+ process.stdout.write(
340
+ 'usage: report_carrier_divergence [--project DIR] [--global DIR] [--source DIR]\n',
341
+ );
342
+ return 0;
343
+ } else if (a !== undefined && a.startsWith('--') && a !== '--quiet') {
344
+ process.stderr.write(`report_carrier_divergence: unknown flag ${a}\n`);
345
+ return 1;
346
+ }
347
+ }
348
+ process.stdout.write(`${render(compareCarriers(projectDir, globalDir, sourceDir))}\n`);
349
+ return 0;
350
+ }
351
+
352
+ function _isCliEntry(): boolean {
353
+ if (process.argv[1] === undefined) return false;
354
+ if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) return true;
355
+ try {
356
+ return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
357
+ } catch {
358
+ return false;
359
+ }
360
+ }
361
+
362
+ if (_isCliEntry()) {
363
+ process.exit(main());
364
+ }