@shrkcrft/inspector 0.1.0-alpha.30 → 0.1.0-alpha.31

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 (693) hide show
  1. package/dist/acceptance-replay.js +1 -1
  2. package/dist/agent-contract-templates.js +6 -6
  3. package/dist/agent-contract.d.ts.map +1 -1
  4. package/dist/agent-contract.js +13 -8
  5. package/dist/agent-orchestration.js +2 -2
  6. package/dist/agent-task-prep.d.ts +16 -0
  7. package/dist/agent-task-prep.d.ts.map +1 -1
  8. package/dist/agent-task-prep.js +51 -4
  9. package/dist/ai-readiness.d.ts.map +1 -1
  10. package/dist/ai-readiness.js +21 -3
  11. package/dist/architecture-map.d.ts +16 -0
  12. package/dist/architecture-map.d.ts.map +1 -1
  13. package/dist/architecture-map.js +42 -26
  14. package/dist/area-explore.d.ts +2 -2
  15. package/dist/area-explore.d.ts.map +1 -1
  16. package/dist/area-explore.js +36 -16
  17. package/dist/area-map.d.ts +56 -11
  18. package/dist/area-map.d.ts.map +1 -1
  19. package/dist/area-map.js +106 -25
  20. package/dist/asset-doctor-proposed-exit.d.ts +21 -0
  21. package/dist/asset-doctor-proposed-exit.d.ts.map +1 -0
  22. package/dist/asset-doctor-proposed-exit.js +22 -0
  23. package/dist/asset-provenance.js +1 -1
  24. package/dist/boundaries-changed-only.d.ts +24 -1
  25. package/dist/boundaries-changed-only.d.ts.map +1 -1
  26. package/dist/boundaries-changed-only.js +12 -2
  27. package/dist/boundary-check-options.model.d.ts +40 -0
  28. package/dist/boundary-check-options.model.d.ts.map +1 -0
  29. package/dist/boundary-check-options.model.js +1 -0
  30. package/dist/boundary-check-result.model.d.ts +89 -0
  31. package/dist/boundary-check-result.model.d.ts.map +1 -0
  32. package/dist/boundary-check-result.model.js +1 -0
  33. package/dist/boundary-configuration-status.d.ts +38 -0
  34. package/dist/boundary-configuration-status.d.ts.map +1 -0
  35. package/dist/boundary-configuration-status.js +118 -0
  36. package/dist/boundary-configuration-status.model.d.ts +37 -0
  37. package/dist/boundary-configuration-status.model.d.ts.map +1 -0
  38. package/dist/boundary-configuration-status.model.js +1 -0
  39. package/dist/boundary-load-issue.model.d.ts +30 -0
  40. package/dist/boundary-load-issue.model.d.ts.map +1 -0
  41. package/dist/boundary-load-issue.model.js +1 -0
  42. package/dist/boundary-rule-check.model.d.ts +44 -0
  43. package/dist/boundary-rule-check.model.d.ts.map +1 -0
  44. package/dist/boundary-rule-check.model.js +1 -0
  45. package/dist/boundary-rule-invalidation.model.d.ts +31 -0
  46. package/dist/boundary-rule-invalidation.model.d.ts.map +1 -0
  47. package/dist/boundary-rule-invalidation.model.js +1 -0
  48. package/dist/boundary-rule-set-diff.model.d.ts +75 -0
  49. package/dist/boundary-rule-set-diff.model.d.ts.map +1 -0
  50. package/dist/boundary-rule-set-diff.model.js +1 -0
  51. package/dist/boundary-rule-sources.d.ts +28 -0
  52. package/dist/boundary-rule-sources.d.ts.map +1 -0
  53. package/dist/boundary-rule-sources.js +110 -0
  54. package/dist/boundary-stale-exception-finding.model.d.ts +12 -0
  55. package/dist/boundary-stale-exception-finding.model.d.ts.map +1 -0
  56. package/dist/boundary-stale-exception-finding.model.js +1 -0
  57. package/dist/boundary-unit-liveness.d.ts +35 -0
  58. package/dist/boundary-unit-liveness.d.ts.map +1 -0
  59. package/dist/boundary-unit-liveness.js +71 -0
  60. package/dist/change-intent.d.ts +3 -0
  61. package/dist/change-intent.d.ts.map +1 -1
  62. package/dist/change-intent.js +61 -27
  63. package/dist/changes-summary.d.ts.map +1 -1
  64. package/dist/changes-summary.js +19 -9
  65. package/dist/ci-integrity-report.js +1 -1
  66. package/dist/ci-predict.d.ts.map +1 -1
  67. package/dist/ci-predict.js +38 -5
  68. package/dist/code-intelligence-doctor.d.ts +6 -0
  69. package/dist/code-intelligence-doctor.d.ts.map +1 -1
  70. package/dist/code-intelligence-doctor.js +22 -7
  71. package/dist/command-recommender.d.ts +45 -2
  72. package/dist/command-recommender.d.ts.map +1 -1
  73. package/dist/command-recommender.js +41 -263
  74. package/dist/command-resolution-status.d.ts +44 -0
  75. package/dist/command-resolution-status.d.ts.map +1 -0
  76. package/dist/command-resolution-status.js +44 -0
  77. package/dist/command-safety-level.d.ts +15 -0
  78. package/dist/command-safety-level.d.ts.map +1 -0
  79. package/dist/command-safety-level.js +43 -0
  80. package/dist/command-taxonomy.js +1 -1
  81. package/dist/compliance-evidence.js +1 -1
  82. package/dist/compliance-profiles.js +1 -1
  83. package/dist/construct-registry.d.ts +40 -0
  84. package/dist/construct-registry.d.ts.map +1 -1
  85. package/dist/construct-registry.js +186 -82
  86. package/dist/context-tuning.d.ts +4 -0
  87. package/dist/context-tuning.d.ts.map +1 -1
  88. package/dist/context-tuning.js +10 -7
  89. package/dist/contract-template-registry.d.ts +10 -0
  90. package/dist/contract-template-registry.d.ts.map +1 -1
  91. package/dist/contract-template-registry.js +59 -33
  92. package/dist/contribution-kind.d.ts +52 -0
  93. package/dist/contribution-kind.d.ts.map +1 -0
  94. package/dist/contribution-kind.js +52 -0
  95. package/dist/contribution-load-failures.d.ts +106 -0
  96. package/dist/contribution-load-failures.d.ts.map +1 -0
  97. package/dist/contribution-load-failures.js +575 -0
  98. package/dist/contributions-report.d.ts +28 -0
  99. package/dist/contributions-report.d.ts.map +1 -0
  100. package/dist/contributions-report.js +242 -0
  101. package/dist/convention-registry.d.ts +22 -0
  102. package/dist/convention-registry.d.ts.map +1 -1
  103. package/dist/convention-registry.js +79 -39
  104. package/dist/coverage-report.d.ts.map +1 -1
  105. package/dist/coverage-report.js +6 -6
  106. package/dist/custom-checks.d.ts +60 -1
  107. package/dist/custom-checks.d.ts.map +1 -1
  108. package/dist/custom-checks.js +159 -6
  109. package/dist/dashboard/dashboard-data.d.ts +6 -0
  110. package/dist/dashboard/dashboard-data.d.ts.map +1 -1
  111. package/dist/dashboard/dashboard-data.js +99 -8
  112. package/dist/dashboard/dashboard-knowledge.d.ts.map +1 -1
  113. package/dist/dashboard/dashboard-knowledge.js +14 -2
  114. package/dist/decision-records.d.ts +20 -0
  115. package/dist/decision-records.d.ts.map +1 -1
  116. package/dist/decision-records.js +68 -20
  117. package/dist/declared-cross-references.d.ts +61 -0
  118. package/dist/declared-cross-references.d.ts.map +1 -0
  119. package/dist/declared-cross-references.js +550 -0
  120. package/dist/declared-gate-plane-rules.d.ts +24 -0
  121. package/dist/declared-gate-plane-rules.d.ts.map +1 -0
  122. package/dist/declared-gate-plane-rules.js +39 -0
  123. package/dist/declared-reference-coverage.d.ts +27 -0
  124. package/dist/declared-reference-coverage.d.ts.map +1 -0
  125. package/dist/declared-reference-coverage.js +50 -0
  126. package/dist/declared-xref-status.d.ts +19 -0
  127. package/dist/declared-xref-status.d.ts.map +1 -0
  128. package/dist/declared-xref-status.js +19 -0
  129. package/dist/delegate-pack-recipes.d.ts +13 -2
  130. package/dist/delegate-pack-recipes.d.ts.map +1 -1
  131. package/dist/delegate-pack-recipes.js +45 -14
  132. package/dist/demo-package.js +2 -2
  133. package/dist/demo-script.js +2 -2
  134. package/dist/demo-workflow.js +3 -3
  135. package/dist/dev-cycle.js +1 -1
  136. package/dist/dev-session.js +1 -1
  137. package/dist/doc-references.d.ts +23 -2
  138. package/dist/doc-references.d.ts.map +1 -1
  139. package/dist/doc-references.js +96 -17
  140. package/dist/doctor-result.d.ts +8 -0
  141. package/dist/doctor-result.d.ts.map +1 -1
  142. package/dist/doctor-verdict-kind.d.ts +19 -0
  143. package/dist/doctor-verdict-kind.d.ts.map +1 -0
  144. package/dist/doctor-verdict-kind.js +19 -0
  145. package/dist/doctor-verdict.d.ts +18 -0
  146. package/dist/doctor-verdict.d.ts.map +1 -0
  147. package/dist/doctor-verdict.js +28 -0
  148. package/dist/drift.d.ts +17 -0
  149. package/dist/drift.d.ts.map +1 -1
  150. package/dist/drift.js +40 -10
  151. package/dist/entrypoint-matrix.d.ts.map +1 -1
  152. package/dist/entrypoint-matrix.js +4 -1
  153. package/dist/feedback-ingestion.d.ts +28 -2
  154. package/dist/feedback-ingestion.d.ts.map +1 -1
  155. package/dist/feedback-ingestion.js +68 -19
  156. package/dist/fix-preview.d.ts.map +1 -1
  157. package/dist/fix-preview.js +9 -5
  158. package/dist/framework-extractor-outcomes.d.ts +27 -0
  159. package/dist/framework-extractor-outcomes.d.ts.map +1 -0
  160. package/dist/framework-extractor-outcomes.js +83 -0
  161. package/dist/fuzzy-impact.js +1 -1
  162. package/dist/healing-plan.d.ts.map +1 -1
  163. package/dist/healing-plan.js +3 -4
  164. package/dist/helper-catalog.d.ts +25 -0
  165. package/dist/helper-catalog.d.ts.map +1 -0
  166. package/dist/helper-catalog.js +91 -0
  167. package/dist/helper-registry.d.ts.map +1 -1
  168. package/dist/helper-registry.js +17 -1
  169. package/dist/helper-view.d.ts +33 -0
  170. package/dist/helper-view.d.ts.map +1 -0
  171. package/dist/helper-view.js +1 -0
  172. package/dist/i-asset-doctor-flags.d.ts +16 -0
  173. package/dist/i-asset-doctor-flags.d.ts.map +1 -0
  174. package/dist/i-asset-doctor-flags.js +1 -0
  175. package/dist/i-command-resolution.d.ts +22 -0
  176. package/dist/i-command-resolution.d.ts.map +1 -0
  177. package/dist/i-command-resolution.js +1 -0
  178. package/dist/i-command-resolve-options.d.ts +24 -0
  179. package/dist/i-command-resolve-options.d.ts.map +1 -0
  180. package/dist/i-command-resolve-options.js +1 -0
  181. package/dist/i-command-safety.d.ts +13 -0
  182. package/dist/i-command-safety.d.ts.map +1 -0
  183. package/dist/i-command-safety.js +1 -0
  184. package/dist/i-contribution-accepted-entry.d.ts +18 -0
  185. package/dist/i-contribution-accepted-entry.d.ts.map +1 -0
  186. package/dist/i-contribution-accepted-entry.js +1 -0
  187. package/dist/i-contribution-entry-rejection.d.ts +21 -0
  188. package/dist/i-contribution-entry-rejection.d.ts.map +1 -0
  189. package/dist/i-contribution-entry-rejection.js +1 -0
  190. package/dist/i-contribution-file-issue.d.ts +18 -0
  191. package/dist/i-contribution-file-issue.d.ts.map +1 -0
  192. package/dist/i-contribution-file-issue.js +1 -0
  193. package/dist/i-contribution-file-report.d.ts +40 -0
  194. package/dist/i-contribution-file-report.d.ts.map +1 -0
  195. package/dist/i-contribution-file-report.js +1 -0
  196. package/dist/i-contribution-file-validation.d.ts +24 -0
  197. package/dist/i-contribution-file-validation.d.ts.map +1 -0
  198. package/dist/i-contribution-file-validation.js +1 -0
  199. package/dist/i-contributions-report.d.ts +31 -0
  200. package/dist/i-contributions-report.d.ts.map +1 -0
  201. package/dist/i-contributions-report.js +1 -0
  202. package/dist/i-declared-xref-field.d.ts +25 -0
  203. package/dist/i-declared-xref-field.d.ts.map +1 -0
  204. package/dist/i-declared-xref-field.js +1 -0
  205. package/dist/i-declared-xref-issue.d.ts +25 -0
  206. package/dist/i-declared-xref-issue.d.ts.map +1 -0
  207. package/dist/i-declared-xref-issue.js +1 -0
  208. package/dist/i-declared-xref-report.d.ts +48 -0
  209. package/dist/i-declared-xref-report.d.ts.map +1 -0
  210. package/dist/i-declared-xref-report.js +1 -0
  211. package/dist/i-declared-xref-row.d.ts +29 -0
  212. package/dist/i-declared-xref-row.d.ts.map +1 -0
  213. package/dist/i-declared-xref-row.js +1 -0
  214. package/dist/i-doctor-verdict.d.ts +10 -0
  215. package/dist/i-doctor-verdict.d.ts.map +1 -0
  216. package/dist/i-doctor-verdict.js +1 -0
  217. package/dist/i-kind-outcomes.d.ts +13 -0
  218. package/dist/i-kind-outcomes.d.ts.map +1 -0
  219. package/dist/i-kind-outcomes.js +1 -0
  220. package/dist/i-pack-doctor-verdict.d.ts +15 -0
  221. package/dist/i-pack-doctor-verdict.d.ts.map +1 -0
  222. package/dist/i-pack-doctor-verdict.js +1 -0
  223. package/dist/i-probed-id-field.d.ts +17 -0
  224. package/dist/i-probed-id-field.d.ts.map +1 -0
  225. package/dist/i-probed-id-field.js +1 -0
  226. package/dist/i-reference-kind-declaration.d.ts +38 -0
  227. package/dist/i-reference-kind-declaration.d.ts.map +1 -0
  228. package/dist/i-reference-kind-declaration.js +1 -0
  229. package/dist/i-reference-warm-options.d.ts +21 -0
  230. package/dist/i-reference-warm-options.d.ts.map +1 -0
  231. package/dist/i-reference-warm-options.js +1 -0
  232. package/dist/i-registry-outcomes.d.ts +16 -0
  233. package/dist/i-registry-outcomes.d.ts.map +1 -0
  234. package/dist/i-registry-outcomes.js +1 -0
  235. package/dist/i-scaffold-pattern-doctor-report.d.ts +35 -0
  236. package/dist/i-scaffold-pattern-doctor-report.d.ts.map +1 -0
  237. package/dist/i-scaffold-pattern-doctor-report.js +1 -0
  238. package/dist/i-search-tuning-key-probe.d.ts +30 -0
  239. package/dist/i-search-tuning-key-probe.d.ts.map +1 -0
  240. package/dist/i-search-tuning-key-probe.js +1 -0
  241. package/dist/i-search-tuning-lint-issue.d.ts +61 -0
  242. package/dist/i-search-tuning-lint-issue.d.ts.map +1 -0
  243. package/dist/i-search-tuning-lint-issue.js +1 -0
  244. package/dist/i-search-tuning-lint-report.d.ts +38 -0
  245. package/dist/i-search-tuning-lint-report.d.ts.map +1 -0
  246. package/dist/i-search-tuning-lint-report.js +1 -0
  247. package/dist/i-unresolvable-reference-scan.d.ts +14 -0
  248. package/dist/i-unresolvable-reference-scan.d.ts.map +1 -0
  249. package/dist/i-unresolvable-reference-scan.js +1 -0
  250. package/dist/i-unresolvable-reference.d.ts +29 -0
  251. package/dist/i-unresolvable-reference.d.ts.map +1 -0
  252. package/dist/i-unresolvable-reference.js +1 -0
  253. package/dist/i-workspace-profile-payload.d.ts +12 -0
  254. package/dist/i-workspace-profile-payload.d.ts.map +1 -0
  255. package/dist/i-workspace-profile-payload.js +1 -0
  256. package/dist/impact-analysis.d.ts +15 -0
  257. package/dist/impact-analysis.d.ts.map +1 -1
  258. package/dist/impact-analysis.js +58 -13
  259. package/dist/impact-render.d.ts.map +1 -1
  260. package/dist/impact-render.js +22 -0
  261. package/dist/import-hygiene.d.ts +35 -1
  262. package/dist/import-hygiene.d.ts.map +1 -1
  263. package/dist/import-hygiene.js +107 -13
  264. package/dist/index.d.ts +141 -0
  265. package/dist/index.d.ts.map +1 -1
  266. package/dist/index.js +172 -0
  267. package/dist/inspection-discovery.d.ts +45 -0
  268. package/dist/inspection-discovery.d.ts.map +1 -0
  269. package/dist/inspection-discovery.js +70 -0
  270. package/dist/knowledge-advisory-code.d.ts +17 -0
  271. package/dist/knowledge-advisory-code.d.ts.map +1 -0
  272. package/dist/knowledge-advisory-code.js +17 -0
  273. package/dist/knowledge-aged-entry.d.ts +10 -0
  274. package/dist/knowledge-aged-entry.d.ts.map +1 -0
  275. package/dist/knowledge-aged-entry.js +1 -0
  276. package/dist/knowledge-authoring.d.ts +24 -1
  277. package/dist/knowledge-authoring.d.ts.map +1 -1
  278. package/dist/knowledge-authoring.js +46 -7
  279. package/dist/knowledge-entry-verdict-record.d.ts +22 -0
  280. package/dist/knowledge-entry-verdict-record.d.ts.map +1 -0
  281. package/dist/knowledge-entry-verdict-record.js +1 -0
  282. package/dist/knowledge-entry-verdict.d.ts +17 -0
  283. package/dist/knowledge-entry-verdict.d.ts.map +1 -0
  284. package/dist/knowledge-entry-verdict.js +17 -0
  285. package/dist/knowledge-graph.d.ts +6 -0
  286. package/dist/knowledge-graph.d.ts.map +1 -1
  287. package/dist/knowledge-graph.js +18 -7
  288. package/dist/knowledge-kind-bucket.d.ts +19 -0
  289. package/dist/knowledge-kind-bucket.d.ts.map +1 -0
  290. package/dist/knowledge-kind-bucket.js +1 -0
  291. package/dist/knowledge-load-failure.d.ts +23 -0
  292. package/dist/knowledge-load-failure.d.ts.map +1 -0
  293. package/dist/knowledge-load-failure.js +1 -0
  294. package/dist/knowledge-reference-kind-bucket.d.ts +11 -0
  295. package/dist/knowledge-reference-kind-bucket.d.ts.map +1 -0
  296. package/dist/knowledge-reference-kind-bucket.js +1 -0
  297. package/dist/knowledge-stale-advisory.d.ts +14 -0
  298. package/dist/knowledge-stale-advisory.d.ts.map +1 -0
  299. package/dist/knowledge-stale-advisory.js +1 -0
  300. package/dist/knowledge-stale-coverage.d.ts +19 -0
  301. package/dist/knowledge-stale-coverage.d.ts.map +1 -0
  302. package/dist/knowledge-stale-coverage.js +1 -0
  303. package/dist/knowledge-stale-gate-flags.d.ts +33 -0
  304. package/dist/knowledge-stale-gate-flags.d.ts.map +1 -0
  305. package/dist/knowledge-stale-gate-flags.js +1 -0
  306. package/dist/knowledge-stale-gate-input.d.ts +46 -0
  307. package/dist/knowledge-stale-gate-input.d.ts.map +1 -0
  308. package/dist/knowledge-stale-gate-input.js +1 -0
  309. package/dist/knowledge-stale-gate-result.d.ts +33 -0
  310. package/dist/knowledge-stale-gate-result.d.ts.map +1 -0
  311. package/dist/knowledge-stale-gate-result.js +1 -0
  312. package/dist/knowledge-stale-gate-rule.d.ts +33 -0
  313. package/dist/knowledge-stale-gate-rule.d.ts.map +1 -0
  314. package/dist/knowledge-stale-gate-rule.js +1 -0
  315. package/dist/knowledge-stale-gate-violation.d.ts +13 -0
  316. package/dist/knowledge-stale-gate-violation.d.ts.map +1 -0
  317. package/dist/knowledge-stale-gate-violation.js +1 -0
  318. package/dist/knowledge-stale-gate.d.ts +83 -0
  319. package/dist/knowledge-stale-gate.d.ts.map +1 -0
  320. package/dist/knowledge-stale-gate.js +429 -0
  321. package/dist/knowledge-stale-quality-gate.d.ts +26 -0
  322. package/dist/knowledge-stale-quality-gate.d.ts.map +1 -0
  323. package/dist/knowledge-stale-quality-gate.js +110 -0
  324. package/dist/knowledge-stale.d.ts +122 -9
  325. package/dist/knowledge-stale.d.ts.map +1 -1
  326. package/dist/knowledge-stale.js +996 -291
  327. package/dist/knowledge-unverifiable-reason.d.ts +12 -0
  328. package/dist/knowledge-unverifiable-reason.d.ts.map +1 -0
  329. package/dist/knowledge-unverifiable-reason.js +12 -0
  330. package/dist/loader-diagnostics.d.ts +14 -2
  331. package/dist/loader-diagnostics.d.ts.map +1 -1
  332. package/dist/match-terms.d.ts +48 -0
  333. package/dist/match-terms.d.ts.map +1 -0
  334. package/dist/match-terms.js +155 -0
  335. package/dist/migration-profile-registry.d.ts +10 -0
  336. package/dist/migration-profile-registry.d.ts.map +1 -1
  337. package/dist/migration-profile-registry.js +57 -34
  338. package/dist/migration-readiness.d.ts.map +1 -1
  339. package/dist/migration-readiness.js +1 -3
  340. package/dist/nearest-id.d.ts +10 -26
  341. package/dist/nearest-id.d.ts.map +1 -1
  342. package/dist/nearest-id.js +9 -59
  343. package/dist/pack-asset-freshness.d.ts +65 -0
  344. package/dist/pack-asset-freshness.d.ts.map +1 -0
  345. package/dist/pack-asset-freshness.js +340 -0
  346. package/dist/pack-author-ux.d.ts +24 -3
  347. package/dist/pack-author-ux.d.ts.map +1 -1
  348. package/dist/pack-author-ux.js +87 -93
  349. package/dist/pack-contributions-inventory.d.ts +79 -39
  350. package/dist/pack-contributions-inventory.d.ts.map +1 -1
  351. package/dist/pack-contributions-inventory.js +443 -286
  352. package/dist/pack-doctor-verdict.d.ts +29 -0
  353. package/dist/pack-doctor-verdict.d.ts.map +1 -0
  354. package/dist/pack-doctor-verdict.js +45 -0
  355. package/dist/pack-doctor.d.ts +74 -1
  356. package/dist/pack-doctor.d.ts.map +1 -1
  357. package/dist/pack-doctor.js +281 -1
  358. package/dist/pack-helper-plan.d.ts +24 -0
  359. package/dist/pack-helper-plan.d.ts.map +1 -0
  360. package/dist/pack-helper-plan.js +77 -0
  361. package/dist/pack-helper-registry.d.ts +14 -0
  362. package/dist/pack-helper-registry.d.ts.map +1 -1
  363. package/dist/pack-helper-registry.js +76 -37
  364. package/dist/pack-manifest-reader.d.ts +8 -0
  365. package/dist/pack-manifest-reader.d.ts.map +1 -0
  366. package/dist/pack-manifest-reader.js +79 -0
  367. package/dist/pack-release-check.d.ts +14 -1
  368. package/dist/pack-release-check.d.ts.map +1 -1
  369. package/dist/pack-release-check.js +23 -1
  370. package/dist/pack-signature-status.d.ts +18 -6
  371. package/dist/pack-signature-status.d.ts.map +1 -1
  372. package/dist/pack-signature-status.js +59 -105
  373. package/dist/pack-test-runner.d.ts +27 -5
  374. package/dist/pack-test-runner.d.ts.map +1 -1
  375. package/dist/pack-test-runner.js +91 -35
  376. package/dist/pack-typecheck.d.ts +8 -0
  377. package/dist/pack-typecheck.d.ts.map +1 -0
  378. package/dist/pack-typecheck.js +76 -0
  379. package/dist/plan-review.d.ts +2 -0
  380. package/dist/plan-review.d.ts.map +1 -1
  381. package/dist/plan-review.js +17 -5
  382. package/dist/plan-simulation.d.ts.map +1 -1
  383. package/dist/plan-simulation.js +16 -5
  384. package/dist/playbook-load-issue.d.ts +16 -0
  385. package/dist/playbook-load-issue.d.ts.map +1 -0
  386. package/dist/playbook-load-issue.js +1 -0
  387. package/dist/playbook-registry.d.ts +32 -1
  388. package/dist/playbook-registry.d.ts.map +1 -1
  389. package/dist/playbook-registry.js +144 -55
  390. package/dist/policy-declaration.d.ts +18 -0
  391. package/dist/policy-declaration.d.ts.map +1 -0
  392. package/dist/policy-declaration.js +1 -0
  393. package/dist/policy-engine.d.ts +8 -0
  394. package/dist/policy-engine.d.ts.map +1 -1
  395. package/dist/policy-engine.js +26 -9
  396. package/dist/policy-overrides.d.ts.map +1 -1
  397. package/dist/policy-overrides.js +4 -4
  398. package/dist/policy-registry.d.ts +27 -0
  399. package/dist/policy-registry.d.ts.map +1 -1
  400. package/dist/policy-registry.js +87 -20
  401. package/dist/probed-id-fields.d.ts +21 -0
  402. package/dist/probed-id-fields.d.ts.map +1 -0
  403. package/dist/probed-id-fields.js +25 -0
  404. package/dist/probed-id-source.d.ts +11 -0
  405. package/dist/probed-id-source.d.ts.map +1 -0
  406. package/dist/probed-id-source.js +11 -0
  407. package/dist/profile-registry.d.ts +25 -2
  408. package/dist/profile-registry.d.ts.map +1 -1
  409. package/dist/profile-registry.js +59 -6
  410. package/dist/quality-gate-examination.d.ts +15 -0
  411. package/dist/quality-gate-examination.d.ts.map +1 -0
  412. package/dist/quality-gate-examination.js +15 -0
  413. package/dist/quality-gate-row-status.d.ts +19 -0
  414. package/dist/quality-gate-row-status.d.ts.map +1 -0
  415. package/dist/quality-gate-row-status.js +28 -0
  416. package/dist/quality-gate-status.d.ts +16 -0
  417. package/dist/quality-gate-status.d.ts.map +1 -0
  418. package/dist/quality-gate-status.js +16 -0
  419. package/dist/quality-html.d.ts.map +1 -1
  420. package/dist/quality-html.js +14 -3
  421. package/dist/quality-report-coverage.d.ts +26 -0
  422. package/dist/quality-report-coverage.d.ts.map +1 -0
  423. package/dist/quality-report-coverage.js +48 -0
  424. package/dist/quality-report.d.ts +28 -1
  425. package/dist/quality-report.d.ts.map +1 -1
  426. package/dist/quality-report.js +263 -35
  427. package/dist/query-intent-kind.d.ts +18 -0
  428. package/dist/query-intent-kind.d.ts.map +1 -0
  429. package/dist/query-intent-kind.js +18 -0
  430. package/dist/query-intent-result.d.ts +23 -0
  431. package/dist/query-intent-result.d.ts.map +1 -0
  432. package/dist/query-intent-result.js +1 -0
  433. package/dist/query-intent.d.ts +18 -0
  434. package/dist/query-intent.d.ts.map +1 -0
  435. package/dist/query-intent.js +336 -0
  436. package/dist/query-resolver.d.ts +18 -0
  437. package/dist/query-resolver.d.ts.map +1 -1
  438. package/dist/query-resolver.js +7 -4
  439. package/dist/ranked-recommendations.d.ts +36 -0
  440. package/dist/ranked-recommendations.d.ts.map +1 -0
  441. package/dist/ranked-recommendations.js +1 -0
  442. package/dist/ranker-explainability.d.ts.map +1 -1
  443. package/dist/ranker-explainability.js +38 -10
  444. package/dist/recommend-recipe.d.ts +17 -0
  445. package/dist/recommend-recipe.d.ts.map +1 -0
  446. package/dist/recommend-recipe.js +1 -0
  447. package/dist/recommend-recipes.d.ts +9 -0
  448. package/dist/recommend-recipes.d.ts.map +1 -0
  449. package/dist/recommend-recipes.js +106 -0
  450. package/dist/recommendation-candidate.d.ts +30 -0
  451. package/dist/recommendation-candidate.d.ts.map +1 -0
  452. package/dist/recommendation-candidate.js +1 -0
  453. package/dist/recommendation-confidence.d.ts +27 -0
  454. package/dist/recommendation-confidence.d.ts.map +1 -0
  455. package/dist/recommendation-confidence.js +1 -0
  456. package/dist/recommendation-ranking-options.d.ts +24 -0
  457. package/dist/recommendation-ranking-options.d.ts.map +1 -0
  458. package/dist/recommendation-ranking-options.js +1 -0
  459. package/dist/recommendation-ranking.d.ts +50 -0
  460. package/dist/recommendation-ranking.d.ts.map +1 -0
  461. package/dist/recommendation-ranking.js +527 -0
  462. package/dist/recommendation-source.d.ts +23 -0
  463. package/dist/recommendation-source.d.ts.map +1 -0
  464. package/dist/recommendation-source.js +23 -0
  465. package/dist/recommendation-suppression.d.ts +14 -0
  466. package/dist/recommendation-suppression.d.ts.map +1 -0
  467. package/dist/recommendation-suppression.js +14 -0
  468. package/dist/reference-asset-kind.d.ts +14 -0
  469. package/dist/reference-asset-kind.d.ts.map +1 -0
  470. package/dist/reference-asset-kind.js +14 -0
  471. package/dist/reference-content-check.d.ts +32 -0
  472. package/dist/reference-content-check.d.ts.map +1 -0
  473. package/dist/reference-content-check.js +143 -0
  474. package/dist/reference-failure.d.ts +31 -0
  475. package/dist/reference-failure.d.ts.map +1 -0
  476. package/dist/reference-failure.js +31 -0
  477. package/dist/reference-id-status.d.ts +15 -0
  478. package/dist/reference-id-status.d.ts.map +1 -0
  479. package/dist/reference-id-status.js +15 -0
  480. package/dist/reference-kind-declarations.d.ts +42 -0
  481. package/dist/reference-kind-declarations.d.ts.map +1 -0
  482. package/dist/reference-kind-declarations.js +142 -0
  483. package/dist/reference-lookup.d.ts +6 -0
  484. package/dist/reference-lookup.d.ts.map +1 -1
  485. package/dist/reference-lookup.js +13 -5
  486. package/dist/reference-registry.d.ts +109 -11
  487. package/dist/reference-registry.d.ts.map +1 -1
  488. package/dist/reference-registry.js +186 -30
  489. package/dist/reference-subject.d.ts +20 -0
  490. package/dist/reference-subject.d.ts.map +1 -0
  491. package/dist/reference-subject.js +1 -0
  492. package/dist/registration-hint-discovery-status.d.ts +25 -0
  493. package/dist/registration-hint-discovery-status.d.ts.map +1 -0
  494. package/dist/registration-hint-discovery-status.js +25 -0
  495. package/dist/registration-hint-registry.d.ts +80 -0
  496. package/dist/registration-hint-registry.d.ts.map +1 -1
  497. package/dist/registration-hint-registry.js +356 -138
  498. package/dist/registry-lifecycle.d.ts +200 -19
  499. package/dist/registry-lifecycle.d.ts.map +1 -1
  500. package/dist/registry-lifecycle.js +526 -136
  501. package/dist/release-readiness.d.ts.map +1 -1
  502. package/dist/release-readiness.js +10 -7
  503. package/dist/release-smoke.d.ts.map +1 -1
  504. package/dist/release-smoke.js +11 -7
  505. package/dist/report-site.d.ts.map +1 -1
  506. package/dist/report-site.js +25 -7
  507. package/dist/repository-intelligence.d.ts.map +1 -1
  508. package/dist/repository-intelligence.js +3 -2
  509. package/dist/repository-knowledge-model.d.ts.map +1 -1
  510. package/dist/repository-knowledge-model.js +18 -5
  511. package/dist/resolve-project-config.d.ts +64 -0
  512. package/dist/resolve-project-config.d.ts.map +1 -1
  513. package/dist/resolve-project-config.js +240 -32
  514. package/dist/reuse/compute-reuse-coverage.d.ts +29 -0
  515. package/dist/reuse/compute-reuse-coverage.d.ts.map +1 -0
  516. package/dist/reuse/compute-reuse-coverage.js +198 -0
  517. package/dist/reuse/rank-reuse-candidates.d.ts +38 -0
  518. package/dist/reuse/rank-reuse-candidates.d.ts.map +1 -0
  519. package/dist/reuse/rank-reuse-candidates.js +212 -0
  520. package/dist/reuse/rank-reuse-options.d.ts +20 -0
  521. package/dist/reuse/rank-reuse-options.d.ts.map +1 -0
  522. package/dist/reuse/rank-reuse-options.js +1 -0
  523. package/dist/reuse/rank-reuse-suggestions.d.ts +15 -0
  524. package/dist/reuse/rank-reuse-suggestions.d.ts.map +1 -0
  525. package/dist/reuse/rank-reuse-suggestions.js +31 -0
  526. package/dist/reuse/resolve-curated-reuse.d.ts +43 -0
  527. package/dist/reuse/resolve-curated-reuse.d.ts.map +1 -0
  528. package/dist/reuse/resolve-curated-reuse.js +115 -0
  529. package/dist/reuse/reuse-candidate.d.ts +29 -0
  530. package/dist/reuse/reuse-candidate.d.ts.map +1 -0
  531. package/dist/reuse/reuse-candidate.js +1 -0
  532. package/dist/reuse/reuse-coverage-report.d.ts +74 -0
  533. package/dist/reuse/reuse-coverage-report.d.ts.map +1 -0
  534. package/dist/reuse/reuse-coverage-report.js +1 -0
  535. package/dist/reuse/reuse-curated-coverage.d.ts +36 -0
  536. package/dist/reuse/reuse-curated-coverage.d.ts.map +1 -0
  537. package/dist/reuse/reuse-curated-coverage.js +1 -0
  538. package/dist/reuse/reuse-curated-resolution.d.ts +36 -0
  539. package/dist/reuse/reuse-curated-resolution.d.ts.map +1 -0
  540. package/dist/reuse/reuse-curated-resolution.js +1 -0
  541. package/dist/reuse/reuse-curated-status.d.ts +21 -0
  542. package/dist/reuse/reuse-curated-status.d.ts.map +1 -0
  543. package/dist/reuse/reuse-curated-status.js +21 -0
  544. package/dist/reuse/reuse-match-detail.d.ts +15 -0
  545. package/dist/reuse/reuse-match-detail.d.ts.map +1 -0
  546. package/dist/reuse/reuse-match-detail.js +1 -0
  547. package/dist/reuse/reuse-ranking.d.ts +37 -0
  548. package/dist/reuse/reuse-ranking.d.ts.map +1 -0
  549. package/dist/reuse/reuse-ranking.js +1 -0
  550. package/dist/reuse/reuse-suggestion.d.ts +19 -0
  551. package/dist/reuse/reuse-suggestion.d.ts.map +1 -0
  552. package/dist/reuse/reuse-suggestion.js +1 -0
  553. package/dist/reuse/reuse-symbol-lookup.d.ts +28 -0
  554. package/dist/reuse/reuse-symbol-lookup.d.ts.map +1 -0
  555. package/dist/reuse/reuse-symbol-lookup.js +1 -0
  556. package/dist/reuse/reuse-tokenize.d.ts +11 -0
  557. package/dist/reuse/reuse-tokenize.d.ts.map +1 -0
  558. package/dist/reuse/reuse-tokenize.js +26 -0
  559. package/dist/reuse/score-reuse-name.d.ts +16 -0
  560. package/dist/reuse/score-reuse-name.d.ts.map +1 -0
  561. package/dist/reuse/score-reuse-name.js +35 -0
  562. package/dist/reuse/score-reuse-primitive.d.ts +33 -0
  563. package/dist/reuse/score-reuse-primitive.d.ts.map +1 -0
  564. package/dist/reuse/score-reuse-primitive.js +92 -0
  565. package/dist/review-packet.d.ts +2 -0
  566. package/dist/review-packet.d.ts.map +1 -1
  567. package/dist/review-packet.js +13 -6
  568. package/dist/role-views.d.ts.map +1 -1
  569. package/dist/role-views.js +16 -16
  570. package/dist/routing-recommends-channels.d.ts +24 -0
  571. package/dist/routing-recommends-channels.d.ts.map +1 -0
  572. package/dist/routing-recommends-channels.js +19 -0
  573. package/dist/run-boundary-check.d.ts +118 -0
  574. package/dist/run-boundary-check.d.ts.map +1 -0
  575. package/dist/run-boundary-check.js +592 -0
  576. package/dist/safety-audit.js +1 -1
  577. package/dist/scaffold-coverage.js +1 -1
  578. package/dist/scaffold-pattern-doctor-report.d.ts +19 -0
  579. package/dist/scaffold-pattern-doctor-report.d.ts.map +1 -0
  580. package/dist/scaffold-pattern-doctor-report.js +54 -0
  581. package/dist/scaffold-patterns.d.ts +75 -1
  582. package/dist/scaffold-patterns.d.ts.map +1 -1
  583. package/dist/scaffold-patterns.js +375 -84
  584. package/dist/schema-inventory.d.ts.map +1 -1
  585. package/dist/schema-inventory.js +4 -3
  586. package/dist/search-document-id.d.ts +52 -0
  587. package/dist/search-document-id.d.ts.map +1 -0
  588. package/dist/search-document-id.js +72 -0
  589. package/dist/search-document-prefix.d.ts +7 -0
  590. package/dist/search-document-prefix.d.ts.map +1 -0
  591. package/dist/search-document-prefix.js +1 -0
  592. package/dist/search-index.d.ts.map +1 -1
  593. package/dist/search-index.js +20 -23
  594. package/dist/search-tuning-explain.d.ts +6 -0
  595. package/dist/search-tuning-explain.d.ts.map +1 -1
  596. package/dist/search-tuning-explain.js +19 -17
  597. package/dist/search-tuning-key-probes.d.ts +16 -0
  598. package/dist/search-tuning-key-probes.d.ts.map +1 -0
  599. package/dist/search-tuning-key-probes.js +65 -0
  600. package/dist/search-tuning-key-resolution.d.ts +25 -0
  601. package/dist/search-tuning-key-resolution.d.ts.map +1 -0
  602. package/dist/search-tuning-key-resolution.js +1 -0
  603. package/dist/search-tuning-key-resolver.d.ts +4 -0
  604. package/dist/search-tuning-key-resolver.d.ts.map +1 -0
  605. package/dist/search-tuning-key-resolver.js +107 -0
  606. package/dist/search-tuning-key-status.d.ts +24 -0
  607. package/dist/search-tuning-key-status.d.ts.map +1 -0
  608. package/dist/search-tuning-key-status.js +24 -0
  609. package/dist/search-tuning-lint.d.ts +9 -0
  610. package/dist/search-tuning-lint.d.ts.map +1 -0
  611. package/dist/search-tuning-lint.js +468 -0
  612. package/dist/search-tuning-registry.d.ts +43 -0
  613. package/dist/search-tuning-registry.d.ts.map +1 -1
  614. package/dist/search-tuning-registry.js +214 -49
  615. package/dist/self-audit.d.ts +12 -0
  616. package/dist/self-audit.d.ts.map +1 -1
  617. package/dist/self-audit.js +36 -10
  618. package/dist/self-config-doctor-v2.d.ts +145 -3
  619. package/dist/self-config-doctor-v2.d.ts.map +1 -1
  620. package/dist/self-config-doctor-v2.js +1304 -181
  621. package/dist/self-config-doctor.d.ts +20 -1
  622. package/dist/self-config-doctor.d.ts.map +1 -1
  623. package/dist/self-config-doctor.js +53 -280
  624. package/dist/settled-unit-states.d.ts +9 -0
  625. package/dist/settled-unit-states.d.ts.map +1 -0
  626. package/dist/settled-unit-states.js +14 -0
  627. package/dist/sharkcraft-inspector.d.ts +34 -0
  628. package/dist/sharkcraft-inspector.d.ts.map +1 -1
  629. package/dist/sharkcraft-inspector.js +273 -26
  630. package/dist/spec/spec-evidence.d.ts.map +1 -1
  631. package/dist/spec/spec-evidence.js +14 -20
  632. package/dist/split-identifier.d.ts +21 -0
  633. package/dist/split-identifier.d.ts.map +1 -0
  634. package/dist/split-identifier.js +27 -0
  635. package/dist/start-here.d.ts.map +1 -1
  636. package/dist/start-here.js +1 -2
  637. package/dist/substitute-placeholders.d.ts +8 -0
  638. package/dist/substitute-placeholders.d.ts.map +1 -0
  639. package/dist/substitute-placeholders.js +9 -0
  640. package/dist/symbol-index.d.ts +33 -0
  641. package/dist/symbol-index.d.ts.map +1 -1
  642. package/dist/symbol-index.js +241 -3
  643. package/dist/symbol-member-entry.d.ts +26 -0
  644. package/dist/symbol-member-entry.d.ts.map +1 -0
  645. package/dist/symbol-member-entry.js +1 -0
  646. package/dist/symbol-member-kind.d.ts +14 -0
  647. package/dist/symbol-member-kind.d.ts.map +1 -0
  648. package/dist/symbol-member-kind.js +14 -0
  649. package/dist/task-packet.d.ts.map +1 -1
  650. package/dist/task-packet.js +52 -9
  651. package/dist/task-ranker.d.ts +8 -0
  652. package/dist/task-ranker.d.ts.map +1 -1
  653. package/dist/task-ranker.js +25 -13
  654. package/dist/task-risk.js +1 -1
  655. package/dist/task-routing-hint-registry.d.ts +20 -1
  656. package/dist/task-routing-hint-registry.d.ts.map +1 -1
  657. package/dist/task-routing-hint-registry.js +186 -42
  658. package/dist/template-drift.d.ts.map +1 -1
  659. package/dist/template-drift.js +30 -9
  660. package/dist/template-lint.d.ts.map +1 -1
  661. package/dist/template-lint.js +160 -2
  662. package/dist/term-query.d.ts +10 -0
  663. package/dist/term-query.d.ts.map +1 -0
  664. package/dist/term-query.js +1 -0
  665. package/dist/test-definitions.d.ts +6 -3
  666. package/dist/test-definitions.d.ts.map +1 -1
  667. package/dist/test-runner.d.ts +95 -10
  668. package/dist/test-runner.d.ts.map +1 -1
  669. package/dist/test-runner.js +309 -308
  670. package/dist/tuning-query-tokens.d.ts +29 -0
  671. package/dist/tuning-query-tokens.d.ts.map +1 -0
  672. package/dist/tuning-query-tokens.js +51 -0
  673. package/dist/typecheck-files.d.ts +36 -0
  674. package/dist/typecheck-files.d.ts.map +1 -0
  675. package/dist/typecheck-files.js +158 -0
  676. package/dist/uncertainty.js +1 -1
  677. package/dist/universal-search.d.ts.map +1 -1
  678. package/dist/universal-search.js +5 -3
  679. package/dist/unregistered-exports.d.ts +19 -0
  680. package/dist/unregistered-exports.d.ts.map +1 -0
  681. package/dist/unregistered-exports.js +141 -0
  682. package/dist/unresolvable-reason.d.ts +11 -0
  683. package/dist/unresolvable-reason.d.ts.map +1 -0
  684. package/dist/unresolvable-reason.js +11 -0
  685. package/dist/upgrade-advisor.d.ts.map +1 -1
  686. package/dist/upgrade-advisor.js +16 -9
  687. package/dist/validate-contribution-file.d.ts +4 -0
  688. package/dist/validate-contribution-file.d.ts.map +1 -0
  689. package/dist/validate-contribution-file.js +228 -0
  690. package/dist/why-file.d.ts +3 -0
  691. package/dist/why-file.d.ts.map +1 -1
  692. package/dist/why-file.js +7 -3
  693. package/package.json +17 -17
@@ -17,12 +17,32 @@
17
17
  */
18
18
  import { existsSync } from 'node:fs';
19
19
  import * as nodePath from 'node:path';
20
- import { listTaskRoutingHints } from "./task-routing-hint-registry.js";
21
- import { listRegistrationHints } from "./registration-hint-registry.js";
22
- import { listDecisions } from "./decision-records.js";
23
- import { referenceIdExistsInAnyKind, referenceIdsFor, warmReferenceRegistries, } from "./reference-registry.js";
24
- import { listPlaybooks } from "./playbook-registry.js";
25
- import { buildPackContributionsInventory } from "./pack-contributions-inventory.js";
20
+ import { coverageShortfall, DEAD_SELECTOR_CAUSES, formatCoverage, RejectionCause, settleUnitLiveness, UnitDeadCause, UnitDeadWeight, UnitLivenessState, } from '@shrkcrft/core';
21
+ import { searchTuningKeyProbes } from "./search-tuning-key-probes.js";
22
+ import { ContributionKind } from "./contribution-kind.js";
23
+ import { resolveEntryFile } from "./contribution-load-failures.js";
24
+ import { UnresolvableReason } from "./unresolvable-reason.js";
25
+ import { listTaskRoutingHintIssues, loadTaskRoutingHints, listTaskRoutingHints, routingHintCanMatch, } from "./task-routing-hint-registry.js";
26
+ import { buildRegistrationHintDoctorReport, listRegistrationHints } from "./registration-hint-registry.js";
27
+ import { listDecisions, loadTsDecisions } from "./decision-records.js";
28
+ import { COMMAND_INDEX_NOT_INJECTED, emptyReferenceKinds, hasCommandResolver, referenceIdExists, referenceIdsFor, referenceKindsOf, resolveCommandReference, resolveShrkCommandReference, warmReferenceRegistries, } from "./reference-registry.js";
29
+ import { nearestIds } from "./nearest-id.js";
30
+ import { formatReferenceKindDeclaration, isReferenceKindDeclarable, referenceKindListVerb, } from "./reference-kind-declarations.js";
31
+ import { PROBED_ID_FIELDS } from "./probed-id-fields.js";
32
+ import { ProbedIdSource } from "./probed-id-source.js";
33
+ import { listConventions } from "./convention-registry.js";
34
+ import { ROUTING_RECOMMENDS_CHANNEL_KEYS, ROUTING_RECOMMENDS_CHANNELS, } from "./routing-recommends-channels.js";
35
+ import { doctorScaffoldPatterns, enumerateScaffoldPatternCandidates, loadScaffoldPatternsFromInspection, scaffoldPatternCoverage, } from "./scaffold-patterns.js";
36
+ import { lintSearchTuning } from "./search-tuning-lint.js";
37
+ import { listSearchTuningIssues, loadSearchTuning } from "./search-tuning-registry.js";
38
+ import { resolveSearchTuningKey } from "./search-tuning-key-resolver.js";
39
+ import { SearchTuningKeyStatus } from "./search-tuning-key-status.js";
40
+ import { CommandResolutionStatus } from "./command-resolution-status.js";
41
+ import { collectDeclaredXrefs, declaredXrefCoverage, declaredXrefSummaryLine, } from "./declared-cross-references.js";
42
+ import { DeclaredXrefStatus } from "./declared-xref-status.js";
43
+ import { listConstructs } from "./construct-registry.js";
44
+ import { listPlaybooks, loadPlaybooksWithIssues } from "./playbook-registry.js";
45
+ import { buildPackContributionsInventoryAsync, } from "./pack-contributions-inventory.js";
26
46
  export const SELF_CONFIG_DOCTOR_V2_SCHEMA = 'sharkcraft.self-config-doctor/v2';
27
47
  export var SelfConfigSeverityV2;
28
48
  (function (SelfConfigSeverityV2) {
@@ -53,24 +73,39 @@ async function buildLookupsV2(inspection) {
53
73
  playbooks: ids('playbook'),
54
74
  conventions: ids('convention'),
55
75
  contractTemplates: ids('contract-template'),
56
- migrationProfiles: ids('migration-profile'),
57
76
  helpers: ids('helper'),
58
77
  routingHints: ids('routing-hint'),
59
78
  registrationHints: ids('registration-hint'),
60
79
  decisions: ids('decision'),
61
80
  scaffoldPatterns: ids('scaffold-pattern'),
62
- // The catalog lives in the CLI package, above this layer. `command`
63
- // resolves by SHAPE in the shared registry rather than by a list this
64
- // layer cannot see.
65
- commands: new Set(),
81
+ // No `commands` set: it was initialised EMPTY (the command index lives in
82
+ // the CLI, above this layer), so every command in every hint and agent test
83
+ // was reported missing — correct ones included, and an agent test's correct
84
+ // `expectedCommands` failed the doctor at error severity. Commands resolve
85
+ // through the injected resolver instead — see `checkCommandStrings`.
66
86
  };
67
87
  }
68
88
  function findingId(parts) {
69
89
  return `${parts.sourceKind}:${parts.sourceId}|${parts.relation}|${parts.targetKind}:${parts.targetId}`;
70
90
  }
91
+ /**
92
+ * Append a finding — or MERGE it into an existing one with the same id and
93
+ * code (bumping `occurrences`), so the documented unique id is unique for
94
+ * every check. The same tuning key in three task hints used to be three
95
+ * identical warnings sharing one id. A same-id finding with a DIFFERENT code
96
+ * keeps its own entry, its id suffixed with `|<code>`.
97
+ */
71
98
  function pushFinding(out, input) {
99
+ const baseId = findingId(input);
100
+ const codedId = `${baseId}|${input.code}`;
101
+ const same = out.findIndex((f) => (f.id === baseId || f.id === codedId) && f.code === input.code);
102
+ if (same >= 0) {
103
+ const prev = out[same];
104
+ out[same] = { ...prev, occurrences: (prev.occurrences ?? 1) + (input.occurrences ?? 1) };
105
+ return;
106
+ }
72
107
  const entry = {
73
- id: findingId(input),
108
+ id: out.some((f) => f.id === baseId) ? codedId : baseId,
74
109
  severity: input.severity,
75
110
  code: input.code,
76
111
  sourceKind: input.sourceKind,
@@ -83,9 +118,231 @@ function pushFinding(out, input) {
83
118
  ...(input.file !== undefined ? { file: input.file } : {}),
84
119
  ...(input.suggestedFix !== undefined ? { suggestedFix: input.suggestedFix } : {}),
85
120
  ...(input.nextCommand !== undefined ? { nextCommand: input.nextCommand } : {}),
121
+ ...(input.occurrences !== undefined && input.occurrences > 1 ? { occurrences: input.occurrences } : {}),
86
122
  };
87
123
  out.push(entry);
88
124
  }
125
+ function severityV2(s) {
126
+ return s === 'error'
127
+ ? SelfConfigSeverityV2.Error
128
+ : s === 'warning'
129
+ ? SelfConfigSeverityV2.Warning
130
+ : SelfConfigSeverityV2.Info;
131
+ }
132
+ /**
133
+ * THE reference-kind → finding-kind table: every kind maps onto ITSELF. The
134
+ * mapped type makes a kind added to `ReferenceKind` without a row here a
135
+ * compile error, and a row naming a kind `SelfConfigKind` lacks a compile error
136
+ * in `selfKindOf`. This was a `switch` whose default was `'unknown'`, so any
137
+ * kind it forgot rendered every resolvable id of that kind `unknown:`.
138
+ */
139
+ const SELF_KIND_OF_REFERENCE_KIND = Object.freeze({
140
+ file: 'file',
141
+ directory: 'directory',
142
+ symbol: 'symbol',
143
+ command: 'command',
144
+ package: 'package',
145
+ url: 'url',
146
+ template: 'template',
147
+ pipeline: 'pipeline',
148
+ playbook: 'playbook',
149
+ policy: 'policy',
150
+ construct: 'construct',
151
+ helper: 'helper',
152
+ 'boundary-rule': 'boundary-rule',
153
+ 'path-convention': 'path-convention',
154
+ rule: 'rule',
155
+ knowledge: 'knowledge',
156
+ decision: 'decision',
157
+ convention: 'convention',
158
+ 'contract-template': 'contract-template',
159
+ 'migration-profile': 'migration-profile',
160
+ 'workspace-profile': 'workspace-profile',
161
+ 'routing-hint': 'routing-hint',
162
+ 'registration-hint': 'registration-hint',
163
+ 'scaffold-pattern': 'scaffold-pattern',
164
+ });
165
+ /** The ONE place a finding's `unknown` kind comes from: a target THE resolvers placed in no registry. */
166
+ const RESOLVED_NOWHERE = 'unknown';
167
+ /**
168
+ * A reference kind's finding kind. `undefined` — the caller's resolver placed
169
+ * the id in NO registry — is `unknown`, and nothing else is: a caller holding a
170
+ * resolvable id must pass the kind it resolved in (round 12, 12.4).
171
+ */
172
+ function selfKindOf(kind) {
173
+ return kind === undefined ? RESOLVED_NOWHERE : SELF_KIND_OF_REFERENCE_KIND[kind];
174
+ }
175
+ /** The `<field>` a validator issue message (`<field>: <message>`) names. */
176
+ function validatorField(message) {
177
+ const at = message.indexOf(':');
178
+ return at > 0 ? message.slice(0, at) : message;
179
+ }
180
+ /**
181
+ * THE contribution kind → rejection-finding table (round 12, 12.1): the
182
+ * finding kind a rejected entry is labelled with (also its code prefix,
183
+ * `<kind>-invalid` / `<kind>-duplicate-id`) and the doctor that shows the
184
+ * loader's own view of it. A mapped type over the enum: a contribution kind
185
+ * without a row is a compile error, so a new kind can never fall to a borrowed
186
+ * label (the `unknown:` alarm of 12.4). The routing / registration hint codes
187
+ * (`routing-hint-invalid`, `registration-hint-duplicate-id`, …) are the ones
188
+ * those families always emitted.
189
+ */
190
+ const REJECTION_FINDING = Object.freeze({
191
+ [ContributionKind.Knowledge]: { kind: 'knowledge', doctor: 'shrk doctor' },
192
+ [ContributionKind.Rule]: { kind: 'rule', doctor: 'shrk doctor' },
193
+ [ContributionKind.Path]: { kind: 'path-convention', doctor: 'shrk doctor' },
194
+ [ContributionKind.PathConvention]: { kind: 'path-convention', doctor: 'shrk doctor' },
195
+ [ContributionKind.Docs]: { kind: 'docs', doctor: 'shrk doctor' },
196
+ [ContributionKind.Template]: { kind: 'template', doctor: 'shrk templates doctor' },
197
+ [ContributionKind.Pipeline]: { kind: 'pipeline', doctor: 'shrk pipelines list' },
198
+ [ContributionKind.Preset]: { kind: 'preset', doctor: 'shrk presets list' },
199
+ [ContributionKind.Boundary]: { kind: 'boundary-rule', doctor: 'shrk check boundaries' },
200
+ [ContributionKind.ScaffoldPattern]: { kind: 'scaffold-pattern', doctor: 'shrk scaffolds doctor' },
201
+ [ContributionKind.Policy]: { kind: 'policy', doctor: 'shrk policy list' },
202
+ [ContributionKind.Construct]: { kind: 'construct', doctor: 'shrk constructs list' },
203
+ [ContributionKind.ConstructFacet]: { kind: 'construct-facet', doctor: 'shrk constructs list' },
204
+ [ContributionKind.Playbook]: { kind: 'playbook', doctor: 'shrk playbooks list' },
205
+ [ContributionKind.SearchTuning]: { kind: 'search-tuning', doctor: 'shrk search tuning doctor' },
206
+ [ContributionKind.FeedbackRule]: { kind: 'feedback-rule', doctor: 'shrk packs contributions' },
207
+ [ContributionKind.Decision]: { kind: 'decision', doctor: 'shrk packs contributions' },
208
+ [ContributionKind.ContractTemplate]: { kind: 'contract-template', doctor: 'shrk contract template list' },
209
+ [ContributionKind.MigrationProfile]: { kind: 'migration-profile', doctor: 'shrk profiles doctor' },
210
+ [ContributionKind.ContextTest]: { kind: 'context-test', doctor: 'shrk test context' },
211
+ [ContributionKind.AgentTest]: { kind: 'agent-test', doctor: 'shrk test agent' },
212
+ [ContributionKind.Helper]: { kind: 'helper', doctor: 'shrk helper doctor' },
213
+ [ContributionKind.TaskRoutingHint]: { kind: 'routing-hint', doctor: 'shrk packs contributions' },
214
+ [ContributionKind.RegistrationHint]: { kind: 'registration-hint', doctor: 'shrk registrations doctor' },
215
+ [ContributionKind.Convention]: { kind: 'convention', doctor: 'shrk conventions doctor' },
216
+ [ContributionKind.DelegateRecipe]: { kind: 'delegate-recipe', doctor: 'shrk packs contributions' },
217
+ [ContributionKind.FrameworkExtractor]: { kind: 'framework-extractor', doctor: 'shrk packs contributions' },
218
+ [ContributionKind.WiringRule]: { kind: 'wiring-rule', doctor: 'shrk gates coverage' },
219
+ [ContributionKind.Registry]: { kind: 'registry', doctor: 'shrk gates coverage' },
220
+ [ContributionKind.RegistrationIdiom]: { kind: 'registration-idiom', doctor: 'shrk gates coverage' },
221
+ [ContributionKind.PolicyRule]: { kind: 'policy-rule', doctor: 'shrk gates coverage' },
222
+ [ContributionKind.ReusePrimitive]: { kind: 'reuse-primitive', doctor: 'shrk reuse coverage' },
223
+ [ContributionKind.Baseline]: { kind: 'baseline', doctor: 'shrk gates coverage' },
224
+ [ContributionKind.GeneratedArtifact]: { kind: 'generated-artifact', doctor: 'shrk gates coverage' },
225
+ [ContributionKind.DocReference]: { kind: 'doc-reference', doctor: 'shrk docs references check' },
226
+ });
227
+ /** A rejected entry's finding kind and code — THE table above, never a fallback. */
228
+ export function rejectionFindingOf(kind, cause) {
229
+ const row = REJECTION_FINDING[kind];
230
+ return {
231
+ kind: row.kind,
232
+ code: `${row.kind}-${cause === RejectionCause.DuplicateId ? 'duplicate-id' : 'invalid'}`,
233
+ doctor: row.doctor,
234
+ };
235
+ }
236
+ function newIdProbe() {
237
+ return {
238
+ probed: 0,
239
+ resolved: 0,
240
+ missing: 0,
241
+ unverified: 0,
242
+ unverifiedLabels: [],
243
+ unverifiedRefs: [],
244
+ emptyKinds: [],
245
+ };
246
+ }
247
+ /**
248
+ * The loud-skip reason. It names how to fill each empty kind, from THE
249
+ * declaration table (`REFERENCE_KIND_DECLARATIONS`), so a NOT VERIFIED unit is
250
+ * actionable instead of a dead end: `… — declare migration-profile via pack
251
+ * key migrationProfileFiles · sharkcraft/migration-profiles.ts …`.
252
+ */
253
+ function emptyRegistryReason(probe) {
254
+ const base = "could not be checked (their kind's registry is empty in this workspace";
255
+ if (probe.emptyKinds.length === 0)
256
+ return `${base})`;
257
+ const how = probe.emptyKinds.map((k) => `${k} via ${formatReferenceKindDeclaration(k)}`).join('; ');
258
+ return `${base} — declare ${how})`;
259
+ }
260
+ /** Read a dotted id-list field (`discovery.profileIds`) off an asset; anything but an array reads as absent. */
261
+ function readIdList(asset, field) {
262
+ let cur = asset;
263
+ for (const part of field.split('.')) {
264
+ if (!cur || typeof cur !== 'object')
265
+ return undefined;
266
+ cur = cur[part];
267
+ }
268
+ return Array.isArray(cur) ? cur : undefined;
269
+ }
270
+ /** How a `*-missing` message ends: a workspace profile id is a vocabulary word, not a registration. */
271
+ function notResolvedTail(kind) {
272
+ return kind === 'workspace-profile' ? ', which is not a WorkspaceProfile id' : ' but it is not registered';
273
+ }
274
+ /**
275
+ * THE "this finding reports a reference that resolved nowhere" predicate: a
276
+ * `*-missing` code (an id, or a referenced file), any severity — the family
277
+ * r73 reads as "unknown id". The CLI's exit-0 line counts the INFO-severity
278
+ * ones instead of certifying "every checked probe resolved" over them.
279
+ */
280
+ export function isUnresolvedReferenceFinding(finding) {
281
+ return finding.code.endsWith('-missing');
282
+ }
283
+ /**
284
+ * THE "this reference resolved, but only as a kind its field does not accept"
285
+ * predicate: a `*-wrong-kind` code (`xref-wrong-kind`, a pipeline step naming a
286
+ * construct). Not unresolved — `unknown:` is reserved for that — and not a
287
+ * clean resolution either, so the CLI's exit-0 line counts an info-severity one
288
+ * instead of certifying "every checked probe resolved".
289
+ */
290
+ export function isWrongKindReferenceFinding(finding) {
291
+ return finding.code.endsWith('-wrong-kind');
292
+ }
293
+ function probeIds(inspection, kind, ids, probe, label, onMissing, site) {
294
+ if (!Array.isArray(ids) || ids.length === 0)
295
+ return;
296
+ const empty = emptyReferenceKinds(inspection, [kind]).length > 0;
297
+ for (const raw of ids) {
298
+ if (typeof raw !== 'string' || raw.length === 0)
299
+ continue;
300
+ probe.probed += 1;
301
+ if (empty) {
302
+ probe.unverified += 1;
303
+ probe.unverifiedLabels.push(label(raw));
304
+ if (site) {
305
+ probe.unverifiedRefs.push({
306
+ ...(site.file ? { file: resolveEntryFile(inspection, site.file, site.packageName) } : {}),
307
+ ...(site.packageName ? { packageName: site.packageName } : {}),
308
+ sourceKind: site.sourceKind,
309
+ sourceId: site.sourceId,
310
+ field: site.field,
311
+ kind,
312
+ id: raw,
313
+ // THE declarability authority: an empty registry nothing can EVER
314
+ // fill is a different fact from one this workspace has not filled.
315
+ reason: isReferenceKindDeclarable(kind) ? UnresolvableReason.RegistryEmpty : UnresolvableReason.UndeclarableKind,
316
+ });
317
+ }
318
+ if (!probe.emptyKinds.includes(kind))
319
+ probe.emptyKinds.push(kind);
320
+ continue;
321
+ }
322
+ if (referenceIdExists(inspection, kind, raw)) {
323
+ probe.resolved += 1;
324
+ continue;
325
+ }
326
+ probe.missing += 1;
327
+ onMissing(raw, nearestIds(raw, referenceIdsFor(inspection, kind), 1)[0]?.id);
328
+ }
329
+ }
330
+ function idProbeCoverage(subject, unit, probe) {
331
+ if (probe.probed === 0)
332
+ return [];
333
+ return [
334
+ {
335
+ subject,
336
+ unit,
337
+ expected: probe.probed,
338
+ examined: probe.probed - probe.unverified,
339
+ ...(probe.unverified > 0
340
+ ? { unexamined: probe.unverifiedLabels.slice(0, 20), unexaminedTotal: probe.unverified }
341
+ : {}),
342
+ reason: emptyRegistryReason(probe),
343
+ },
344
+ ];
345
+ }
89
346
  // ─── 1. Knowledge → file references ───────────────────────────────────────
90
347
  function checkKnowledgeFileRefs(inspection, findings) {
91
348
  for (const k of inspection.knowledgeEntries) {
@@ -115,36 +372,182 @@ function checkKnowledgeFileRefs(inspection, findings) {
115
372
  }
116
373
  }
117
374
  }
118
- // ─── 2. Search-tuning targets ─────────────────────────────────────────────
119
- async function checkSearchTuning(inspection, lookups, findings) {
120
- const { listSearchTuning } = await import("./search-tuning-registry.js");
121
- const entries = listSearchTuning(inspection);
122
- for (const entry of entries) {
123
- const boostMaps = [entry.boostIds, ...(entry.taskHints ?? []).map((h) => h.boostIds)];
124
- for (const idMap of boostMaps) {
125
- if (!idMap)
126
- continue;
127
- for (const targetId of Object.keys(idMap)) {
128
- // Not a hand-maintained chain of `lookups.x.has(...)`: that list
129
- // silently omitted policies, decisions and scaffold patterns, so ids
130
- // that WERE registered got reported as unknown. Adding a kind to the
131
- // registry now widens this automatically.
132
- if (referenceIdExistsInAnyKind(inspection, targetId))
133
- continue;
134
- pushFinding(findings, {
135
- severity: SelfConfigSeverityV2.Warning,
136
- code: 'search-tuning-target-missing',
137
- sourceKind: 'unknown',
138
- sourceId: entry.id,
139
- targetKind: 'unknown',
140
- targetId,
141
- relation: 'tunes',
142
- file: entry.sourceFile,
143
- message: `Search tuning "${entry.id}" boosts unknown id "${targetId}".`,
144
- suggestedFix: 'Register the target id or remove the boost entry.',
145
- });
146
- }
375
+ /**
376
+ * The search-tuning boost keys as a REFERENCE probe (round 13): the key probes
377
+ * THE lint judged (`searchTuningKeyProbes`), one per (entry, key), and every
378
+ * one THE key resolver could not check — its kind's registry is empty here
379
+ * (`registry-empty`), or its document kind has no id registry at all
380
+ * (`undeclarable-kind`, kind `search-document`). `packs contributions` reads
381
+ * this, so it and `search tuning doctor` can no longer disagree about the same
382
+ * contributed boost.
383
+ */
384
+ function tuningReferenceProbe(inspection, probes) {
385
+ const probe = newIdProbe();
386
+ for (const p of probes) {
387
+ const r = p.resolution;
388
+ probe.probed += 1;
389
+ if (r.status === SearchTuningKeyStatus.Resolved)
390
+ probe.resolved += 1;
391
+ else if (r.status === SearchTuningKeyStatus.Missing)
392
+ probe.missing += 1;
393
+ if (r.status !== SearchTuningKeyStatus.Unverified)
394
+ continue;
395
+ const kind = r.referenceKind;
396
+ probe.unverified += 1;
397
+ probe.unverifiedLabels.push(`${p.tuningId} → ${kind ?? 'search-document'} ${kind !== undefined ? r.id : p.key}`);
398
+ probe.unverifiedRefs.push({
399
+ ...(p.sourceFile ? { file: resolveEntryFile(inspection, p.sourceFile, p.packageName) } : {}),
400
+ ...(p.packageName ? { packageName: p.packageName } : {}),
401
+ sourceKind: 'search-tuning',
402
+ sourceId: p.tuningId,
403
+ field: p.lists[0] ?? 'boostIds',
404
+ kind: kind ?? 'search-document',
405
+ id: kind !== undefined ? r.id : p.key,
406
+ reason: kind !== undefined && isReferenceKindDeclarable(kind)
407
+ ? UnresolvableReason.RegistryEmpty
408
+ : UnresolvableReason.UndeclarableKind,
409
+ });
410
+ if (kind !== undefined && !probe.emptyKinds.includes(kind))
411
+ probe.emptyKinds.push(kind);
412
+ }
413
+ return probe;
414
+ }
415
+ /**
416
+ * THE search-tuning lint (`lintSearchTuning`, shared with `shrk search tuning
417
+ * doctor`), each issue prefixed `search-tuning-`.
418
+ *
419
+ * A boost key is a search-document id, `<kind>:<id>`. This used to look the
420
+ * WHOLE prefixed key up in bare-id registries, so every correctly prefixed key
421
+ * (which fires) was reported missing and a bare key (which never fires)
422
+ * passed — the exact inverse of the truth. The key is now split on its first
423
+ * `:` and resolved within the named kind; a bare key is `key-unprefixed`.
424
+ */
425
+ async function checkSearchTuning(inspection, findings) {
426
+ const lint = await lintSearchTuning(inspection);
427
+ for (const issue of lint.issues) {
428
+ const fix = issue.code === 'target-missing'
429
+ ? issue.suggestion
430
+ ? `Use "${issue.suggestion}", or register ${issue.referenceKind} "${issue.targetId}", or remove the boost.`
431
+ : `Register ${issue.referenceKind} "${issue.targetId}" or remove the boost.`
432
+ : issue.code === 'key-unprefixed' || issue.code === 'key-unknown-kind'
433
+ ? issue.suggestion
434
+ ? `Rewrite the key as "${issue.suggestion}".`
435
+ : 'Prefix the key with its document kind (`knowledge:`, `rule:`, `template:`, …) or remove it.'
436
+ : issue.suggestion
437
+ ? `Did you mean "${issue.suggestion}"?`
438
+ : undefined;
439
+ const target = searchTuningTarget(issue);
440
+ pushFinding(findings, {
441
+ severity: severityV2(issue.severity),
442
+ code: `search-tuning-${issue.code}`,
443
+ sourceKind: 'search-tuning',
444
+ sourceId: issue.tuningId,
445
+ targetKind: target.targetKind,
446
+ targetId: target.targetId,
447
+ relation: target.relation,
448
+ file: issue.source,
449
+ message: issue.message,
450
+ suggestedFix: fix,
451
+ nextCommand: 'shrk search tuning doctor',
452
+ occurrences: issue.occurrences,
453
+ });
454
+ }
455
+ return {
456
+ probes: lint.probes,
457
+ coverage: lint.coverage,
458
+ deadUnits: lint.deadUnits,
459
+ liveness: lint.liveness,
460
+ refProbe: tuningReferenceProbe(inspection, lint.keyProbes),
461
+ };
462
+ }
463
+ /**
464
+ * THE label of a search-tuning finding's target, in order:
465
+ *
466
+ * 1. a kind THE key resolver (or the index) placed it in → `<kind>:<id>`
467
+ * (a missing / excluded / resolvable bare key, a capped document);
468
+ * 2. a document with no id registry, or the cap summary → `search-document`;
469
+ * 3. a key no registry can be asked about (`doc:`, `preset:`, …) →
470
+ * `search-document`;
471
+ * 4. a key that resolves NOWHERE (a bare id no registry lists, an unknown
472
+ * `<kind>:` prefix) → `unknown:<the whole key>` — the genuine case;
473
+ * 5. a declaration-shape finding (triggers, vocabulary) → `schema:<field>`.
474
+ *
475
+ * Everything without a `referenceKind` used to fall to `unknown`, so a capped
476
+ * document (`unknown:knowledge:gamma.entry`), a resolvable bare key, a tag, a
477
+ * doc id and the finding CODE itself rendered `unknown:` — while the one really
478
+ * missing id did not (round 12, 12.4).
479
+ */
480
+ function searchTuningTarget(t) {
481
+ if (t.referenceKind !== undefined) {
482
+ return {
483
+ targetKind: selfKindOf(t.referenceKind),
484
+ targetId: t.targetId ?? t.key ?? t.docId ?? t.code,
485
+ relation: 'tunes',
486
+ };
487
+ }
488
+ if (t.docId !== undefined)
489
+ return { targetKind: 'search-document', targetId: t.docId, relation: 'tunes' };
490
+ if (t.moreDocuments !== undefined) {
491
+ return { targetKind: 'search-document', targetId: `${t.moreDocuments} more document(s)`, relation: 'tunes' };
492
+ }
493
+ if (t.key !== undefined) {
494
+ if (t.status === SearchTuningKeyStatus.Unverified) {
495
+ return { targetKind: 'search-document', targetId: t.key, relation: 'tunes' };
147
496
  }
497
+ // The WHOLE key: an unknown prefix's right-hand id may well exist.
498
+ return { targetKind: selfKindOf(undefined), targetId: t.key, relation: 'tunes' };
499
+ }
500
+ return { targetKind: 'schema', targetId: t.field ?? t.code, relation: 'validates' };
501
+ }
502
+ /**
503
+ * A loader issue's target. A file-level issue (load-failed, missing-file, an
504
+ * entry with no id) is about the file. A clamped `boostIds` key is a
505
+ * search-document id, labelled through THE key resolver exactly like the lint's
506
+ * keys (the registries are warm: `buildLookupsV2` and the lint both warmed
507
+ * them); a clamped tag / source / kind name is no id at all — it is the
508
+ * declaration `schema:<map>.<name>`. Every clamped key used to read
509
+ * `unknown:<key>`: a resolvable id, or a tag labelled an unknown id.
510
+ */
511
+ function searchTuningLoadTarget(inspection, i) {
512
+ if (i.key === undefined)
513
+ return { targetKind: 'file', targetId: i.source ?? i.code };
514
+ if (i.field === 'boostIds' || i.field?.endsWith('.boostIds') === true) {
515
+ const r = resolveSearchTuningKey(inspection, i.key);
516
+ const t = searchTuningTarget({
517
+ code: i.code,
518
+ key: i.key,
519
+ status: r.status,
520
+ referenceKind: r.referenceKind,
521
+ targetId: r.id,
522
+ });
523
+ return { targetKind: t.targetKind, targetId: t.targetId };
524
+ }
525
+ return { targetKind: 'schema', targetId: i.field !== undefined ? `${i.field}.${i.key}` : i.key };
526
+ }
527
+ /**
528
+ * The tuning loader's own issues (a file that failed to import, a clamped
529
+ * boost). A `missing-id` entry is a REJECTED entry: THE rejection family
530
+ * reports it (error, `search-tuning-invalid`) — never a second time here.
531
+ */
532
+ function checkSearchTuningLoadIssues(inspection, findings) {
533
+ for (const i of listSearchTuningIssues(inspection)) {
534
+ // A refused entry (no id; round 13: a malformed boost value or marker) is
535
+ // THE rejection family's to report — once, as an error.
536
+ if (i.code === 'missing-id' || i.code === 'invalid-entry')
537
+ continue;
538
+ const target = searchTuningLoadTarget(inspection, i);
539
+ pushFinding(findings, {
540
+ severity: severityV2(i.severity),
541
+ code: `search-tuning-${i.code}`,
542
+ sourceKind: 'search-tuning',
543
+ sourceId: i.tuningId ?? i.source ?? 'search-tuning',
544
+ targetKind: target.targetKind,
545
+ targetId: target.targetId,
546
+ relation: 'validates',
547
+ file: i.source,
548
+ message: i.message,
549
+ nextCommand: 'shrk search tuning doctor',
550
+ });
148
551
  }
149
552
  }
150
553
  // ─── 3. Agent-tests → helpers / commands / playbooks / policies ───────────
@@ -182,108 +585,160 @@ async function checkAgentTests(inspection, lookups, findings) {
182
585
  probe(t, t.expectedHelpers, 'helper', lookups.helpers, 'expects');
183
586
  probe(t, t.expectedPlaybooks, 'playbook', lookups.playbooks, 'expects');
184
587
  probe(t, t.expectedPolicies, 'policy', lookups.policies, 'expects');
185
- probe(t, t.expectedCommands, 'command', lookups.commands, 'expects');
588
+ // `expectedCommands` go through `checkCommandStrings` (the injected resolver).
186
589
  }
187
590
  }
188
591
  // ─── 4. Templates → conventions / helpers / profiles / registration hints ─
189
- function checkTemplateMetadata(inspection, lookups, findings) {
592
+ /**
593
+ * Template `metadata` id fields, each probed through THE id resolver per THE
594
+ * binding table (`PROBED_ID_FIELDS`): `requiredProfileIds` → workspace-profile,
595
+ * `requiredConventionIds` → convention, `requiredHelperIds` → helper,
596
+ * `registrationHintIds` → registration-hint.
597
+ *
598
+ * This used bare `lookups.X.has(id)` loops: `requiredProfileIds` resolved
599
+ * against the MIGRATION registry, and every field warned "not registered"
600
+ * over an EMPTY registry — while the same id in a registration hint was a loud
601
+ * skip. One question, two answers (round 12, 12.3b). An id checked against an
602
+ * empty registry is now an unexamined unit (NOT VERIFIED), never a warning.
603
+ */
604
+ function checkTemplateMetadata(inspection, findings) {
190
605
  const templates = (inspection.templates ?? []);
606
+ const rows = PROBED_ID_FIELDS.filter((r) => r.source === ProbedIdSource.Template);
607
+ const probe = newIdProbe();
191
608
  for (const t of templates) {
192
- const m = t.metadata;
193
- if (!m)
609
+ if (!t.metadata)
194
610
  continue;
195
- for (const id of m.requiredConventionIds ?? []) {
196
- if (lookups.conventions.has(id))
197
- continue;
198
- pushFinding(findings, {
611
+ const src = inspection.templateSources?.get(t.id);
612
+ for (const row of rows) {
613
+ probeIds(inspection, row.kind, readIdList(t, row.field), probe,
614
+ // The unexamined label names the KIND resolved against (the report's
615
+ // `kind`), never the code label — `profile` was ambiguous once
616
+ // `workspace-profile` existed (round 12 review, T4).
617
+ (id) => `${t.id} → ${row.kind} ${id}`, (id, nearest) => pushFinding(findings, {
199
618
  severity: SelfConfigSeverityV2.Warning,
200
- code: 'template-convention-missing',
619
+ code: `template-${row.label}-missing`,
201
620
  sourceKind: 'template',
202
621
  sourceId: t.id,
203
- targetKind: 'convention',
622
+ targetKind: selfKindOf(row.kind),
204
623
  targetId: id,
205
624
  relation: 'requires',
206
- message: `Template "${t.id}" requires convention "${id}" but it is not registered.`,
207
- nextCommand: 'shrk conventions list --source pack',
208
- });
625
+ message: `Template "${t.id}" ${row.field} names ${row.label} "${id}"${notResolvedTail(row.kind)}.`,
626
+ ...(nearest ? { suggestedFix: `Did you mean "${nearest}"?` } : {}),
627
+ nextCommand: referenceKindListVerb(row.kind),
628
+ }), { sourceKind: 'template', sourceId: t.id, field: row.field, file: src?.file, packageName: src?.packageName });
209
629
  }
210
- for (const id of m.requiredHelperIds ?? []) {
211
- if (lookups.helpers.has(id))
212
- continue;
213
- pushFinding(findings, {
214
- severity: SelfConfigSeverityV2.Warning,
215
- code: 'template-helper-missing',
216
- sourceKind: 'template',
217
- sourceId: t.id,
218
- targetKind: 'helper',
219
- targetId: id,
220
- relation: 'requires',
221
- message: `Template "${t.id}" requires helper "${id}" but it is not registered.`,
222
- nextCommand: 'shrk helper list --source pack',
223
- });
224
- }
225
- for (const id of m.requiredProfileIds ?? []) {
226
- if (lookups.migrationProfiles.has(id))
227
- continue;
228
- pushFinding(findings, {
229
- severity: SelfConfigSeverityV2.Warning,
230
- code: 'template-profile-missing',
231
- sourceKind: 'template',
232
- sourceId: t.id,
233
- targetKind: 'profile',
234
- targetId: id,
235
- relation: 'requires',
236
- message: `Template "${t.id}" requires profile "${id}" but it is not registered.`,
237
- nextCommand: 'shrk profiles list',
238
- });
239
- }
240
- for (const id of m.registrationHintIds ?? []) {
241
- if (lookups.registrationHints.has(id))
630
+ }
631
+ return { coverage: idProbeCoverage('templates', 'required ids', probe), deadUnits: [], refProbe: probe };
632
+ }
633
+ // ─── 5. Routing hints → commands / templates / helpers / playbooks / profiles ───
634
+ /**
635
+ * Routing hints: the loader's issues (which had NO consumer), whether each
636
+ * hint can match at all, and every `recommends` id through THE channel table
637
+ * and THE id resolver. The old probe list skipped the declared `knowledge` and
638
+ * `policies` channels entirely. An invalid hint or a duplicate id is a
639
+ * REJECTED entry: THE rejection family reports it (`routing-hint-invalid` /
640
+ * `routing-hint-duplicate-id`, one reporter) — this pass skips those codes.
641
+ */
642
+ async function checkRoutingHints(inspection, findings) {
643
+ const loaded = await loadTaskRoutingHints(inspection);
644
+ const entries = loaded.entries;
645
+ for (const i of await listTaskRoutingHintIssues(inspection)) {
646
+ if (i.code === 'invalid-hint' || i.code === 'duplicate-id')
647
+ continue;
648
+ pushFinding(findings, {
649
+ severity: severityV2(i.severity),
650
+ code: `routing-hint-${i.code}`,
651
+ sourceKind: 'routing-hint',
652
+ sourceId: i.hintId ?? i.source ?? 'routing-hints',
653
+ targetKind: i.code === 'load-failed' || i.code === 'missing-file' ? 'file' : 'schema',
654
+ targetId: i.code,
655
+ relation: 'validates',
656
+ file: i.source,
657
+ message: i.message,
658
+ });
659
+ }
660
+ const probe = newIdProbe();
661
+ // A hint that can never match is dead by SHAPE (out of expectEmpty scope — no
662
+ // target can ever make it match); it still settles through THE liveness
663
+ // authority, unmarked, so this file's dead list has one derivation.
664
+ const hintObservations = [];
665
+ for (const e of entries) {
666
+ const canMatch = routingHintCanMatch(e.hint);
667
+ hintObservations.push({
668
+ list: 'routing hints',
669
+ unit: e.hint.id,
670
+ exists: canMatch,
671
+ live: canMatch,
672
+ ...(canMatch
673
+ ? {}
674
+ : { cause: UnitDeadCause.Defect, deadReason: 'declares no criterion the matcher scores, so it can never match' }),
675
+ });
676
+ const rec = (e.hint.recommends ?? {});
677
+ for (const channel of ROUTING_RECOMMENDS_CHANNEL_KEYS) {
678
+ const spec = ROUTING_RECOMMENDS_CHANNELS[channel];
679
+ // `recommends.commands` go through `checkCommandStrings` (the injected resolver).
680
+ if (spec.kind === 'command')
242
681
  continue;
243
- pushFinding(findings, {
244
- severity: SelfConfigSeverityV2.Warning,
245
- code: 'template-registration-hint-missing',
246
- sourceKind: 'template',
247
- sourceId: t.id,
248
- targetKind: 'registration-hint',
682
+ const kind = spec.kind;
683
+ probeIds(inspection, kind, rec[channel], probe, (id) => `${e.hint.id} → ${kind} ${id}`, (id, nearest) => pushFinding(findings, {
684
+ severity: SelfConfigSeverityV2.Info,
685
+ code: `routing-hint-${spec.label}-missing`,
686
+ sourceKind: 'routing-hint',
687
+ sourceId: e.hint.id,
688
+ // THE kind the id was resolved against — no relabel. (`profile` was a
689
+ // display alias no registry or list verb has; round 12, 12.3.)
690
+ targetKind: selfKindOf(kind),
249
691
  targetId: id,
250
- relation: 'requires',
251
- message: `Template "${t.id}" references registration hint "${id}" but it is not registered.`,
252
- nextCommand: 'shrk registrations list',
692
+ relation: 'routes-to',
693
+ file: e.sourceFile,
694
+ message: `Routing hint "${e.hint.id}" recommends ${kind === 'migration-profile' ? 'migration profile' : spec.label} "${id}" (recommends.${channel}) but it is not registered.`,
695
+ ...(nearest ? { suggestedFix: `Did you mean "${nearest}"?` } : {}),
696
+ nextCommand: referenceKindListVerb(kind),
697
+ }), {
698
+ sourceKind: 'routing-hint',
699
+ sourceId: e.hint.id,
700
+ field: `recommends.${channel}`,
701
+ file: e.sourceFile,
702
+ packageName: e.packageName,
253
703
  });
254
704
  }
255
705
  }
256
- }
257
- // ─── 5. Routing hints → commands / templates / helpers / playbooks / profiles ───
258
- async function checkRoutingHints(inspection, lookups, findings) {
259
- const entries = await listTaskRoutingHints(inspection);
260
- for (const e of entries) {
261
- const rec = e.hint.recommends;
262
- const probe = (ids, kind, lookup) => {
263
- if (!ids)
264
- return;
265
- for (const id of ids) {
266
- if (lookup.has(id))
267
- continue;
268
- pushFinding(findings, {
269
- severity: SelfConfigSeverityV2.Info,
270
- code: `routing-hint-${kind}-missing`,
271
- sourceKind: 'routing-hint',
272
- sourceId: e.hint.id,
273
- targetKind: kind,
274
- targetId: id,
275
- relation: 'routes-to',
276
- message: `Routing hint "${e.hint.id}" recommends ${kind} "${id}" but it is not registered.`,
277
- });
278
- }
279
- };
280
- probe(rec.templates, 'template', lookups.templates);
281
- probe(rec.helpers, 'helper', lookups.helpers);
282
- probe(rec.conventions, 'convention', lookups.conventions);
283
- probe(rec.profiles, 'profile', lookups.migrationProfiles);
284
- probe(rec.commands, 'command', lookups.commands);
285
- probe(rec.playbooks, 'playbook', lookups.playbooks);
706
+ const hints = settleUnitLiveness({
707
+ subject: 'routing hints',
708
+ unitLabel: 'hints',
709
+ weight: UnitDeadWeight.Coverage,
710
+ observations: hintObservations,
711
+ marks: [],
712
+ deadSummary: 'declare no criterion the matcher scores (keyword, phrase, compiling regex), so they can never match',
713
+ });
714
+ const coverage = [...hints.coverage];
715
+ // A hint file that failed to import (or a pack-declared one that is absent)
716
+ // hides every hint in it: the file is unexamined, never a healthy registry.
717
+ const brokenHintFiles = [
718
+ ...new Set(loaded.issues
719
+ .filter((i) => i.code === 'load-failed' || i.code === 'missing-file')
720
+ .map((i) => nodePath.relative(inspection.projectRoot, i.source ?? '') || (i.source ?? '?'))),
721
+ ];
722
+ if (loaded.files > 0) {
723
+ coverage.push({
724
+ subject: 'routing hints',
725
+ unit: 'hint files',
726
+ expected: loaded.files,
727
+ examined: Math.max(0, loaded.files - brokenHintFiles.length),
728
+ ...(brokenHintFiles.length > 0
729
+ ? { unexamined: brokenHintFiles.slice(0, 20), unexaminedTotal: brokenHintFiles.length }
730
+ : {}),
731
+ reason: 'failed to load or are missing, so none of their hints are known',
732
+ });
286
733
  }
734
+ coverage.push(...idProbeCoverage('routing hints', 'recommended ids', probe));
735
+ return {
736
+ probes: { probed: probe.probed, resolved: probe.resolved, missing: probe.missing, unverified: probe.unverified },
737
+ coverage,
738
+ deadUnits: hints.dead.map((u) => `routing hint ${u.unit} (can never match)`),
739
+ refProbe: probe,
740
+ liveness: [hints],
741
+ };
287
742
  }
288
743
  // ─── 6. Pipelines → templates / commands (via cliCommands) ────────────────
289
744
  function checkPipelines(inspection, lookups, findings) {
@@ -291,7 +746,8 @@ function checkPipelines(inspection, lookups, findings) {
291
746
  (inspection.pipelines ?? []);
292
747
  for (const p of pipelines) {
293
748
  for (const step of p.steps ?? []) {
294
- // Templates referenced by id should resolve.
749
+ // A step reference names a knowledge entry / template / path convention
750
+ // (`IPipelineStep.references`), or a convention / helper.
295
751
  for (const ref of step.references ?? []) {
296
752
  if (lookups.templates.has(ref) ||
297
753
  lookups.knowledge.has(ref) ||
@@ -299,16 +755,24 @@ function checkPipelines(inspection, lookups, findings) {
299
755
  lookups.paths.has(ref) ||
300
756
  lookups.helpers.has(ref))
301
757
  continue;
758
+ // Not an accepted kind — but THE resolver may still place it (a
759
+ // construct id): that is the wrong kind, never "unknown". It used to
760
+ // read `references unknown:<id>` for an id `self-config resolve` finds.
761
+ const kinds = referenceKindsOf(inspection, ref);
762
+ const resolvedAs = kinds[0];
302
763
  pushFinding(findings, {
303
764
  severity: SelfConfigSeverityV2.Info,
304
- code: 'pipeline-reference-missing',
765
+ code: resolvedAs === undefined ? 'pipeline-reference-missing' : 'pipeline-reference-wrong-kind',
305
766
  sourceKind: 'pipeline',
306
767
  sourceId: p.id,
307
- targetKind: 'unknown',
768
+ targetKind: selfKindOf(resolvedAs),
308
769
  targetId: ref,
309
770
  relation: 'references',
310
771
  file: p.source?.origin ?? undefined,
311
- message: `Pipeline "${p.id}" step "${step.id}" references unknown id "${ref}".`,
772
+ message: resolvedAs === undefined
773
+ ? `Pipeline "${p.id}" step "${step.id}" references unknown id "${ref}".`
774
+ : `Pipeline "${p.id}" step "${step.id}" references "${ref}", which is a ${kinds.join(' | ')} — a step reference names a knowledge entry, template, path convention, convention or helper.`,
775
+ nextCommand: `shrk self-config resolve ${ref}`,
312
776
  confidence: 'medium',
313
777
  });
314
778
  }
@@ -316,13 +780,31 @@ function checkPipelines(inspection, lookups, findings) {
316
780
  }
317
781
  }
318
782
  // ─── 7. Playbooks → templates / helpers / commands / profiles ─────────────
783
+ /**
784
+ * Playbooks: the loader's issues (an import failure used to be swallowed, so a
785
+ * broken `playbooks.ts` reported a healthy, merely smaller registry), file
786
+ * coverage, and each playbook's template / pipeline ids. Reads the LOADER —
787
+ * the cache-only `listPlaybooks` answered "no playbooks" whenever nothing had
788
+ * warmed it.
789
+ */
319
790
  async function checkPlaybooks(inspection, lookups, findings) {
320
- let playbooks;
321
- try {
322
- playbooks = (await listPlaybooks(inspection));
323
- }
324
- catch {
325
- return;
791
+ const { playbooks, issues, files } = await loadPlaybooksWithIssues(inspection);
792
+ const brokenFiles = [];
793
+ for (const i of issues) {
794
+ const rel = nodePath.relative(inspection.projectRoot, i.source) || i.source;
795
+ if (!brokenFiles.includes(rel))
796
+ brokenFiles.push(rel);
797
+ pushFinding(findings, {
798
+ severity: severityV2(i.severity),
799
+ code: `playbook-${i.code}`,
800
+ sourceKind: 'playbook',
801
+ sourceId: i.packageName ? `${i.packageName}:${rel}` : rel,
802
+ targetKind: 'file',
803
+ targetId: i.code,
804
+ relation: 'validates',
805
+ file: i.source,
806
+ message: i.message,
807
+ });
326
808
  }
327
809
  for (const p of playbooks) {
328
810
  for (const id of p.recommendedTemplateIds ?? []) {
@@ -356,36 +838,178 @@ async function checkPlaybooks(inspection, lookups, findings) {
356
838
  });
357
839
  }
358
840
  }
841
+ const coverage = files > 0
842
+ ? [
843
+ {
844
+ subject: 'playbooks',
845
+ unit: 'playbook files',
846
+ expected: files,
847
+ examined: Math.max(0, files - brokenFiles.length),
848
+ ...(brokenFiles.length > 0
849
+ ? { unexamined: brokenFiles.slice(0, 20), unexaminedTotal: brokenFiles.length }
850
+ : {}),
851
+ reason: 'failed to load or are missing, so none of their playbooks are known',
852
+ },
853
+ ]
854
+ : [];
855
+ return { coverage, deadUnits: [] };
359
856
  }
360
857
  // ─── 8. Registration hints → templates / conventions / profiles ───────────
361
- async function checkRegistrationHints(inspection, lookups, findings) {
362
- const entries = await listRegistrationHints(inspection);
858
+ /**
859
+ * Registration hints, through THE discovery authority
860
+ * (`buildRegistrationHintDoctorReport` — the one `registrations doctor` and
861
+ * `preview` read): load issues, dead / ambiguous / capped discovery, missing
862
+ * anchors. Plus the hint's real cross-references, `discovery.conventionIds` /
863
+ * `discovery.profileIds`. This used to read `relatedTemplateIds` & co. through
864
+ * a cast to fields `IRegistrationHint` does not declare — a probe that could
865
+ * never fire.
866
+ */
867
+ async function checkRegistrationHints(inspection, findings) {
868
+ const report = await buildRegistrationHintDoctorReport(inspection);
869
+ for (const i of report.issues) {
870
+ // An invalid hint / a duplicate id is a REJECTED entry — THE rejection
871
+ // family reports it (`registration-hint-invalid` / `-duplicate-id`).
872
+ if (i.code === 'invalid-hint' || i.code === 'duplicate-id')
873
+ continue;
874
+ pushFinding(findings, {
875
+ severity: severityV2(i.severity),
876
+ code: `registration-hint-${i.code}`,
877
+ sourceKind: 'registration-hint',
878
+ sourceId: i.hintId ?? i.source ?? 'registration-hints',
879
+ targetKind: 'file',
880
+ targetId: i.target ?? i.code,
881
+ relation: 'validates',
882
+ file: i.source,
883
+ message: i.message,
884
+ nextCommand: 'shrk registrations doctor',
885
+ });
886
+ }
887
+ const ids = await probeRegistrationHintIds(inspection, findings);
888
+ return {
889
+ coverage: [...report.coverage, ...ids.coverage],
890
+ deadUnits: report.deadUnits,
891
+ ...(ids.refProbe ? { refProbe: ids.refProbe } : {}),
892
+ liveness: [report.liveness],
893
+ };
894
+ }
895
+ /**
896
+ * A registration hint's cross-references — THE reference-probe half of the
897
+ * family, with no discovery walk, so `collectUnresolvableReferences` runs
898
+ * exactly what the doctor runs.
899
+ */
900
+ async function probeRegistrationHintIds(inspection, findings) {
901
+ // `discovery.conventionIds` / `discovery.profileIds`, per THE binding table:
902
+ // profileIds are WorkspaceProfile ids (`workspace-profile`). They were bound
903
+ // to `migration-profile` and relabelled `profile` — NOT VERIFIED forever with
904
+ // no migration profiles, a false "not registered" with any (round 12, 12.3).
905
+ const probe = newIdProbe();
906
+ const rows = PROBED_ID_FIELDS.filter((r) => r.source === ProbedIdSource.RegistrationHint);
907
+ for (const e of await listRegistrationHints(inspection)) {
908
+ for (const row of rows) {
909
+ probeIds(inspection, row.kind, readIdList(e.hint, row.field), probe, (id) => `${e.hint.id} → ${row.kind} ${id}`, (id, nearest) => pushFinding(findings, {
910
+ severity: SelfConfigSeverityV2.Info,
911
+ code: `registration-hint-${row.label}-missing`,
912
+ sourceKind: 'registration-hint',
913
+ sourceId: e.hint.id,
914
+ targetKind: selfKindOf(row.kind),
915
+ targetId: id,
916
+ relation: 'related',
917
+ file: e.sourceFile,
918
+ message: `Registration hint "${e.hint.id}" ${row.field} references ${row.label} "${id}"${notResolvedTail(row.kind)}.`,
919
+ ...(nearest ? { suggestedFix: `Did you mean "${nearest}"?` } : {}),
920
+ nextCommand: referenceKindListVerb(row.kind),
921
+ }), {
922
+ sourceKind: 'registration-hint',
923
+ sourceId: e.hint.id,
924
+ field: row.field,
925
+ file: e.sourceFile,
926
+ packageName: e.packageName,
927
+ });
928
+ }
929
+ }
930
+ return { coverage: idProbeCoverage('registration hints', 'related ids', probe), deadUnits: [], refProbe: probe };
931
+ }
932
+ // ─── 8b. Conventions → applicability profile ids ──────────────────────────
933
+ /**
934
+ * A convention's `appliesTo` id fields, per THE binding table — today
935
+ * `appliesTo.profileIds` → `workspace-profile`. It was never probed at all, so
936
+ * a typo'd profile id could never surface. Resolution only: `conventions
937
+ * check` does not yet FILTER on profileIds (making a firing gate fire less is
938
+ * not strictly more honest), which the field's JSDoc says.
939
+ */
940
+ async function checkConventionApplicability(inspection, findings) {
941
+ let entries;
942
+ try {
943
+ entries = await listConventions(inspection);
944
+ }
945
+ catch {
946
+ // A convention file that will not load is `conventions doctor`'s to report.
947
+ return { coverage: [], deadUnits: [] };
948
+ }
949
+ const rows = PROBED_ID_FIELDS.filter((r) => r.source === ProbedIdSource.Convention);
950
+ const probe = newIdProbe();
363
951
  for (const e of entries) {
364
- const h = e.hint;
365
- const probe = (ids, kind, lookup) => {
366
- if (!ids)
367
- return;
368
- for (const id of ids) {
369
- if (lookup.has(id))
370
- continue;
371
- pushFinding(findings, {
372
- severity: SelfConfigSeverityV2.Info,
373
- code: `registration-hint-${kind}-missing`,
374
- sourceKind: 'registration-hint',
375
- sourceId: h.id,
376
- targetKind: kind,
377
- targetId: id,
378
- relation: 'related',
379
- file: e.sourceFile,
380
- message: `Registration hint "${h.id}" references ${kind} "${id}" but it is not registered.`,
381
- confidence: 'medium',
382
- });
383
- }
384
- };
385
- probe(h.relatedTemplateIds, 'template', lookups.templates);
386
- probe(h.relatedConventionIds, 'convention', lookups.conventions);
387
- probe(h.relatedProfileIds, 'profile', lookups.migrationProfiles);
952
+ for (const row of rows) {
953
+ probeIds(inspection, row.kind, readIdList(e.convention, row.field), probe, (id) => `${e.convention.id} → ${row.kind} ${id}`, (id, nearest) => pushFinding(findings, {
954
+ severity: SelfConfigSeverityV2.Info,
955
+ code: `convention-${row.label}-missing`,
956
+ sourceKind: 'convention',
957
+ sourceId: e.convention.id,
958
+ targetKind: selfKindOf(row.kind),
959
+ targetId: id,
960
+ relation: 'related',
961
+ file: e.sourceFile,
962
+ message: `Convention "${e.convention.id}" ${row.field} names ${row.label} "${id}"${notResolvedTail(row.kind)}.`,
963
+ ...(nearest ? { suggestedFix: `Did you mean "${nearest}"?` } : {}),
964
+ nextCommand: referenceKindListVerb(row.kind),
965
+ }), {
966
+ sourceKind: 'convention',
967
+ sourceId: e.convention.id,
968
+ field: row.field,
969
+ file: e.sourceFile,
970
+ packageName: e.packageName,
971
+ });
972
+ }
973
+ }
974
+ return {
975
+ coverage: idProbeCoverage('conventions', 'applicability profile ids', probe),
976
+ deadUnits: [],
977
+ refProbe: probe,
978
+ };
979
+ }
980
+ /**
981
+ * Scaffold patterns' dead selectors: a `matchPaths` glob matching no file, a
982
+ * pattern matching none after `excludePaths` — counted by THE enumeration
983
+ * `infer templates` attributes candidates with. (The definition checks stay
984
+ * `shrk scaffolds doctor`'s.)
985
+ */
986
+ async function checkScaffoldPatterns(inspection, findings) {
987
+ let patterns;
988
+ try {
989
+ patterns = (await loadScaffoldPatternsFromInspection(inspection)).patterns;
990
+ }
991
+ catch {
992
+ return { coverage: [], deadUnits: [] };
993
+ }
994
+ if (patterns.length === 0)
995
+ return { coverage: [], deadUnits: [] };
996
+ const enumeration = enumerateScaffoldPatternCandidates(inspection.projectRoot, patterns);
997
+ for (const i of doctorScaffoldPatterns(patterns, inspection, enumeration)) {
998
+ if (!i.code)
999
+ continue;
1000
+ pushFinding(findings, {
1001
+ severity: severityV2(i.severity),
1002
+ code: `scaffold-pattern-${i.code}`,
1003
+ sourceKind: 'scaffold-pattern',
1004
+ sourceId: i.patternId,
1005
+ targetKind: 'file',
1006
+ targetId: i.target ?? '(any file)',
1007
+ relation: 'references',
1008
+ message: `Scaffold pattern "${i.patternId}" ${i.message}.`,
1009
+ nextCommand: 'shrk scaffolds doctor',
1010
+ });
388
1011
  }
1012
+ return scaffoldPatternCoverage(patterns, enumeration);
389
1013
  }
390
1014
  /**
391
1015
  * Decision records are parsed from Markdown via the heading text; the
@@ -464,51 +1088,451 @@ function checkDecisions(inspection, lookups, findings) {
464
1088
  }
465
1089
  }
466
1090
  }
467
- // ─── 10. Stale pack signatures (surface as warning) ───────────────────────
468
- function checkStaleSignatures(inspection, findings) {
1091
+ // ─── 9b. Declared cross-references (knowledge / construct / boundary / template) ─
1092
+ /**
1093
+ * Every id in a declared `related`-style field (knowledge `related` / `seeAlso`
1094
+ * / `supersededBy` / `actionHints.related*`, construct `related*` + facets that
1095
+ * declare `resolvesAs`, boundary `related*`, template `related`), read from THE
1096
+ * collector — this pass never walks a field itself. `buildLookupsV2` already
1097
+ * warmed the registries, so the collector's sync half is enough.
1098
+ *
1099
+ * A dangling id is a warning (it shrinks a result, it does not misroute —
1100
+ * `--strict` fails on it); a dangling `supersededBy`, a supersession cycle and
1101
+ * a facet naming an unknown kind are errors. An id that could not be looked up
1102
+ * is `xref-unverified` (info) AND an unexamined unit in the coverage record,
1103
+ * so the doctor cannot settle to a pass over it.
1104
+ */
1105
+ function checkDeclaredCrossReferences(inspection, findings) {
1106
+ const report = collectDeclaredXrefs(inspection);
1107
+ for (const row of report.rows) {
1108
+ if (row.status === DeclaredXrefStatus.Ok)
1109
+ continue;
1110
+ const firstAccepted = row.accepts === 'any' ? undefined : row.accepts[0];
1111
+ const wrongKindAs = row.status === DeclaredXrefStatus.WrongKind ? row.resolvedAs[0] : undefined;
1112
+ pushFinding(findings, {
1113
+ severity: severityV2(row.severity),
1114
+ code: row.status === DeclaredXrefStatus.Dangling
1115
+ ? 'xref-dangling'
1116
+ : row.status === DeclaredXrefStatus.WrongKind
1117
+ ? 'xref-wrong-kind'
1118
+ : 'xref-unverified',
1119
+ sourceKind: selfKindOf(row.sourceKind),
1120
+ sourceId: row.sourceId,
1121
+ targetKind: selfKindOf(wrongKindAs ?? firstAccepted),
1122
+ targetId: row.targetId,
1123
+ relation: row.relation,
1124
+ file: row.file,
1125
+ message: row.message,
1126
+ suggestedFix: row.status === DeclaredXrefStatus.Unverified
1127
+ ? 'Run `shrk self-config doctor` from the CLI (it warms every registry), or fix the empty registry the id resolves against.'
1128
+ : row.didYouMean.length > 0
1129
+ ? `Did you mean "${row.didYouMean[0]}"? Correct the id, register the target, or remove it from \`${row.field}\`.`
1130
+ : `Register the target, or remove the id from \`${row.field}\`.`,
1131
+ nextCommand: `shrk self-config resolve ${row.targetId}`,
1132
+ confidence: 'high',
1133
+ });
1134
+ }
1135
+ for (const issue of report.issues) {
1136
+ const superseded = issue.code === 'xref-superseded-cycle' || issue.code === 'xref-superseded-chain';
1137
+ // A supersession issue names the successor (a knowledge id). A declaration
1138
+ // issue (`xref-unknown-kind`, `xref-malformed`) is about the FIELD: the
1139
+ // facet value there may well resolve — its declared KIND is what is wrong.
1140
+ // It used to read `related unknown:<value>` (round 12, 12.4).
1141
+ const target = superseded
1142
+ ? { targetKind: 'knowledge', targetId: issue.targetId ?? issue.field, relation: 'supersedes' }
1143
+ : {
1144
+ targetKind: 'schema',
1145
+ targetId: issue.facetId !== undefined ? `${issue.field}[${issue.facetId}]` : issue.field,
1146
+ relation: 'validates',
1147
+ };
1148
+ pushFinding(findings, {
1149
+ severity: severityV2(issue.severity),
1150
+ code: issue.code,
1151
+ sourceKind: selfKindOf(issue.sourceKind),
1152
+ sourceId: issue.sourceId,
1153
+ targetKind: target.targetKind,
1154
+ targetId: target.targetId,
1155
+ relation: target.relation,
1156
+ file: issue.file,
1157
+ message: issue.message,
1158
+ confidence: 'high',
1159
+ });
1160
+ }
1161
+ // Like every probe family: a workspace that declares no cross-reference ids
1162
+ // contributes no record (it is not "nothing examined" for the whole doctor).
1163
+ const coverage = report.counts.ids > 0 || report.examined.unreadSources.length > 0
1164
+ ? [{ ...declaredXrefCoverage(report), subject: 'declared cross-references' }]
1165
+ : [];
1166
+ return {
1167
+ summary: { examined: report.examined, counts: report.counts, summary: declaredXrefSummaryLine(report) },
1168
+ coverage,
1169
+ report,
1170
+ };
1171
+ }
1172
+ /** THE declared-xref rows that could not be looked up, as unresolvable references (one per row). */
1173
+ function xrefUnresolvableReferences(inspection, report) {
1174
+ return report.rows
1175
+ .filter((r) => r.status === DeclaredXrefStatus.Unverified)
1176
+ .map((r) => {
1177
+ const kind = r.accepts === 'any' ? 'any' : r.accepts[0];
1178
+ const declarable = kind === undefined || kind === 'any' || isReferenceKindDeclarable(kind);
1179
+ return {
1180
+ // The collector records the file project-relative when possible.
1181
+ ...(r.file ? { file: nodePath.resolve(inspection.projectRoot, r.file) } : {}),
1182
+ ...(r.packageName ? { packageName: r.packageName } : {}),
1183
+ sourceKind: r.sourceKind,
1184
+ sourceId: r.sourceId,
1185
+ field: r.field,
1186
+ kind: kind ?? 'any',
1187
+ id: r.targetId,
1188
+ reason: declarable ? UnresolvableReason.RegistryEmpty : UnresolvableReason.UndeclarableKind,
1189
+ };
1190
+ });
1191
+ }
1192
+ /**
1193
+ * THE unresolvable-reference scan over the reference families' probes and the
1194
+ * declared-xref report: what was probed (`expected` — the reference families'
1195
+ * units plus the xref ids), what could be checked, and each that could not.
1196
+ * The doctor carries it; `collectUnresolvableReferences` returns it.
1197
+ */
1198
+ function referenceScanOf(inspection, probes, xrefs) {
1199
+ const present = probes.filter((p) => p !== undefined);
1200
+ const references = [...present.flatMap((p) => p.unverifiedRefs), ...xrefUnresolvableReferences(inspection, xrefs)];
1201
+ const expected = present.reduce((n, p) => n + p.probed, 0) + xrefs.counts.ids;
1202
+ return { expected, examined: expected - references.length, references };
1203
+ }
1204
+ // ─── 10. Pack contribution conflicts (every kind) ─────────────────────────
1205
+ /**
1206
+ * EVERY pack contribution conflict (duplicate id, shadowed, invalid,
1207
+ * missing-loader, stale signature). This used to surface only stale
1208
+ * signatures, so a duplicate-id or shadowed conflict was visible over MCP (v1)
1209
+ * and invisible to `shrk self-config doctor` (v2). Stale signatures keep their
1210
+ * v2 code `pack-signature-stale`; the v1 projection maps it back to
1211
+ * `pack-conflict:stale-signature`.
1212
+ */
1213
+ async function checkPackConflicts(inspection, findings) {
469
1214
  try {
470
- const inv = buildPackContributionsInventory(inspection);
1215
+ // The ASYNC inventory (loaders consulted); the stale-signature conflict it
1216
+ // carries is read from the one pack-asset freshness authority.
1217
+ const inv = await buildPackContributionsInventoryAsync(inspection);
1218
+ checkContributionRejections(findings, inv.rejections);
471
1219
  for (const c of inv.conflicts) {
472
- if (c.kind !== 'stale-signature')
473
- continue;
1220
+ const stale = c.kind === 'stale-signature';
474
1221
  pushFinding(findings, {
475
- severity: SelfConfigSeverityV2.Warning,
476
- code: 'pack-signature-stale',
1222
+ severity: stale ? SelfConfigSeverityV2.Warning : severityV2(c.severity),
1223
+ code: stale ? 'pack-signature-stale' : `pack-conflict:${c.kind}`,
477
1224
  sourceKind: 'pack',
478
1225
  sourceId: c.id,
479
- targetKind: 'schema',
480
- targetId: 'sharkcraft.pack-manifest/v1',
1226
+ targetKind: stale ? 'schema' : 'pack',
1227
+ targetId: stale ? 'sharkcraft.pack-manifest/v1' : String(c.contributionKind),
481
1228
  relation: 'validates',
482
1229
  message: c.message,
483
1230
  ...(c.nextCommand ? { nextCommand: c.nextCommand } : {}),
484
1231
  confidence: 'high',
485
1232
  });
486
1233
  }
1234
+ return inv;
487
1235
  }
488
1236
  catch {
489
- // ignore
1237
+ // An inventory that cannot be built is `shrk packs doctor`'s to report.
1238
+ return null;
490
1239
  }
491
1240
  }
1241
+ /**
1242
+ * THE rejection family (round 12, 12.1): every declared contribution entry a
1243
+ * loader refused — from THE rejection channel the inventory carries — is an
1244
+ * ERROR, one finding per failing field (`<kind>-invalid`, or
1245
+ * `<kind>-duplicate-id`), local and pack alike: an entry that never takes
1246
+ * effect is a defect, and it used to reach no surface but its kind's doctor
1247
+ * (conventions, helpers) or none at all (knowledge, templates, playbooks, …).
1248
+ * The routing / registration hint families no longer report their own
1249
+ * `invalid-hint` / `duplicate-id` issues, so each rejection has one reporter.
1250
+ */
1251
+ function checkContributionRejections(findings, rejections) {
1252
+ for (const r of rejections) {
1253
+ const f = rejectionFindingOf(r.kind, r.cause);
1254
+ const where = r.index >= 0 ? `${r.exportName ?? ''}[${r.index}]` : (r.exportName ?? 'default');
1255
+ const who = r.entryId !== undefined ? `"${r.entryId}"` : `at ${where}`;
1256
+ for (const reason of r.reasons) {
1257
+ pushFinding(findings, {
1258
+ severity: SelfConfigSeverityV2.Error,
1259
+ code: f.code,
1260
+ sourceKind: f.kind,
1261
+ sourceId: r.entryId ?? `${r.file}:${where}`,
1262
+ targetKind: 'schema',
1263
+ targetId: validatorField(reason),
1264
+ relation: 'validates',
1265
+ file: r.file,
1266
+ message: `${f.kind} ${who} in ${r.file} (${where}) was rejected by its loader — ${reason}. It does not take effect.`,
1267
+ nextCommand: r.packageName ? `shrk packs contributions --pack ${r.packageName}` : f.doctor,
1268
+ confidence: 'high',
1269
+ });
1270
+ }
1271
+ }
1272
+ }
1273
+ /**
1274
+ * A reference that resolves nowhere may name an entry that WAS declared — and
1275
+ * refused by its loader. Its `*-missing` finding then says so instead of
1276
+ * offering a did-you-mean (round 12, 12.1): the id is not a typo.
1277
+ */
1278
+ function annotateRejectedTargets(findings, rejections) {
1279
+ const byId = new Map();
1280
+ for (const r of rejections)
1281
+ if (r.entryId !== undefined && !byId.has(r.entryId))
1282
+ byId.set(r.entryId, r);
1283
+ if (byId.size === 0)
1284
+ return;
1285
+ findings.forEach((f, i) => {
1286
+ if (!isUnresolvedReferenceFinding(f))
1287
+ return;
1288
+ const r = byId.get(f.targetId);
1289
+ if (!r)
1290
+ return;
1291
+ const rejectedAs = rejectionFindingOf(r.kind, r.cause).kind;
1292
+ if (f.targetKind !== rejectedAs && f.targetKind !== selfKindOf(undefined))
1293
+ return;
1294
+ findings[i] = {
1295
+ ...f,
1296
+ suggestedFix: `'${f.targetId}' is declared in ${r.file} but was rejected: ${r.reasons.join('; ')}`,
1297
+ };
1298
+ });
1299
+ }
1300
+ function actionHintCommand(c) {
1301
+ if (typeof c === 'string')
1302
+ return c;
1303
+ if (c && typeof c === 'object' && typeof c.command === 'string') {
1304
+ return c.command;
1305
+ }
1306
+ return undefined;
1307
+ }
1308
+ /**
1309
+ * Every command string an asset prescribes, with where it came from. A broken
1310
+ * source degrades to "no commands from it" — that asset's own loader reports
1311
+ * the load failure.
1312
+ */
1313
+ async function collectCommandSites(inspection) {
1314
+ const sites = [];
1315
+ const add = (sourceKind, sourceId, field, commands, severity, relation, file, assumeShrk = false) => {
1316
+ for (const raw of commands ?? []) {
1317
+ const command = actionHintCommand(raw)?.trim();
1318
+ if (command) {
1319
+ sites.push({
1320
+ sourceKind,
1321
+ sourceId,
1322
+ field,
1323
+ command,
1324
+ severity,
1325
+ relation,
1326
+ file,
1327
+ ...(assumeShrk ? { assumeShrk: true } : {}),
1328
+ });
1329
+ }
1330
+ }
1331
+ };
1332
+ const W = SelfConfigSeverityV2.Warning;
1333
+ try {
1334
+ const { loadAgentContractTests } = await import("./test-runner.js");
1335
+ // An agent test's expectation is load-bearing: a dead expected command fails
1336
+ // the test forever, so it is an error here too.
1337
+ for (const t of await loadAgentContractTests(inspection)) {
1338
+ add('agent-test', t.id, 'expectedCommands', t.expectedCommands, SelfConfigSeverityV2.Error, 'expects', undefined, true);
1339
+ }
1340
+ }
1341
+ catch {
1342
+ // agent-test loader failure is reported by `shrk test agent`
1343
+ }
1344
+ try {
1345
+ for (const e of await listTaskRoutingHints(inspection)) {
1346
+ add('routing-hint', e.hint.id, 'recommends.commands', e.hint.recommends?.commands, W, 'routes-to', e.sourceFile);
1347
+ }
1348
+ }
1349
+ catch {
1350
+ // reported by the routing-hint loader
1351
+ }
1352
+ try {
1353
+ for (const e of await listRegistrationHints(inspection)) {
1354
+ add('registration-hint', e.hint.id, 'validationCommands', e.hint.validationCommands, W, 'validates', e.sourceFile);
1355
+ }
1356
+ }
1357
+ catch {
1358
+ // reported by the registration-hint loader
1359
+ }
1360
+ for (const p of inspection.pipelineRegistry?.list?.() ?? []) {
1361
+ for (const step of p.steps ?? []) {
1362
+ add('pipeline', p.id, `steps.${step.id}.cliCommands`, step.cliCommands, W, 'references');
1363
+ }
1364
+ }
1365
+ for (const p of listPlaybooks(inspection)) {
1366
+ // The loader refuses a playbook without an array `steps` (12.1d: an
1367
+ // accepted one crashed the whole doctor here); guard anyway.
1368
+ (Array.isArray(p.steps) ? p.steps : []).forEach((step, i) => {
1369
+ add('playbook', p.id, `steps[${i}].commands`, step?.commands, W, 'references');
1370
+ });
1371
+ }
1372
+ for (const c of listConstructs(inspection)) {
1373
+ add('construct', c.id, 'commands', c.commands, W, 'references');
1374
+ }
1375
+ for (const p of inspection.presetRegistry?.list?.() ?? []) {
1376
+ add('preset', p.id, 'recommendedNextCommands', p.recommendedNextCommands, W, 'references');
1377
+ }
1378
+ for (const k of inspection.knowledgeEntries) {
1379
+ const origin = k.source?.origin ?? undefined;
1380
+ add('knowledge', k.id, 'actionHints.commands', k.actionHints?.commands, W, 'references', origin);
1381
+ const refCommands = (k.references ?? [])
1382
+ .filter((r) => r.kind === 'command')
1383
+ .map((r) => r.id ?? r.command)
1384
+ .filter((c) => typeof c === 'string' && c.length > 0);
1385
+ add('knowledge', k.id, 'references', refCommands, W, 'references', origin, true);
1386
+ }
1387
+ try {
1388
+ for (const d of await loadTsDecisions(inspection)) {
1389
+ add('decision', d.id, 'relatedCommands', d.relatedCommands, W, 'related', d.sourceFile);
1390
+ }
1391
+ }
1392
+ catch {
1393
+ // reported by the decisions loader
1394
+ }
1395
+ return sites;
1396
+ }
1397
+ /**
1398
+ * The command-strings pass: every command an asset prescribes, resolved
1399
+ * through THE injected command resolver, deduped per (asset, command).
1400
+ *
1401
+ * Without a resolver (outside the CLI) nothing is checked, and the pass says
1402
+ * so ONCE (`command-probe-unverified`, info) — never a finding per command,
1403
+ * and never an error. The old probes checked against a set initialised empty,
1404
+ * so every command in every hint was "missing", correct ones included.
1405
+ */
1406
+ async function checkCommandStrings(inspection, findings) {
1407
+ const seen = new Set();
1408
+ const sites = (await collectCommandSites(inspection)).filter((s) => {
1409
+ // The reading is part of the key: one asset may cite `doctor` both as free
1410
+ // shell text and as a command reference, and the two resolve differently.
1411
+ const key = `${s.sourceKind}:${s.sourceId}|${s.assumeShrk === true ? 'ref' : 'sh'}|${s.command}`;
1412
+ if (seen.has(key))
1413
+ return false;
1414
+ seen.add(key);
1415
+ return true;
1416
+ });
1417
+ const counts = { probed: sites.length, exists: 0, prefixOnly: 0, notShrk: 0, unknown: 0, unverified: 0 };
1418
+ if (sites.length === 0)
1419
+ return counts;
1420
+ if (!hasCommandResolver(inspection)) {
1421
+ counts.unverified = sites.length;
1422
+ pushFinding(findings, {
1423
+ severity: SelfConfigSeverityV2.Info,
1424
+ code: 'command-probe-unverified',
1425
+ // The doctor's own probe summary — it has a kind (it borrowed `unknown`).
1426
+ sourceKind: 'self-config',
1427
+ sourceId: 'command-probes',
1428
+ targetKind: 'command',
1429
+ targetId: `${sites.length} command string(s)`,
1430
+ relation: 'references',
1431
+ message: `${sites.length} command string(s) prescribed by assets were NOT checked: ${COMMAND_INDEX_NOT_INJECTED}.`,
1432
+ suggestedFix: 'Run `shrk self-config doctor` from the CLI, which injects the live command index.',
1433
+ confidence: 'high',
1434
+ });
1435
+ return counts;
1436
+ }
1437
+ for (const site of sites) {
1438
+ const resolution = site.assumeShrk
1439
+ ? resolveShrkCommandReference(inspection, site.command)
1440
+ : resolveCommandReference(inspection, site.command);
1441
+ switch (resolution.status) {
1442
+ case CommandResolutionStatus.Ok:
1443
+ counts.exists += 1;
1444
+ break;
1445
+ case CommandResolutionStatus.PrefixOnly:
1446
+ counts.prefixOnly += 1;
1447
+ break;
1448
+ case CommandResolutionStatus.NotShrk:
1449
+ counts.notShrk += 1;
1450
+ break;
1451
+ case CommandResolutionStatus.Unverified:
1452
+ counts.unverified += 1;
1453
+ break;
1454
+ default: {
1455
+ counts.unknown += 1;
1456
+ const closest = resolution.closest ?? [];
1457
+ pushFinding(findings, {
1458
+ severity: site.severity,
1459
+ code: 'unknown-command',
1460
+ sourceKind: site.sourceKind,
1461
+ sourceId: site.sourceId,
1462
+ targetKind: 'command',
1463
+ targetId: site.command,
1464
+ relation: site.relation,
1465
+ file: site.file,
1466
+ message: `${site.sourceKind} "${site.sourceId}" ${site.field} prescribes \`${site.command}\`, which does not resolve (${resolution.status}${resolution.reason ? ` — ${resolution.reason}` : ''}).`,
1467
+ suggestedFix: closest.length > 0
1468
+ ? `Did you mean \`${closest[0]}\`? Update or remove the command.`
1469
+ : 'Update or remove the command.',
1470
+ nextCommand: 'shrk surface list',
1471
+ confidence: 'high',
1472
+ });
1473
+ }
1474
+ }
1475
+ }
1476
+ return counts;
1477
+ }
492
1478
  // ─── Public entry ─────────────────────────────────────────────────────────
493
1479
  export async function buildSelfConfigDoctorReportV2(inspection) {
494
1480
  const findings = [];
495
1481
  const lookups = await buildLookupsV2(inspection);
496
1482
  checkKnowledgeFileRefs(inspection, findings);
497
- await checkSearchTuning(inspection, lookups, findings);
1483
+ const tuning = await checkSearchTuning(inspection, findings);
1484
+ checkSearchTuningLoadIssues(inspection, findings);
498
1485
  await checkAgentTests(inspection, lookups, findings);
499
- checkTemplateMetadata(inspection, lookups, findings);
500
- await checkRoutingHints(inspection, lookups, findings);
1486
+ const templateFamily = checkTemplateMetadata(inspection, findings);
1487
+ const routing = await checkRoutingHints(inspection, findings);
501
1488
  checkPipelines(inspection, lookups, findings);
502
- await checkPlaybooks(inspection, lookups, findings);
503
- await checkRegistrationHints(inspection, lookups, findings);
1489
+ const playbookFamily = await checkPlaybooks(inspection, lookups, findings);
1490
+ const registration = await checkRegistrationHints(inspection, findings);
1491
+ const conventionFamily = await checkConventionApplicability(inspection, findings);
1492
+ const scaffolds = await checkScaffoldPatterns(inspection, findings);
504
1493
  checkDecisions(inspection, lookups, findings);
505
- checkStaleSignatures(inspection, findings);
1494
+ const xrefs = checkDeclaredCrossReferences(inspection, findings);
1495
+ const inventory = await checkPackConflicts(inspection, findings);
1496
+ const commandProbes = await checkCommandStrings(inspection, findings);
1497
+ if (inventory)
1498
+ annotateRejectedTargets(findings, inventory.rejections);
1499
+ const referenceScan = referenceScanOf(inspection, [templateFamily.refProbe, routing.refProbe, registration.refProbe, conventionFamily.refProbe, tuning.refProbe], xrefs.report);
1500
+ const selectorUnits = [
1501
+ ...(tuning.liveness ?? []),
1502
+ ...(routing.liveness ?? []),
1503
+ ...(registration.liveness ?? []),
1504
+ ...(scaffolds.liveness ?? []),
1505
+ ]
1506
+ .flatMap((s) => s.units)
1507
+ .filter((u) => u.state !== UnitLivenessState.Live);
1508
+ const coverage = [
1509
+ ...commandProbeCoverage(commandProbes),
1510
+ ...tuning.coverage,
1511
+ ...routing.coverage,
1512
+ ...playbookFamily.coverage,
1513
+ ...registration.coverage,
1514
+ ...conventionFamily.coverage,
1515
+ ...templateFamily.coverage,
1516
+ ...scaffolds.coverage,
1517
+ ...xrefs.coverage,
1518
+ ];
1519
+ const deadUnits = [
1520
+ ...tuning.deadUnits,
1521
+ ...routing.deadUnits,
1522
+ ...registration.deadUnits,
1523
+ ...scaffolds.deadUnits,
1524
+ ];
506
1525
  const totals = computeTotalsV2(findings);
507
- const verdict = totals.error > 0 ? 'errors' : totals.warning > 0 ? 'warnings' : 'ok';
1526
+ // One derivation: core's `coverageShortfall`, the rule the CLI settles with.
1527
+ const unverified = coverage.some((c) => coverageShortfall(c) !== undefined);
1528
+ const verdict = totals.error > 0 ? 'errors' : unverified ? 'unverified' : totals.warning > 0 ? 'warnings' : 'ok';
508
1529
  const nextCommands = [];
509
1530
  if (totals.error > 0) {
510
1531
  nextCommands.push('Fix the errored ids first — agent-test expectations and template requirements are load-bearing.', 'shrk packs conflicts');
511
1532
  }
1533
+ else if (unverified) {
1534
+ nextCommands.push(`Some units were NOT verified (see coverage): a dead selector / boost is a ${DEAD_SELECTOR_CAUSES} — fix it, or mark a planned target expectEmpty; and run from the CLI, which injects the command index.`);
1535
+ }
512
1536
  else if (totals.warning > 0) {
513
1537
  nextCommands.push('Review warnings — most resolve by registering the missing id or removing the stale link.');
514
1538
  }
@@ -523,8 +1547,91 @@ export async function buildSelfConfigDoctorReportV2(inspection) {
523
1547
  totals,
524
1548
  verdict,
525
1549
  nextCommands,
1550
+ coverage,
1551
+ deadUnits,
1552
+ selectorUnits,
1553
+ unresolvableReferences: referenceScan.references,
1554
+ probes: {
1555
+ command: commandProbes,
1556
+ 'search-tuning-target': tuning.probes,
1557
+ 'routing-hint-target': routing.probes,
1558
+ },
1559
+ crossReferences: xrefs.summary,
526
1560
  };
527
1561
  }
1562
+ /**
1563
+ * Every declared reference that could not be resolved because its kind's
1564
+ * registry is empty here, or can never be filled (round 12, ONE-CHANGE) —
1565
+ * running EXACTLY the doctor's reference probes (template metadata, routing
1566
+ * `recommends`, registration-hint and convention id fields, declared
1567
+ * cross-references) over a warmed inspection, without the rest of the doctor.
1568
+ * `buildSelfConfigDoctorReportV2` carries the same list, so the doctor and
1569
+ * `shrk packs contributions` cannot disagree.
1570
+ */
1571
+ export async function collectUnresolvableReferences(inspection) {
1572
+ await warmReferenceRegistries(inspection);
1573
+ const scratch = [];
1574
+ const templateFamily = checkTemplateMetadata(inspection, scratch);
1575
+ const routing = await checkRoutingHints(inspection, scratch);
1576
+ const registration = await probeRegistrationHintIds(inspection, scratch);
1577
+ const conventionFamily = await checkConventionApplicability(inspection, scratch);
1578
+ const xrefs = checkDeclaredCrossReferences(inspection, scratch);
1579
+ // THE key probes the tuning lint judges (round 13) — the same function, so
1580
+ // `packs contributions` and `search tuning doctor` answer from one resolution.
1581
+ const { entries: tunings } = await loadSearchTuning(inspection);
1582
+ const tuning = tuningReferenceProbe(inspection, searchTuningKeyProbes(inspection, tunings));
1583
+ return referenceScanOf(inspection, [templateFamily.refProbe, routing.refProbe, registration.refProbe, conventionFamily.refProbe, tuning], xrefs.report);
1584
+ }
1585
+ /**
1586
+ * The command-string probes' coverage: every prescribed command string must
1587
+ * have been resolved. Only present when assets prescribe any — a repo with no
1588
+ * command strings is not "nothing examined" for the whole doctor.
1589
+ *
1590
+ * `not-shrk` strings (git, tsc, a script with no package.json to read) are a
1591
+ * DELIBERATE narrowing — not this engine's to judge — so they leave `expected`
1592
+ * rather than counting as examined. A set that is ALL not-shrk narrows to
1593
+ * nothing and carries no record, exactly like a set with no strings at all.
1594
+ * `prefix-only` strings stay in scope (the verb was proven); the clean line
1595
+ * names how many tails went unproven.
1596
+ */
1597
+ function commandProbeCoverage(c) {
1598
+ const expected = c.probed - c.notShrk;
1599
+ if (expected <= 0)
1600
+ return [];
1601
+ return [
1602
+ {
1603
+ unit: 'command strings',
1604
+ expected,
1605
+ examined: expected - c.unverified,
1606
+ // `reason` names why a GAP exists. Only the no-resolver path returns
1607
+ // Unverified, so `unverified > 0` means exactly "index not injected";
1608
+ // a fully examined run carries no reason (a verified row labelled
1609
+ // NOT VERIFIED misled JSON readers).
1610
+ ...(c.unverified > 0 ? { reason: COMMAND_INDEX_NOT_INJECTED } : {}),
1611
+ },
1612
+ ];
1613
+ }
1614
+ /** One-line summary of the command-string probes, shared by the renderers. */
1615
+ function commandProbeLine(report) {
1616
+ const c = report.probes.command;
1617
+ return `probed ${c.probed} · unknown ${c.unknown} · prefix-only ${c.prefixOnly} · not-shrk ${c.notShrk} · unverified ${c.unverified}`;
1618
+ }
1619
+ /**
1620
+ * The tuning-key probes on ONE line — so a 100% failure rate reads as a probe
1621
+ * fault, not as N independent content errors.
1622
+ */
1623
+ function tuningProbeLine(report) {
1624
+ const t = report.probes['search-tuning-target'];
1625
+ return `probed ${t.probed} · resolved ${t.resolved} · missing ${t.missing} · unprefixed ${t.unprefixed} · unknown-kind ${t.unknownKind} · unverified ${t.unverified}`;
1626
+ }
1627
+ function routingProbeLine(report) {
1628
+ const r = report.probes['routing-hint-target'];
1629
+ return `probed ${r.probed} · resolved ${r.resolved} · missing ${r.missing} · unverified ${r.unverified}`;
1630
+ }
1631
+ /** `<subject>: <formatCoverage>` per record — what each family examined. */
1632
+ function coverageLines(report) {
1633
+ return report.coverage.map((c) => `${c.subject ? `${c.subject}: ` : ''}${formatCoverage(c)}`);
1634
+ }
528
1635
  function computeTotalsV2(findings) {
529
1636
  const byRelation = {};
530
1637
  const bySourceKind = {};
@@ -554,13 +1661,22 @@ export function renderSelfConfigDoctorV2Text(report) {
554
1661
  lines.push(` errors ${report.totals.error}`);
555
1662
  lines.push(` warnings ${report.totals.warning}`);
556
1663
  lines.push(` info ${report.totals.info}`);
1664
+ lines.push(` commands ${commandProbeLine(report)}`);
1665
+ lines.push(` tuning keys ${tuningProbeLine(report)}`);
1666
+ lines.push(` routing ids ${routingProbeLine(report)}`);
1667
+ if (report.crossReferences)
1668
+ lines.push(` xrefs ${report.crossReferences.summary}`);
1669
+ for (const c of coverageLines(report))
1670
+ lines.push(` coverage ${c}`);
557
1671
  lines.push('');
558
1672
  if (report.findings.length === 0) {
559
- lines.push(' No cross-reference issues. ✓');
1673
+ // No ✓ here: whether this is a pass is the settled verdict's call (a dead
1674
+ // or unverified unit is not a finding, and still is not a pass).
1675
+ lines.push(' No findings.');
560
1676
  return lines.join('\n') + '\n';
561
1677
  }
562
1678
  for (const f of report.findings.slice(0, 200)) {
563
- lines.push(` ${f.severity.padEnd(7)} [${f.code}] ${f.sourceKind}:${f.sourceId} ${f.relation} ${f.targetKind}:${f.targetId}`);
1679
+ lines.push(` ${f.severity.padEnd(7)} [${f.code}] ${f.sourceKind}:${f.sourceId} ${f.relation} ${f.targetKind}:${f.targetId}${f.occurrences && f.occurrences > 1 ? ` (×${f.occurrences})` : ''}`);
564
1680
  lines.push(` ${f.message}`);
565
1681
  if (f.suggestedFix)
566
1682
  lines.push(` fix: ${f.suggestedFix}`);
@@ -586,9 +1702,16 @@ export function renderSelfConfigDoctorV2Markdown(report) {
586
1702
  lines.push(`- errors: ${report.totals.error}`);
587
1703
  lines.push(`- warnings: ${report.totals.warning}`);
588
1704
  lines.push(`- info: ${report.totals.info}`);
1705
+ lines.push(`- commands: ${commandProbeLine(report)}`);
1706
+ lines.push(`- tuning keys: ${tuningProbeLine(report)}`);
1707
+ lines.push(`- routing ids: ${routingProbeLine(report)}`);
1708
+ if (report.crossReferences)
1709
+ lines.push(`- xrefs: ${report.crossReferences.summary}`);
1710
+ for (const c of coverageLines(report))
1711
+ lines.push(`- coverage: ${c}`);
589
1712
  lines.push('');
590
1713
  if (report.findings.length === 0) {
591
- lines.push('No cross-reference issues. ✓');
1714
+ lines.push('No findings.');
592
1715
  return lines.join('\n') + '\n';
593
1716
  }
594
1717
  lines.push('| Severity | Source | Relation | Target | Message | Next |');