akm-cli 0.9.0-rc.0 → 0.9.0-rc.13

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 (598) hide show
  1. package/CHANGELOG.md +1283 -22
  2. package/README.md +62 -37
  3. package/SECURITY.md +46 -31
  4. package/dist/akm +162 -38
  5. package/dist/akm-migrate +44 -0
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/hints/cli-hints-full.md +268 -118
  8. package/dist/assets/hints/cli-hints-short.md +87 -24
  9. package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
  10. package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
  11. package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
  12. package/dist/assets/improve-strategies/frequent.json +15 -0
  13. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
  14. package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
  15. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
  16. package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
  17. package/dist/assets/improve-strategies/reflect-distill.json +30 -0
  18. package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
  19. package/dist/assets/prompts/consolidate-system.md +5 -5
  20. package/dist/assets/prompts/extract-session.md +2 -6
  21. package/dist/assets/prompts/memory-infer-user.md +2 -3
  22. package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
  23. package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
  24. package/dist/assets/prompts/reflect-output-repair.md +3 -0
  25. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  26. package/dist/assets/stash-skeleton/README.md +38 -10
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  39. package/dist/assets/tasks/core/extract.yml +3 -2
  40. package/dist/assets/tasks/core/improve.yml +2 -1
  41. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  42. package/dist/assets/tasks/core/sync.yml +1 -0
  43. package/dist/assets/tasks/core/version-check.yml +2 -1
  44. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  45. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  46. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  48. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  49. package/dist/assets/templates/html/health.html +5 -4
  50. package/dist/assets/workflows/workflow-template.md +31 -15
  51. package/dist/cli/invocation.js +279 -0
  52. package/dist/cli/parse-args.js +5 -90
  53. package/dist/cli/retired-commands.js +78 -0
  54. package/dist/cli/shared.js +158 -48
  55. package/dist/cli-node.mjs +2 -1
  56. package/dist/cli.js +747 -293
  57. package/dist/commands/agent/agent-dispatch.js +19 -18
  58. package/dist/commands/agent/agent-support.js +0 -24
  59. package/dist/commands/agent/contribute-cli.js +43 -97
  60. package/dist/commands/completions.js +80 -23
  61. package/dist/commands/config-cli.js +44 -281
  62. package/dist/commands/env/env-binding.js +99 -0
  63. package/dist/commands/env/env-cli.js +84 -224
  64. package/dist/commands/env/env.js +12 -163
  65. package/dist/commands/env/marker-path.js +6 -0
  66. package/dist/commands/env/secret-cli.js +45 -61
  67. package/dist/commands/env/secret.js +32 -62
  68. package/dist/commands/feedback-cli.js +179 -85
  69. package/dist/commands/health/accept-rate.js +58 -0
  70. package/dist/commands/health/advisories.js +7 -8
  71. package/dist/commands/health/checks.js +279 -94
  72. package/dist/commands/health/html-report.js +197 -578
  73. package/dist/commands/health/improve-metrics.js +277 -246
  74. package/dist/commands/health/llm-usage.js +19 -19
  75. package/dist/commands/health/md-report.js +16 -7
  76. package/dist/commands/health/metrics.js +67 -32
  77. package/dist/commands/health/renderers.js +47 -0
  78. package/dist/commands/health/report-view-model.js +508 -0
  79. package/dist/commands/health/stash-exposure.js +1 -1
  80. package/dist/commands/health/surfaces.js +16 -56
  81. package/dist/commands/health/task-runs.js +3 -67
  82. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  83. package/dist/commands/health/types-improve.js +29 -0
  84. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  85. package/dist/commands/health/types-result.js +7 -0
  86. package/dist/commands/health/types-runs.js +4 -0
  87. package/dist/commands/health/types-session-log.js +4 -0
  88. package/dist/commands/health/types-windows.js +4 -0
  89. package/dist/commands/health/types.js +26 -21
  90. package/dist/commands/health/windows.js +2 -3
  91. package/dist/commands/health.js +296 -167
  92. package/dist/commands/improve/anti-collapse.js +5 -5
  93. package/dist/commands/improve/autonomy-gate.js +68 -0
  94. package/dist/commands/improve/collapse-detector.js +65 -52
  95. package/dist/commands/improve/consolidate/chunking.js +9 -7
  96. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  97. package/dist/commands/improve/consolidate/merge.js +4 -0
  98. package/dist/commands/improve/consolidate.js +454 -1354
  99. package/dist/commands/improve/content-hash.js +39 -0
  100. package/dist/commands/improve/distill/content-repair.js +4 -10
  101. package/dist/commands/improve/distill/promote-memory.js +89 -64
  102. package/dist/commands/improve/distill/quality-gate.js +118 -42
  103. package/dist/commands/improve/distill-guards.js +1 -1
  104. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  105. package/dist/commands/improve/distill.js +607 -363
  106. package/dist/commands/improve/eligibility.js +165 -79
  107. package/dist/commands/improve/extract-cli.js +35 -126
  108. package/dist/commands/improve/extract-prompt.js +6 -35
  109. package/dist/commands/improve/extract.js +640 -391
  110. package/dist/commands/improve/feedback-valence.js +2 -12
  111. package/dist/commands/improve/improve-cli.js +134 -135
  112. package/dist/commands/improve/improve-result-file.js +30 -50
  113. package/dist/commands/improve/improve-run-types.js +4 -0
  114. package/dist/commands/improve/improve-strategies.js +135 -0
  115. package/dist/commands/improve/improve.js +904 -701
  116. package/dist/commands/improve/locks.js +64 -111
  117. package/dist/commands/improve/loop-stages.js +1110 -923
  118. package/dist/commands/improve/memory/derived-ref.js +124 -0
  119. package/dist/commands/improve/memory/memory-belief.js +79 -7
  120. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  121. package/dist/commands/improve/memory/memory-improve.js +25 -37
  122. package/dist/commands/improve/outcome-loop.js +25 -88
  123. package/dist/commands/improve/preparation.js +1034 -813
  124. package/dist/commands/improve/proactive-maintenance.js +34 -9
  125. package/dist/commands/improve/proposal-envelope.js +31 -0
  126. package/dist/commands/improve/reflect.js +983 -794
  127. package/dist/commands/improve/run-context.js +119 -0
  128. package/dist/commands/improve/salience.js +24 -127
  129. package/dist/commands/improve/session-asset.js +7 -3
  130. package/dist/commands/improve/shared.js +14 -34
  131. package/dist/commands/improve/source-identity.js +28 -0
  132. package/dist/commands/improve/triage.js +20 -17
  133. package/dist/commands/lint/base-linter.js +340 -313
  134. package/dist/commands/lint/env-key-rules.js +31 -47
  135. package/dist/commands/lint/index.js +185 -30
  136. package/dist/commands/{events.js → log.js} +28 -38
  137. package/dist/commands/migrate-cli.js +54 -0
  138. package/dist/commands/migration-tool.js +55 -0
  139. package/dist/commands/observability-cli.js +70 -208
  140. package/dist/commands/proposal/diff-format.js +50 -0
  141. package/dist/commands/proposal/drain-policies.js +0 -6
  142. package/dist/commands/proposal/drain.js +91 -40
  143. package/dist/commands/proposal/proposal-cli.js +134 -132
  144. package/dist/commands/proposal/proposal-types.js +56 -0
  145. package/dist/commands/proposal/proposal.js +83 -65
  146. package/dist/commands/proposal/propose-cli.js +88 -0
  147. package/dist/commands/proposal/propose.js +105 -88
  148. package/dist/commands/proposal/repository.js +1303 -278
  149. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  150. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  151. package/dist/commands/proposal/validators/proposals.js +6 -8
  152. package/dist/commands/read/curate.js +78 -73
  153. package/dist/commands/read/knowledge.js +510 -13
  154. package/dist/commands/read/registry-search.js +2 -2
  155. package/dist/commands/read/remember-cli.js +84 -15
  156. package/dist/commands/read/search-cli.js +203 -96
  157. package/dist/commands/read/search.js +126 -94
  158. package/dist/commands/read/show.js +226 -250
  159. package/dist/commands/registry-cli.js +34 -60
  160. package/dist/commands/remember.js +18 -57
  161. package/dist/commands/sources/add-cli.js +104 -49
  162. package/dist/commands/sources/bundle-cli.js +166 -0
  163. package/dist/commands/sources/bundle-config-ops.js +63 -0
  164. package/dist/commands/sources/info.js +27 -15
  165. package/dist/commands/sources/init.js +30 -40
  166. package/dist/commands/sources/installed-stashes.js +469 -172
  167. package/dist/commands/sources/migration-help.js +7 -4
  168. package/dist/commands/sources/schema-repair.js +10 -9
  169. package/dist/commands/sources/self-update.js +182 -121
  170. package/dist/commands/sources/source-add.js +169 -178
  171. package/dist/commands/sources/source-clone.js +144 -41
  172. package/dist/commands/sources/source-manage.js +94 -59
  173. package/dist/commands/sources/sources-cli.js +64 -205
  174. package/dist/commands/sources/stash-cli.js +91 -54
  175. package/dist/commands/sources/stash-skeleton.js +1 -1
  176. package/dist/commands/tasks/tasks-cli.js +106 -104
  177. package/dist/commands/tasks/tasks.js +445 -262
  178. package/dist/commands/workflow-cli.js +232 -121
  179. package/dist/core/action-contributors.js +1 -1
  180. package/dist/core/activation-policy.js +49 -0
  181. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  182. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  183. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  184. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  185. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  186. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  187. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  188. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  189. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  190. package/dist/core/adapter/adapters/index.js +80 -0
  191. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  192. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  193. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  194. package/dist/core/adapter/adapters/shared.js +286 -0
  195. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  196. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  197. package/dist/core/adapter/bundle-adapter.js +4 -0
  198. package/dist/core/adapter/detect-adapter.js +17 -0
  199. package/dist/core/adapter/recognize-match.js +44 -0
  200. package/dist/core/adapter/registry.js +56 -0
  201. package/dist/core/adapter/types.js +4 -0
  202. package/dist/core/asset/akm-markdown.js +30 -0
  203. package/dist/core/asset/asset-placement.js +243 -0
  204. package/dist/core/asset/asset-ref.js +110 -79
  205. package/dist/core/asset/asset-serialize.js +20 -0
  206. package/dist/core/asset/frontmatter.js +28 -12
  207. package/dist/core/asset/markdown.js +40 -51
  208. package/dist/core/asset/resolve-ref.js +274 -0
  209. package/dist/core/asset/stash-meta.js +2 -2
  210. package/dist/core/bundle-id.js +51 -0
  211. package/dist/core/common.js +281 -86
  212. package/dist/core/config/config-io.js +42 -128
  213. package/dist/core/config/config-schema.js +233 -834
  214. package/dist/core/config/config-sources.js +162 -39
  215. package/dist/core/config/config-types.js +16 -11
  216. package/dist/core/config/config-version.js +29 -0
  217. package/dist/core/config/config-walker.js +126 -37
  218. package/dist/core/config/config.js +154 -331
  219. package/dist/core/config/deep-merge.js +41 -0
  220. package/dist/core/config/engine-semantics.js +28 -0
  221. package/dist/core/config/experimental.js +21 -0
  222. package/dist/core/config/schema/embedding.js +38 -0
  223. package/dist/core/config/schema/engines.js +116 -0
  224. package/dist/core/config/schema/experimental.js +47 -0
  225. package/dist/core/config/schema/feedback.js +31 -0
  226. package/dist/core/config/schema/improve-processes.js +389 -0
  227. package/dist/core/config/schema/improve.js +94 -0
  228. package/dist/core/config/schema/index-config.js +176 -0
  229. package/dist/core/config/schema/output.js +18 -0
  230. package/dist/core/config/schema/primitives.js +94 -0
  231. package/dist/core/config/schema/search.js +30 -0
  232. package/dist/core/config/schema/setup.js +18 -0
  233. package/dist/core/config/schema/sources-bundles.js +169 -0
  234. package/dist/core/config/schema/workflow.js +29 -0
  235. package/dist/core/env-secret-ref.js +155 -20
  236. package/dist/core/errors.js +17 -15
  237. package/dist/core/events-types.js +4 -0
  238. package/dist/core/events.js +46 -128
  239. package/dist/core/extra-params.js +62 -0
  240. package/dist/core/file-change.js +17 -0
  241. package/dist/core/file-lock.js +202 -57
  242. package/dist/core/fs-txn.js +392 -0
  243. package/dist/core/git-message.js +59 -0
  244. package/dist/core/improve-result.js +167 -0
  245. package/dist/core/json-schema.js +142 -0
  246. package/dist/core/lesson-lint.js +1 -17
  247. package/dist/core/logs-db.js +1 -1
  248. package/dist/core/maintenance-barrier.js +135 -0
  249. package/dist/core/migration-operation.js +44 -0
  250. package/dist/core/mutation-target.js +78 -0
  251. package/dist/core/paths.js +22 -25
  252. package/dist/core/platform.js +10 -0
  253. package/dist/core/recognition-util.js +128 -0
  254. package/dist/core/redaction.js +392 -0
  255. package/dist/core/standards/resolve-standards-context.js +36 -65
  256. package/dist/core/standards/resolve-stash-standards.js +2 -2
  257. package/dist/core/standards/resolve-type-conventions.js +5 -5
  258. package/dist/core/state/migrations.js +242 -11
  259. package/dist/core/state-db.js +98 -10
  260. package/dist/core/structured.js +1 -1
  261. package/dist/core/subprocess.js +303 -0
  262. package/dist/core/text-truncation.js +9 -5
  263. package/dist/core/time.js +20 -0
  264. package/dist/core/type-presentation.js +130 -0
  265. package/dist/core/warn.js +0 -3
  266. package/dist/core/write-source.js +834 -118
  267. package/dist/indexer/bundle-identity-guard.js +92 -0
  268. package/dist/indexer/db/graph-db.js +1 -25
  269. package/dist/indexer/db/llm-cache.js +1 -1
  270. package/dist/indexer/ensure-index.js +30 -9
  271. package/dist/indexer/graph/graph-boost.js +9 -30
  272. package/dist/indexer/graph/graph-extraction.js +41 -27
  273. package/dist/indexer/graph/graph-types.js +4 -0
  274. package/dist/indexer/index-writer-lock.js +93 -49
  275. package/dist/indexer/index-written-assets.js +100 -53
  276. package/dist/indexer/indexer.js +746 -329
  277. package/dist/indexer/init.js +18 -25
  278. package/dist/indexer/installations.js +142 -0
  279. package/dist/indexer/passes/dir-staleness.js +18 -10
  280. package/dist/indexer/passes/memory-inference.js +25 -15
  281. package/dist/indexer/passes/metadata.js +412 -243
  282. package/dist/indexer/scan/doc-to-entry.js +160 -0
  283. package/dist/indexer/scan/drain-dir.js +134 -0
  284. package/dist/indexer/search/db-search.js +292 -108
  285. package/dist/indexer/search/fts-query.js +64 -0
  286. package/dist/indexer/search/ranking-contributors.js +145 -25
  287. package/dist/indexer/search/ranking-types.js +4 -0
  288. package/dist/indexer/search/ranking.js +28 -71
  289. package/dist/indexer/search/search-attribution.js +67 -0
  290. package/dist/indexer/search/search-fields.js +18 -3
  291. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  292. package/dist/indexer/search/search-source.js +157 -111
  293. package/dist/indexer/search/semantic-status.js +4 -1
  294. package/dist/indexer/usage/usage-events.js +10 -30
  295. package/dist/indexer/walk/file-context.js +3 -45
  296. package/dist/indexer/walk/matchers.js +42 -34
  297. package/dist/indexer/walk/path-resolver.js +11 -5
  298. package/dist/indexer/walk/walker.js +42 -14
  299. package/dist/integrations/agent/builder-shared.js +7 -0
  300. package/dist/integrations/agent/builders.js +5 -56
  301. package/dist/integrations/agent/config.js +3 -143
  302. package/dist/integrations/agent/detect.js +17 -2
  303. package/dist/integrations/agent/engine-resolution.js +231 -0
  304. package/dist/integrations/agent/index.js +1 -2
  305. package/dist/integrations/agent/model-aliases.js +16 -2
  306. package/dist/integrations/agent/profiles.js +36 -62
  307. package/dist/integrations/agent/prompts.js +46 -18
  308. package/dist/integrations/agent/runner-dispatch.js +93 -4
  309. package/dist/integrations/agent/runner.js +76 -208
  310. package/dist/integrations/agent/spawn.js +88 -196
  311. package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
  312. package/dist/integrations/harnesses/aider/index.js +48 -0
  313. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  314. package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
  315. package/dist/integrations/harnesses/amazonq/index.js +45 -0
  316. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  317. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  318. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  319. package/dist/integrations/harnesses/claude/index.js +24 -35
  320. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  321. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  322. package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
  323. package/dist/integrations/harnesses/codex/index.js +52 -0
  324. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  325. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  326. package/dist/integrations/harnesses/copilot/index.js +48 -0
  327. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  328. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  329. package/dist/integrations/harnesses/gemini/index.js +48 -0
  330. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  331. package/dist/integrations/harnesses/ids.js +24 -0
  332. package/dist/integrations/harnesses/index.js +54 -34
  333. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  334. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  335. package/dist/integrations/harnesses/opencode/index.js +14 -32
  336. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  337. package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
  338. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  339. package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
  340. package/dist/integrations/harnesses/openhands/index.js +48 -0
  341. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  342. package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
  343. package/dist/integrations/harnesses/pi/index.js +45 -0
  344. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  345. package/dist/integrations/harnesses/shared.js +17 -0
  346. package/dist/integrations/harnesses/types.js +43 -32
  347. package/dist/integrations/lockfile.js +211 -24
  348. package/dist/integrations/session-logs/index.js +36 -39
  349. package/dist/integrations/session-logs/provider-base.js +113 -0
  350. package/dist/llm/client.js +182 -110
  351. package/dist/llm/embedders/deterministic.js +2 -2
  352. package/dist/llm/embedders/remote.js +21 -9
  353. package/dist/llm/feature-gate.js +17 -57
  354. package/dist/llm/graph-extract.js +12 -13
  355. package/dist/llm/index-passes.js +8 -42
  356. package/dist/llm/memory-infer.js +144 -1
  357. package/dist/llm/metadata-enhance.js +45 -30
  358. package/dist/llm/structured-call.js +16 -8
  359. package/dist/llm/usage-persist.js +30 -5
  360. package/dist/llm/usage-telemetry.js +59 -6
  361. package/dist/output/cli-hints.js +1 -2
  362. package/dist/output/command-registry.js +27 -0
  363. package/dist/output/context.js +22 -7
  364. package/dist/output/format-exempt.js +80 -0
  365. package/dist/output/generic-render.js +251 -0
  366. package/dist/output/html-render.js +11 -16
  367. package/dist/output/render-registry.js +57 -0
  368. package/dist/output/renderers.js +14 -279
  369. package/dist/output/shapes/curate.js +10 -1
  370. package/dist/output/shapes/events.js +12 -7
  371. package/dist/output/shapes/helpers.js +58 -84
  372. package/dist/output/shapes/passthrough.js +11 -39
  373. package/dist/output/shapes/proposal/producer.js +15 -7
  374. package/dist/output/shapes/registry.js +12 -6
  375. package/dist/output/shapes.js +0 -9
  376. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  377. package/dist/output/text/bundle-show.js +7 -0
  378. package/dist/output/text/command-format.js +562 -0
  379. package/dist/output/text/env.js +1 -3
  380. package/dist/output/text/events.js +8 -7
  381. package/dist/output/text/helpers.js +15 -1164
  382. package/dist/output/text/proposal/producer.js +4 -2
  383. package/dist/output/text/proposal-format.js +202 -0
  384. package/dist/output/text/registry-commands.js +1 -2
  385. package/dist/output/text/registry.js +12 -6
  386. package/dist/output/text/show-directives.js +117 -0
  387. package/dist/output/text/show-format.js +103 -0
  388. package/dist/output/text/sync.js +5 -0
  389. package/dist/output/text/workflow-format.js +332 -0
  390. package/dist/output/text/workflow.js +3 -2
  391. package/dist/output/text.js +10 -19
  392. package/dist/registry/factory.js +4 -6
  393. package/dist/registry/origin-resolve.js +16 -27
  394. package/dist/registry/providers/skills-sh.js +3 -3
  395. package/dist/registry/providers/static-index.js +15 -25
  396. package/dist/registry/resolve.js +43 -94
  397. package/dist/registry/semver.js +43 -0
  398. package/dist/runtime.js +81 -12
  399. package/dist/scripts/akm-migrate.js +35529 -0
  400. package/dist/setup/detect.js +5 -7
  401. package/dist/setup/detected-engines.js +136 -0
  402. package/dist/setup/engine-config.js +100 -0
  403. package/dist/setup/registry-stash-loader.js +3 -3
  404. package/dist/setup/semantic-assets.js +12 -9
  405. package/dist/setup/setup.js +444 -208
  406. package/dist/setup/steps/connection-shared.js +120 -0
  407. package/dist/setup/steps/connection.js +108 -305
  408. package/dist/setup/steps/platforms.js +13 -12
  409. package/dist/setup/steps/semantic.js +15 -3
  410. package/dist/setup/steps/sources.js +21 -15
  411. package/dist/setup/steps/stashdir.js +6 -4
  412. package/dist/setup/steps/tasks.js +236 -119
  413. package/dist/setup/steps.js +3 -2
  414. package/dist/sources/freshness.js +39 -0
  415. package/dist/sources/provider-factory.js +11 -17
  416. package/dist/sources/providers/filesystem.js +2 -3
  417. package/dist/sources/providers/git-install.js +278 -34
  418. package/dist/sources/providers/git-provider.js +54 -56
  419. package/dist/sources/providers/git-stash.js +420 -91
  420. package/dist/sources/providers/git.js +2 -2
  421. package/dist/sources/providers/npm.js +16 -19
  422. package/dist/sources/providers/provider-utils.js +47 -22
  423. package/dist/sources/providers/sync-from-ref.js +3 -9
  424. package/dist/sources/providers/website.js +2 -2
  425. package/dist/sources/resolve.js +11 -10
  426. package/dist/sources/snapshot-fetchers/types.js +4 -0
  427. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  428. package/dist/storage/database.js +60 -4
  429. package/dist/storage/engines/sqlite-migrations.js +156 -5
  430. package/dist/storage/locations.js +1 -2
  431. package/dist/storage/repositories/canaries-repository.js +1 -1
  432. package/dist/storage/repositories/events-repository.js +51 -11
  433. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  434. package/dist/storage/repositories/index-connection.js +79 -0
  435. package/dist/storage/repositories/index-db.js +4 -3
  436. package/dist/storage/repositories/index-entries-repository.js +863 -0
  437. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  438. package/dist/storage/repositories/index-entry-types.js +4 -0
  439. package/dist/storage/repositories/index-fts-repository.js +167 -0
  440. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  441. package/dist/storage/repositories/index-meta-repository.js +49 -0
  442. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  443. package/dist/storage/repositories/index-sql.js +12 -0
  444. package/dist/storage/repositories/index-utility-repository.js +356 -0
  445. package/dist/storage/repositories/index-vec-repository.js +250 -0
  446. package/dist/storage/repositories/outcome-repository.js +119 -0
  447. package/dist/storage/repositories/proposals-repository.js +317 -75
  448. package/dist/storage/repositories/registry-cache.js +1 -1
  449. package/dist/storage/repositories/salience-repository.js +172 -0
  450. package/dist/storage/repositories/task-history-repository.js +110 -3
  451. package/dist/storage/repositories/workflow-runs-repository.js +240 -19
  452. package/dist/tasks/backends/cron.js +169 -46
  453. package/dist/tasks/backends/exec-utils.js +76 -3
  454. package/dist/tasks/backends/index.js +6 -9
  455. package/dist/tasks/backends/launchd.js +292 -55
  456. package/dist/tasks/backends/schtasks.js +557 -70
  457. package/dist/tasks/backends/types.js +4 -0
  458. package/dist/tasks/command-executable.js +93 -0
  459. package/dist/tasks/embedded.js +56 -38
  460. package/dist/tasks/parser.js +156 -64
  461. package/dist/tasks/resolve-akm-bin.js +144 -51
  462. package/dist/tasks/runner.js +377 -209
  463. package/dist/tasks/schedule.js +108 -19
  464. package/dist/tasks/scheduler-invocation.js +296 -0
  465. package/dist/tasks/schema.js +1 -1
  466. package/dist/tasks/task-id.js +35 -0
  467. package/dist/tasks/validator.js +30 -16
  468. package/dist/text-import-hook.mjs +1 -1
  469. package/dist/workflows/authoring/authoring.js +104 -43
  470. package/dist/workflows/authoring/scope-key.js +1 -1
  471. package/dist/workflows/cli.js +0 -16
  472. package/dist/workflows/concurrency-policy.js +15 -0
  473. package/dist/workflows/exec/brief.js +450 -0
  474. package/dist/workflows/exec/frozen-judge.js +47 -0
  475. package/dist/workflows/exec/native-executor.js +1038 -0
  476. package/dist/workflows/exec/param-secrets.js +115 -0
  477. package/dist/workflows/exec/report.js +1460 -0
  478. package/dist/workflows/exec/run-workflow.js +602 -0
  479. package/dist/workflows/exec/scheduler.js +71 -0
  480. package/dist/workflows/exec/step-work.js +1190 -0
  481. package/dist/workflows/exec/unit-writer.js +23 -0
  482. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  483. package/dist/workflows/exec/worktree.js +171 -0
  484. package/dist/workflows/ir/compile.js +246 -0
  485. package/dist/workflows/ir/freeze.js +233 -0
  486. package/dist/workflows/ir/params.js +54 -0
  487. package/dist/workflows/ir/plan-hash.js +68 -0
  488. package/dist/workflows/ir/schema.js +540 -0
  489. package/dist/workflows/parser.js +878 -304
  490. package/dist/workflows/program/expressions.js +181 -0
  491. package/dist/workflows/program/schema.js +51 -0
  492. package/dist/workflows/renderer.js +100 -45
  493. package/dist/workflows/resource-limits.js +22 -0
  494. package/dist/workflows/runtime/agent-identity.js +59 -14
  495. package/dist/workflows/runtime/checkin.js +1 -1
  496. package/dist/workflows/runtime/plan-classifier.js +131 -0
  497. package/dist/workflows/runtime/runs.js +376 -119
  498. package/dist/workflows/runtime/unit-checkin.js +45 -0
  499. package/dist/workflows/runtime/unit-phases.js +20 -0
  500. package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
  501. package/dist/workflows/schema.js +1 -11
  502. package/dist/workflows/validate-summary.js +2 -3
  503. package/dist/workflows/validator.js +52 -30
  504. package/docs/README.md +42 -78
  505. package/docs/migration/README.md +8 -0
  506. package/docs/migration/release-notes/0.6.0.md +1 -1
  507. package/docs/migration/release-notes/0.7.0.md +9 -8
  508. package/docs/migration/release-notes/0.9.0.md +158 -14
  509. package/docs/migration/v0.7-to-v0.8.md +46 -47
  510. package/docs/migration/v0.8-to-v0.9.md +844 -0
  511. package/docs/reference/README.md +12 -0
  512. package/docs/reference/data-and-telemetry.md +333 -0
  513. package/package.json +21 -17
  514. package/schemas/akm-asset-envelope.json +93 -0
  515. package/schemas/akm-config.json +4636 -0
  516. package/schemas/akm-task.json +87 -0
  517. package/schemas/akm-workflow.json +373 -0
  518. package/dist/akm-migrate-storage +0 -38
  519. package/dist/assets/help/help-accept.md +0 -12
  520. package/dist/assets/help/help-improve.md +0 -84
  521. package/dist/assets/help/help-proposals.md +0 -17
  522. package/dist/assets/help/help-propose.md +0 -17
  523. package/dist/assets/help/help-reject.md +0 -11
  524. package/dist/assets/profiles/frequent.json +0 -13
  525. package/dist/assets/profiles/recombine-only.json +0 -21
  526. package/dist/assets/profiles/reflect-distill.json +0 -30
  527. package/dist/assets/profiles/synthesize.json +0 -15
  528. package/dist/assets/prompts/procedural-system.md +0 -44
  529. package/dist/assets/prompts/recombine-system.md +0 -40
  530. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  531. package/dist/assets/tasks/core/backup.yml +0 -4
  532. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  533. package/dist/assets/templates/html/default.html +0 -78
  534. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  535. package/dist/assets/wiki/index-template.md +0 -12
  536. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  537. package/dist/assets/wiki/log-template.md +0 -8
  538. package/dist/assets/wiki/schema-template.md +0 -61
  539. package/dist/cli/config-migrate.js +0 -150
  540. package/dist/cli/config-validate.js +0 -39
  541. package/dist/commands/graph/graph-cli.js +0 -124
  542. package/dist/commands/graph/graph.js +0 -487
  543. package/dist/commands/improve/calibration.js +0 -161
  544. package/dist/commands/improve/dedup.js +0 -482
  545. package/dist/commands/improve/extract-watch.js +0 -140
  546. package/dist/commands/improve/hot-probation.js +0 -45
  547. package/dist/commands/improve/improve-auto-accept.js +0 -276
  548. package/dist/commands/improve/improve-profiles.js +0 -168
  549. package/dist/commands/improve/procedural.js +0 -398
  550. package/dist/commands/improve/recombine.js +0 -818
  551. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  552. package/dist/commands/lint/agent-linter.js +0 -44
  553. package/dist/commands/lint/command-linter.js +0 -44
  554. package/dist/commands/lint/default-linter.js +0 -16
  555. package/dist/commands/lint/fact-linter.js +0 -39
  556. package/dist/commands/lint/knowledge-linter.js +0 -16
  557. package/dist/commands/lint/memory-linter.js +0 -61
  558. package/dist/commands/lint/registry.js +0 -41
  559. package/dist/commands/lint/skill-linter.js +0 -45
  560. package/dist/commands/lint/task-linter.js +0 -50
  561. package/dist/commands/lint/workflow-linter.js +0 -81
  562. package/dist/commands/proposal/legacy-import.js +0 -115
  563. package/dist/commands/sources/history.js +0 -196
  564. package/dist/commands/tasks/default-tasks.js +0 -186
  565. package/dist/commands/wiki-cli.js +0 -292
  566. package/dist/core/asset/asset-registry.js +0 -76
  567. package/dist/core/asset/asset-spec.js +0 -259
  568. package/dist/core/config/config-migration.js +0 -602
  569. package/dist/core/deep-merge.js +0 -38
  570. package/dist/core/eval/rank-metrics.js +0 -113
  571. package/dist/core/ripgrep/install.js +0 -163
  572. package/dist/core/ripgrep/resolve.js +0 -81
  573. package/dist/indexer/db/db.js +0 -1413
  574. package/dist/indexer/manifest.js +0 -170
  575. package/dist/indexer/passes/metadata-contributors.js +0 -31
  576. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  577. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
  578. package/dist/llm/call-ai.js +0 -62
  579. package/dist/llm/memory-infer-impl.js +0 -138
  580. package/dist/output/shapes/distill.js +0 -14
  581. package/dist/output/shapes/history.js +0 -11
  582. package/dist/output/text/distill.js +0 -6
  583. package/dist/output/text/enable-disable.js +0 -8
  584. package/dist/output/text/history.js +0 -6
  585. package/dist/output/text/wiki.js +0 -16
  586. package/dist/registry/build-index.js +0 -386
  587. package/dist/scripts/migrate-storage.js +0 -19108
  588. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
  589. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  590. package/dist/setup/legacy-config.js +0 -106
  591. package/dist/storage/repositories/consolidation-repository.js +0 -38
  592. package/dist/storage/repositories/recombine-repository.js +0 -213
  593. package/dist/wiki/wiki-templates.js +0 -15
  594. package/dist/wiki/wiki.js +0 -1012
  595. package/dist/workflows/db.js +0 -215
  596. package/docs/data-and-telemetry.md +0 -226
  597. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  598. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
@@ -10,7 +10,7 @@
10
10
  // `<type>:<slug>` -> on-disk-asset question and MUST agree on the set of
11
11
  // reachable refs for any given stash layout.
12
12
  //
13
- // The lock is enforced by `tests/contracts/ref-resolver-contract.test.ts`,
13
+ // The lock is enforced by `tests/integration/contracts/ref-resolver-contract.test.ts`,
14
14
  // which drives this implementation through a canonical fixture set. The
15
15
  // akm-plugins repo ships an equivalent test that drives its copy through the
16
16
  // SAME inputs and asserts identical outcomes. Any change to the resolver
@@ -19,27 +19,24 @@
19
19
  //
20
20
  // Cases the contract covers (see fixture in the contract test):
21
21
  // - existing memory / knowledge / agent / workflow / skill refs
22
- // - knowledge subdirectory layout (knowledge/<category>/<slug>.md)
22
+ // - namespaced knowledge paths (knowledge/<category>/<slug>.md)
23
23
  // - skill multi-file layout (skills/<slug>/SKILL.md)
24
- // - memory `.derived.md` sibling
25
24
  // - namespaced slugs containing `/`
26
25
  // - env (`env/.env`, `env/<name>.env`) and secret (`secrets/<name>`) refs
27
26
  // - non-existent refs
28
- // - script type (unresolvable by design — both must return false)
27
+ // - script paths with explicit extensions
29
28
  //
30
- // As of 0.9 the type alternation in `REF_RE` and the path mapping in
31
- // `refToRelPath` are DERIVED FROM THE ASSET REGISTRY (`getAssetTypes()` /
32
- // `resolveAssetPathFromName` in `src/core/asset/asset-spec.ts`) rather than
33
- // hand-encoded, so they can no longer drift from the registry. The previously
34
- // hand-listed `vault` type was removed from the registry in 0.9 (replaced by
35
- // `env`); `vault:` refs are therefore no longer matched here. `env:`/`secret:`
36
- // refs are now matched and path-resolved. `script` stays unresolvable and
37
- // `task` keeps its legacy `.md` resolution (see refToRelPath for both).
29
+ // As of 0.9 the path mapping in `refToRelPath` is DERIVED FROM THE PLACEMENT
30
+ // SPECS (`assetPathForName` in `src/core/asset/asset-placement.ts`) rather than
31
+ // hand-encoded, so it can no longer drift from the placement layer. `env`/
32
+ // `secret`, `script`, and `task` refs are path-resolved.
38
33
  // ----------------------------------------------------------------------------
39
34
  import fs from "node:fs";
40
35
  import path from "node:path";
41
- import { getAssetTypes, resolveAssetPathFromName, TYPE_DIRS } from "../../core/asset/asset-spec.js";
42
- import { findFenceRegions, findSafeInsertionPoint } from "./markdown-insertion.js";
36
+ import { assetPathForName, stashDirFor } from "../../core/asset/asset-placement.js";
37
+ import { BUNDLE_REF_RE } from "../../core/asset/asset-ref.js";
38
+ import { typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
39
+ import { findFenceRegions } from "./markdown-insertion.js";
43
40
  // ── Helpers ───────────────────────────────────────────────────────────────────
44
41
  function formatDate(d) {
45
42
  const y = d.getFullYear();
@@ -130,52 +127,24 @@ function stripFencedBlocks(body) {
130
127
  return lines.join("\n");
131
128
  }
132
129
  // ── missing-ref helpers ───────────────────────────────────────────────────────
133
- /**
134
- * Type alternation for {@link REF_RE}, derived from the asset registry at
135
- * module load so it can never drift from `ASSET_SPECS`. Longest-first ordering
136
- * is defensive (no built-in type is a prefix of another, but a future custom
137
- * `registerAssetType` one might be) so the alternation prefers the longest
138
- * match. Regex metacharacters are escaped in case a custom type introduces one.
139
- */
140
- function buildRefTypeAlternation() {
141
- const types = [...getAssetTypes()].sort((a, b) => b.length - a.length);
142
- return types.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
143
- }
144
- // Only the TYPE alternation is registry-derived; the surrounding grammar
145
- // (boundary prefix, capture group, slug charset) is byte-identical to the
146
- // legacy hand-written pattern. Deriving the types from `getAssetTypes()` means
147
- // `env`/`secret` (added in 0.9) are now matched, and the removed `vault` type
148
- // is not — both follow the registry automatically.
149
- const REF_RE = new RegExp(`(?:^|[\\s\`"'(])((${buildRefTypeAlternation()}):[^\\s"'\`)\\]>,\\n]+)`, "gm");
150
130
  /**
151
131
  * Map from ref type to relative path pattern within stashRoot. Returns null to
152
132
  * skip (type is unresolvable by the slug walker).
153
133
  *
154
- * Path layout is owned by the asset registry: we resolve through
155
- * `resolveAssetPathFromName(type, TYPE_DIRS[type], name)` so the linter and the
156
- * rest of the CLI agree on where an asset lives. Two legacy carve-outs are
157
- * preserved to keep pre-0.9 behaviour byte-identical:
158
- * - `script`: returns null (scripts live in nested dirs with arbitrary
159
- * extensions — unresolvable by the slug-based walker, as the contract pins).
160
- * - `task`: M1 fix — tasks are stored as `<id>.yml` on disk, so resolve
161
- * `task:` refs against `tasks/<id>.yml` to match actual on-disk layout.
134
+ * Path layout is owned by the placement layer: we resolve through
135
+ * `assetPathForName(type, stashDirFor(type), name)` so the linter and the
136
+ * rest of the CLI agree on where an asset lives.
162
137
  *
163
138
  * Exported for contract testing — see header CONTRACT block.
164
139
  */
165
140
  export function refToRelPath(refType, refName) {
166
- // script is intentionally unresolvable (contract-pinned).
167
- if (refType === "script")
168
- return null;
169
- // M1: tasks are stored as .yml on disk; resolve task: refs against tasks/<id>.yml.
170
- if (refType === "task")
171
- return path.join(TYPE_DIRS.task ?? "tasks", `${refName}.yml`);
172
- const typeDir = TYPE_DIRS[refType];
141
+ const typeDir = stashDirFor(refType);
173
142
  if (!typeDir)
174
143
  return null; // unknown type — skip
175
- // resolveAssetPathFromName returns a path rooted at the type dir we pass in,
144
+ // assetPathForName returns a path rooted at the type dir we pass in,
176
145
  // i.e. "<typeDir>/<...>" — exactly the stash-relative path this helper has
177
146
  // always returned.
178
- return resolveAssetPathFromName(refType, typeDir, refName);
147
+ return assetPathForName(refType, typeDir, refName);
179
148
  }
180
149
  /**
181
150
  * Returns true if `relPath` resolves to a real file (or multi-file directory
@@ -185,114 +154,182 @@ export function refToRelPath(refType, refName) {
185
154
  */
186
155
  export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
187
156
  for (const root of stashRoots) {
188
- const absPath = path.join(root, relPath);
189
- if (fs.existsSync(absPath))
190
- return true;
191
- // Multi-file skill layout: directory containing SKILL.md
192
- const bareDir = absPath.replace(/\.md$/, "");
193
- if (fs.existsSync(bareDir) && fs.existsSync(path.join(bareDir, "SKILL.md")))
194
- return true;
195
- // .derived.md variant for memory refs
196
- if (refType === "memory") {
197
- const derivedPath = path.join(root, "memories", `${refName}.derived.md`);
198
- if (fs.existsSync(derivedPath))
199
- return true;
200
- }
201
- // Knowledge-specific: search subdirectories like knowledge/projects/, knowledge/tools/, etc.
202
- if (refType === "knowledge") {
203
- try {
204
- const knowledgeDir = path.join(root, "knowledge");
205
- if (fs.existsSync(knowledgeDir) && fs.statSync(knowledgeDir).isDirectory()) {
206
- const entries = fs.readdirSync(knowledgeDir, { withFileTypes: true });
207
- for (const entry of entries) {
208
- if (!entry.isDirectory())
209
- continue;
210
- const subPath = path.join(knowledgeDir, entry.name, `${refName}.md`);
211
- if (fs.existsSync(subPath))
212
- return true;
213
- }
214
- }
215
- }
216
- catch {
217
- // Ignore errors reading directory
218
- }
219
- }
220
- // Fallback: the refName may already encode the full stash-relative path
221
- // (e.g. knowledge:skills/foo/references/bar where the file lives at
222
- // <stash>/skills/foo/references/bar.md, not <stash>/knowledge/skills/...).
223
- const directPath = path.join(root, `${refName}.md`);
224
- if (fs.existsSync(directPath))
225
- return true;
226
- const directDir = path.join(root, refName);
227
- if (fs.existsSync(directDir) && fs.existsSync(path.join(directDir, "SKILL.md")))
157
+ if (resolveRefPathInStash(relPath, refType, refName, root) !== null)
228
158
  return true;
229
159
  }
230
160
  return false;
231
161
  }
232
162
  /**
233
- * Returns an array of {ref, resolvedRelPath} for every local AKM ref in the
234
- * body that does not resolve to a real file under any of the provided stash roots.
163
+ * Resolve the on-disk primary file for a ref within a SINGLE stash root, using
164
+ * the same reachability rules (in the same order) as
165
+ * {@link refExistsInAnyStash}, which delegates here. Returns the absolute path
166
+ * of the file that makes the ref "exist" — for a multi-file skill directory
167
+ * that is its `SKILL.md` primary — or `null` when the ref does not resolve in
168
+ * this root.
235
169
  *
236
- * Skips false-positive patterns:
237
- * - Shell variables: memory:$(cmd) or knowledge:${VAR}
238
- * - ACP type notation: agent::Type (double colons are C++/ACP syntax)
239
- * - Incomplete/placeholder refs: slug is single character or "**"
170
+ * Extracted for SPEC-5 (`--supersedes` demotion): write commands need the
171
+ * superseded asset's actual file to mutate, and forking a second resolver
172
+ * would drift from lint's. NOT part of the akm-plugins ref-resolver contract
173
+ * (the contract pins `refToRelPath` + `refExistsInAnyStash`; this is the
174
+ * shared internal both build on).
240
175
  */
241
- function checkMissingRefs(body, stashRoot, extraStashRoots = []) {
242
- const allRoots = [stashRoot, ...extraStashRoots];
176
+ export function resolveRefPathInStash(relPath, _refType, _refName, root) {
177
+ const absPath = path.join(root, relPath);
178
+ if (fs.existsSync(absPath))
179
+ return absPath;
180
+ return null;
181
+ }
182
+ /**
183
+ * A `(refType, refName)` pair that is not a lint-checkable local asset ref —
184
+ * shared skip-guard for recognized `bundle//conceptId` refs. Filters the
185
+ * false-positive patterns:
186
+ * - Shell variables: memory:$(cmd) or knowledge:${VAR} (guarded by callers on
187
+ * the raw token, before it is split).
188
+ * - Empty names or names that look like absolute paths / home dirs / URLs.
189
+ * - Incomplete/placeholder refs: single-character slug or "**".
190
+ * - Template placeholder refs like skill:<name> / workflow:<my-workflow>.
191
+ */
192
+ function isNonRefName(refName) {
193
+ if (!refName || refName.startsWith("/") || refName.startsWith("~") || refName.startsWith("http"))
194
+ return true;
195
+ if (refName.length <= 1 || refName === "**")
196
+ return true;
197
+ if (refName.startsWith("<") || refName.includes("<"))
198
+ return true;
199
+ return false;
200
+ }
201
+ /**
202
+ * Resolve a `(refType, refName)` pair against `allRoots`. Returns the resolved
203
+ * stash-relative path when the ref is MISSING (no file under any root), or
204
+ * `null` when it resolves, is a skipped/unresolvable type, or is a
205
+ * non-ref-shaped name. The single existence check both grammars route through.
206
+ */
207
+ function localRefMissingRelPath(refType, refName, allRoots) {
208
+ if (isNonRefName(refName))
209
+ return null;
210
+ const relPath = refToRelPath(refType, refName);
211
+ if (relPath === null)
212
+ return null; // type is skipped / unresolvable
213
+ return refExistsInAnyStash(relPath, refType, refName, allRoots) ? null : relPath;
214
+ }
215
+ /**
216
+ * 0.9.0 grammar recognition: fully-qualified `bundle//conceptId` body-refs
217
+ * (`BUNDLE_REF_RE`, the anchored prose form — spec §11.1 / ref-grammar decision
218
+ * D-R3). The conceptId is reverse-translated to its legacy `type`/`name` via the
219
+ * D-R2 static table (`typeNameFromConceptId`) so the SAME on-disk existence
220
+ * check applies; a conceptId whose leading segment names no known stash-subdir
221
+ * is not a local asset ref and is skipped (foreign-adapter / cross-bundle prose).
222
+ */
223
+ function scanBundleRefs(scanBody, allRoots) {
243
224
  const missing = [];
225
+ const re = new RegExp(BUNDLE_REF_RE.source, BUNDLE_REF_RE.flags);
244
226
  let match;
245
- const re = new RegExp(REF_RE.source, REF_RE.flags);
246
- // C1: Strip fenced code blocks so example refs inside ``` are not flagged.
247
- const scanBody = stripFencedBlocks(body);
248
227
  // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex loop
249
228
  while ((match = re.exec(scanBody)) !== null) {
250
- const fullRef = match[1]; // e.g. "workflow:foo" or "local//workflow:foo"
251
- // Skip shell variables: memory:$(cmd) or knowledge:${VAR}
252
- if (fullRef.includes("$(") || fullRef.includes("${")) {
253
- continue;
254
- }
255
- // Skip ACP type notation: agent::Type (double colons)
256
- if (fullRef.includes("::")) {
257
- continue;
258
- }
259
- // Strip leading "local//" prefix if present
260
- let ref = fullRef;
261
- if (ref.startsWith("local//")) {
262
- ref = ref.slice("local//".length);
263
- }
264
- else if (fullRef.includes("//")) {
265
- // Has a remote origin prefix (e.g. "npm:", "github:", "owner/repo//") — skip
229
+ const token = match[1]; // e.g. "core//memories/foo"
230
+ if (token.includes("$(") || token.includes("${") || token.includes("::"))
266
231
  continue;
267
- }
268
- // Skip refs that start with obvious remote prefixes
269
- const colonIdx = ref.indexOf(":");
270
- if (colonIdx === -1)
232
+ const boundary = token.indexOf("//");
233
+ if (boundary < 0)
271
234
  continue;
272
- const refType = ref.slice(0, colonIdx);
273
- const refName = ref.slice(colonIdx + 1);
274
- // Guard against empty names or names that look like paths/URLs
275
- if (!refName || refName.startsWith("/") || refName.startsWith("~") || refName.startsWith("http")) {
235
+ const found = classifyConceptRef(token.slice(boundary + 2), allRoots);
236
+ if (found !== null)
237
+ missing.push({ ref: token, resolvedRelPath: found });
238
+ }
239
+ return missing;
240
+ }
241
+ /**
242
+ * Map a bare 0.9.0 conceptId (`<stash-subdir>/<name>`, e.g. `memories/foo`) to
243
+ * its legacy `type`/`name` and run the shared existence check. Returns the
244
+ * missing relPath, or `null` when it resolves or is not a known local
245
+ * asset-type prefix. Drops a trailing `#fragment` (export selector) before
246
+ * mapping.
247
+ */
248
+ function classifyConceptRef(rawConceptId, allRoots) {
249
+ const conceptId = rawConceptId.split("#", 1)[0];
250
+ const parts = typeNameFromConceptId(conceptId);
251
+ if (parts === undefined)
252
+ return null; // foreign type / not a local asset ref
253
+ return localRefMissingRelPath(parts.type, parts.name, allRoots);
254
+ }
255
+ /**
256
+ * Returns an array of {ref, resolvedRelPath} for every local AKM ref in the
257
+ * PROSE body that does not resolve to a real file under any of the provided
258
+ * stash roots. Recognizes the 0.9.0 fully-qualified `bundle//conceptId` grammar
259
+ * ({@link scanBundleRefs}). Bare short conceptIds are NOT refs in prose (D-R3) —
260
+ * those are recognized only in the ref-list channels
261
+ * ({@link checkMissingRefsInList}).
262
+ */
263
+ function checkMissingRefs(body, stashRoot, extraStashRoots = []) {
264
+ const allRoots = [stashRoot, ...extraStashRoots];
265
+ // C1: Strip fenced code blocks so example refs inside ``` are not flagged.
266
+ const scanBody = stripFencedBlocks(body);
267
+ return dedupeMissing(scanBundleRefs(scanBody, allRoots));
268
+ }
269
+ /**
270
+ * Missing-ref check for the REF-LIST channels (frontmatter `refs:` /
271
+ * `xrefs:` / `supersededBy:` / `contradictedBy:`) where EACH value is a whole
272
+ * ref, not prose. Unlike the body scan, a bare short conceptId (`memories/foo`)
273
+ * IS a ref here (the value's whole purpose is to name one asset), so the flipped
274
+ * short-conceptId frontmatter the 0.9.0 output emits is no longer invisible.
275
+ * Recognizes, per value:
276
+ * - fully-qualified `bundle//conceptId`;
277
+ * - bare short `conceptId` (`<stash-subdir>/<name>`).
278
+ */
279
+ function checkMissingRefsInList(values, stashRoot, extraStashRoots = []) {
280
+ const allRoots = [stashRoot, ...extraStashRoots];
281
+ const missing = [];
282
+ for (const raw of values) {
283
+ const value = raw.trim();
284
+ if (!value || value.includes("$(") || value.includes("${") || value.includes("::"))
276
285
  continue;
277
- }
278
- // Skip placeholder/incomplete refs: single character slug or "**"
279
- if (refName.length <= 1 || refName === "**") {
286
+ const boundary = value.indexOf("//");
287
+ if (boundary >= 0) {
288
+ // Qualified: `bundle//conceptId` (0.9.0). A colon in the tail marks a
289
+ // legacy/remote `origin//type:name` — not the new grammar, so skip it.
290
+ const tail = value.slice(boundary + 2);
291
+ if (tail.includes(":"))
292
+ continue;
293
+ const rel = classifyConceptRef(tail, allRoots);
294
+ if (rel !== null)
295
+ missing.push({ ref: value, resolvedRelPath: rel });
280
296
  continue;
281
297
  }
282
- // Skip template placeholder refs like skill:<name> or workflow:<my-workflow>
283
- if (refName.startsWith("<") || refName.includes("<")) {
298
+ // Un-prefixed: a 0.9.0 short `conceptId`. (Post-Chunk-8 the durable
299
+ // frontmatter xref channel is conceptId-spelled — the legacy `type:name`
300
+ // ref-list arm is retired.)
301
+ const rel = classifyConceptRef(value, allRoots);
302
+ if (rel !== null)
303
+ missing.push({ ref: value, resolvedRelPath: rel });
304
+ }
305
+ return dedupeMissing(missing);
306
+ }
307
+ /** Dedupe missing-ref records by their `ref` token (both arms can flag one ref). */
308
+ function dedupeMissing(rows) {
309
+ const seen = new Set();
310
+ const out = [];
311
+ for (const row of rows) {
312
+ if (seen.has(row.ref))
284
313
  continue;
285
- }
286
- const relPath = refToRelPath(refType, refName);
287
- if (relPath === null)
288
- continue; // type is skipped
289
- if (!refExistsInAnyStash(relPath, refType, refName, allRoots)) {
290
- missing.push({ ref: fullRef, resolvedRelPath: relPath });
291
- }
314
+ seen.add(row.ref);
315
+ out.push(row);
292
316
  }
293
- return missing;
317
+ return out;
294
318
  }
295
319
  // ── frontmatter refs ─────────────────────────────────────────────────────────
320
+ /**
321
+ * Frontmatter keys that carry cross-reference lists per the stash
322
+ * organization conventions: `xrefs:` (provenance / associative links),
323
+ * `supersededBy:` and `contradictedBy:` (belief-state correction links).
324
+ * The missing-ref check validates each of these in ADDITION to the body /
325
+ * `refs:` scan — they are the channel the conventions mandate, and a rename
326
+ * would otherwise dangle them silently.
327
+ *
328
+ * `sources:` is deliberately excluded (non-wiki `sources:` was rejected as a
329
+ * typed channel; wiki `sources:` is checked by lintWiki). `evidenceSources:` is
330
+ * excluded because it can point at merged-away or pruned assets.
331
+ */
332
+ const XREF_FRONTMATTER_KEYS = ["xrefs", "supersededBy", "contradictedBy"];
296
333
  /**
297
334
  * Return the `refs:` array from frontmatter when it is present and is an
298
335
  * array of strings; otherwise return `null` to signal the caller should
@@ -342,6 +379,22 @@ function readRefsArray(value) {
342
379
  }
343
380
  return out;
344
381
  }
382
+ /**
383
+ * Like {@link readRefsArray} but also accepts a single scalar string,
384
+ * normalizing it to a one-element list. The indexer's
385
+ * `normalizeNonEmptyStringList` treats `supersededBy: memory:x` and
386
+ * `supersededBy: [memory:x]` identically — both are live data — so the
387
+ * frontmatter xref-channel check must validate both shapes; the array-only
388
+ * reader silently skipped dangling scalar refs. Returns `null` for any other
389
+ * type (missing key, number, object) and for a blank scalar.
390
+ */
391
+ function readRefStringOrArray(value) {
392
+ if (typeof value === "string") {
393
+ const trimmed = value.trim();
394
+ return trimmed ? [trimmed] : null;
395
+ }
396
+ return readRefsArray(value);
397
+ }
345
398
  /**
346
399
  * Detect a leading nested frontmatter block in `body` (i.e. a `---\n…\n---`
347
400
  * pair that opens within the first few lines of the body). When present,
@@ -407,204 +460,178 @@ function parseInnerFrontmatterBlock(body) {
407
460
  }
408
461
  return data;
409
462
  }
410
- // ── BaseLinter ────────────────────────────────────────────────────────────────
463
+ // ── Base checks ─────────────────────────────────────────────────────────────
411
464
  /**
412
- * Abstract base class providing the two cross-type checks shared by all asset
413
- * linters: `unquoted-colon` and `missing-updated`.
465
+ * The cross-type checks every asset linter runs first: `unquoted-colon`,
466
+ * `missing-updated`, `stale-path`, and `missing-ref`.
467
+ *
468
+ * akm 0.9.0 chunk-3 (plan §12): this was the `BaseLinter.runBaseChecks`
469
+ * protected method every per-type linter class inherited. Those classes are
470
+ * gone — the format-generic checks live here as ONE shared function (this), and
471
+ * the per-`type` extra rules moved to the `akm` adapter's `validate`
472
+ * (`core/adapter/adapters/akm-lint.ts`). The live `akmLint` command
473
+ * (`commands/lint/index.ts`) calls this directly, then appends the adapter's
474
+ * per-type findings.
414
475
  *
415
- * Subclasses call `runBaseChecks(ctx)` and append any type-specific issues.
416
- * File mutations triggered by base checks are flushed to disk inside this
417
- * method; subclasses must re-read `ctx.raw` if they need the post-fix content
418
- * (in practice the base class updates `ctx.raw` in place when `fix` is true).
476
+ * File mutations triggered by the fixable base checks (`unquoted-colon`,
477
+ * `missing-updated`) are flushed to disk here when `ctx.fix` is set, and
478
+ * `ctx.raw` is updated in place so a caller can re-parse the post-fix content.
419
479
  */
420
- export class BaseLinter {
421
- /**
422
- * Check for missing `name` or `type` fields in frontmatter.
423
- *
424
- * Returns a detail string if fields are absent/empty, `null` if all present.
425
- */
426
- checkMissingNameOrType(data, frontmatterText) {
427
- if (!frontmatterText)
428
- return null;
429
- const missingFields = [];
430
- if (!("name" in data) || !data.name)
431
- missingFields.push("name");
432
- if (!("type" in data) || !data.type)
433
- missingFields.push("type");
434
- if (missingFields.length === 0)
435
- return null;
436
- return `missing fields: ${missingFields.join(", ")}`;
437
- }
438
- /**
439
- * Validate that the `type` field value is one of an allowed set.
440
- *
441
- * Returns a detail string if the value is present but invalid, `null` if valid or absent.
442
- */
443
- checkInvalidTypeValue(data, allowedTypes) {
444
- if (!("type" in data) || !data.type)
445
- return null; // absent — covered by checkMissingNameOrType
446
- const value = String(data.type);
447
- if (allowedTypes.includes(value))
448
- return null;
449
- return `type field has invalid value '${value}'; expected one of: ${allowedTypes.join(", ")}`;
450
- }
451
- /**
452
- * Derive a URL-safe slug from a file path.
453
- */
454
- suggestSlug(filePath) {
455
- return path
456
- .basename(filePath, ".md")
457
- .toLowerCase()
458
- .replace(/[^a-z0-9-]+/g, "-")
459
- .replace(/-+/g, "-")
460
- .replace(/^-|-$/g, "");
461
- }
462
- /**
463
- * Insert one or more lines into a markdown body at a safe location.
464
- *
465
- * "Safe" means: not inside a markdown table, HTML table, fenced code block,
466
- * or indented code block. If `proposedLineNumber` falls inside one of those
467
- * regions, the helper pushes the insertion to immediately after the region.
468
- * This is a regression guard against the class of bug where an auto-fix
469
- * splits a table fence by injecting a callout between the separator row
470
- * and the first data row (broke `knowledge/akm-cli-reference.md` in 0.8.0).
471
- *
472
- * Subclasses that perform line-based body insertion MUST route through this
473
- * helper instead of calling `splice` directly. Insertion fixers must NOT
474
- * touch frontmatter — use `fixMissingUpdated` / `fixUnquotedColon` style
475
- * regex edits for that case (those already operate inside the `---…---`
476
- * fence and don't intersect with body line numbers).
477
- *
478
- * @param raw Full file contents (frontmatter + body).
479
- * @param newLines Lines to insert (without trailing newlines).
480
- * @param proposedLineNumber 0-based line index within `raw` where the
481
- * caller wants the new content to appear.
482
- * @returns The mutated file contents with `newLines` spliced at the
483
- * adjusted safe position.
484
- */
485
- insertLinesSafely(raw, newLines, proposedLineNumber) {
486
- const lines = raw.split(/\r?\n/);
487
- const safeIdx = findSafeInsertionPoint(lines, proposedLineNumber);
488
- lines.splice(safeIdx, 0, ...newLines);
489
- return lines.join("\n");
490
- }
491
- runBaseChecks(ctx) {
492
- const issues = [];
493
- let currentRaw = ctx.raw;
494
- let modified = false;
495
- // M8: Parse lint_skip from frontmatter for per-file rule suppression.
496
- // Accept both an array (`lint_skip: [missing-ref, stale-path]`) and a
497
- // single scalar (`lint_skip: missing-ref`). Non-string entries are coerced
498
- // and trimmed so loosely-typed YAML still gates correctly.
499
- const rawLintSkip = ctx.data?.lint_skip;
500
- const lintSkip = (Array.isArray(rawLintSkip) ? rawLintSkip : rawLintSkip != null ? [rawLintSkip] : [])
501
- .map((v) => String(v).trim())
502
- .filter(Boolean);
503
- const shouldRun = (issueType) => !lintSkip.includes(issueType);
504
- // ── 1. unquoted-colon ──────────────────────────────────────────────────
505
- if (shouldRun("unquoted-colon")) {
506
- const unquotedColonDetail = checkUnquotedColon(ctx.frontmatter);
507
- if (unquotedColonDetail) {
508
- if (ctx.fix) {
509
- currentRaw = fixUnquotedColon(currentRaw);
510
- modified = true;
511
- issues.push({
512
- file: ctx.relPath,
513
- issue: "unquoted-colon",
514
- detail: unquotedColonDetail,
515
- fixed: true,
516
- });
517
- }
518
- else {
519
- issues.push({
520
- file: ctx.relPath,
521
- issue: "unquoted-colon",
522
- detail: unquotedColonDetail,
523
- fixed: false,
524
- });
525
- }
526
- }
527
- } // end shouldRun("unquoted-colon")
528
- // ── 2. missing-updated ─────────────────────────────────────────────────
529
- if (shouldRun("missing-updated") && checkMissingUpdated(ctx.data, ctx.frontmatter)) {
480
+ export function runBaseChecks(ctx) {
481
+ const issues = [];
482
+ let currentRaw = ctx.raw;
483
+ let modified = false;
484
+ // M8: Parse lint_skip from frontmatter for per-file rule suppression.
485
+ // Accept both an array (`lint_skip: [missing-ref, stale-path]`) and a
486
+ // single scalar (`lint_skip: missing-ref`). Non-string entries are coerced
487
+ // and trimmed so loosely-typed YAML still gates correctly.
488
+ const rawLintSkip = ctx.data?.lint_skip;
489
+ const lintSkip = (Array.isArray(rawLintSkip) ? rawLintSkip : rawLintSkip != null ? [rawLintSkip] : [])
490
+ .map((v) => String(v).trim())
491
+ .filter(Boolean);
492
+ const shouldRun = (issueType) => !lintSkip.includes(issueType);
493
+ // ── 1. unquoted-colon ──────────────────────────────────────────────────
494
+ if (shouldRun("unquoted-colon")) {
495
+ const unquotedColonDetail = checkUnquotedColon(ctx.frontmatter);
496
+ if (unquotedColonDetail) {
530
497
  if (ctx.fix) {
531
- let mtime;
532
- try {
533
- mtime = fs.statSync(ctx.filePath).mtime;
534
- }
535
- catch {
536
- mtime = new Date();
537
- }
538
- currentRaw = fixMissingUpdated(currentRaw, mtime);
498
+ currentRaw = fixUnquotedColon(currentRaw);
539
499
  modified = true;
540
500
  issues.push({
541
501
  file: ctx.relPath,
542
- issue: "missing-updated",
543
- detail: `stamped updated: ${formatDate(mtime)}`,
502
+ issue: "unquoted-colon",
503
+ detail: unquotedColonDetail,
544
504
  fixed: true,
545
505
  });
546
506
  }
547
507
  else {
548
508
  issues.push({
549
509
  file: ctx.relPath,
550
- issue: "missing-updated",
551
- detail: "no updated field in frontmatter",
510
+ issue: "unquoted-colon",
511
+ detail: unquotedColonDetail,
552
512
  fixed: false,
553
513
  });
554
514
  }
555
515
  }
556
- if (modified) {
557
- fs.writeFileSync(ctx.filePath, currentRaw, "utf8");
558
- // Propagate the mutated raw back so subclasses can re-parse if needed
559
- ctx.raw = currentRaw;
560
- }
561
- // ── 3. stale-path ──────────────────────────────────────────────────────
562
- // M3: checkStalePath returns all stale matches; push one issue per path.
563
- // M4: Also scan ctx.frontmatter for stale paths (absolute paths in frontmatter).
564
- if (shouldRun("stale-path")) {
565
- const staleInBody = checkStalePath(ctx.body);
566
- const staleInFrontmatter = ctx.frontmatter ? checkStalePath(ctx.frontmatter) : [];
567
- for (const candidate of [...staleInBody, ...staleInFrontmatter]) {
568
- // M4: Suggest portable replacement when path is under stashRoot.
569
- const portableHint = candidate.startsWith(ctx.stashRoot)
570
- ? ` (portable form: $AKM_STASH_DIR${candidate.slice(ctx.stashRoot.length)})`
571
- : "";
572
- issues.push({
573
- file: ctx.relPath,
574
- issue: "stale-path",
575
- detail: `nonexistent path: ${candidate}${portableHint}`,
576
- fixed: false,
577
- });
516
+ } // end shouldRun("unquoted-colon")
517
+ // ── 2. missing-updated ─────────────────────────────────────────────────
518
+ if (shouldRun("missing-updated") && checkMissingUpdated(ctx.data, ctx.frontmatter)) {
519
+ if (ctx.fix) {
520
+ let mtime;
521
+ try {
522
+ mtime = fs.statSync(ctx.filePath).mtime;
578
523
  }
524
+ catch {
525
+ mtime = new Date();
526
+ }
527
+ currentRaw = fixMissingUpdated(currentRaw, mtime);
528
+ modified = true;
529
+ issues.push({
530
+ file: ctx.relPath,
531
+ issue: "missing-updated",
532
+ detail: `stamped updated: ${formatDate(mtime)}`,
533
+ fixed: true,
534
+ });
535
+ }
536
+ else {
537
+ issues.push({
538
+ file: ctx.relPath,
539
+ issue: "missing-updated",
540
+ detail: "no updated field in frontmatter",
541
+ fixed: false,
542
+ });
579
543
  }
580
- // ── 4. missing-ref ─────────────────────────────────────────────────────
581
- // Carve-out for assets that declare an explicit `refs:` array in
582
- // frontmatter (e.g. session-checkpoint memories captured by the
583
- // claude-code hook). The frontmatter array is the *authoritative*
584
- // ref list — any ref-shaped tokens in the body are treated as
585
- // literal strings (heredocs, grep patterns, JSON values, regex
586
- // patterns embedded in tool transcripts). Without this carve-out
587
- // every session capture produces a fresh batch of `missing-ref`
588
- // flags on every literal `<type>:<slug>` token in a transcript.
544
+ }
545
+ if (modified) {
546
+ fs.writeFileSync(ctx.filePath, currentRaw, "utf8");
547
+ // Propagate the mutated raw back so subclasses can re-parse if needed
548
+ ctx.raw = currentRaw;
549
+ }
550
+ // ── 3. stale-path ──────────────────────────────────────────────────────
551
+ // M3: checkStalePath returns all stale matches; push one issue per path.
552
+ // M4: Also scan ctx.frontmatter for stale paths (absolute paths in frontmatter).
553
+ if (shouldRun("stale-path")) {
554
+ const staleInBody = checkStalePath(ctx.body);
555
+ const staleInFrontmatter = ctx.frontmatter ? checkStalePath(ctx.frontmatter) : [];
556
+ for (const candidate of [...staleInBody, ...staleInFrontmatter]) {
557
+ // M4: Suggest portable replacement when path is under stashRoot.
558
+ const portableHint = candidate.startsWith(ctx.stashRoot)
559
+ ? ` (portable form: $AKM_BUNDLE_DIR${candidate.slice(ctx.stashRoot.length)})`
560
+ : "";
561
+ issues.push({
562
+ file: ctx.relPath,
563
+ issue: "stale-path",
564
+ detail: `nonexistent path: ${candidate}${portableHint}`,
565
+ fixed: false,
566
+ });
567
+ }
568
+ }
569
+ // ── 4. missing-ref ─────────────────────────────────────────────────────
570
+ // Carve-out for assets that declare an explicit `refs:` array in
571
+ // frontmatter (e.g. session-checkpoint memories captured by the
572
+ // claude-code hook). The frontmatter array is the *authoritative*
573
+ // ref list — any ref-shaped tokens in the body are treated as
574
+ // literal strings (heredocs, grep patterns, JSON values, regex
575
+ // patterns embedded in tool transcripts). Without this carve-out
576
+ // every session capture produces a fresh batch of `missing-ref`
577
+ // flags on every literal `<type>:<slug>` token in a transcript.
578
+ //
579
+ // The producer guarantees that entries in `refs:` already resolve
580
+ // (it validates against the live stash before writing), so we
581
+ // still run `checkMissingRefs` against the array itself to catch
582
+ // refs that were valid at capture time but later removed from the
583
+ // stash.
584
+ if (shouldRun("missing-ref")) {
585
+ const explicitRefs = extractFrontmatterRefs(ctx.data, ctx.body);
586
+ // An explicit `refs:` array is a REF LIST (each value is a whole ref —
587
+ // short conceptIds included); a bare body is PROSE (anchored refs only).
588
+ const missingRefs = explicitRefs !== null
589
+ ? checkMissingRefsInList(explicitRefs, ctx.stashRoot, ctx.extraStashRoots)
590
+ : checkMissingRefs(ctx.body, ctx.stashRoot, ctx.extraStashRoots);
591
+ for (const { ref, resolvedRelPath } of missingRefs) {
592
+ issues.push({
593
+ file: ctx.relPath,
594
+ issue: "missing-ref",
595
+ detail: `missing ref: ${ref} (resolved to ${resolvedRelPath})`,
596
+ fixed: false,
597
+ });
598
+ }
599
+ // Frontmatter xref channels (xrefs / supersededBy / contradictedBy).
600
+ // Runs regardless of the `refs:` body-scan carve-out above — that
601
+ // carve-out governs only the BODY scan (`refs: []` declares "no
602
+ // outbound refs in the body", not "skip my correction links").
603
+ // Non-ref-shaped values (URLs, `raw/<slug>`, `<placeholder>`
604
+ // templates, shell vars) fall out via checkMissingRefs' guards.
589
605
  //
590
- // The producer guarantees that entries in `refs:` already resolve
591
- // (it validates against the live stash before writing), so we
592
- // still run `checkMissingRefs` against the array itself to catch
593
- // refs that were valid at capture time but later removed from the
594
- // stash.
595
- if (shouldRun("missing-ref")) {
596
- const explicitRefs = extractFrontmatterRefs(ctx.data, ctx.body);
597
- const refSource = explicitRefs !== null ? explicitRefs.join("\n") : ctx.body;
598
- const missingRefs = checkMissingRefs(refSource, ctx.stashRoot, ctx.extraStashRoots);
599
- for (const { ref, resolvedRelPath } of missingRefs) {
600
- issues.push({
601
- file: ctx.relPath,
602
- issue: "missing-ref",
603
- detail: `missing ref: ${ref} (resolved to ${resolvedRelPath})`,
604
- fixed: false,
605
- });
606
+ // Gate: runs when the file has a frontmatter block OR when an
607
+ // authoritative `refs:` list was extracted. On the task/YAML path
608
+ // (lint/index.ts) ctx.frontmatter is always null and the whole file
609
+ // IS the body (`body === raw`); the top-level YAML keys land in
610
+ // ctx.data. Without `refs:` the body scan above already catches ref
611
+ // values under these keys, so running the pass would double-report —
612
+ // skip it. With `refs:` present the body scan is suppressed
613
+ // (refSource is the refs list), so this pass is the ONLY thing that
614
+ // validates the xref keys — it must run or dangling task xrefs go
615
+ // unreported. The two cases are mutually exclusive, so no ref is
616
+ // ever double-reported. Md files without a frontmatter block and
617
+ // without `refs:` land in the skip branch with empty ctx.data, so
618
+ // nothing is lost for them either.
619
+ if (ctx.frontmatter !== null || explicitRefs !== null) {
620
+ for (const key of XREF_FRONTMATTER_KEYS) {
621
+ const values = readRefStringOrArray(ctx.data?.[key]);
622
+ if (values === null)
623
+ continue;
624
+ const missingXrefs = checkMissingRefsInList(values, ctx.stashRoot, ctx.extraStashRoots);
625
+ for (const { ref, resolvedRelPath } of missingXrefs) {
626
+ issues.push({
627
+ file: ctx.relPath,
628
+ issue: "missing-ref",
629
+ detail: `missing ref: ${ref} (frontmatter ${key}; resolved to ${resolvedRelPath})`,
630
+ fixed: false,
631
+ });
632
+ }
606
633
  }
607
634
  }
608
- return issues;
609
635
  }
636
+ return issues;
610
637
  }