@event4u/agent-config 14.21.0 → 14.23.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 (235) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +164 -187
  5. package/CREDITS.md +37 -0
  6. package/README.md +1 -1
  7. package/dist/agent-src/commands/analyze/inbox.md +380 -5
  8. package/dist/agent-src/commands/analyze/repo.md +4 -2
  9. package/dist/agent-src/commands/pr/merge.md +47 -38
  10. package/dist/agent-src/commands/roadmap/next.md +8 -0
  11. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
  12. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
  13. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
  14. package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
  15. package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
  16. package/dist/agent-src/rules/token-efficiency.md +26 -3
  17. package/dist/agent-src/scripts/_cli_entry.ts +51 -0
  18. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
  19. package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
  20. package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
  21. package/dist/agent-src/scripts/stubs_due.ts +4 -18
  22. package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
  23. package/dist/agent-src/skills/code-intelligence/SKILL.md +82 -1
  24. package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
  25. package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
  26. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
  27. package/dist/agent-src/templates/agent-settings.md +0 -1
  28. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  29. package/dist/cli/registry.js +1 -1
  30. package/dist/cli-delegate/_cli_entry.js +8 -0
  31. package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
  32. package/dist/cli-delegate/archival_sweep.js +8 -0
  33. package/dist/cli-delegate/archive_completed_roadmaps.js +542 -0
  34. package/dist/cli-delegate/check_update_banner.js +923 -0
  35. package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-2KKXJW25.js} +23 -9
  36. package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
  37. package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
  38. package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
  39. package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
  40. package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
  41. package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-KCNTUSPI.js} +7 -1
  42. package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
  43. package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
  44. package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
  45. package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
  46. package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
  47. package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
  48. package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
  49. package/dist/cli-delegate/cmd_conformance.js +2 -2
  50. package/dist/cli-delegate/cmd_export.js +2 -2
  51. package/dist/cli-delegate/cmd_handoff.js +45 -13
  52. package/dist/cli-delegate/cmd_prune.js +2 -2
  53. package/dist/cli-delegate/cmd_session_recycle.js +4 -14
  54. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  55. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  56. package/dist/cli-delegate/cmd_sync.js +2 -2
  57. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  58. package/dist/cli-delegate/cmd_validate.js +2 -2
  59. package/dist/cli-delegate/dashboard_mode.js +16 -0
  60. package/dist/cli-delegate/gate_budget.js +24 -0
  61. package/dist/cli-delegate/gate_execute.js +25 -0
  62. package/dist/cli-delegate/guarded_baseline.js +18 -0
  63. package/dist/cli-delegate/resume_probe.js +22 -0
  64. package/dist/cli-delegate/roadmap_gates.js +638 -0
  65. package/dist/cli-delegate/roadmap_set_step.js +173 -0
  66. package/dist/cli-delegate/stub_queue.js +47 -0
  67. package/dist/cli-delegate/stubs_due.js +38 -0
  68. package/dist/cli-delegate/ticket_bundles.js +59 -0
  69. package/dist/cli-delegate/update_roadmap_progress.js +60 -0
  70. package/dist/discovery/deprecation-report.md +1 -1
  71. package/dist/discovery/discovery-manifest.json +13 -13
  72. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  73. package/dist/discovery/discovery-manifest.summary.md +1 -1
  74. package/dist/discovery/dormancy-report.md +1 -1
  75. package/dist/discovery/orphan-report.md +1 -1
  76. package/dist/discovery/packs.json +2 -2
  77. package/dist/discovery/trust-report.md +1 -1
  78. package/dist/discovery/workspaces.json +2 -2
  79. package/dist/hooks/dispatch.js +1813 -946
  80. package/dist/install/claudeLayerCarriage.js +237 -0
  81. package/dist/install/claudePathsPlan.js +79 -0
  82. package/dist/install/hostLayerFingerprint.js +1 -1
  83. package/dist/install/install.mjs +21 -3
  84. package/dist/install/partitionEligibility.js +231 -117
  85. package/dist/install/ruleLayerPartition.js +10 -7
  86. package/dist/mcp/registry-manifest.json +2 -2
  87. package/dist/mcp/server.json +2 -2
  88. package/dist/mcp/server.mjs +10870 -7641
  89. package/dist/scripts/_lib/lean_projection_mode.js +108 -0
  90. package/dist/scripts/_lib/router_match.js +243 -0
  91. package/dist/scripts/_lib/rule_injection.js +216 -0
  92. package/dist/scripts/condense.js +52 -45
  93. package/dist/scripts/project_thin_rules.js +139 -19
  94. package/dist/server/schemas/settings.js +16 -3
  95. package/docs/MIGRATION.md +105 -1
  96. package/docs/contracts/settings-classes.md +40 -8
  97. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
  98. package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
  99. package/llms.txt +1 -1
  100. package/package.json +7 -4
  101. package/src/agent-src/scripts/_cli_entry.ts +51 -0
  102. package/src/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
  103. package/src/agent-src/scripts/roadmap_gates.ts +4 -20
  104. package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
  105. package/src/agent-src/scripts/stubs_due.ts +4 -18
  106. package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
  107. package/src/config/agent-settings.template.yml +47 -1
  108. package/src/config/continuity-surface.json +516 -0
  109. package/src/config/estate-count-budget.json +1 -1
  110. package/src/config/evaluator-budgets.json +4 -4
  111. package/src/config/gate-coverage.yml +29 -8
  112. package/src/config/gate-violation-baselines.json +8 -8
  113. package/src/config/gitignore-block.txt +19 -0
  114. package/src/config/hook-token-budget.json +7 -6
  115. package/src/config/host-injection-effect.json +73 -0
  116. package/src/config/packed-binary-manifest.json +33 -0
  117. package/src/config/preamble-payload-budget.json +112 -105
  118. package/src/config/publish-surface.json +4 -2
  119. package/src/config/routing-coverage-seed.json +9 -2
  120. package/src/install/claudeLayerCarriage.ts +273 -0
  121. package/src/install/claudePathsPlan.ts +76 -0
  122. package/src/install/hostLayerFingerprint.ts +1 -1
  123. package/src/install/partitionEligibility.ts +261 -127
  124. package/src/install/ruleLayerPartition.ts +19 -23
  125. package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
  126. package/src/scripts/_cli/handoff_sessions.ts +14 -44
  127. package/src/scripts/_dispatch.bash +77 -27
  128. package/src/scripts/_lib/auto_dispatch.ts +2 -2
  129. package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
  130. package/src/scripts/_lib/continuity_slot.ts +295 -0
  131. package/src/scripts/_lib/continuity_surface.ts +296 -0
  132. package/src/scripts/_lib/continuity_writer.ts +253 -0
  133. package/src/scripts/_lib/corpus_manifest.ts +26 -9
  134. package/src/scripts/_lib/hook_settings.ts +86 -1
  135. package/src/scripts/_lib/injection_effect.ts +151 -0
  136. package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
  137. package/src/scripts/_lib/lean_projection_mode.ts +127 -0
  138. package/src/scripts/_lib/packed_binary_predicate.ts +262 -0
  139. package/src/scripts/_lib/pin_resolver.ts +13 -0
  140. package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
  141. package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
  142. package/src/scripts/_lib/retired_status.ts +74 -0
  143. package/src/scripts/_lib/session_eol.ts +44 -0
  144. package/src/scripts/_lib/session_index_trust.ts +346 -0
  145. package/src/scripts/_lib/skill_catalogue.ts +156 -21
  146. package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
  147. package/src/scripts/_lib/subagent_capsule.ts +117 -8
  148. package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
  149. package/src/scripts/ai_council/api_transport.ts +5 -0
  150. package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
  151. package/src/scripts/ai_council/prompts.ts +9 -0
  152. package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
  153. package/src/scripts/audit_mcp_tools.ts +69 -25
  154. package/src/scripts/build_mcp_catalog.ts +24 -1
  155. package/src/scripts/capture_skill_catalogue.ts +24 -11
  156. package/src/scripts/check_candidate_lines.ts +499 -0
  157. package/src/scripts/check_continuity_surface.ts +475 -0
  158. package/src/scripts/check_dependency_floors.ts +13 -6
  159. package/src/scripts/check_estate_count.ts +40 -79
  160. package/src/scripts/check_host_tree_parity.ts +291 -0
  161. package/src/scripts/check_pack_size.ts +262 -23
  162. package/src/scripts/check_preamble_payload_budget.ts +136 -7
  163. package/src/scripts/check_roadmap_trackable.ts +19 -16
  164. package/src/scripts/check_rule_layer_partition.ts +54 -34
  165. package/src/scripts/check_rule_projection_integrity.ts +102 -2
  166. package/src/scripts/check_single_delivery.ts +97 -31
  167. package/src/scripts/check_update_banner.ts +13 -0
  168. package/src/scripts/ci_settle.ts +205 -5
  169. package/src/scripts/code_graph/build.ts +217 -15
  170. package/src/scripts/code_graph/cli.ts +334 -15
  171. package/src/scripts/code_graph/detect.ts +38 -0
  172. package/src/scripts/code_graph/extract.ts +11 -0
  173. package/src/scripts/code_graph/grammar_probe.ts +43 -0
  174. package/src/scripts/code_graph/loader.ts +88 -14
  175. package/src/scripts/code_graph/query.ts +144 -11
  176. package/src/scripts/code_graph/resolution_tiers.ts +238 -0
  177. package/src/scripts/code_graph/sqlite_store.ts +262 -6
  178. package/src/scripts/code_graph/types.ts +117 -3
  179. package/src/scripts/code_graph/validate.ts +26 -1
  180. package/src/scripts/code_graph/verbs.ts +590 -0
  181. package/src/scripts/condense.ts +54 -42
  182. package/src/scripts/corpus_manifest.ts +5 -5
  183. package/src/scripts/council_cli.ts +11 -12
  184. package/src/scripts/count_intent_disagreement.ts +426 -0
  185. package/src/scripts/handoff_context_hook.ts +118 -1
  186. package/src/scripts/hook_effect_doctor.ts +82 -1
  187. package/src/scripts/hook_manifest.json +1 -1
  188. package/src/scripts/hook_manifest.yaml +87 -12
  189. package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
  190. package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
  191. package/src/scripts/hooks/code_graph_context_hook.ts +201 -0
  192. package/src/scripts/hooks/concern_registry.ts +4 -2
  193. package/src/scripts/hooks/host_semantics.ts +1 -1
  194. package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
  195. package/src/scripts/hooks/push_settle_hook.ts +3 -2
  196. package/src/scripts/hooks/rule_inject_hook.ts +108 -21
  197. package/src/scripts/hooks/session_eol_hook.ts +120 -2
  198. package/src/scripts/hooks/skill_route_hook.ts +11 -4
  199. package/src/scripts/hot_context_hook.ts +62 -7
  200. package/src/scripts/inbox_source_census.ts +322 -0
  201. package/src/scripts/install-hooks.sh +82 -0
  202. package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
  203. package/src/scripts/lint_plan_risk_register.ts +13 -17
  204. package/src/scripts/lint_roadmap_blockers.ts +164 -1
  205. package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
  206. package/src/scripts/mcp_server/graph_tools.ts +294 -0
  207. package/src/scripts/mcp_server/path_util.ts +31 -0
  208. package/src/scripts/mcp_server/suggest_skill.ts +72 -0
  209. package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
  210. package/src/scripts/mcp_server/tools.ts +5 -69
  211. package/src/scripts/media/lib/adapter-contract.md +7 -2
  212. package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
  213. package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
  214. package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
  215. package/src/scripts/model_rule_injection.ts +61 -4
  216. package/src/scripts/project_thin_rules.ts +149 -16
  217. package/src/scripts/rdp_candidates_delta.ts +276 -0
  218. package/src/scripts/rdp_quality_eval.ts +217 -19
  219. package/src/scripts/release_mix_taxonomy.json +3 -2
  220. package/src/scripts/report_host_injection_effect.ts +189 -0
  221. package/src/scripts/report_layer_overlap.ts +41 -0
  222. package/src/scripts/report_skill_menu_census.ts +341 -0
  223. package/src/scripts/report_standing_payload_by_host.ts +650 -0
  224. package/src/scripts/schemas/agent-settings.schema.json +7 -1
  225. package/src/scripts/session_memory_index.ts +81 -8
  226. package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
  227. package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
  228. package/src/server/schemas/settings.ts +23 -4
  229. package/src/vendor/grammars/README.md +86 -0
  230. package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
  231. package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
  232. package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
  233. package/src/scripts/_lib/carrier_status.ts +0 -118
  234. package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
  235. package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
@@ -39,7 +39,7 @@ import { _domains_command_logical, artefact_roots as _agent_src_artefact_roots,
39
39
  // bundled-CLI-entry-guard landmine. Verified before wiring, not assumed.
40
40
  import { build_thin } from './project_thin_rules.js';
41
41
  import { build_claude_hook_matrix } from './_lib/claude_settings_hooks.js';
42
- import { normalizeLeanProjectionMode, writesThinFiles } from './_lib/lean_projection_mode.js';
42
+ import { describeDroppedHosts, normalizeLeanProjectionMode, resolveLeanProjectionHosts, thinsHost, writesThinFiles } from './_lib/lean_projection_mode.js';
43
43
  import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
44
44
  import { project_settings_path, load_agent_settings } from './_lib/agent_settings.js';
45
45
  import { rule_is_compile_enabled } from './_lib/compile_time_toggles.js';
@@ -232,9 +232,9 @@ export { rule_in_scope } from '../install/ruleInScope.js';
232
232
  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';
233
233
  import { rule_in_scope } from '../install/ruleInScope.js';
234
234
  import { pruneEmptyDirs } from './_lib/prune_empty_dirs.js';
235
- import { commandsWithheld, partitionActive, personaPartition, setPartitionAnnounce } from '../install/partitionEligibility.js'; // ADR-236
235
+ import { claudeLayerHolds, commandWithheld, keepInProjectLayer, personaPartition } from '../install/partitionEligibility.js'; // ADR-236
236
236
  import { dedupableRules, partitionRulesForDir } from '../install/ruleLayerPartition.js'; // ADR-236, per-host evidence
237
- import { _claude_paths_plan, derive_trigger_globs } from '../install/claudePathsPlan.js';
237
+ import { _claude_paths_plan, applies_when, derive_trigger_globs } from '../install/claudePathsPlan.js';
238
238
  const _HERE = path.dirname(fileURLToPath(import.meta.url)); // <repo>/src/scripts
239
239
  const _DEFAULT_PROJECT_ROOT = path.resolve(_HERE, '..', '..');
240
240
  function _deriveState(root) {
@@ -263,7 +263,6 @@ function _deriveState(root) {
263
263
  };
264
264
  }
265
265
  export const MODULE_STATE = _deriveState(_DEFAULT_PROJECT_ROOT);
266
- setPartitionAnnounce(success); // ADR-236: the mode line must be visible at the default level
267
266
  /**
268
267
  * Test seam — reassign one or more module-state fields, mirroring the pytest
269
268
  * monkeypatch of `condense.<NAME> = ...`. When `PROJECT_ROOT` is overridden,
@@ -384,13 +383,16 @@ function _read_projection_scope_dedup() {
384
383
  }
385
384
  return false;
386
385
  }
387
- function _lean_projection_mode() {
386
+ function _lean_projection_settings() {
388
387
  const data = load_agent_settings({ project_path: MODULE_STATE.SETTINGS_FILE });
389
388
  const lean = data['lean_projection'];
390
- const raw = typeof lean === 'object' && lean !== null && !Array.isArray(lean)
391
- ? lean['mode']
392
- : '';
393
- return normalizeLeanProjectionMode(raw);
389
+ const obj = typeof lean === 'object' && lean !== null && !Array.isArray(lean)
390
+ ? lean
391
+ : {};
392
+ return {
393
+ mode: normalizeLeanProjectionMode(obj['mode'] ?? ''),
394
+ hosts: resolveLeanProjectionHosts(obj['hosts']),
395
+ };
394
396
  }
395
397
  // --- hashing -----------------------------------------------------------------
396
398
  export function apply_path_rewriter(relative_path) {
@@ -986,20 +988,17 @@ export function projected_rule_trees(user_home = process.env['HOME'] ?? os.homed
986
988
  export function generate_rule_symlinks() {
987
989
  const rules = _scoped_rule_basenames();
988
990
  const tool_dirs = _filter_tool_dirs(TOOL_DIRS);
991
+ const lean = _lean_projection_settings();
992
+ for (const warning of describeDroppedHosts(lean.hosts))
993
+ _print(` ⚠️ ${warning}`);
989
994
  let thin_files = null;
990
- if (writesThinFiles(_lean_projection_mode())) {
991
- // DEAD-SWITCH REPAIR (road-to-renewal-foundation Phase 2). This branch
992
- // used to THROW: the port was skipped as "out of scope, not exercised by
993
- // golden parity", which left a documented, settings-selectable mode that
994
- // could only ever crash. `build_thin` was ported and has been present the
995
- // whole time — the wiring was the only thing missing.
996
- //
997
- // Scope note: this repairs the SWITCH only. The default stays
998
- // `eager-all`. Flipping it is parked behind the thin-projection honest
999
- // null (thin win-rate 36.2% < the 48% pre-registered threshold), and
1000
- // nothing here disturbs that verdict — but a mode that throws cannot
1001
- // even be re-measured, which is why the repair earns its place alone.
1002
- thin_files = Object.fromEntries(build_thin(MODULE_STATE.RULES_SOURCE, _read_rule_workspaces()));
995
+ if (writesThinFiles(lean.mode)) {
996
+ // `thin` stays parked behind the thin-projection honest null (win-rate
997
+ // 36.2% against a 48% pre-registered threshold, ADR-202). `delivery` is
998
+ // NOT gated by that null and citing it as one is K7: delivery re-delivers
999
+ // identical bytes, is licensed by `docs/CLAIMS.md:365` on delivery
1000
+ // equivalence and cost, and needs a recall floor rather than a judge.
1001
+ thin_files = Object.fromEntries(build_thin(MODULE_STATE.RULES_SOURCE, _read_rule_workspaces(), (m) => _print(` ⚠️ ${m}`)));
1003
1002
  }
1004
1003
  const user_home = process.env['HOME'] ?? os.homedir();
1005
1004
  // Read the emit plan from the exported function rather than recomputing the
@@ -1045,7 +1044,7 @@ export function generate_rule_symlinks() {
1045
1044
  if (_existsOrSymlink(link)) {
1046
1045
  fs.unlinkSync(link);
1047
1046
  }
1048
- if (thin_files !== null) {
1047
+ if (thin_files !== null && thinsHost(lean.mode, lean.hosts.hosts, _DIR_TOOL_ID[tool_dir] ?? '')) {
1049
1048
  _writeText(link, thin_files[rule]);
1050
1049
  }
1051
1050
  else if (tool_dir === '.claude/rules') {
@@ -1149,7 +1148,7 @@ export function _emit_cursor_mdc(source, target) {
1149
1148
  const globs = always_apply ? [] : derive_trigger_globs(meta);
1150
1149
  const lines = [
1151
1150
  '---',
1152
- `description: ${_yaml_scalar(description)}`,
1151
+ `description: ${_yaml_scalar(always_apply ? description : applies_when(description, meta))}`,
1153
1152
  `globs: ${globs.join(',')}`,
1154
1153
  `alwaysApply: ${always_apply ? 'true' : 'false'}`,
1155
1154
  '---',
@@ -1170,7 +1169,7 @@ export function _emit_windsurf_rule(source, target) {
1170
1169
  const lines = [
1171
1170
  '---',
1172
1171
  `trigger: ${trigger}`,
1173
- `description: ${_yaml_scalar(description)}`,
1172
+ `description: ${_yaml_scalar(always_apply ? description : applies_when(description, meta))}`,
1174
1173
  `globs: ${globs.join(',')}`,
1175
1174
  '---',
1176
1175
  '',
@@ -1448,23 +1447,21 @@ function _render_native_model_md(src_md, tier) {
1448
1447
  return text.replace(_MODEL_TIER_RE, `model: ${model}`);
1449
1448
  }
1450
1449
  export function generate_claude_skills(active_skill_names = null) {
1451
- if (partitionActive(MODULE_STATE.PROJECT_ROOT))
1452
- active_skill_names = new Set();
1453
1450
  if (!_exists(MODULE_STATE.SKILLS_SOURCE)) {
1454
1451
  process.stderr.write(' ⚠️ dist/agent-src/skills/ not found — skipping skills\n');
1455
1452
  return 0;
1456
1453
  }
1457
- let skills = _iterdirSorted(MODULE_STATE.SKILLS_SOURCE)
1454
+ const all_skills = _iterdirSorted(MODULE_STATE.SKILLS_SOURCE)
1458
1455
  .filter((p) => _isDir(p))
1459
1456
  .map((p) => path.basename(p))
1460
1457
  .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
1458
+ // ADR-236: withhold every skill `~/.claude/skills` holds — see `keepInProjectLayer`.
1459
+ let skills = keepInProjectLayer('skills', all_skills);
1461
1460
  if (active_skill_names !== null) {
1462
1461
  skills = skills.filter((s) => active_skill_names.has(s));
1463
1462
  }
1464
1463
  const skill_set = new Set(skills);
1465
- const command_slugs = partitionActive(MODULE_STATE.PROJECT_ROOT)
1466
- ? new Set()
1467
- : new Set([...iterCommands()].map(([, slug]) => slug));
1464
+ const command_slugs = _emitted_wrapper_slugs(new Set(all_skills));
1468
1465
  _mkdirp(MODULE_STATE.CLAUDE_SKILLS_DIR);
1469
1466
  const auto = _read_model_auto_switch() === 'auto';
1470
1467
  for (const item of _iterdirSorted(MODULE_STATE.CLAUDE_SKILLS_DIR)) {
@@ -1549,6 +1546,17 @@ function _nested_command_subpath(source_file) {
1549
1546
  const sub = logical.slice('commands/'.length).replace(/\.md$/, '');
1550
1547
  return sub.includes('/') ? sub : null;
1551
1548
  }
1549
+ /** The emitter's four skips, in one place — see `emittedWrapperSlugs`. */
1550
+ const _wrapper_is_emitted = (f, s, skills) => !skills.has(s) &&
1551
+ _nested_command_subpath(f) === null &&
1552
+ !is_claude_builtin_name(s) &&
1553
+ !claudeLayerHolds('skills', s);
1554
+ /** Wrapper slugs the command emitter really writes — see `emittedWrapperSlugs`. */
1555
+ function _emitted_wrapper_slugs(skill_names) {
1556
+ return new Set([...iterCommands()]
1557
+ .filter(([f, s]) => _wrapper_is_emitted(f, s, skill_names))
1558
+ .map(([, slug]) => slug));
1559
+ }
1552
1560
  /**
1553
1561
  * Project-scope `.claude/commands/<cluster>/<sub>.md` — the colon form.
1554
1562
  *
@@ -1561,15 +1569,12 @@ function _nested_command_subpath(source_file) {
1561
1569
  *
1562
1570
  * Emitting the nested form here lets `generate_claude_commands` stop wrapping
1563
1571
  * the clustered commands: one listing, same reachability, and reachability no
1564
- * longer depends on a global deploy. Dedup AND precedence: `commandsWithheld`.
1572
+ * longer depends on a global deploy. Dedup AND precedence: `commandWithheld`.
1565
1573
  *
1566
1574
  * ADR-003 (colon canonical for clusters) and ADR-044 (flat commands stay
1567
1575
  * hyphenated) both hold: flat commands are untouched and keep their wrapper.
1568
1576
  */
1569
1577
  export function generate_claude_project_commands(active_command_slugs = null) {
1570
- if (commandsWithheld(MODULE_STATE.PROJECT_ROOT))
1571
- active_command_slugs = new Set();
1572
- // Returns before the stale sweep — invariant, see `commandsWithheld`.
1573
1578
  if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
1574
1579
  return 0;
1575
1580
  }
@@ -1579,7 +1584,7 @@ export function generate_claude_project_commands(active_command_slugs = null) {
1579
1584
  continue;
1580
1585
  }
1581
1586
  const sub = _nested_command_subpath(source_file);
1582
- if (sub !== null) {
1587
+ if (sub !== null && !commandWithheld(`${sub}.md`)) {
1583
1588
  nested.push([source_file, sub]);
1584
1589
  }
1585
1590
  }
@@ -1613,8 +1618,6 @@ export function generate_claude_project_commands(active_command_slugs = null) {
1613
1618
  return count;
1614
1619
  }
1615
1620
  export function generate_claude_commands(active_command_slugs = null) {
1616
- if (partitionActive(MODULE_STATE.PROJECT_ROOT))
1617
- active_command_slugs = new Set();
1618
1621
  if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
1619
1622
  process.stderr.write(' ⚠️ src/domains/ not found — skipping commands\n');
1620
1623
  return 0;
@@ -1630,6 +1633,7 @@ export function generate_claude_commands(active_command_slugs = null) {
1630
1633
  let count = 0;
1631
1634
  let skipped = 0;
1632
1635
  let reserved = 0;
1636
+ let withheld = 0;
1633
1637
  let rendered = 0;
1634
1638
  const auto = _read_model_auto_switch() === 'auto';
1635
1639
  for (const [source_file, slug] of iterCommands()) {
@@ -1658,6 +1662,12 @@ export function generate_claude_commands(active_command_slugs = null) {
1658
1662
  reserved += 1;
1659
1663
  continue;
1660
1664
  }
1665
+ // ADR-236 per-name: the INSTALLER re-projects visible flat commands as
1666
+ // `~/.claude/skills/<slug>/SKILL.md` (install.ts:_apply_claude_flat_command_wrappers).
1667
+ if (claudeLayerHolds('skills', slug)) {
1668
+ withheld += 1;
1669
+ continue;
1670
+ }
1661
1671
  current_slugs.add(slug);
1662
1672
  const skill_dir = path.join(MODULE_STATE.CLAUDE_SKILLS_DIR, slug);
1663
1673
  _mkdirp(skill_dir);
@@ -1706,6 +1716,9 @@ export function generate_claude_commands(active_command_slugs = null) {
1706
1716
  if (reserved) {
1707
1717
  msg += ` (${reserved} withheld — Claude Code built-in name)`;
1708
1718
  }
1719
+ if (withheld > 0) {
1720
+ msg += ` (${withheld} withheld — ~/.claude/skills carries the wrapper)`;
1721
+ }
1709
1722
  if (removed_dirs) {
1710
1723
  msg += ` (${removed_dirs} stale dirs removed)`;
1711
1724
  }
@@ -2019,19 +2032,13 @@ export function generate_persona_symlinks() {
2019
2032
  _print(' ⚠️ dist/agent-src/personas/ not found — skipping personas');
2020
2033
  return 0;
2021
2034
  }
2022
- const personas = _rglobSorted(MODULE_STATE.PERSONAS_SOURCE, '*.md')
2023
- .filter((p) => _isFile(p) && path.basename(p, '.md') !== 'README')
2024
- .map((p) => path.basename(p))
2025
- // glob('*.md') is non-recursive in Python; mirror that — only top level.
2026
- .filter((name) => _isFile(path.join(MODULE_STATE.PERSONAS_SOURCE, name)));
2027
2035
  // Python uses PERSONAS_SOURCE.glob("*.md") (non-recursive). Re-derive
2028
2036
  // from a direct listing to match exactly.
2029
2037
  const direct = _iterdirSorted(MODULE_STATE.PERSONAS_SOURCE)
2030
2038
  .filter((p) => p.endsWith('.md') && _isFile(p) && path.basename(p, '.md') !== 'README')
2031
2039
  .map((p) => path.basename(p))
2032
2040
  .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
2033
- void personas;
2034
- const partition = personaPartition(MODULE_STATE.PROJECT_ROOT, direct);
2041
+ const partition = personaPartition(direct);
2035
2042
  const tool_dirs = _filter_tool_dirs(PERSONA_TOOL_DIRS);
2036
2043
  let total = 0;
2037
2044
  for (const [tool_dir, rel_prefix] of Object.entries(tool_dirs)) {
@@ -19,7 +19,7 @@
19
19
  * `.augment/` projections.
20
20
  *
21
21
  * NOTE (faithful literal): the thin-entry pointer keeps the verbatim
22
- * `Body: [`{rule_id}`](../../.agent-src.uncondensed/rules/{rule_id}.md)`
22
+ * `Body: [`{rule_id}`](../../dist/agent-src/rules/{rule_id}.md)`
23
23
  * Markdown link from the retired Python implementation. The same-basename `.py` twin
24
24
  * carries that literal, so the ADR-051 legacy-path guard exempts this `.ts`
25
25
  * file by twin-parity.
@@ -34,6 +34,7 @@ import * as fs from 'node:fs';
34
34
  import * as path from 'node:path';
35
35
  import process from 'node:process';
36
36
  import { fileURLToPath, pathToFileURL } from 'node:url';
37
+ import { pathOnlyRuleIds, triggerlessRuleIds, } from './_lib/rule_injection.js';
37
38
  import * as token_count from './_lib/token_count.js';
38
39
  const _HERE = fileURLToPath(import.meta.url);
39
40
  // REPO_ROOT = Path(__file__).resolve().parent.parent.parent — three dirs up
@@ -72,21 +73,85 @@ export function kernel_ids() {
72
73
  * visible instead of being absorbed into the pointer count
73
74
  * (road-to-trigger-delivered-rule-bodies 1.3).
74
75
  */
76
+ /**
77
+ * Rules whose ONLY triggers are path-shaped — reachable from `pre_tool_use`
78
+ * and from no other slot (road-to-delivery-for-every-host 3.2).
79
+ *
80
+ * They must never be thinned while the delivery concern is NOT bound on
81
+ * `pre_tool_use`, because a stub plus a slot nobody listens on is a rule that
82
+ * reaches the model at no scope at all. This is the same failure the
83
+ * `no_trigger_ids` exemption prevents, one step out: there the rule has nothing
84
+ * to match on, here it has nothing to match ON.
85
+ *
86
+ * Stated as a PROPERTY rather than a list of ids. If the concern is ever bound
87
+ * on `pre_tool_use` again, this exemption is what should be reconsidered — not
88
+ * three names someone has to remember.
89
+ *
90
+ * WHAT THIS EXEMPTION DOES **NOT** COVER — recorded 2026-09-08, R2 finding 1,
91
+ * because the silence read as coverage. The predicate is `every` trigger
92
+ * path-shaped. A rule with BOTH path and non-path triggers is therefore THINNED,
93
+ * and its path-shaped half then has no carrier at all: the stub carries no
94
+ * frontmatter (`condense.ts` writes it with `_writeText`, bypassing
95
+ * `_emit_claude_rule`'s host-native `paths:` key), `pre_tool_use` lost the
96
+ * binding under owner ruling E2, and `user_prompt_submit` never populates
97
+ * `openFiles`. Measured over the current `dist/router.json`: **18 such rules**,
98
+ * all non-kernel and all thinned — augment-edit-discipline, design-fidelity,
99
+ * doc-screenshot-hygiene, domain-adoption-policy,
100
+ * framework-neutrality-in-generic-skills, image-likeness-and-rights,
101
+ * laravel-translations, lethal-trifecta-guard, linked-projects-onboarding-gate,
102
+ * low-impact-corpus-privacy-floor, markdown-safe-codeblocks, onboarding-gate,
103
+ * persona-governance, php-coding, provider-lifecycle-discipline,
104
+ * roadmap-ci-steps-policy, roadmap-progress-sync, settings-ask-protocol.
105
+ *
106
+ * They do NOT reach the model at no scope: every non-path route
107
+ * (keyword / phrase / command) still delivers the body through the concern, and
108
+ * on `claude-code` they never had a separate host-native path route to lose —
109
+ * `_claude_paths_plan` emits no `paths:` for a mixed-trigger rule on purpose, so
110
+ * under `eager-all` they loaded UNCONDITIONALLY. What is lost is the difference
111
+ * between unconditional and prompt-triggered: a session that touches a matching
112
+ * file and says nothing that matches gets nothing.
113
+ *
114
+ * The labelled corpus cannot see this and the endpoint says so rather than
115
+ * implying otherwise — `model_rule_injection --endpoints` (b) now publishes the
116
+ * shipped `user_prompt_submit` reach beside the pre-registered reading, and
117
+ * every one of these 18 has at least one prompt-matching positive, so none
118
+ * appears in its "reachable only via a path trigger" column.
119
+ *
120
+ * WHY THIS IS NOT WIDENED TO `some` HERE. Measured: exempting all 18 moves the
121
+ * thin rule layer 23,592 -> 39,921 GPT tok (+16,329, +69 %), which is a budget
122
+ * move of a magnitude no agent may take, and it would erase most of the saving
123
+ * the flip is licensed on. Restoring a path route needs either that exemption
124
+ * under a re-anchored baseline or a `pre_tool_use` binding under a raised slot
125
+ * cap — both owner-reserved, so the closure is tracked rather than taken here.
126
+ * ADR-267's Consequences section names the receiver.
127
+ */
128
+ /**
129
+ * ONE spelling of the router-derived sets, shared with the injector (R2
130
+ * finding 11).
131
+ *
132
+ * `path_only_ids` and `no_trigger_ids` were each a second, hand-rolled
133
+ * implementation of `_lib/rule_injection.ts::pathOnlyRuleIds` /
134
+ * `triggerlessRuleIds` — verified identical over the current router at the time
135
+ * (symmetric difference empty), so drift risk rather than a live defect. It is
136
+ * exactly the hazard `THIN_ENTRY_MARKER`'s own comment argues against one
137
+ * screen below ("a gate that re-spelled this string would drift from the writer
138
+ * silently"), and the two consumers here are the PROJECTOR's exemption set and
139
+ * the INJECTOR's reachability set: they must not be able to disagree about which
140
+ * rules the delivery path can reach.
141
+ *
142
+ * The finding named `path_only_ids`. The defect-pattern sweep found the SECOND
143
+ * instance one function down — `no_trigger_ids` against `triggerlessRuleIds` —
144
+ * and it is delegated in the same change. Count: 2 of 2 duplicated
145
+ * router-derived sets in this file, 0 remaining.
146
+ */
147
+ function _router() {
148
+ return JSON.parse(fs.readFileSync(ROUTER, 'utf-8'));
149
+ }
150
+ export function path_only_ids() {
151
+ return pathOnlyRuleIds(_router());
152
+ }
75
153
  export function no_trigger_ids() {
76
- const data = JSON.parse(fs.readFileSync(ROUTER, 'utf-8'));
77
- const out = new Set();
78
- for (const tier of ['tier_1', 'tier_2']) {
79
- const entries = data[tier];
80
- if (!Array.isArray(entries))
81
- continue;
82
- for (const e of entries) {
83
- const obj = e;
84
- const t = obj.triggers;
85
- if (!Array.isArray(t) || t.length === 0)
86
- out.add(String(obj.id));
87
- }
88
- }
89
- return out;
154
+ return new Set(triggerlessRuleIds(_router()));
90
155
  }
91
156
  /**
92
157
  * id → workspaces for every non-kernel router entry (router.json schema v2,
@@ -179,6 +244,42 @@ function _trigger_hint(fm) {
179
244
  function _title(s) {
180
245
  return s.replace(/[A-Za-z]+/g, (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase());
181
246
  }
247
+ /**
248
+ * The marker that makes a projected entry recognisable as a stub.
249
+ *
250
+ * Exported so the WRITER and every DETECTOR share one definition. A gate that
251
+ * re-spelled this string would drift from the writer silently, and the failure
252
+ * would be invisible in exactly the direction that matters: a stub the gate
253
+ * fails to recognise reads as a complete rule body.
254
+ */
255
+ export const THIN_ENTRY_MARKER = '> Routed rule — load the body on trigger-match.';
256
+ /** Is this projected entry a pointer stub rather than a rule body? */
257
+ export function is_thin_entry(text) {
258
+ return text.includes(THIN_ENTRY_MARKER);
259
+ }
260
+ /**
261
+ * Where a stub tells a reader the body actually is.
262
+ *
263
+ * FIXED 2026-09-07: this pointed into the uncondensed source tree ADR-051
264
+ * retired — the literal path is deliberately NOT reproduced here, because
265
+ * `check_no_uncondensed_refs` matches the string and cannot tell a repair
266
+ * record from a live reference; `git log -S` on this file finds the old value.
267
+ * It exists in no checkout, so EVERY stub's fallback pointer
268
+ * resolved to nothing — invisible under `delivery`, where the hook loads the
269
+ * body from `dist/agent-src/rules` and never follows this link, and total under
270
+ * `thin`, where the pointer is the only path to the body there is.
271
+ *
272
+ * KNOWN LIMIT, stated rather than discovered later: one relative prefix cannot
273
+ * be right for every tree, because they sit at different depths — `.claude/rules`
274
+ * and `.cursor/rules` are two levels below the repo root and `.clinerules` is
275
+ * one. `../../` is correct for the first two, which are the only trees the
276
+ * shipped `lean_projection.hosts` default can thin. A stub in `.clinerules`
277
+ * needs an operator to have added `cline` to that list, and its pointer is off
278
+ * by one level. That is strictly better than the previous state, where the
279
+ * pointer was dead in all three, and it is recorded here so the remaining case
280
+ * is a known limit rather than a surprise.
281
+ */
282
+ const BODY_LINK_PREFIX = '../../dist/agent-src/rules/';
182
283
  /** Build the minimal progressive-disclosure pointer for a non-kernel rule. */
183
284
  export function thin_entry(rule_id, text) {
184
285
  const [fm] = split_frontmatter(text);
@@ -187,13 +288,25 @@ export function thin_entry(rule_id, text) {
187
288
  const title = _title(rule_id.replace(/-/g, ' '));
188
289
  const fires = hint ? ` Fires on: ${hint}.` : '';
189
290
  return (`## ${title}\n` +
190
- `> Routed rule — load the body on trigger-match.${fires} ${desc} ` +
191
- `Body: [\`${rule_id}\`](../../.agent-src.uncondensed/rules/${rule_id}.md)\n`);
291
+ `${THIN_ENTRY_MARKER}${fires} ${desc} ` +
292
+ `Body: [\`${rule_id}\`](${BODY_LINK_PREFIX}${rule_id}.md)\n`);
192
293
  }
193
294
  /** Map {filename: thin_or_full_text} for every rule. Kernel stays full. */
194
- export function build_thin(rules_dir = RULES_SOURCE, scope = null) {
295
+ export function build_thin(rules_dir = RULES_SOURCE, scope = null,
296
+ /**
297
+ * Optional sink for the D3 diagnostic
298
+ * (road-to-delivery-for-every-host 2.2).
299
+ *
300
+ * An `auto` rule the router gives NO trigger is already kept full-bodied by
301
+ * `noTrigger` below, which is the substantive protection and predates this
302
+ * roadmap. What did not exist is any way to SEE it happen: the rule is
303
+ * silently exempted, so an author who removes a rule's last trigger gets a
304
+ * silently eager rule and no signal. The line makes the exemption audible.
305
+ */
306
+ announce = null) {
195
307
  const kernel = kernel_ids();
196
308
  const noTrigger = no_trigger_ids();
309
+ const pathOnly = path_only_ids();
197
310
  const wsMap = scope !== null ? rule_workspaces_map() : new Map();
198
311
  const out = new Map();
199
312
  for (const p of _globSortedMd(rules_dir)) {
@@ -202,7 +315,14 @@ export function build_thin(rules_dir = RULES_SOURCE, scope = null) {
202
315
  if (!id_in_scope(stem, scope, kernel, wsMap, fm_workspaces(text))) {
203
316
  continue; // out of workspace scope — no body, no pointer line
204
317
  }
205
- const full = kernel.has(stem) || noTrigger.has(stem);
318
+ const full = kernel.has(stem) || noTrigger.has(stem) || pathOnly.has(stem);
319
+ if (announce !== null && noTrigger.has(stem) && !kernel.has(stem)) {
320
+ announce(`D3: trigger-less auto rule ${path.basename(p)} — kept full-bodied, never thinned`);
321
+ }
322
+ if (announce !== null && pathOnly.has(stem) && !kernel.has(stem) && !noTrigger.has(stem)) {
323
+ announce(`E2: path-only auto rule ${path.basename(p)} — kept full-bodied; its only triggers ` +
324
+ `are path-shaped and the delivery concern is not bound on pre_tool_use`);
325
+ }
206
326
  out.set(path.basename(p), full ? text : thin_entry(stem, text));
207
327
  }
208
328
  return out;
@@ -29,7 +29,8 @@ const confidenceBand = z.enum(['off', 'low', 'medium', 'high']);
29
29
  const onBlock = z.enum(['stop', 'ask', 'warn']);
30
30
  const onBlockFallback = z.enum(['stop', 'warn']);
31
31
  const modelAutoSwitch = z.enum(['auto', 'suggest', 'off']);
32
- const leanProjectionMode = z.enum(['eager-all', 'thin']);
32
+ const leanProjectionMode = z.enum(['eager-all', 'thin', 'delivery']);
33
+ const leanProjectionHost = z.enum(['claude-code', 'cursor', 'cline']);
33
34
  const projectionMode = z.enum(['legacy-all', 'scoped']);
34
35
  const memoryCadence = z.enum(['auto', 'always', 'never']);
35
36
  const projectAudience = z.enum(['self', 'internal', 'client', 'public']);
@@ -46,8 +47,16 @@ export const settingsSchema = z.object({
46
47
  rule_loading_tier: ruleLoadingTier.default('balanced').describe('Master switch for which rule tiers load and how cautiously the agent spends tokens. minimal = only the 9 kernel rules (cheapest, fewest guardrails). balanced = kernel + tier-1 (recommended default). full = kernel + tier-1 + tier-2 (most guardrails, highest token cost). custom = roll your own in agents/overrides/. LEGACY: superseded by discipline_profile — when that key is set it wins (mapping: minimal→off, balanced→essential, full→full).'),
47
48
  discipline_profile: disciplineProfile.optional().describe('The ONE runtime knob for the discipline-rule tier (successor of rule_loading_tier; council 2026-07-07). off = kernel only (~1x tokens). essential = kernel + the measured lift-carrying rules (~3.3x, keeps the weak-host discipline lift). full = everything (~11.7x, EXPERIMENTAL — residual lift over essential not established). auto = resolve per session against the evidence-gated NULL-lift disable-list in src/config/host-capabilities.yml (measured-null host → off, otherwise → essential). Optional and opt-in until the P1/P2 evidence gates pass (agents/roadmaps/road-to-discipline-profile-tiering.md); absent = legacy rule_loading_tier applies.'),
48
49
  lean_projection: z.object({
49
- mode: leanProjectionMode.default('eager-all').describe('How the per-tool projector emits the rule layer. eager-all = every rule body inlined into every projection (default, safe). thin = kernel rules full-bodied + non-kernel rules as router-resolved pointers (~45k GPT-tok lighter per session). EXPERIMENTAL: validate with the live A/B before flipping; one-flip revert to eager-all.'),
50
- }).default({ mode: 'eager-all' }),
50
+ mode: leanProjectionMode.default('eager-all').describe('How the per-tool projector emits the rule layer. eager-all = every rule body inlined into every projection (safe; what an ABSENT key still resolves to, per ADR-267 decision point 4). thin = kernel rules full-bodied + non-kernel rules as router-resolved pointers (~45k GPT-tok lighter per session), with no delivery mechanism. delivery = thin stubs PLUS the rule-inject hook concern, which delivers a body back on a trigger match; this is the value the SHIPPED TEMPLATE carries for Claude Code since ADR-267, and the activation charge for it is paid in src/config/hook-token-budget.json (user_prompt_submit slot sum 4,096 -> 16,384).'),
51
+ hosts: z.array(leanProjectionHost).default(['claude-code']).describe('Which hosts a thinning mode may thin (ADR-267). Absent resolves to [claude-code]. '
52
+ + 'Only the three hosts with a per-rule rule tree are accepted, and this enum is the '
53
+ + 'AUTHORING layer: an id outside it is a hard validation FAILURE here and under '
54
+ + 'validate_agent_settings, never a warning. resolveLeanProjectionHosts is the '
55
+ + 'READ-TIME backstop for a hand-edited .agent-settings.yml that passed neither, and '
56
+ + 'THERE the id is dropped with a warning (R2 finding 6 — the two layers answer '
57
+ + 'differently and neither said so). Every host outside this list receives exactly '
58
+ + 'what eager-all writes, asserted byte-for-byte by check_host_tree_parity.'),
59
+ }).default({ mode: 'eager-all', hosts: ['claude-code'] }),
51
60
  cost: z.object({
52
61
  budgets: z.object({
53
62
  daily: z.number().min(0).default(0).describe('Daily USD ceiling across all model calls. The agent warns at 50% / 75% / 90% and either stops or warns at 100% depending on cost.enforcement. Set 0 to disable the daily budget entirely.'),
@@ -229,6 +238,10 @@ export const settingsSchema = z.object({
229
238
  api_paths: z.array(z.string()).default([]).describe('Optional glob list that makes API-endpoint detection explicit instead of heuristic (e.g. ["app/Http/Controllers/Api/**", "src/pages/api/**"]). Empty (default) = a light path/extension heuristic that fails open.'),
230
239
  }),
231
240
  }),
241
+ continuity: z.object({
242
+ auto_record: z.enum(['on', 'off']).default('off').describe('Deterministic continuity-record writer at session end (road-to-continuity-writer-activation Phase 1). on = the session-eol concern writes the continuity_record capsule variant on Stop for a substantive session that has claimed a roadmap; every field is computed from on-disk state, with no model spend and no subprocess. off (default) = no automatic record — while session:recycle is still the normal path, a second producer on it before the parity evidence is in would be unverified.'),
243
+ run_checkpoints: z.enum(['on', 'off']).default('on').describe('Deterministic run-checkpoint production at session end (road-to-continuity-writer-activation Phase 1.4). on (default) = a session above the recycle threshold and inside a running roadmap contract leaves agents/runtime/state/checkpoints/<run>.json, so a killed run resumes from a derived checkpoint rather than from bookkeeping. off = no checkpoint; continuity writing, the recycle advisory and the context-fill surface are unaffected. Default ON because it is the behaviour the tree already had — the switch exists to make the three session-end handlers independently disableable, not to change what ships.'),
244
+ }),
232
245
  memory: z.object({
233
246
  cadence: memoryCadence.default('always').describe('Cadence of the 🧠 memory-visibility line after a memory-consulting step. always (default) = show whenever a memory type was asked; auto = show only when 3+ types were consulted (less noise); never = suppress. Distinct from rule_loading_tier — owns its own key since the 2026-06-01 untangle.'),
234
247
  review_threshold: z.number().int().min(0).default(10).describe('Maximum number of memory entries /memory:load surfaces inline before falling back to a summary view. Default 10 keeps the chat readable. Raise to see more candidates, lower to keep the context tight.'),
package/docs/MIGRATION.md CHANGED
@@ -17,7 +17,7 @@ releaser here on every `release:major`.
17
17
  | Surface | Committed | Deprecation notice due | Removal due | Reversal condition |
18
18
  |---|---|---|---|---|
19
19
  | Free-text `compatibility` skill-frontmatter field (`src/scripts/schemas/skill.schema.json`) | 2026-08-14 | shipped 2026-08-14 — `deprecated: true` + superseded-by note in the schema; `harness_compat` lands beside it, additively | next major after the notice — **not pinned here** (removal date is maintainer-owned, same stance as the manifest `tier` entry, whose `sunset` shipped `null`) | The public Agent-Skills spec makes `compatibility` load-bearing for cross-host portability, or an external consumer is found reading it. `harness_compat` is package-local; `compatibility` mirrors the public spec, so keeping the mirror stays cheaply reversible. Superseded by `harness_compat` (class) + `runtime_requires` (probeable detail); both users (`docx-authoring`, `pdf-tools`) already carry both fields, so nothing breaks on removal day. |
20
- | `code_graph` native code-graph engine (`code_graph.enabled`, the `code-intelligence` skill's native arm, `code_graph_nudge_hook`) | 2026-07-28 | next major after 9.x | **REVISED 2026-08-15 — no removal date; see below** | A consumer case the graph answers and disciplined grep cannot. Measured null: recall 0.365 vs grep 0.797 (`docs/CLAIMS.md` `code-graph-retrieval-null`), measured 2026-07-28 against a build predating the 2026-08-22 extractor repair. `enabled: false` stays the DEFAULT — "permanent" is retracted here on 2026-08-26, because this row's own revision below withdraws the removal commitment and a withdrawn schedule cannot leave a permanence behind it. **Revision (ADR-232 track, maintainer decision 2026-08-15).** The original row committed removal to 11.0; the tree reached 12.0.0 with the runtime paths still registered, i.e. the commitment was missed. Rather than quietly re-dating it, the commitment is withdrawn with its reason recorded, because the measurement changed what removal is worth: **the payload this deprecation existed for has already shipped** — the parser pair (`web-tree-sitter@0.24.7` / `tree-sitter-wasms@0.1.13`, ~51 MB unpacked) moved from `dependencies` to `devDependencies` ahead of schedule, so no consumer installs it. What source removal would still free is ~112 K against a 27 M tree (0.4 %). Against that it costs a breaking change across four consumer-visible surfaces (CLI verb, `code-intelligence` skill, `external-code-graph-interop` rule, `hooks.code_graph.enabled`) plus one surface that is re-plumbing rather than deletion: `_lib/auto_dispatch.ts` routes the `definition` / `references` lookup classes to `primitive: 'code-graph-query'`, and `_lib/judgment_ladder.ts` calls that live at Rung 0. There is also no cheap middle — `code_graph/detect.ts` handles the `consumer` / `scip` / `native` source kinds in one type union, so stripping only the native engine while keeping consumer-index interop is a redesign. **New commitment:** the surfaces stay registered and disabled; removal happens when a concrete reason appears (a maintenance cost that bites, a conflicting redesign, or a consumer asking), not on a date. This row stays in the table so the decision is visible rather than forgotten — a withdrawn commitment recorded is not the folklore this table exists to prevent; an unrecorded one would be. |
20
+ | `code_graph` native code-graph engine (`code_graph.enabled`, the `code-intelligence` skill's native arm, `code_graph_nudge_hook`) | 2026-07-28 | next major after 9.x | **REVISED 2026-08-15 — no removal date; see below** | A consumer case the graph answers and disciplined grep cannot. Measured null: recall 0.365 vs grep 0.797 (`docs/CLAIMS.md` `code-graph-retrieval-null`), measured 2026-07-28 against a build predating the 2026-08-22 extractor repair. `enabled: false` stays the DEFAULT — "permanent" is retracted here on 2026-08-26, because this row's own revision below withdraws the removal commitment and a withdrawn schedule cannot leave a permanence behind it. **Revision (ADR-232 track, maintainer decision 2026-08-15).** The original row committed removal to 11.0; the tree reached 12.0.0 with the runtime paths still registered, i.e. the commitment was missed. Rather than quietly re-dating it, the commitment is withdrawn with its reason recorded, because the measurement changed what removal is worth: **the payload this deprecation existed for has already shipped** — the parser pair (`web-tree-sitter@0.24.7` / `tree-sitter-wasms@0.1.13`, ~51 MB unpacked) moved from `dependencies` to `devDependencies` ahead of schedule, so no consumer installed it. **REVERSED 2026-09-07 by ADR-259 (as amended) — this clause no longer holds and is kept only so the reversal is legible.** Delivery and measurement are decoupled: `web-tree-sitter` is a runtime `dependency` again and the three loadable grammars are vendored at `src/vendor/grammars/`, so a consumer now DOES install the engine. The measured null above is untouched and `enabled: false` stays the default — what changed is that the reopen trigger ("a consumer case the graph answers") became reachable at all, which it was not while no consumer could receive the engine. The install delta is recorded below under "14.21.x — the code-graph engine ships to consumers". What source removal would still free is ~112 K against a 27 M tree (0.4 %). Against that it costs a breaking change across four consumer-visible surfaces (CLI verb, `code-intelligence` skill, `external-code-graph-interop` rule, `hooks.code_graph.enabled`) plus one surface that is re-plumbing rather than deletion: `_lib/auto_dispatch.ts` routes the `definition` / `references` lookup classes to `primitive: 'code-graph-query'`, and `_lib/judgment_ladder.ts` calls that live at Rung 0. There is also no cheap middle — `code_graph/detect.ts` handles the `consumer` / `scip` / `native` source kinds in one type union, so stripping only the native engine while keeping consumer-index interop is a redesign. **New commitment:** the surfaces stay registered and disabled; removal happens when a concrete reason appears (a maintenance cost that bites, a conflicting redesign, or a consumer asking), not on a date. This row stays in the table so the decision is visible rather than forgotten — a withdrawn commitment recorded is not the folklore this table exists to prevent; an unrecorded one would be. |
21
21
  | `telegraph-speak` condenser (`telegraph.speak`, `src/rules/telegraph-speak.md`, `src/scripts/_lib/compile_time_toggles.ts`, `src/scripts/validate_telegraph_carveouts.ts`) | 2026-07-29 | shipped 2026-07-29 — dormant by default, `speak` defaults `false` | **not pinned here** — removal is authorized in principle and deliberately not executed (ADR `telegraph/0002` § Decision part 3), pending a `prose_only` bench (~$0.80). The date is maintainer-owned, same stance as the `compatibility` row above | An output-side bench clears the kill-criterion bar, at which point `telegraph.speak: true` restores the feature intact. Measured basis for dormancy: median vs_terse **−9.27 %** (API) / −5.47 % (exact `cl100k_base`) — the condenser emits MORE than a plain "be terse". Tracked as a row rather than left dormant-and-unlisted: dormancy without a row is untracked in *both* directions, neither scheduled nor recorded as a keep. Deliberately NOT a due version (that would invent a commitment ADR 0002 declined to make) and NOT a permanent keep (that would contradict its authorized removal). |
22
22
 
23
23
  ### Row status — what is currently late, and who owns it
@@ -42,6 +42,110 @@ reason, which no arithmetic can.
42
42
  or that promises a future breaking removal, gets a row here in the same commit
43
43
  that makes the promise. A promise with no row is not tracked and will be missed.
44
44
 
45
+ ## 14.22.x — the code-graph engine gets readers, and the MCP install hint changes
46
+
47
+ 💡 advisory · 🔄 automatic — **one exception, below.**
48
+
49
+ The 14.21.x entry above shipped the engine to consumers. This one ships the
50
+ things that read it (`road-to-a-graph-that-is-shipped`, Phases 3 and 4).
51
+
52
+ ### Additive — nothing to do
53
+
54
+ - **Four new `code-graph` subcommands**, all answering questions about a DIFF
55
+ rather than a symbol: `impact --diff <rev>`, `tests-for <symbol>`,
56
+ `untested --diff <rev>`, `dead`. Each prints the resolution mechanisms it
57
+ accepted, the mechanisms it refused, and the graph's three-state staleness.
58
+ No existing subcommand changed, and no new top-level verb was added.
59
+ - **Five new MCP tools** on the existing stdio server — `graph_impact`,
60
+ `graph_tests_for`, `graph_dead`, `graph_query`, `graph_path` — taking the
61
+ catalogue from 31 tools to 36. Four are read-only; `graph_impact` runs
62
+ `git diff --name-only` to resolve its rev and declares `side_effect: shell`
63
+ for that reason.
64
+ - **A new `tests` edge relation**, derived in the build pass from a test file's
65
+ imports. `SCHEMA_VERSION` 3 → 4 and `GRAPH_STORE_VERSION` 3 → 4, so an
66
+ existing cache and its SQLite twin are refused and rebuilt on the next
67
+ `code-graph build`. The cache is derived and disposable
68
+ (ADR-129) — there is nothing to migrate, and a rebuild takes seconds.
69
+
70
+ ### The one thing to check — `dead` refuses by default
71
+
72
+ `agent-config code-graph dead` exits **1** with an empty list unless you either
73
+ supply an entry-point list (`--entry-points <file>`) or accept the gap
74
+ (`--accept-missing-exports`). That is deliberate: this engine records no
75
+ exportedness, so without a supplied list an exported-but-not-yet-imported public
76
+ symbol is indistinguishable from a dead one, and a confident false "dead" invites
77
+ a deletion the graph cannot justify. If you wire `dead` into a script, handle the
78
+ refusal — an exit 1 here is not a crash, and treating it as an empty dead list
79
+ is the false negative the refusal exists to prevent.
80
+
81
+ ### One consumer-visible string changed
82
+
83
+ `install_hint_stdio` in the published MCP tool catalogue was
84
+ `npx -y @event4u/agent-config mcp-server` and is now **`agent-config mcp-server`**
85
+ — the installed-binary form the setup docs already lead with. It resolves no
86
+ dist-tag, so the server you run is the version you installed rather than
87
+ whatever the registry served most recently. **It assumes `agent-config` is on
88
+ PATH.** If you read that field programmatically and rely on `npx`, keep using
89
+ the pinned form from [`mcp-server.md`](mcp-server.md) —
90
+ `npx -y @event4u/agent-config@<version> mcp-server` — which is unchanged and
91
+ still the documented client configuration.
92
+
93
+ ### Standing context cost
94
+
95
+ Registering the stdio MCP server now costs **4,876 tokens** of standing context
96
+ per session, up from 3,886 — the five graph tools measure 993 between them. It
97
+ remains roughly 4x under the ~20,000-token Tool Search deferral threshold at a
98
+ 200k window, so the surface still loads upfront. Measured, with the prior reading
99
+ kept: `agents/evidence/metrics/mcp-tool-standing-cost.jsonl`.
100
+
101
+ ## 14.21.x — the code-graph engine ships to consumers
102
+
103
+ 💡 advisory · 🔄 automatic — no action required.
104
+
105
+ `npm install @event4u/agent-config` now carries everything the native
106
+ code-graph engine needs, so `agent-config code-graph build` works on the next
107
+ command with no manual parser install (ADR-259, as amended 2026-09-07).
108
+
109
+ ### What changed
110
+
111
+ - `web-tree-sitter@0.24.7` moved from `devDependencies` to `dependencies`.
112
+ - Three compiled tree-sitter grammars are **vendored** in the published tarball
113
+ at `src/vendor/grammars/` — php, typescript (which also serves `.tsx`), and
114
+ javascript. These are the three `GRAMMAR_WASM` entries the engine can load.
115
+ - `tree-sitter-wasms` stays a `devDependency`. It is the source the vendored
116
+ copies are refreshed from, not a runtime dependency, so consumers do **not**
117
+ receive the 36-grammar pack.
118
+
119
+ ### Install delta, measured
120
+
121
+ Measured on 2026-09-07 with `npm pack --dry-run --json --ignore-scripts`,
122
+ against the same tree with the vendored directory excluded from `files[]`:
123
+
124
+ | Axis | Before | After | Delta |
125
+ |---|---|---|---|
126
+ | Compressed tarball | 9,821,600 B | 10,195,522 B | **+373,922 B** (+0.357 MiB) |
127
+ | Vendored grammars, on disk | 0 B | 3,802,618 B | **+3,802,618 B** (+3.63 MiB) |
128
+ | `web-tree-sitter` in `node_modules` | 0 B (dev-only) | ~389,120 B | **+~380 KiB** |
129
+
130
+ Per-grammar, uncompressed: `tree-sitter-php.wasm` 812,594 B ·
131
+ `tree-sitter-typescript.wasm` 2,342,690 B · `tree-sitter-javascript.wasm`
132
+ 647,334 B.
133
+
134
+ For contrast, the two rejected shapes: depending on `tree-sitter-wasms`
135
+ directly would have delivered all 36 grammars (51,765,657 B apparent, 49 MiB on
136
+ disk), and the 13-grammar set ADR-259 originally named measures 9,085,798 B
137
+ apparent / 1,016,804 B compressed — the latter would have put the tarball at
138
+ ~10.84 MB. Only three grammars are loadable today, so the other ten would have
139
+ been payload with no reader.
140
+
141
+ ### Rolling back
142
+
143
+ Set `hooks.code_graph.enabled: false` — which is already the default; the
144
+ engine ships disabled. Nothing runs the parsers unless you build a graph. To
145
+ drop the payload entirely, remove `src/vendor/grammars/` from `files[]` and
146
+ move `web-tree-sitter` back to `devDependencies`; the loader falls back to the
147
+ `tree-sitter-wasms` pack, which is this repository's own dev path.
148
+
45
149
  ## 8.x → 9.0.0 — consumer rule projection scoped by default
46
150
 
47
151
  9.0.0 flips the consumer rule-projection default to **scoped**: fresh