@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
@@ -5,7 +5,7 @@
5
5
  * ## Why the decision moved from per-run to per-directory
6
6
  *
7
7
  * The filter used to sit in `condense.ts::_scoped_rule_basenames()`, which runs
8
- * once per generation, so `partitionActive` — a claude-only fingerprint against
8
+ * once per generation, so the then-current `partitionActive` — a claude-only fingerprint against
9
9
  * `installed.lock` — decided for every host at once. That contradicted
10
10
  * `partitionEligibility.ts`'s own fail-safe reasoning, which refuses to withhold a
11
11
  * cursor artefact on claude's evidence because it "would deliver it nowhere", and
@@ -32,7 +32,7 @@
32
32
  * Four call sites consume it — the symlink emit plan, the cursor `.mdc` emitter,
33
33
  * the windsurf emitter, the augment projector — and four sites deciding this
34
34
  * separately is exactly how three of them came to disagree. It is in `src/install/`
35
- * beside {@link partitionActive} and {@link hostLayerCarries}, the two predicates
35
+ * beside {@link hostLayerCarries}, the predicate
36
36
  * it composes, rather than in the 2,700-line generator that calls it.
37
37
  *
38
38
  * ## The classification source is the PROJECTION, not the authored tree
@@ -48,7 +48,7 @@ import * as fs from 'node:fs';
48
48
  import * as os from 'node:os';
49
49
  import * as path from 'node:path';
50
50
 
51
- import { isExclusivelyPackageOnly, partitionActive } from './partitionEligibility.js';
51
+ import { isExclusivelyPackageOnly } from './partitionEligibility.js';
52
52
  import { hostLayerCarries, toolIdForProjectRuleDir } from './globalRuleLayers.js';
53
53
 
54
54
  export interface PartitionRulesOptions {
@@ -56,28 +56,21 @@ export interface PartitionRulesOptions {
56
56
  readonly toolDir: string;
57
57
  /** Candidate rule basenames, already scope- and manual-filtered. */
58
58
  readonly rules: readonly string[];
59
- /** Absolute project root — what `partitionActive` verifies against. */
59
+ /**
60
+ * Absolute project root.
61
+ *
62
+ * Retained after the 2026-09-07 change removed the only reader that needed
63
+ * it (`partitionActive`, which verified it against `installed.lock`). It
64
+ * stays because every call site already passes it and because a rule
65
+ * partition that cannot name the tree it is partitioning is harder to debug,
66
+ * not because a branch below reads it. Stated rather than quietly dropped.
67
+ */
60
68
  readonly projectRoot: string;
61
69
  /** Absolute directory the basenames resolve against for classification. */
62
70
  readonly rulesSource: string;
63
71
  readonly userHome?: string;
64
72
  /** Where the kept-full notice goes. Silent when omitted. */
65
73
  readonly announce?: (message: string) => void;
66
- /**
67
- * Override the partition-active decision instead of reading `installed.lock`.
68
- *
69
- * Split out for the reason `partitionEligibility.ts` states about its own
70
- * `personaWithheldFor`: "so the decision is testable in BOTH directions". A
71
- * test over the un-injected path can only assert whatever the machine happens
72
- * to be — and this one bit differs between a maintainer checkout (installed,
73
- * so active) and CI (nothing installed, so inactive), which made the first
74
- * version of `rule_partition_per_host.test.ts` green locally and red on every
75
- * CI shard. That is the environment-dependent gate this whole change exists to
76
- * remove, reproduced in its own test suite.
77
- *
78
- * Production callers omit it.
79
- */
80
- readonly active?: boolean;
81
74
  }
82
75
 
83
76
  /**
@@ -90,10 +83,13 @@ export interface PartitionRulesOptions {
90
83
  */
91
84
  export function partitionRulesForDir(opts: PartitionRulesOptions): string[] {
92
85
  const { toolDir, rules, projectRoot, rulesSource } = opts;
93
- const active = opts.active ?? partitionActive(projectRoot);
94
- if (!active) {
95
- return [...rules];
96
- }
86
+ void projectRoot;
87
+ // There is no repo-wide gate above this any more (owner decision, 2026-09-07).
88
+ // `partitionActive` used to run first and veto every directory on one
89
+ // claude-only version-and-fingerprint check against `installed.lock` — the
90
+ // exact borrowed-evidence extrapolation this module's own header refuses. The
91
+ // per-directory evidence below was always the stronger test; it is now the
92
+ // only one.
97
93
  const toolId = toolIdForProjectRuleDir(toolDir);
98
94
  if (toolId === null) {
99
95
  // An unmapped directory has no global layer this code can point at, so
@@ -33,8 +33,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
33
33
  import { ORIGIN_CWD_FALLBACK, resolve_project_root } from '../_lib/agent_settings.js';
34
34
  import {
35
35
  RECYCLE_ENVELOPE_MAX_BYTES,
36
- recycle_consumed_rel,
37
36
  recycle_envelope_rel,
37
+ resolvePredecessor,
38
38
  } from '../_lib/recycle_envelope_paths.js';
39
39
  import {
40
40
  CAPSULE_SCHEMA_VERSION,
@@ -84,30 +84,7 @@ export function templateEnvelope(): Record<string, unknown> {
84
84
  };
85
85
  }
86
86
 
87
- /**
88
- * Which session this one continues — read, never composed.
89
- *
90
- * The chain is derivable without any new artifact: when this session started,
91
- * its reader consumed the predecessor's record and MOVED it to this session's
92
- * consumed path (`recycle_consumed_rel`). So the file sitting there is, by
93
- * construction, the record this session resumed from, and its `session_id` is
94
- * the predecessor.
95
- *
96
- * Returns the explicit string `none` when there is nothing there. `none` is a
97
- * CLAIM — "this session starts a chain" — and is deliberately not an empty
98
- * value: a reader must be able to tell "no predecessor" from "nobody wrote the
99
- * field", because only the second one is a reason to go looking.
100
- */
101
- export function resolvePredecessor(projectRoot: string, sessionId: string | null): string {
102
- try {
103
- const consumed = path.join(projectRoot, recycle_consumed_rel(sessionId));
104
- const raw = JSON.parse(fs.readFileSync(consumed, 'utf-8')) as { session_id?: unknown };
105
- const prior = String(raw.session_id ?? '').trim();
106
- return prior === '' ? 'none' : prior;
107
- } catch {
108
- return 'none';
109
- }
110
- }
87
+ export { resolvePredecessor };
111
88
 
112
89
  interface ParsedArgv {
113
90
  ok: boolean;
@@ -32,7 +32,13 @@ import * as path from 'node:path';
32
32
 
33
33
  import { projectStoreSlug } from '../_lib/cc_transcript.js';
34
34
  import { derive_session_tag, list_sessions, read_entries } from '../chat_history.js';
35
- import { eolSessionKey, readEolCounters, type StoredEolCounters } from '../_lib/session_eol.js';
35
+ import {
36
+ eolSessionKey,
37
+ is_substantive,
38
+ readEolCounters,
39
+ type StoredEolCounters,
40
+ SUBSTANTIVE_TOKEN_FLOOR,
41
+ } from '../_lib/session_eol.js';
36
42
 
37
43
  export const DEFAULT_CAP = 15;
38
44
  /**
@@ -42,24 +48,16 @@ export const DEFAULT_CAP = 15;
42
48
  * on the observed store 10 of 217 sessions are filtered (~5%), so 3× covers
43
49
  * that comfortably while keeping the per-file scan bounded.
44
50
  */
51
+ /**
52
+ * Re-exported from `_lib/session_eol.ts`, where both now live so the Stop-path
53
+ * hook can reach them without importing this CLI module. Kept exported HERE
54
+ * because the resume picker is the surface every existing importer names.
55
+ */
56
+ export { is_substantive, SUBSTANTIVE_TOKEN_FLOOR };
57
+
45
58
  const FETCH_OVERSHOOT = 3;
46
59
  const SUMMARY_SNIPPET_CHARS = 120;
47
60
 
48
- /**
49
- * Committed substantive-content floor, in parsed transcript tokens (billable
50
- * input of the last main-chain assistant record, per `cc_transcript.ts`) — the
51
- * OR-arm for a session that produced real discussion without ever calling a
52
- * tool.
53
- *
54
- * Derivation (`agents/evidence/analysis/handoff-substantive-threshold.md`,
55
- * 217 sessions of the local Claude store, 2026-08-10): 206 of the 207 sessions
56
- * carrying an assistant record also carry a `tool_use` block, so this arm
57
- * serves the ~0.5 % tail and the hosts whose transcripts log no tool blocks at
58
- * all. 10 000 sits **25× below** the p10 of real working sessions (254 939),
59
- * so it cannot hide one, and above a single trivial exchange. Changing it is a
60
- * PR citing evidence, never a drive-by edit.
61
- */
62
- export const SUBSTANTIVE_TOKEN_FLOOR = 10_000;
63
61
 
64
62
  export type HandoffSource = 'chat-history' | 'claude-transcript' | 'codex-session';
65
63
 
@@ -454,34 +452,6 @@ function _sort_key(s: HandoffSession): string {
454
452
  return s.endedAt ?? s.startedAt ?? '';
455
453
  }
456
454
 
457
- /**
458
- * Does this session hold anything worth resuming?
459
- *
460
- * `≥ 1 assistant turn AND (≥ 1 tool call OR parsed tokens ≥ the committed
461
- * floor)`, read from the counts-only session-eol state.
462
- *
463
- * **Fail-open, deliberately** (Phase 1.2): absent, unreadable or mis-shaped
464
- * state LISTS the candidate rather than filtering it, and a `tool_calls` key
465
- * missing from a state file written before that counter existed reads as
466
- * *unknown*, never as zero. A wrongly listed candidate is noise the user
467
- * scrolls past; a wrongly hidden one is data loss they cannot even see.
468
- */
469
- export function is_substantive(counters: StoredEolCounters | null): boolean {
470
- if (counters === null) return true;
471
- // Every "unknown" below is a NUMBER test, not an `=== undefined` test.
472
- // Absent, null and NaN are all unknown, and each one reaches this code by
473
- // a real path: a state file written before the counter existed is absent,
474
- // `JSON.stringify` turns NaN into null on the way to disk, and a
475
- // half-written file yields a parseable object with missing keys. Reading
476
- // any of them as "counted zero" hides a session that did real work.
477
- if (!Number.isFinite(counters.assistant_records)) return true;
478
- if ((counters.assistant_records as number) < 1) return false;
479
- if (!Number.isFinite(counters.tool_calls)) return true;
480
- if ((counters.tool_calls as number) >= 1) return true;
481
- if (!Number.isFinite(counters.final_context_tokens)) return true;
482
- return (counters.final_context_tokens as number) >= SUBSTANTIVE_TOKEN_FLOOR;
483
- }
484
-
485
455
  function _derive_tag(id: string): string | null {
486
456
  try {
487
457
  return derive_session_tag(id);
@@ -463,8 +463,9 @@ print_version() {
463
463
  # dependency since the road-to-credible-install Phase 1 flip — the consumer
464
464
  # runtime tree ships no tsx, so every consumer-reachable surface must resolve
465
465
  # a precompiled bundle instead (hooks → dist/hooks/dispatch.js, mcp:run →
466
- # dist/mcp/server.mjs, the `_cli` delegate commands → dist/cli-delegate/,
467
- # see exec_ts below). This resolver therefore only ever fires in a DEV tree
466
+ # dist/mcp/server.mjs, the `_cli` AND `dist/agent-src/scripts/` delegate
467
+ # commands → dist/cli-delegate/, see exec_ts below). This resolver therefore
468
+ # only ever fires in a DEV tree
468
469
  # (node_modules/.bin/tsx present) or as a genuine last-resort fallback.
469
470
  # `npx tsx` is that last resort: npx runs against the CONSUMER project's cwd,
470
471
  # so its npm config and devEngines/engines constraints apply — a consumer
@@ -495,14 +496,23 @@ require_tsx() {
495
496
  exit 127
496
497
  }
497
498
 
498
- # Map a `src/scripts/_cli/<name>.ts` path to its precompiled bundle entry
499
- # under `dist/cli-delegate/<name>.js`, echoing the bundle path when it exists
500
- # and node is available. Empty output means "no bundle — use the tsx path".
499
+ # Map a delegate source path to its precompiled bundle entry under
500
+ # `dist/cli-delegate/<name>.js`, echoing the bundle path when it exists and node
501
+ # is available. Empty output means "no bundle — use the tsx path".
501
502
  #
502
- # Scoped to `_cli/` on purpose: that directory is the consumer-tier delegate
503
- # command surface (`sync`, `doctor`, `validate`, `upgrade`, …) and its
504
- # basenames are unique, so the mapping is collision-free. Other exec_ts
505
- # callers (resolve_script results, the work engine, …) are unaffected.
503
+ # TWO source roots are mapped, both bundled into that one outdir:
504
+ # - `src/scripts/_cli/` — the consumer-tier delegate commands (`sync`,
505
+ # `doctor`, `validate`, `upgrade`, …), per ADR-204.
506
+ # - `dist/agent-src/scripts/` — the roadmap command family (`roadmap:progress`,
507
+ # `roadmap:set-step`, `roadmap:archive`, `roadmap:gates`, `stubs:due`).
508
+ # ADR-204 closed the `npx tsx` exposure for `_cli/` and left this root on it:
509
+ # these commands still reached `require_tsx`, and `npx` resolves against the
510
+ # CONSUMER's cwd, so a project pinning `devEngines.runtime` away from the
511
+ # local Node hard-failed with EBADDEVENGINES.
512
+ #
513
+ # Basenames are unique ACROSS both roots (`_cli/` is all `cmd_*`, agent-src is
514
+ # none), so one flat outdir stays collision-free. Other exec_ts callers (the
515
+ # remaining resolve_script results, the work engine, …) are unaffected.
506
516
  #
507
517
  # In a DEV tree the bundle is also suppressed when any `src/**/*.ts` is newer
508
518
  # than it — see cli_delegate_bundle_is_stale.
@@ -510,6 +520,7 @@ cli_delegate_bundle() {
510
520
  local ts_abs="$1"
511
521
  case "$ts_abs" in
512
522
  "$PACKAGE_ROOT/src/scripts/_cli/"*) ;;
523
+ "$PACKAGE_ROOT/dist/agent-src/scripts/"*) ;;
513
524
  *) return 0 ;;
514
525
  esac
515
526
  local name
@@ -540,12 +551,40 @@ cli_delegate_bundle() {
540
551
  # edit is never silently ignored in favour of a stale compile.
541
552
  cli_delegate_bundle_is_stale() {
542
553
  local bundle="$1"
554
+ # The dev-tree test is package-local tsx, NOT `-d src`. The published tarball
555
+ # SHIPS `src/` (files[] carries `src/scripts/` and `src/agent-src/scripts/`),
556
+ # so the `-d src` test called every consumer install a dev tree — and npm's
557
+ # extraction leaves shipped sources newer than the prepack-built bundle.
558
+ # Measured 2026-09-07 on a global 14.21.0 install: 1348 shipped `src/**/*.ts`
559
+ # were newer than `dist/cli-delegate/cmd_versions.js`, so `cli_delegate_bundle`
560
+ # returned empty and `agent-config versions` fell through to `npx tsx` —
561
+ # ADR-204's fast path was inert in every published install, which is also why
562
+ # it never protected the roadmap family this change adds.
563
+ # Without a local tsx the only alternative to a "stale" bundle is `npx tsx`
564
+ # against the CONSUMER's npm config, which is strictly worse than a compile
565
+ # that may lag a source edit no consumer can make.
566
+ [[ -x "$PACKAGE_ROOT/node_modules/.bin/tsx" ]] || return 1
543
567
  [[ -d "$PACKAGE_ROOT/src" ]] || return 1
544
568
  local newer
545
569
  newer="$(find "$PACKAGE_ROOT/src" -name '*.ts' -newer "$bundle" -print -quit 2>/dev/null)"
546
570
  [[ -n "$newer" ]]
547
571
  }
548
572
 
573
+ # Echo a usable `dist/cli-delegate/` bundle for a path RELATIVE to that dir, or
574
+ # nothing. Used by the two probes that run on EVERY invocation — the pin
575
+ # resolver and the update banner — which have their own soft tsx lookup rather
576
+ # than going through exec_ts. Both reached `npx tsx` in a consumer install and
577
+ # therefore printed the consumer's own npm failure (EBADDEVENGINES on a
578
+ # mismatched `devEngines`) on stderr before every single command.
579
+ delegate_aux_bundle() {
580
+ local rel="$1"
581
+ local bundle="$PACKAGE_ROOT/dist/cli-delegate/$rel"
582
+ [[ -f "$bundle" ]] || return 0
583
+ command -v node >/dev/null 2>&1 || return 0
584
+ cli_delegate_bundle_is_stale "$bundle" && return 0
585
+ printf '%s' "$bundle"
586
+ }
587
+
549
588
  # Run an absolute script path. The argument is an absolute path that may carry
550
589
  # a `.py` (legacy) or `.ts` extension; we resolve the `.ts` twin.
551
590
  #
@@ -1622,20 +1661,28 @@ maybe_pin_reexec() {
1622
1661
  help|--help|-h|--version|-V|update|migrate|init|"") return 0 ;;
1623
1662
  esac
1624
1663
  # Best-effort: soft tsx probe (no hard-exit — this runs around dispatch).
1625
- local tsx_bin=""
1626
- if [[ -x "$PACKAGE_ROOT/node_modules/.bin/tsx" ]]; then
1627
- tsx_bin="$PACKAGE_ROOT/node_modules/.bin/tsx"
1628
- elif command -v npx >/dev/null 2>&1; then
1629
- tsx_bin="npx tsx"
1664
+ local runner=""
1665
+ local target=""
1666
+ local aux
1667
+ aux="$(delegate_aux_bundle '_lib/pin_resolver.js')"
1668
+ if [[ -n "$aux" ]]; then
1669
+ runner="node"
1670
+ target="$aux"
1671
+ elif [[ -x "$PACKAGE_ROOT/node_modules/.bin/tsx" ]]; then
1672
+ runner="$PACKAGE_ROOT/node_modules/.bin/tsx"
1673
+ target="$PACKAGE_ROOT/src/scripts/_lib/pin_resolver.ts"
1630
1674
  else
1675
+ # Deliberately NO `npx tsx` last resort here: this probe runs before EVERY
1676
+ # command, and npx resolves against the CONSUMER's cwd, so a project whose
1677
+ # `devEngines` rejects the local Node made every invocation print npm's
1678
+ # error. A missing runner means no pin re-exec, which is the same
1679
+ # best-effort outcome the `|| true` below already accepts.
1631
1680
  return 0
1632
1681
  fi
1633
1682
  local installed
1634
1683
  installed="$(print_version)"
1635
1684
  [[ -z "$installed" || "$installed" == "unknown" ]] && return 0
1636
- # shellcheck disable=SC2086
1637
- $tsx_bin "$PACKAGE_ROOT/src/scripts/_lib/pin_resolver.ts" \
1638
- --cwd "$CONSUMER_ROOT" --installed "$installed" -- "$@" || true
1685
+ "$runner" "$target" --cwd "$CONSUMER_ROOT" --installed "$installed" -- "$@" || true
1639
1686
  }
1640
1687
 
1641
1688
  # Post-subcommand banner: best-effort daily update-check notice on
@@ -1649,19 +1696,22 @@ run_update_check_banner() {
1649
1696
  case "$cmd" in
1650
1697
  help|--help|-h|--version|-V|"") return 0 ;;
1651
1698
  esac
1652
- # Best-effort: soft tsx probe (no hard-exit — banner is post-dispatch).
1653
- local tsx_bin=""
1654
- if [[ -x "$PACKAGE_ROOT/node_modules/.bin/tsx" ]]; then
1655
- tsx_bin="$PACKAGE_ROOT/node_modules/.bin/tsx"
1656
- elif command -v npx >/dev/null 2>&1; then
1657
- tsx_bin="npx tsx"
1699
+ # Best-effort: bundle first, then a dev-tree tsx. No `npx tsx` last resort —
1700
+ # same reason as maybe_pin_reexec: npx runs against the CONSUMER's npm config.
1701
+ local runner=""
1702
+ local target=""
1703
+ local aux
1704
+ aux="$(delegate_aux_bundle 'check_update_banner.js')"
1705
+ if [[ -n "$aux" ]]; then
1706
+ runner="node"
1707
+ target="$aux"
1708
+ elif [[ -x "$PACKAGE_ROOT/node_modules/.bin/tsx" && -f "$PACKAGE_ROOT/src/scripts/check_update_banner.ts" ]]; then
1709
+ runner="$PACKAGE_ROOT/node_modules/.bin/tsx"
1710
+ target="$PACKAGE_ROOT/src/scripts/check_update_banner.ts"
1658
1711
  else
1659
1712
  return 0
1660
1713
  fi
1661
- local banner_script="$PACKAGE_ROOT/src/scripts/check_update_banner.ts"
1662
- [[ -f "$banner_script" ]] || return 0
1663
- # shellcheck disable=SC2086
1664
- $tsx_bin "$banner_script" --cwd "$CONSUMER_ROOT" 2>/dev/null || true
1714
+ "$runner" "$target" --cwd "$CONSUMER_ROOT" 2>/dev/null || true
1665
1715
  }
1666
1716
 
1667
1717
  # Global `--root <path>` / `--root=<path>` parsing (Step 8 A3).
@@ -191,8 +191,8 @@ export function classifyLookup(
191
191
  reason:
192
192
  primitive === p.primitive
193
193
  ? `lookup-class ${p.cls} — deterministic primitive, no spawn`
194
- : `lookup-class ${p.cls} — capped grep; the code-graph accelerant is ` +
195
- 'gated on hooks.code_graph.enabled and it is not on',
194
+ : `lookup-class ${p.cls} — capped grep; the code-graph accelerant needs a ` +
195
+ 'usable graph and the caller passed none',
196
196
  };
197
197
  }
198
198
  }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Do the two lists that name `~/.claude/<family>` agree?
3
+ *
4
+ * **The defect this exists to make visible.**
5
+ *
6
+ * Two independently authored constants name the SAME directories and, until
7
+ * 2026-09-07, referenced each other nowhere:
8
+ *
9
+ * - the DELIVERY side — `claudeLayerCarriage.CLAUDE_ARTEFACT_DIRS` and
10
+ * `hostLayerFingerprint.hostLayerInputs` — which decides what the project
11
+ * layer withholds because the host layer already carries it;
12
+ * - the READER side — `skill_catalogue.DEFAULT_CATALOGUE_ROOTS` — which
13
+ * decides which trees the skill ranker sees.
14
+ *
15
+ * `git grep` over the dependency sets of both returned two disjoint sets. So
16
+ * `~/.claude/skills` was a directory the delivery side treated as authoritative
17
+ * and the reader side did not read AT ALL — for as long as both existed, and
18
+ * with nothing anywhere that could notice.
19
+ *
20
+ * The cost was measured on 2026-09-07: the reader resolved ONE root and stopped,
21
+ * so `suggest_skill_for_task` and the `skill-route` concern ranked whichever
22
+ * single tree came first and reported it as if it were the catalogue. A consumer
23
+ * whose project carries its own skills while the shipped set sits in
24
+ * `~/.claude/skills` got an answer over half its catalogue, with no signal that
25
+ * it was half.
26
+ *
27
+ * **Why a comparison and not an instruction.**
28
+ *
29
+ * The reasoning failure behind it — repairing a mechanism's PARAMETER while
30
+ * inheriting its SHAPE — is model-carried and nothing can observe it. But the
31
+ * RESULT is two lists of strings, and two lists of strings can be compared. This
32
+ * module is that comparison, so the omission stops being a thing someone has to
33
+ * notice and becomes a thing that reds.
34
+ *
35
+ * **What it does NOT claim.**
36
+ *
37
+ * It compares the directories the two sides NAME. It does not establish that a
38
+ * reader ranks well, that a family needs a reader, or that the host actually
39
+ * loads any of it. A family with no reader is legal and common — it just has to
40
+ * SAY so, which is what {@link FAMILIES_WITHOUT_READER} is for.
41
+ */
42
+
43
+ import { CLAUDE_ARTEFACT_DIRS, type ClaudeArtefactFamily } from '../../install/claudeLayerCarriage.js';
44
+ import { hostLayerInputs } from '../../install/hostLayerFingerprint.js';
45
+ import {
46
+ DEFAULT_CATALOGUE_ROOTS,
47
+ HOST_CATALOGUE_ROOT,
48
+ PROJECT_CATALOGUE_ROOTS,
49
+ } from './skill_catalogue.js';
50
+
51
+ /**
52
+ * Families the delivery side knows and NO reader resolves — each with the reason.
53
+ *
54
+ * An entry here is a declaration, not a suppression: a family absent from both
55
+ * this map and the reader coverage below is the omission this module reds on.
56
+ * Adding a reader later means deleting one line here.
57
+ */
58
+ export const FAMILIES_WITHOUT_READER: Readonly<Partial<Record<ClaudeArtefactFamily, string>>> = {
59
+ personas: 'personas are selected by name from a catalog, never ranked over a tree',
60
+ commands: 'commands reach the host by path; no ranker reads the commands tree',
61
+ };
62
+
63
+ /** Family → the project- and host-relative roots a reader must cover. */
64
+ const READER_COVERAGE: Readonly<Partial<Record<ClaudeArtefactFamily, () => string[]>>> = {
65
+ skills: () => [...PROJECT_CATALOGUE_ROOTS, `~/${HOST_CATALOGUE_ROOT}`],
66
+ };
67
+
68
+ /** One disagreement between the two sides. */
69
+ export interface ParityFinding {
70
+ readonly family: string;
71
+ readonly reason: string;
72
+ }
73
+
74
+ /**
75
+ * Every disagreement, or an empty array.
76
+ *
77
+ * Total function, no I/O: it reads constants, so the verdict is the same on every
78
+ * machine — which is the point. The reader lists are injectable so the polarity
79
+ * of each branch is testable without editing a production constant.
80
+ */
81
+ export function catalogueLayerParity(
82
+ deliveryDirs: Readonly<Record<string, string>> = CLAUDE_ARTEFACT_DIRS,
83
+ coverage: Readonly<Record<string, () => string[]>> = READER_COVERAGE as Readonly<
84
+ Record<string, () => string[]>
85
+ >,
86
+ withoutReader: Readonly<Record<string, string>> = FAMILIES_WITHOUT_READER as Readonly<
87
+ Record<string, string>
88
+ >,
89
+ ): ParityFinding[] {
90
+ const findings: ParityFinding[] = [];
91
+ for (const [family, projectDir] of Object.entries(deliveryDirs)) {
92
+ const cover = coverage[family];
93
+ if (cover === undefined) {
94
+ if (withoutReader[family] === undefined) {
95
+ findings.push({
96
+ family,
97
+ reason:
98
+ `the delivery side treats \`${projectDir}\` as an artefact source, but no ` +
99
+ 'reader covers it and no reason is declared in FAMILIES_WITHOUT_READER',
100
+ });
101
+ }
102
+ continue;
103
+ }
104
+ const roots = cover();
105
+ // The PROJECT-relative directory the delivery side names must be a root.
106
+ if (!roots.includes(projectDir)) {
107
+ findings.push({
108
+ family,
109
+ reason: `the reader does not read \`${projectDir}\`, which the delivery side names`,
110
+ });
111
+ }
112
+ // And so must the HOST-relative twin — the leg that was missing entirely.
113
+ const hostForm = `~/${projectDir.replace(/^\.claude\//u, '.claude/')}`;
114
+ if (!roots.includes(hostForm)) {
115
+ findings.push({
116
+ family,
117
+ reason: `the reader does not read \`${hostForm}\`, the host layer the partition withholds against`,
118
+ });
119
+ }
120
+ }
121
+ return findings;
122
+ }
123
+
124
+ /**
125
+ * The three constants that must name the same `~/.claude/skills`.
126
+ *
127
+ * `hostLayerInputs` is the third author of that path — the fingerprint side —
128
+ * and it is included because a rename there would silently un-verify the layer
129
+ * the reader reads. Returns the resolved absolute paths for one home so a caller
130
+ * can compare them directly.
131
+ */
132
+ export function hostSkillsDirTriple(home: string): {
133
+ readonly fingerprint: string | null;
134
+ readonly delivery: string;
135
+ readonly reader: string;
136
+ } {
137
+ const fp = hostLayerInputs(home).find((l) => l.label === 'skills');
138
+ return {
139
+ fingerprint: fp?.root ?? null,
140
+ delivery: `${home}/${CLAUDE_ARTEFACT_DIRS.skills}`,
141
+ reader: `${home}/${HOST_CATALOGUE_ROOT}`,
142
+ };
143
+ }
144
+
145
+ /** For the test's own message, and to prove the list is read rather than assumed. */
146
+ export const READER_ROOT_LABELS: readonly string[] = [...DEFAULT_CATALOGUE_ROOTS];