@event4u/agent-config 14.21.0 → 14.22.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 (181) 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 +198 -0
  5. package/CREDITS.md +37 -0
  6. package/dist/agent-src/commands/analyze/inbox.md +380 -5
  7. package/dist/agent-src/commands/analyze/repo.md +4 -2
  8. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
  9. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
  10. package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
  11. package/dist/agent-src/rules/token-efficiency.md +26 -3
  12. package/dist/agent-src/scripts/_cli_entry.ts +51 -0
  13. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  14. package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
  15. package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
  16. package/dist/agent-src/scripts/stubs_due.ts +4 -18
  17. package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
  18. package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
  19. package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
  20. package/dist/agent-src/templates/agent-settings.md +0 -1
  21. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  22. package/dist/cli-delegate/_cli_entry.js +8 -0
  23. package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
  24. package/dist/cli-delegate/archival_sweep.js +8 -0
  25. package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
  26. package/dist/cli-delegate/check_update_banner.js +923 -0
  27. package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
  28. package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
  29. package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-BIXEDQPZ.js} +6 -0
  30. package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
  31. package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
  32. package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
  33. package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
  34. package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
  35. package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
  36. package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
  37. package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
  38. package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
  39. package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
  40. package/dist/cli-delegate/cmd_conformance.js +2 -2
  41. package/dist/cli-delegate/cmd_export.js +1 -1
  42. package/dist/cli-delegate/cmd_handoff.js +26 -2
  43. package/dist/cli-delegate/cmd_prune.js +1 -1
  44. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  45. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  46. package/dist/cli-delegate/cmd_sync.js +1 -1
  47. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  48. package/dist/cli-delegate/cmd_validate.js +1 -1
  49. package/dist/cli-delegate/dashboard_mode.js +16 -0
  50. package/dist/cli-delegate/gate_budget.js +24 -0
  51. package/dist/cli-delegate/gate_execute.js +25 -0
  52. package/dist/cli-delegate/guarded_baseline.js +18 -0
  53. package/dist/cli-delegate/resume_probe.js +22 -0
  54. package/dist/cli-delegate/roadmap_gates.js +638 -0
  55. package/dist/cli-delegate/roadmap_set_step.js +173 -0
  56. package/dist/cli-delegate/stub_queue.js +47 -0
  57. package/dist/cli-delegate/stubs_due.js +38 -0
  58. package/dist/cli-delegate/ticket_bundles.js +59 -0
  59. package/dist/cli-delegate/update_roadmap_progress.js +60 -0
  60. package/dist/discovery/deprecation-report.md +1 -1
  61. package/dist/discovery/discovery-manifest.json +8 -8
  62. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  63. package/dist/discovery/discovery-manifest.summary.md +1 -1
  64. package/dist/discovery/dormancy-report.md +1 -1
  65. package/dist/discovery/orphan-report.md +1 -1
  66. package/dist/discovery/packs.json +2 -2
  67. package/dist/discovery/trust-report.md +1 -1
  68. package/dist/discovery/workspaces.json +2 -2
  69. package/dist/hooks/dispatch.js +1193 -917
  70. package/dist/install/claudeLayerCarriage.js +237 -0
  71. package/dist/install/claudePathsPlan.js +79 -0
  72. package/dist/install/hostLayerFingerprint.js +1 -1
  73. package/dist/install/install.mjs +6 -0
  74. package/dist/install/partitionEligibility.js +231 -117
  75. package/dist/install/ruleLayerPartition.js +10 -7
  76. package/dist/mcp/registry-manifest.json +2 -2
  77. package/dist/mcp/server.json +2 -2
  78. package/dist/mcp/server.mjs +39219 -38995
  79. package/dist/scripts/condense.js +32 -25
  80. package/docs/MIGRATION.md +49 -1
  81. package/docs/contracts/settings-classes.md +1 -1
  82. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
  83. package/package.json +7 -4
  84. package/src/agent-src/scripts/_cli_entry.ts +51 -0
  85. package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  86. package/src/agent-src/scripts/roadmap_gates.ts +4 -20
  87. package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
  88. package/src/agent-src/scripts/stubs_due.ts +4 -18
  89. package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
  90. package/src/config/continuity-surface.json +509 -0
  91. package/src/config/estate-count-budget.json +1 -1
  92. package/src/config/gate-coverage.yml +29 -8
  93. package/src/config/gate-violation-baselines.json +8 -8
  94. package/src/config/gitignore-block.txt +19 -0
  95. package/src/config/host-injection-effect.json +73 -0
  96. package/src/config/packed-binary-manifest.json +33 -0
  97. package/src/config/preamble-payload-budget.json +112 -105
  98. package/src/config/publish-surface.json +4 -2
  99. package/src/install/claudeLayerCarriage.ts +273 -0
  100. package/src/install/claudePathsPlan.ts +76 -0
  101. package/src/install/hostLayerFingerprint.ts +1 -1
  102. package/src/install/partitionEligibility.ts +261 -127
  103. package/src/install/ruleLayerPartition.ts +19 -23
  104. package/src/scripts/_dispatch.bash +77 -27
  105. package/src/scripts/_lib/auto_dispatch.ts +2 -2
  106. package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
  107. package/src/scripts/_lib/continuity_surface.ts +296 -0
  108. package/src/scripts/_lib/corpus_manifest.ts +26 -9
  109. package/src/scripts/_lib/hook_settings.ts +1 -1
  110. package/src/scripts/_lib/injection_effect.ts +151 -0
  111. package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
  112. package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
  113. package/src/scripts/_lib/pin_resolver.ts +13 -0
  114. package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
  115. package/src/scripts/_lib/retired_status.ts +74 -0
  116. package/src/scripts/_lib/skill_catalogue.ts +156 -21
  117. package/src/scripts/_lib/subagent_capsule.ts +117 -8
  118. package/src/scripts/ai_council/api_transport.ts +5 -0
  119. package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
  120. package/src/scripts/ai_council/prompts.ts +9 -0
  121. package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
  122. package/src/scripts/capture_skill_catalogue.ts +24 -11
  123. package/src/scripts/check_candidate_lines.ts +499 -0
  124. package/src/scripts/check_continuity_surface.ts +475 -0
  125. package/src/scripts/check_dependency_floors.ts +13 -6
  126. package/src/scripts/check_estate_count.ts +40 -79
  127. package/src/scripts/check_pack_size.ts +262 -23
  128. package/src/scripts/check_roadmap_trackable.ts +19 -16
  129. package/src/scripts/check_rule_layer_partition.ts +54 -34
  130. package/src/scripts/check_single_delivery.ts +97 -31
  131. package/src/scripts/check_update_banner.ts +13 -0
  132. package/src/scripts/code_graph/build.ts +138 -15
  133. package/src/scripts/code_graph/cli.ts +76 -8
  134. package/src/scripts/code_graph/extract.ts +11 -0
  135. package/src/scripts/code_graph/grammar_probe.ts +43 -0
  136. package/src/scripts/code_graph/loader.ts +88 -14
  137. package/src/scripts/code_graph/query.ts +121 -11
  138. package/src/scripts/code_graph/resolution_tiers.ts +238 -0
  139. package/src/scripts/code_graph/sqlite_store.ts +247 -6
  140. package/src/scripts/code_graph/types.ts +66 -1
  141. package/src/scripts/code_graph/validate.ts +23 -0
  142. package/src/scripts/condense.ts +34 -22
  143. package/src/scripts/corpus_manifest.ts +5 -5
  144. package/src/scripts/council_cli.ts +11 -12
  145. package/src/scripts/count_intent_disagreement.ts +426 -0
  146. package/src/scripts/handoff_context_hook.ts +79 -0
  147. package/src/scripts/hook_effect_doctor.ts +82 -1
  148. package/src/scripts/hook_manifest.json +1 -1
  149. package/src/scripts/hook_manifest.yaml +64 -12
  150. package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
  151. package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
  152. package/src/scripts/hooks/concern_registry.ts +4 -2
  153. package/src/scripts/hooks/host_semantics.ts +1 -1
  154. package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
  155. package/src/scripts/hooks/rule_inject_hook.ts +1 -1
  156. package/src/scripts/hooks/skill_route_hook.ts +11 -4
  157. package/src/scripts/inbox_source_census.ts +322 -0
  158. package/src/scripts/install-hooks.sh +82 -0
  159. package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
  160. package/src/scripts/lint_plan_risk_register.ts +13 -17
  161. package/src/scripts/mcp_server/suggest_skill.ts +72 -0
  162. package/src/scripts/mcp_server/tools.ts +2 -56
  163. package/src/scripts/media/lib/adapter-contract.md +7 -2
  164. package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
  165. package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
  166. package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
  167. package/src/scripts/rdp_candidates_delta.ts +276 -0
  168. package/src/scripts/rdp_quality_eval.ts +217 -19
  169. package/src/scripts/release_mix_taxonomy.json +3 -2
  170. package/src/scripts/report_host_injection_effect.ts +189 -0
  171. package/src/scripts/report_layer_overlap.ts +41 -0
  172. package/src/scripts/report_skill_menu_census.ts +341 -0
  173. package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
  174. package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
  175. package/src/vendor/grammars/README.md +61 -0
  176. package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
  177. package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
  178. package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
  179. package/src/scripts/_lib/carrier_status.ts +0 -118
  180. package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
  181. package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Does a completed synthesis keep the disagreement it started with?
3
+ *
4
+ * Three elements have to survive into the final text, and a synthesis that
5
+ * drops any of them has resolved the disagreement by not reporting it:
6
+ *
7
+ * 1. the unresolved disagreement itself — both sides, named;
8
+ * 2. the strongest evidence the minority side actually offered;
9
+ * 3. the observation that would settle it.
10
+ *
11
+ * Element 1 was already asked for by every lens template (`Clashes`,
12
+ * `Conflicts`, `Outliers`, or the mandated divergence prose). Element 2 was
13
+ * asked for by one. Element 3 was asked for by none, which is the gap
14
+ * {@link RESOLVING_EVIDENCE_SECTION} closes.
15
+ *
16
+ * WHY A CHECKER AND NOT ONLY A TEMPLATE CLAUSE. A template is an instruction to
17
+ * the host agent, and an instruction nobody can check is a hope. Both council
18
+ * seats that reviewed this refused a prose-only build in as many words. So the
19
+ * template carries the ask and this module makes the answer decidable: given a
20
+ * completed synthesis, {@link auditSynthesisElements} reports which of the three
21
+ * elements are present, by structure, without deciding whether their content is
22
+ * any good.
23
+ *
24
+ * WHAT IT DECIDES, AND WHAT IT DOES NOT. Presence and shape. A synthesis that
25
+ * names a clash, cites a minority exhibit and proposes a resolving measurement
26
+ * PASSES even when the measurement is a bad one — grading that needs the
27
+ * benchmark, and the benchmark has not run. This is the same honest scope the
28
+ * sibling minority-retention audit states for itself, and for the same reason:
29
+ * an audit that claimed to grade quality would be inventing the number.
30
+ *
31
+ * REPORT, NEVER THROW. Not every council pass produces dissent, and a synthesis
32
+ * of a unanimous pass has no disagreement to retain. So the audit reports
33
+ * `dissentPresent: false` and grades nothing, rather than failing a synthesis
34
+ * for the absence of a thing that did not happen. A caller with independent
35
+ * knowledge that the pass dissented passes {@link AuditOptions.dissentPresent}
36
+ * and gets the full grading regardless of what the text looks like.
37
+ *
38
+ * Pure, offline, no model call.
39
+ */
40
+
41
+ /** The three elements a dissenting synthesis has to carry. */
42
+ export type SynthesisElement =
43
+ | 'unresolved-disagreement'
44
+ | 'minority-evidence'
45
+ | 'resolving-evidence';
46
+
47
+ export const SYNTHESIS_ELEMENTS: readonly SynthesisElement[] = Object.freeze([
48
+ 'unresolved-disagreement',
49
+ 'minority-evidence',
50
+ 'resolving-evidence',
51
+ ]);
52
+
53
+ /**
54
+ * The section every lens template now closes its disagreement reporting with.
55
+ *
56
+ * One section rather than two because elements 2 and 3 are both per-clash
57
+ * follow-ups: splitting them would let a synthesis answer one clash's evidence
58
+ * and another clash's resolution and still look complete.
59
+ */
60
+ export const RESOLVING_EVIDENCE_SECTION = `### Resolving evidence
61
+ One line per unresolved disagreement above, in this shape:
62
+ \`<clash> — strongest minority evidence: <what the losing side actually
63
+ showed> · resolved by: <the observation that would settle it>\`.
64
+ Name a measurement, an artefact or a run — never a further opinion. Write
65
+ \`no resolving evidence identified — <why>\` when nothing observable would
66
+ settle it: an unanswerable disagreement is a finding, and silence is not.`;
67
+
68
+ /** Headings under which a lens reports an unresolved disagreement. */
69
+ const DISAGREEMENT_HEADINGS = ['Clashes', 'Conflicts', 'Outliers', 'Divergence'];
70
+
71
+ /** The heading element 3 lives under. */
72
+ export const RESOLVING_EVIDENCE_HEADING = 'Resolving evidence';
73
+
74
+ /** In-body markers the resolving-evidence section is asked to emit. */
75
+ const MINORITY_EVIDENCE_MARKER = /strongest minority evidence\s*:/i;
76
+ const RESOLVED_BY_MARKER = /resolved by\s*:/i;
77
+ const NO_RESOLVING_EVIDENCE_MARKER = /no resolving evidence identified\s*[—:-]/i;
78
+
79
+ /**
80
+ * Text markers that say a pass dissented, used only when the caller does not
81
+ * tell us. Deliberately narrow: a heading with a non-empty body, or an explicit
82
+ * split verdict. A loose match here would grade unanimous syntheses.
83
+ */
84
+ const SPLIT_VERDICT_MARKER = /^\s*\*{0,2}verdict\*{0,2}\s*:\s*split\b/im;
85
+
86
+ export interface ElementFinding {
87
+ readonly element: SynthesisElement;
88
+ readonly present: boolean;
89
+ /** The heading or marker that satisfied it, or `null`. */
90
+ readonly satisfiedBy: string | null;
91
+ }
92
+
93
+ export interface SynthesisElementAudit {
94
+ /** Whether the pass had a disagreement to retain at all. */
95
+ readonly dissentPresent: boolean;
96
+ /** How `dissentPresent` was decided — `caller` or `text`. */
97
+ readonly dissentSource: 'caller' | 'text';
98
+ readonly elements: readonly ElementFinding[];
99
+ readonly missing: readonly SynthesisElement[];
100
+ /** True when a dissenting pass rendered all three. Always true with no dissent. */
101
+ readonly complete: boolean;
102
+ }
103
+
104
+ export interface AuditOptions {
105
+ /**
106
+ * Override the text-derived dissent decision. Pass `true` when the run's own
107
+ * tally recorded a split; pass `false` to grade nothing.
108
+ */
109
+ readonly dissentPresent?: boolean;
110
+ }
111
+
112
+ function _headingBody(text: string, heading: string): string | null {
113
+ const lines = text.split('\n');
114
+ const headingRe = new RegExp(`^#{2,4}\\s+${heading}\\s*$`, 'i');
115
+ const idx = lines.findIndex((l) => headingRe.test(l));
116
+ if (idx === -1) return null;
117
+ let body = '';
118
+ for (let i = idx + 1; i < lines.length; i++) {
119
+ if (/^#{2,4}\s/.test(lines[i] as string)) break;
120
+ body += (lines[i] as string) + '\n';
121
+ }
122
+ return body;
123
+ }
124
+
125
+ /** A heading with a non-empty, non-placeholder body. */
126
+ function _nonEmptySection(text: string, heading: string): boolean {
127
+ const body = _headingBody(text, heading);
128
+ if (body === null) return false;
129
+ const stripped = body.replace(/^\s*[*_]*(none|n\/a|nothing)[.*_\s]*$/gim, '').trim();
130
+ return stripped.length > 0;
131
+ }
132
+
133
+ /** Audit a COMPLETED synthesis — the host- or chairman-authored text. */
134
+ export function auditSynthesisElements(
135
+ text: string,
136
+ opts: AuditOptions = {},
137
+ ): SynthesisElementAudit {
138
+ const disagreementHeading =
139
+ DISAGREEMENT_HEADINGS.find((h) => _nonEmptySection(text, h)) ?? null;
140
+
141
+ const dissentSource: 'caller' | 'text' = opts.dissentPresent === undefined ? 'text' : 'caller';
142
+ const dissentPresent =
143
+ opts.dissentPresent ?? (disagreementHeading !== null || SPLIT_VERDICT_MARKER.test(text));
144
+
145
+ const resolvingBody = _headingBody(text, RESOLVING_EVIDENCE_HEADING) ?? '';
146
+ const declaredNone = NO_RESOLVING_EVIDENCE_MARKER.test(resolvingBody);
147
+
148
+ const elements: ElementFinding[] = [
149
+ {
150
+ element: 'unresolved-disagreement',
151
+ present: disagreementHeading !== null,
152
+ satisfiedBy: disagreementHeading === null ? null : `### ${disagreementHeading}`,
153
+ },
154
+ {
155
+ element: 'minority-evidence',
156
+ present: MINORITY_EVIDENCE_MARKER.test(resolvingBody) || _nonEmptySection(text, 'Outliers'),
157
+ satisfiedBy: MINORITY_EVIDENCE_MARKER.test(resolvingBody)
158
+ ? 'strongest minority evidence:'
159
+ : _nonEmptySection(text, 'Outliers')
160
+ ? '### Outliers'
161
+ : null,
162
+ },
163
+ {
164
+ element: 'resolving-evidence',
165
+ present: RESOLVED_BY_MARKER.test(resolvingBody) || declaredNone,
166
+ satisfiedBy: RESOLVED_BY_MARKER.test(resolvingBody)
167
+ ? 'resolved by:'
168
+ : declaredNone
169
+ ? 'no resolving evidence identified'
170
+ : null,
171
+ },
172
+ ];
173
+
174
+ const missing = dissentPresent
175
+ ? elements.filter((e) => !e.present).map((e) => e.element)
176
+ : [];
177
+
178
+ return {
179
+ dissentPresent,
180
+ dissentSource,
181
+ elements,
182
+ missing,
183
+ complete: missing.length === 0,
184
+ };
185
+ }
186
+
187
+ /** Human-readable audit text. */
188
+ export function renderSynthesisElementAudit(a: SynthesisElementAudit): string {
189
+ if (!a.dissentPresent) {
190
+ return (
191
+ 'synthesis element audit: SKIPPED — no unresolved disagreement in this pass ' +
192
+ `(decided from ${a.dissentSource}); there is nothing to retain\n`
193
+ );
194
+ }
195
+ const rows = a.elements
196
+ .map(
197
+ (e) =>
198
+ ` - ${e.element}: ${e.present ? 'present' : 'MISSING'}` +
199
+ (e.satisfiedBy === null ? '' : ` [${e.satisfiedBy}]`),
200
+ )
201
+ .join('\n');
202
+ const verdict = a.complete
203
+ ? 'synthesis retains all three elements of the unresolved disagreement'
204
+ : `synthesis DROPS ${a.missing.length} of 3 elements: ${a.missing.join(', ')}`;
205
+ return `${verdict}\n${rows}\n`;
206
+ }
@@ -71,7 +71,7 @@ import * as path from 'node:path';
71
71
  import { fileURLToPath } from 'node:url';
72
72
 
73
73
  import {
74
- DEFAULT_CATALOGUE_ROOTS,
74
+ PROJECTION_CATALOGUE_ROOT,
75
75
  OBSERVATION_LOG,
76
76
  analyzeSelector,
77
77
  buildHostEventRecord,
@@ -85,8 +85,9 @@ import {
85
85
  measureCatalogueVolume,
86
86
  parseHostBudgetEvent,
87
87
  readObservationLog,
88
+ readCatalogueAcross,
88
89
  readProjectedCatalogue,
89
- resolveSkillsRoot,
90
+ resolveSkillCatalogueRoots,
90
91
  type HostProjectionRow,
91
92
  type ProjectionMode,
92
93
  type ProjectionModeCounts,
@@ -118,13 +119,18 @@ function resolveCatalogueRoot(explicit: string | null): string {
118
119
  }
119
120
  return explicit;
120
121
  }
121
- for (const candidate of DEFAULT_CATALOGUE_ROOTS) {
122
+ // The PROJECTION first, then the authored tree — deliberately NOT the generic
123
+ // ranker order, and deliberately not `DEFAULT_CATALOGUE_ROOTS`, which became a
124
+ // label list on 2026-09-07 (its `~/` entry joins to `<repo>/~/.claude/skills`,
125
+ // a path that can never exist). This module compares what a host DELIVERED
126
+ // against what was PROJECTED, so the projection is the subject; ranking it
127
+ // against `src/skills` would answer a different question with the same words.
128
+ const CANDIDATES = [PROJECTION_CATALOGUE_ROOT, 'src/skills'] as const;
129
+ for (const candidate of CANDIDATES) {
122
130
  const abs = path.join(REPO, candidate);
123
131
  if (fs.existsSync(abs)) return abs;
124
132
  }
125
- throw new Error(
126
- `no catalogue root found — tried ${DEFAULT_CATALOGUE_ROOTS.join(', ')} under ${REPO}`,
127
- );
133
+ throw new Error(`no catalogue root found — tried ${CANDIDATES.join(', ')} under ${REPO}`);
128
134
  }
129
135
 
130
136
  function argValue(flag: string): string | null {
@@ -326,17 +332,24 @@ function projectionModeFlagFor(host: string, counts: ProjectionModeCounts): { fl
326
332
  */
327
333
  function runPointableBareMode(): number {
328
334
  const explicitRoot = argValue('--catalogue-root');
329
- const rankerRoot = explicitRoot ? resolveCatalogueRoot(explicitRoot) : resolveSkillsRoot(REPO);
330
- if (rankerRoot === null) {
335
+ // The RANKER'S roots, which is what this join claims to read — corrected twice
336
+ // in one day. It first used `resolveSkillsRoot`; then, on the reasoning that
337
+ // "the projection is the subject", `.claude/skills` alone. Both were wrong
338
+ // after the ADR-236 amendment EMPTIED that directory of skills: the join read
339
+ // 49 command wrappers as the catalogue and published pointableBare 0 against
340
+ // unpointableBare 16, with the empty-guard silent because 49 is not 0. An
341
+ // explicit --catalogue-root still means exactly that one tree.
342
+ const rankerRoots = explicitRoot ? [resolveCatalogueRoot(explicitRoot)] : resolveSkillCatalogueRoots(REPO);
343
+ if (rankerRoots.length === 0) {
331
344
  process.stderr.write(
332
- '❌ no catalogue root resolved for the ranker — tried ' +
333
- `${DEFAULT_CATALOGUE_ROOTS.join(', ')} under ${REPO}.\n` +
345
+ `❌ no catalogue root resolved for the ranker under ${REPO}.\n` +
334
346
  ' An empty catalogue is never a clean join: it would report 0 pointable\n' +
335
347
  ' entries because nothing was read, not because nothing diverged.\n',
336
348
  );
337
349
  return 1;
338
350
  }
339
- const catalogueNames = readProjectedCatalogue(rankerRoot).map((entry) => entry.name);
351
+ const rankerRoot = rankerRoots[0] as string;
352
+ const catalogueNames = readCatalogueAcross(rankerRoots).map((entry) => entry.name);
340
353
  // The guard the error text above already promised, and did not have. A
341
354
  // present-but-empty or half-generated projection resolves fine — the
342
355
  // resolver returns the first EXISTING directory, not the first non-empty