@event4u/agent-config 14.6.0 → 14.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +255 -145
  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/evals/git-pr-merge.json +13 -0
  7. package/dist/agent-src/commands/evals/roadmap.json +17 -1
  8. package/dist/agent-src/commands/pr/create.md +14 -2
  9. package/dist/agent-src/commands/pr/merge.md +379 -0
  10. package/dist/agent-src/commands/roadmap/process-full.md +272 -32
  11. package/dist/agent-src/commands/roadmap.md +1 -1
  12. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
  13. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
  14. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +95 -2
  15. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +289 -106
  16. package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
  17. package/dist/agent-src/rules/context-hygiene.md +2 -0
  18. package/dist/agent-src/rules/decision-revisit-gate.md +67 -32
  19. package/dist/agent-src/rules/fix-what-you-see.md +108 -0
  20. package/dist/agent-src/rules/no-roadmap-references.md +0 -5
  21. package/dist/agent-src/rules/recurring-criticism.md +110 -0
  22. package/dist/agent-src/rules/roadmap-progress-sync.md +20 -1
  23. package/dist/agent-src/rules/rule-type-governance.md +0 -2
  24. package/dist/agent-src/rules/self-repair-loop.md +17 -0
  25. package/dist/agent-src/rules/skill-quality.md +0 -2
  26. package/dist/agent-src/rules/source-confidentiality.md +37 -7
  27. package/dist/agent-src/scripts/archival_sweep.ts +117 -0
  28. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +26 -7
  29. package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
  30. package/dist/agent-src/scripts/gate_budget.ts +282 -0
  31. package/dist/agent-src/scripts/gate_execute.ts +72 -31
  32. package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
  33. package/dist/agent-src/scripts/update_roadmap_progress.ts +296 -53
  34. package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
  35. package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
  36. package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
  37. package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
  38. package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
  39. package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
  40. package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
  41. package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
  42. package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
  43. package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
  44. package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
  45. package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
  46. package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
  47. package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
  48. package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
  49. package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
  50. package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
  51. package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
  52. package/dist/agent-src/skills/security/SKILL.md +8 -0
  53. package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
  54. package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
  55. package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
  56. package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
  57. package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
  58. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  59. package/dist/agent-src/templates/roadmaps.md +65 -4
  60. package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  61. package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  62. package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  63. package/dist/cli/registry.js +2 -1
  64. package/dist/cli/registry.js.map +1 -1
  65. package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
  66. package/dist/cli-delegate/{chunk-P6LQDIWK.js → chunk-7PRE6ZV2.js} +338 -0
  67. package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
  68. package/dist/cli-delegate/chunk-E4XIZDIB.js +402 -0
  69. package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-EEJUMCDZ.js} +16 -16
  70. package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
  71. package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-H4VIVD4C.js} +385 -284
  72. package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
  73. package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-LPPWO5CE.js} +12 -8
  74. package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
  75. package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-N7CDDGI3.js} +4 -23
  76. package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-PMEKGDED.js} +74 -167
  77. package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
  78. package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
  79. package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
  80. package/dist/cli-delegate/cmd_conformance.js +466 -716
  81. package/dist/cli-delegate/cmd_converge.js +3 -3
  82. package/dist/cli-delegate/cmd_doctor.js +10 -10
  83. package/dist/cli-delegate/cmd_explain.js +1 -1
  84. package/dist/cli-delegate/cmd_export.js +10 -9
  85. package/dist/cli-delegate/cmd_handoff.js +10 -9
  86. package/dist/cli-delegate/cmd_memory_get.js +2 -1
  87. package/dist/cli-delegate/cmd_migrate.js +2 -2
  88. package/dist/cli-delegate/cmd_packs_active.js +1 -1
  89. package/dist/cli-delegate/cmd_preflight.js +5 -5
  90. package/dist/cli-delegate/cmd_prune.js +11 -10
  91. package/dist/cli-delegate/cmd_route_audit.js +6 -5
  92. package/dist/cli-delegate/cmd_route_explain.js +3 -3
  93. package/dist/cli-delegate/cmd_session_recycle.js +840 -30
  94. package/dist/cli-delegate/cmd_settings_get.js +3 -3
  95. package/dist/cli-delegate/cmd_settings_migrate.js +10 -9
  96. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  97. package/dist/cli-delegate/cmd_sync.js +11 -10
  98. package/dist/cli-delegate/cmd_uninstall.js +13 -11
  99. package/dist/cli-delegate/cmd_update.js +4 -4
  100. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  101. package/dist/cli-delegate/cmd_validate.js +11 -10
  102. package/dist/cli-delegate/cmd_versions.js +3 -3
  103. package/dist/discovery/deprecation-report.md +1 -1
  104. package/dist/discovery/discovery-manifest.json +111 -37
  105. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  106. package/dist/discovery/discovery-manifest.summary.md +5 -5
  107. package/dist/discovery/dormancy-report.md +1 -1
  108. package/dist/discovery/orphan-report.md +1 -1
  109. package/dist/discovery/packs.json +15 -12
  110. package/dist/discovery/trust-report.md +3 -3
  111. package/dist/discovery/workspaces.json +11 -8
  112. package/dist/hooks/dispatch.js +3165 -2192
  113. package/dist/install/claudePathsPlan.js +187 -0
  114. package/dist/install/claudePathsPlan.js.map +1 -0
  115. package/dist/install/globalRuleLayers.js +169 -0
  116. package/dist/install/globalRuleLayers.js.map +1 -0
  117. package/dist/install/hostLayerFingerprint.js +136 -0
  118. package/dist/install/hostLayerFingerprint.js.map +1 -0
  119. package/dist/install/install.mjs +553 -425
  120. package/dist/install/partitionEligibility.js +432 -0
  121. package/dist/install/partitionEligibility.js.map +1 -0
  122. package/dist/install/ruleLayerPartition.js +157 -0
  123. package/dist/install/ruleLayerPartition.js.map +1 -0
  124. package/dist/mcp/registry-manifest.json +3 -3
  125. package/dist/mcp/server.json +2 -2
  126. package/dist/mcp/server.mjs +1863 -1234
  127. package/dist/router.json +1 -1
  128. package/dist/scripts/_lib/agent_src.js +41 -0
  129. package/dist/scripts/_lib/agent_src.js.map +1 -1
  130. package/dist/scripts/_lib/install_layout.js +83 -0
  131. package/dist/scripts/_lib/install_layout.js.map +1 -0
  132. package/dist/scripts/_lib/installed_lock.js +444 -0
  133. package/dist/scripts/_lib/installed_lock.js.map +1 -0
  134. package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
  135. package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
  136. package/dist/scripts/condense.js +63 -251
  137. package/dist/scripts/condense.js.map +1 -1
  138. package/dist/server/schemas/settings.js +4 -0
  139. package/dist/server/schemas/settings.js.map +1 -1
  140. package/docs/contracts/settings-classes.md +12 -6
  141. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
  142. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
  143. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
  144. package/docs/guidelines/agent-infra/false-green.md +47 -1
  145. package/docs/guidelines/agent-infra/memory-access.md +17 -0
  146. package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
  147. package/package.json +1 -1
  148. package/src/agent-src/scripts/archival_sweep.ts +117 -0
  149. package/src/agent-src/scripts/archive_completed_roadmaps.ts +26 -7
  150. package/src/agent-src/scripts/dashboard_mode.ts +212 -0
  151. package/src/agent-src/scripts/gate_budget.ts +282 -0
  152. package/src/agent-src/scripts/gate_execute.ts +72 -31
  153. package/src/agent-src/scripts/roadmap_gates.ts +74 -3
  154. package/src/agent-src/scripts/update_roadmap_progress.ts +296 -53
  155. package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  156. package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  157. package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  158. package/src/config/agent-settings.template.yml +27 -2
  159. package/src/config/ci-local-parity.yml +39 -0
  160. package/src/config/estate-count-budget.json +612 -3
  161. package/src/config/evaluator-budgets.json +3 -3
  162. package/src/config/gate-coverage.yml +220 -12
  163. package/src/config/gate-violation-baselines.json +73 -63
  164. package/src/config/gitignore-block.txt +15 -2
  165. package/src/config/hook-latency-budget.json +5 -0
  166. package/src/config/rule-activation-census.json +14 -6
  167. package/src/config/rule-stub-ceilings.json +34 -7
  168. package/src/install/claudePathsPlan.ts +196 -0
  169. package/src/install/globalRuleLayers.ts +197 -0
  170. package/src/install/hostLayerFingerprint.ts +145 -0
  171. package/src/install/partitionEligibility.ts +502 -0
  172. package/src/install/ruleLayerPartition.ts +212 -0
  173. package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
  174. package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
  175. package/src/scripts/_dispatch.bash +17 -1
  176. package/src/scripts/_lib/ac_heading.ts +30 -0
  177. package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
  178. package/src/scripts/_lib/adr_frontmatter.ts +459 -0
  179. package/src/scripts/_lib/agent_src.ts +42 -0
  180. package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
  181. package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
  182. package/src/scripts/_lib/envelope_grounding.ts +49 -9
  183. package/src/scripts/_lib/installed_lock.ts +50 -5
  184. package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
  185. package/src/scripts/_lib/md_section.ts +129 -0
  186. package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
  187. package/src/scripts/_lib/scope_guard.sh +26 -3
  188. package/src/scripts/_lib/self_repair.ts +19 -0
  189. package/src/scripts/_lib/self_repair_class_b.ts +171 -0
  190. package/src/scripts/adr/evidence_census.ts +899 -0
  191. package/src/scripts/adr/regenerate_index.ts +12 -32
  192. package/src/scripts/adr_cite_check.ts +540 -39
  193. package/src/scripts/ai_council/cli_help.ts +55 -0
  194. package/src/scripts/audit_adr_coverage.ts +103 -17
  195. package/src/scripts/before_complete_hook.ts +153 -28
  196. package/src/scripts/bench_ab_v2_complexity.ts +27 -7
  197. package/src/scripts/bench_hook_latency.ts +204 -0
  198. package/src/scripts/build_archive_index.ts +34 -0
  199. package/src/scripts/check_adr_frontmatter.ts +574 -39
  200. package/src/scripts/check_hook_bundle_content.ts +219 -0
  201. package/src/scripts/check_new_adr_evidence.ts +529 -0
  202. package/src/scripts/check_references.ts +78 -0
  203. package/src/scripts/check_release_published.ts +32 -11
  204. package/src/scripts/check_rule_layer_partition.ts +531 -0
  205. package/src/scripts/check_single_delivery.ts +194 -17
  206. package/src/scripts/check_standing_rule_delivery.ts +8 -2
  207. package/src/scripts/ci_settle.ts +170 -0
  208. package/src/scripts/condense.ts +79 -259
  209. package/src/scripts/council_cli.ts +3 -3
  210. package/src/scripts/dispatch_r2_reviewer.ts +6 -14
  211. package/src/scripts/external_sources_denylist.json +4 -1
  212. package/src/scripts/hook_manifest.json +1 -1
  213. package/src/scripts/hook_manifest.yaml +77 -14
  214. package/src/scripts/hooks/block_no_verify.ts +61 -10
  215. package/src/scripts/hooks/block_unauthorized_git.ts +21 -1
  216. package/src/scripts/hooks/concern_registry.ts +4 -0
  217. package/src/scripts/hooks/dispatch_hook.ts +154 -187
  218. package/src/scripts/hooks/dispatch_issues.ts +38 -19
  219. package/src/scripts/hooks/fallback_yaml.ts +66 -0
  220. package/src/scripts/hooks/host_semantics.ts +21 -0
  221. package/src/scripts/hooks/injection_budget.ts +13 -3
  222. package/src/scripts/hooks/py_json_dumps.ts +103 -0
  223. package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
  224. package/src/scripts/hooks/run_continuation_hook.ts +8 -5
  225. package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
  226. package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
  227. package/src/scripts/hooks/state_io.ts +870 -21
  228. package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
  229. package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
  230. package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
  231. package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
  232. package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
  233. package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
  234. package/src/scripts/injection_scan_hook.ts +137 -5
  235. package/src/scripts/install.ts +16 -11
  236. package/src/scripts/language_mirror_hook.ts +561 -25
  237. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  238. package/src/scripts/lint_hook_manifest.ts +49 -0
  239. package/src/scripts/lint_plan_risk_register.ts +4 -3
  240. package/src/scripts/lint_provenance_vocabulary.ts +472 -4
  241. package/src/scripts/lint_skill_router_head.ts +4 -4
  242. package/src/scripts/mcp_server/tools.ts +2 -2
  243. package/src/scripts/new_roadmap.ts +136 -0
  244. package/src/scripts/pack_mcp_content.ts +20 -5
  245. package/src/scripts/release.ts +37 -38
  246. package/src/scripts/report_layer_overlap.ts +6 -0
  247. package/src/scripts/routing_doctor.ts +5 -1
  248. package/src/scripts/self_repair_hook.ts +37 -5
  249. package/src/scripts/session_register_hook.ts +11 -0
  250. package/src/scripts/skill_usage_collect.ts +11 -1
  251. package/src/scripts/skill_usage_report.ts +278 -8
  252. package/src/scripts/sync_pr_branch.ts +170 -20
  253. package/src/server/schemas/settings.ts +8 -0
  254. package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
  255. package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
  256. package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
  257. package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
  258. package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-MMSGVZKV.js} +3 -3
@@ -227,7 +227,13 @@ function _stripChars(s, chars) {
227
227
  // module-level CLI self-invoke into its bundle. Re-exported here because four
228
228
  // in-repo consumers import it from `condense.js`.
229
229
  export { rule_in_scope } from '../install/ruleInScope.js';
230
+ // Re-exported for in-repo consumers; definitions moved beside emit_host_rules_cli.ts (Phase 5).
231
+ export { CLAUDE_PATHS_PATTERN_BUDGET, _claude_paths_plan, _escape_claude_bracket, _expanded_pattern_count, _has_non_path_trigger, _is_unresolved_placeholder, derive_trigger_globs, } from '../install/claudePathsPlan.js';
230
232
  import { rule_in_scope } from '../install/ruleInScope.js';
233
+ import { pruneEmptyDirs } from './_lib/prune_empty_dirs.js';
234
+ import { commandsWithheld, partitionActive, personaPartition, setPartitionAnnounce } from '../install/partitionEligibility.js'; // ADR-236
235
+ import { dedupableRules, partitionRulesForDir } from '../install/ruleLayerPartition.js'; // ADR-236, per-host evidence
236
+ import { _claude_paths_plan, derive_trigger_globs } from '../install/claudePathsPlan.js';
231
237
  const _HERE = path.dirname(fileURLToPath(import.meta.url)); // <repo>/src/scripts
232
238
  const _DEFAULT_PROJECT_ROOT = path.resolve(_HERE, '..', '..');
233
239
  function _deriveState(root) {
@@ -256,6 +262,7 @@ function _deriveState(root) {
256
262
  };
257
263
  }
258
264
  export const MODULE_STATE = _deriveState(_DEFAULT_PROJECT_ROOT);
265
+ setPartitionAnnounce(success); // ADR-236: the mode line must be visible at the default level
259
266
  /**
260
267
  * Test seam — reassign one or more module-state fields, mirroring the pytest
261
268
  * monkeypatch of `condense.<NAME> = ...`. When `PROJECT_ROOT` is overridden,
@@ -361,14 +368,6 @@ function _read_augment_rules_use_symlinks() {
361
368
  }
362
369
  return false;
363
370
  }
364
- /**
365
- * Per-tool user-scope rule directory, for the scope-dedup below. Only tools
366
- * whose host reads a user-scope rules directory can have a redundant twin;
367
- * everything else has nothing to de-duplicate against.
368
- */
369
- const USER_SCOPE_RULE_DIRS = {
370
- '.claude/rules': path.join('.claude', 'rules'),
371
- };
372
371
  function _read_projection_scope_dedup() {
373
372
  const data = load_agent_settings({ project_path: MODULE_STATE.SETTINGS_FILE });
374
373
  const projection = data['projection'];
@@ -384,63 +383,6 @@ function _read_projection_scope_dedup() {
384
383
  }
385
384
  return false;
386
385
  }
387
- /**
388
- * Rules whose user-scope twin is BYTE-IDENTICAL to the source, i.e. safe to
389
- * skip at project scope: the host still loads the same text, once instead of
390
- * twice, so nothing the model sees changes.
391
- *
392
- * Byte-identity is the whole safety argument and is not negotiable. Keying on
393
- * the filename alone would silently let a stale globally-installed copy win
394
- * whenever the two scopes hold different versions — which is the NORMAL state
395
- * while developing the package (measured: 110/110 shared filenames differing in
396
- * bytes). This is deliberately not the thin-projection mechanism: no rule
397
- * becomes trigger-gated, so the quality floor that disabled thin projection
398
- * does not apply here.
399
- */
400
- function _dedupable_rules(tool_dir, rules, userHome) {
401
- const relative = USER_SCOPE_RULE_DIRS[tool_dir];
402
- if (relative === undefined) {
403
- return new Set();
404
- }
405
- // A hostile or simply absent $HOME must make the dedup inert, not
406
- // adventurous. In a container `$HOME` is often unset (so `homedir()` can
407
- // resolve to `/`) or world-writable, and this function decides which rules
408
- // to STOP emitting — reading an unexpected tree there is how a projection
409
- // silently loses a rule. Council review of PR #1055 raised exactly this.
410
- let userDirStat;
411
- const userDir = path.join(userHome, relative);
412
- try {
413
- userDirStat = fs.statSync(userDir);
414
- }
415
- catch {
416
- return new Set();
417
- }
418
- if (!userDirStat.isDirectory()) {
419
- return new Set();
420
- }
421
- // World-writable user scope: anyone on the box could plant a byte-identical
422
- // twin and thereby delete a rule from the project projection. Refuse.
423
- if ((userDirStat.mode & 0o002) !== 0) {
424
- _print(` ⚠️ ${tool_dir}: user-scope rules dir is world-writable — scope-dedup skipped`);
425
- return new Set();
426
- }
427
- const skip = new Set();
428
- for (const rule of rules) {
429
- const twin = path.join(userDir, rule);
430
- const source = path.join(MODULE_STATE.RULES_SOURCE, rule);
431
- try {
432
- if (!_isFile(twin) || !_isFile(source))
433
- continue;
434
- if (fs.readFileSync(twin).equals(fs.readFileSync(source))) {
435
- skip.add(rule);
436
- }
437
- }
438
- catch {
439
- // An unreadable twin is simply not de-duplicable — emit the project copy.
440
- }
441
- }
442
- return skip;
443
- }
444
386
  function _lean_projection_mode() {
445
387
  const data = load_agent_settings({ project_path: MODULE_STATE.SETTINGS_FILE });
446
388
  const lean = data['lean_projection'];
@@ -1007,6 +949,13 @@ function _scoped_rule_basenames() {
1007
949
  .map((p) => path.basename(p))
1008
950
  .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
1009
951
  }
952
+ /** Adapter over {@link partitionRulesForDir}; that module carries the reasoning. */
953
+ export function partition_rules_for_dir(tool_dir, rules, user_home = process.env['HOME'] ?? os.homedir()) {
954
+ return partitionRulesForDir({
955
+ toolDir: tool_dir, rules, userHome: user_home, announce: _print,
956
+ projectRoot: MODULE_STATE.PROJECT_ROOT, rulesSource: MODULE_STATE.RULES_SOURCE,
957
+ });
958
+ }
1010
959
  /**
1011
960
  * The emit plan of {@link generate_rule_symlinks}: active tool dir → the exact
1012
961
  * rule basenames it projects there.
@@ -1029,8 +978,12 @@ export function projected_rule_trees(user_home = process.env['HOME'] ?? os.homed
1029
978
  const dedup_on = _read_projection_scope_dedup();
1030
979
  const out = {};
1031
980
  for (const tool_dir of Object.keys(_filter_tool_dirs(TOOL_DIRS))) {
1032
- const skip = dedup_on ? _dedupable_rules(tool_dir, rules, user_home) : new Set();
1033
- out[tool_dir] = rules.filter((r) => !skip.has(r));
981
+ const skip = dedup_on
982
+ ? dedupableRules({ toolDir: tool_dir, rules, userHome: user_home,
983
+ rulesSource: MODULE_STATE.RULES_SOURCE, announce: _print })
984
+ : new Set();
985
+ const kept = rules.filter((r) => !skip.has(r));
986
+ out[tool_dir] = partition_rules_for_dir(tool_dir, kept, user_home);
1034
987
  }
1035
988
  return out;
1036
989
  }
@@ -1137,7 +1090,10 @@ export function generate_rule_symlinks() {
1137
1090
  return total;
1138
1091
  }
1139
1092
  export function generate_windsurfrules() {
1140
- const rules = _scoped_rule_basenames();
1093
+ // Windsurf's LEGACY single-file surface — same host, so the same evidence, keyed
1094
+ // on `.windsurf/rules`. Without this line removing the per-run filter regressed
1095
+ // it 13 → 113: the per-run filter had been covering it by accident.
1096
+ const rules = partition_rules_for_dir('.windsurf/rules', _scoped_rule_basenames());
1141
1097
  const parts = ['# Auto-generated from dist/agent-src/rules/ — do not edit directly\n'];
1142
1098
  for (const rule of rules) {
1143
1099
  const p = path.join(MODULE_STATE.RULES_SOURCE, rule);
@@ -1188,34 +1144,6 @@ export function _parse_frontmatter(content) {
1188
1144
  function _yaml_scalar(value) {
1189
1145
  return JSON.stringify(value);
1190
1146
  }
1191
- /**
1192
- * Derive host-native activation globs from a rule's `triggers:` frontmatter
1193
- * (road-to-request-scoped-rule-load Phase 2). `file_pattern` triggers map
1194
- * verbatim; `path_prefix` triggers map to `<prefix>**`. Keyword / phrase /
1195
- * command triggers produce no glob — those rules stay
1196
- * description-activated (Cursor Agent-Requested / Windsurf model_decision).
1197
- */
1198
- export function derive_trigger_globs(meta) {
1199
- const triggers = meta['triggers'];
1200
- if (!Array.isArray(triggers)) {
1201
- return [];
1202
- }
1203
- const globs = [];
1204
- for (const t of triggers) {
1205
- if (t === null || typeof t !== 'object' || Array.isArray(t))
1206
- continue;
1207
- const obj = t;
1208
- if (typeof obj['file_pattern'] === 'string' && obj['file_pattern']) {
1209
- globs.push(obj['file_pattern']);
1210
- }
1211
- else if (typeof obj['path_prefix'] === 'string' && obj['path_prefix']) {
1212
- const prefix = obj['path_prefix'];
1213
- globs.push(prefix.endsWith('/') ? `${prefix}**` : `${prefix}**`);
1214
- }
1215
- }
1216
- // De-dup, keep declaration order (deterministic output).
1217
- return [...new Set(globs)];
1218
- }
1219
1147
  export function _emit_cursor_mdc(source, target) {
1220
1148
  const [meta, body] = _parse_frontmatter(_readText(source));
1221
1149
  const description = _strip(String(meta['description'] ?? '').replace(/\n/g, ' '));
@@ -1255,141 +1183,6 @@ export function _emit_windsurf_rule(source, target) {
1255
1183
  _mkdirp(path.dirname(target));
1256
1184
  _writeText(target, lines.join('\n'));
1257
1185
  }
1258
- /**
1259
- * A brace group the host expands, versus an agent-config placeholder that only
1260
- * LOOKS like one.
1261
- *
1262
- * `path_prefix: "{module_root}/"` in `roadmap-ci-steps-policy` is resolved by
1263
- * this suite from `modules.root_paths`. Emitted verbatim into a host `paths:`
1264
- * list it is a brace group with no comma, which the host expands to the literal
1265
- * string `{module_root}` — a pattern that matches no file. The rule would then
1266
- * reach the model **never** instead of on-demand, and nothing would say so: the
1267
- * probed contract records this exact degradation as silent
1268
- * (`claude-code-rules-dir-contract.md` § constraints).
1269
- *
1270
- * Dropping the pattern is fail-safe in the one direction that matters. A rule
1271
- * left with no pattern at all carries no `paths:` key and loads
1272
- * unconditionally — the status quo, not a regression. A rule carrying a
1273
- * no-op pattern is invisible.
1274
- *
1275
- * The discriminator is the comma, not a placeholder allowlist: `{ts,tsx}` is a
1276
- * real alternation, `{module_root}` cannot be one.
1277
- */
1278
- export function _is_unresolved_placeholder(pattern) {
1279
- for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
1280
- if (!m[1].includes(',')) {
1281
- return true;
1282
- }
1283
- }
1284
- return false;
1285
- }
1286
- /**
1287
- * `[` opens a bracket expression for the host. An unparseable one makes that
1288
- * pattern match nothing while the rule's other patterns keep working — so a
1289
- * literal `[` in a trigger is escaped rather than trusted.
1290
- */
1291
- export function _escape_claude_bracket(pattern) {
1292
- return pattern.replace(/\[/g, '\\[');
1293
- }
1294
- /**
1295
- * Expanded-pattern cost of one glob: the product of its brace alternations.
1296
- * The host budgets a rule's whole `paths:` list at 1,000 expanded patterns and
1297
- * 4 MiB, and a list over budget is used UNEXPANDED — braces then match
1298
- * literally, i.e. nothing. Counting keeps the emitter under the cliff instead
1299
- * of discovering it as a silent no-op.
1300
- */
1301
- export function _expanded_pattern_count(pattern) {
1302
- let n = 1;
1303
- for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
1304
- n *= m[1].split(',').length;
1305
- }
1306
- return n;
1307
- }
1308
- /** Host-documented budget for one rule's whole `paths:` list. */
1309
- export const CLAUDE_PATHS_PATTERN_BUDGET = 1000;
1310
- /**
1311
- * Does this rule declare any trigger that is NOT path-shaped?
1312
- *
1313
- * Load-bearing for `_claude_paths_plan` below, because one `triggers:` list
1314
- * means two different things on two hosts. Cursor and Windsurf treat globs as
1315
- * ADDITIVE — a rule with empty globs still reaches the agent through its
1316
- * description (see `_emit_cursor_mdc`'s own comment). Claude Code treats
1317
- * `paths:` as EXCLUSIVE: with it the rule loads on a path match; without it the
1318
- * rule loads unconditionally. The emitter fed both semantics from one list, so
1319
- * a rule that declared keywords *and* one path glob silently became
1320
- * path-gated-only on Claude.
1321
- */
1322
- export function _has_non_path_trigger(meta) {
1323
- const triggers = meta['triggers'];
1324
- if (!Array.isArray(triggers))
1325
- return false;
1326
- for (const t of triggers) {
1327
- if (t === null || typeof t !== 'object' || Array.isArray(t))
1328
- continue;
1329
- const obj = t;
1330
- if (typeof obj['keyword'] === 'string' && obj['keyword'])
1331
- return true;
1332
- if (typeof obj['phrase'] === 'string' && obj['phrase'])
1333
- return true;
1334
- }
1335
- return false;
1336
- }
1337
- /**
1338
- * Derive the host's `paths:` list for one rule, with every drop recorded.
1339
- *
1340
- * Kernel / always-apply rules get an EMPTY list on purpose: absent `paths:` is
1341
- * how this host says "load unconditionally", which is exactly what an Iron-Law
1342
- * rule needs. Scoping one would be a correctness regression dressed as a byte
1343
- * saving — and the same contract notes path-scoped rules are not re-injected
1344
- * after `/compact`, so an obligation that must survive compaction cannot be
1345
- * path-scoped at all.
1346
- */
1347
- export function _claude_paths_plan(meta) {
1348
- const always_apply = Boolean(meta['alwaysApply'] || meta['type'] === 'always');
1349
- if (always_apply) {
1350
- return { globs: [], dropped: [] };
1351
- }
1352
- const globs = [];
1353
- const dropped = [];
1354
- // A rule that ALSO declares keyword / phrase triggers gets no `paths:` at
1355
- // all, and therefore keeps loading unconditionally on Claude Code.
1356
- //
1357
- // Emitting `paths:` here would narrow the rule to a path match and discard
1358
- // every keyword the author wrote, because this host reads the list as the
1359
- // whole gate rather than as one way in. Measured before this guard: 25 of
1360
- // 110 projected rules carried `paths:` and 19 of them also carried keyword
1361
- // triggers — `design-fidelity` lost 21 of them, in exactly the pasted-
1362
- // screenshot and capability-URL handovers its own routing section names as
1363
- // the classes it exists to catch. Nothing in the tree recorded a decision
1364
- // to make that trade, which is why it is treated as a defect.
1365
- //
1366
- // The drop is REPORTED rather than silent, so a rule whose obligation
1367
- // really is path-bound is visible as a candidate for removing its keyword
1368
- // triggers — the narrowing then becomes an authoring decision instead of an
1369
- // emitter side effect. Cursor and Windsurf are untouched: globs are
1370
- // additive there, so the same rule keeps both routes.
1371
- if (_has_non_path_trigger(meta)) {
1372
- for (const raw of derive_trigger_globs(meta)) {
1373
- dropped.push({ pattern: raw, reason: 'mixed-triggers' });
1374
- }
1375
- return { globs, dropped };
1376
- }
1377
- let spent = 0;
1378
- for (const raw of derive_trigger_globs(meta)) {
1379
- if (_is_unresolved_placeholder(raw)) {
1380
- dropped.push({ pattern: raw, reason: 'unresolved-placeholder' });
1381
- continue;
1382
- }
1383
- const cost = _expanded_pattern_count(raw);
1384
- if (spent + cost > CLAUDE_PATHS_PATTERN_BUDGET) {
1385
- dropped.push({ pattern: raw, reason: 'over-budget' });
1386
- continue;
1387
- }
1388
- spent += cost;
1389
- globs.push(_escape_claude_bracket(raw));
1390
- }
1391
- return { globs, dropped };
1392
- }
1393
1186
  /**
1394
1187
  * Emit a Claude Code rule carrying the host's OWN activation key.
1395
1188
  *
@@ -1455,12 +1248,16 @@ function _clean_modern_dir(target_dir, valid_names) {
1455
1248
  export function generate_cursor_mdc_rules() {
1456
1249
  const scope = _read_rule_workspaces();
1457
1250
  const pack_scope = _read_rule_packs();
1458
- const rules = _rglobSorted(MODULE_STATE.RULES_SOURCE, '*.md')
1251
+ let rules = _rglobSorted(MODULE_STATE.RULES_SOURCE, '*.md')
1459
1252
  .filter((p) => _isFile(p))
1460
1253
  .filter((p) => rule_in_scope(p, scope, pack_scope))
1461
1254
  // ADR-004 manual rules are reference-only — never auto-injected into a
1462
1255
  // per-tool tree. Same predicate as generate_rule_symlinks.
1463
1256
  .filter((p) => !_is_manual_rule(p));
1257
+ // ADR-236 per-host partition (src/install/ruleLayerPartition.ts). Narrowing the
1258
+ // list is a REMOVAL, because `_clean_modern_dir` below sweeps to `valid`.
1259
+ const kept = new Set(partition_rules_for_dir('.cursor/rules', rules.map((r) => path.basename(r))));
1260
+ rules = rules.filter((r) => kept.has(path.basename(r)));
1464
1261
  const stems = rules.map((r) => path.basename(r, '.md'));
1465
1262
  const valid = new Set([
1466
1263
  ...stems.map((s) => `${s}.mdc`),
@@ -1476,12 +1273,15 @@ export function generate_cursor_mdc_rules() {
1476
1273
  export function generate_windsurf_modern_rules() {
1477
1274
  const scope = _read_rule_workspaces();
1478
1275
  const pack_scope = _read_rule_packs();
1479
- const rules = _rglobSorted(MODULE_STATE.RULES_SOURCE, '*.md')
1276
+ let rules = _rglobSorted(MODULE_STATE.RULES_SOURCE, '*.md')
1480
1277
  .filter((p) => _isFile(p))
1481
1278
  .filter((p) => rule_in_scope(p, scope, pack_scope))
1482
1279
  // ADR-004 manual rules are reference-only — never auto-injected into a
1483
1280
  // per-tool tree. Same predicate as generate_rule_symlinks.
1484
1281
  .filter((p) => !_is_manual_rule(p));
1282
+ // ADR-236 per-host partition — same shape as the cursor emitter above.
1283
+ const kept = new Set(partition_rules_for_dir('.windsurf/rules', rules.map((r) => path.basename(r))));
1284
+ rules = rules.filter((r) => kept.has(path.basename(r)));
1485
1285
  const valid = new Set(rules.map((r) => path.basename(r)));
1486
1286
  _clean_modern_dir(_WINDSURF_RULES_DIR(), valid);
1487
1287
  for (const rule of rules) {
@@ -1652,6 +1452,8 @@ function _render_native_model_md(src_md, tier) {
1652
1452
  return text.replace(_MODEL_TIER_RE, `model: ${model}`);
1653
1453
  }
1654
1454
  export function generate_claude_skills(active_skill_names = null) {
1455
+ if (partitionActive(MODULE_STATE.PROJECT_ROOT))
1456
+ active_skill_names = new Set();
1655
1457
  if (!_exists(MODULE_STATE.SKILLS_SOURCE)) {
1656
1458
  process.stderr.write(' ⚠️ dist/agent-src/skills/ not found — skipping skills\n');
1657
1459
  return 0;
@@ -1664,7 +1466,9 @@ export function generate_claude_skills(active_skill_names = null) {
1664
1466
  skills = skills.filter((s) => active_skill_names.has(s));
1665
1467
  }
1666
1468
  const skill_set = new Set(skills);
1667
- const command_slugs = new Set([...iterCommands()].map(([, slug]) => slug));
1469
+ const command_slugs = partitionActive(MODULE_STATE.PROJECT_ROOT)
1470
+ ? new Set()
1471
+ : new Set([...iterCommands()].map(([, slug]) => slug));
1668
1472
  _mkdirp(MODULE_STATE.CLAUDE_SKILLS_DIR);
1669
1473
  const auto = _read_model_auto_switch() === 'auto';
1670
1474
  for (const item of _iterdirSorted(MODULE_STATE.CLAUDE_SKILLS_DIR)) {
@@ -1752,23 +1556,24 @@ function _nested_command_subpath(source_file) {
1752
1556
  /**
1753
1557
  * Project-scope `.claude/commands/<cluster>/<sub>.md` — the colon form.
1754
1558
  *
1755
- * Before this, the package repo's own `.claude/` carried NO commands
1756
- * directory: every command reached Claude Code as a hyphen-named skill
1757
- * wrapper in `.claude/skills/`, while the colon form existed only in the
1758
- * user-global tree written by `install.ts`. Every clustered command was
1759
- * therefore listed TWICE for anyone with a global install
1760
- * (`roadmap-process-full` AND `roadmap:process-full`), costing 4,214 GPT tok
1761
- * of always-loaded catalog for zero added reach — measured 2026-08-02.
1559
+ * Before this, the package repo's own `.claude/` carried NO commands directory:
1560
+ * every command reached Claude Code as a hyphen-named skill wrapper in
1561
+ * `.claude/skills/`, while the colon form existed only in the user-global tree
1562
+ * written by `install.ts`. Every clustered command was therefore listed TWICE
1563
+ * for anyone with a global install (`roadmap-process-full` AND
1564
+ * `roadmap:process-full`), costing 4,214 GPT tok of catalog — measured 2026-08-02.
1762
1565
  *
1763
1566
  * Emitting the nested form here lets `generate_claude_commands` stop wrapping
1764
1567
  * the clustered commands: one listing, same reachability, and reachability no
1765
- * longer depends on a global deploy having run. Claude Code dedupes project
1766
- * and user scope by name, so the two copies of `/cluster:sub` collapse.
1568
+ * longer depends on a global deploy. Dedup AND precedence: `commandsWithheld`.
1767
1569
  *
1768
1570
  * ADR-003 (colon canonical for clusters) and ADR-044 (flat commands stay
1769
1571
  * hyphenated) both hold: flat commands are untouched and keep their wrapper.
1770
1572
  */
1771
1573
  export function generate_claude_project_commands(active_command_slugs = null) {
1574
+ if (commandsWithheld(MODULE_STATE.PROJECT_ROOT))
1575
+ active_command_slugs = new Set();
1576
+ // Returns before the stale sweep — invariant, see `commandsWithheld`.
1772
1577
  if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
1773
1578
  return 0;
1774
1579
  }
@@ -1784,8 +1589,7 @@ export function generate_claude_project_commands(active_command_slugs = null) {
1784
1589
  }
1785
1590
  const target_root = _CLAUDE_COMMANDS_DIR();
1786
1591
  const valid = new Set(nested.map(([, sub]) => `${sub}.md`));
1787
- // Sweep stale links first: a renamed or deleted command must not linger as
1788
- // a dangling `/cluster:sub`.
1592
+ // Sweep stale links first — a renamed, deleted or WITHHELD command must not linger.
1789
1593
  if (_exists(target_root)) {
1790
1594
  for (const item of _rglobSorted(target_root, '*.md')) {
1791
1595
  const rel = _relativeToPosix(item, target_root);
@@ -1793,6 +1597,7 @@ export function generate_claude_project_commands(active_command_slugs = null) {
1793
1597
  fs.unlinkSync(item);
1794
1598
  }
1795
1599
  }
1600
+ pruneEmptyDirs(target_root); // else the gate counts 40 empty cluster dirs as 40 commands
1796
1601
  }
1797
1602
  let count = 0;
1798
1603
  for (const [source_file, sub] of nested) {
@@ -1812,6 +1617,8 @@ export function generate_claude_project_commands(active_command_slugs = null) {
1812
1617
  return count;
1813
1618
  }
1814
1619
  export function generate_claude_commands(active_command_slugs = null) {
1620
+ if (partitionActive(MODULE_STATE.PROJECT_ROOT))
1621
+ active_command_slugs = new Set();
1815
1622
  if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
1816
1623
  process.stderr.write(' ⚠️ src/domains/ not found — skipping commands\n');
1817
1624
  return 0;
@@ -2228,7 +2035,7 @@ export function generate_persona_symlinks() {
2228
2035
  .map((p) => path.basename(p))
2229
2036
  .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
2230
2037
  void personas;
2231
- const list = direct;
2038
+ const partition = personaPartition(MODULE_STATE.PROJECT_ROOT, direct);
2232
2039
  const tool_dirs = _filter_tool_dirs(PERSONA_TOOL_DIRS);
2233
2040
  let total = 0;
2234
2041
  for (const [tool_dir, rel_prefix] of Object.entries(tool_dirs)) {
@@ -2236,11 +2043,11 @@ export function generate_persona_symlinks() {
2236
2043
  _mkdirp(target_dir);
2237
2044
  for (const item of _iterdirSorted(target_dir)) {
2238
2045
  const name = path.basename(item);
2239
- if (_isSymlink(item) && !list.includes(name) && name !== 'README.md') {
2046
+ if (_isSymlink(item) && !partition.listFor(tool_dir).includes(name) && name !== 'README.md') {
2240
2047
  fs.unlinkSync(item);
2241
2048
  }
2242
2049
  }
2243
- for (const persona of list) {
2050
+ for (const persona of partition.listFor(tool_dir)) {
2244
2051
  const link = path.join(target_dir, persona);
2245
2052
  const target = path.join(rel_prefix, persona);
2246
2053
  if (_existsOrSymlink(link)) {
@@ -2250,7 +2057,8 @@ export function generate_persona_symlinks() {
2250
2057
  total += 1;
2251
2058
  }
2252
2059
  }
2253
- info(` ✅ Created ${total} persona symlinks across ${Object.keys(tool_dirs).length} tool directories (${list.length} personas each)`);
2060
+ const per_dir = Object.keys(tool_dirs).map((d) => `${d}=${partition.countFor(d)}`);
2061
+ info(` ✅ Created ${total} persona symlinks — ${per_dir.join(' · ')}${partition.note}`);
2254
2062
  return total;
2255
2063
  }
2256
2064
  export function generate_user_type_symlinks() {
@@ -2423,10 +2231,14 @@ export function project_to_augment() {
2423
2231
  const current = new Set();
2424
2232
  let written = 0;
2425
2233
  if (_exists(src_rules)) {
2426
- const rules = _iterdirSorted(src_rules)
2234
+ // ADR-236 per-host partition, ahead of the `use_symlinks` branch so both
2235
+ // writers narrow; the `existing` minus `current` sweep below makes it a
2236
+ // removal. This emitter never had the scope or ADR-004 manual filters
2237
+ // either — why its count is 15 and the symlink trees' 13. Pre-existing.
2238
+ const rules = partition_rules_for_dir('.augment/rules', _iterdirSorted(src_rules)
2427
2239
  .filter((p) => p.endsWith('.md') && _isFile(p))
2428
2240
  .map((p) => path.basename(p))
2429
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
2241
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)));
2430
2242
  for (const rule of rules) {
2431
2243
  const target = path.join(dst_rules, rule);
2432
2244
  if (_isSymlink(target) || _existsOrSymlink(target)) {