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

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 (262) hide show
  1. package/CHANGELOG.md +339 -2
  2. package/SECURITY.md +23 -24
  3. package/dist/assets/help/help-improve.md +10 -10
  4. package/dist/assets/hints/cli-hints-full.md +44 -10
  5. package/dist/assets/hints/cli-hints-short.md +6 -2
  6. package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
  7. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
  8. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
  9. package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
  10. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  11. package/dist/assets/stash-skeleton/README.md +28 -0
  12. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
  24. package/dist/assets/tasks/core/backup.yml +1 -0
  25. package/dist/assets/tasks/core/extract.yml +1 -0
  26. package/dist/assets/tasks/core/improve.yml +1 -0
  27. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  28. package/dist/assets/tasks/core/sync.yml +1 -0
  29. package/dist/assets/tasks/core/version-check.yml +1 -0
  30. package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
  31. package/dist/assets/templates/html/health.html +5 -1
  32. package/dist/cli/config-migrate.js +31 -138
  33. package/dist/cli/config-validate.js +10 -8
  34. package/dist/cli.js +48 -14
  35. package/dist/commands/agent/agent-dispatch.js +17 -16
  36. package/dist/commands/agent/agent-support.js +0 -24
  37. package/dist/commands/agent/contribute-cli.js +5 -15
  38. package/dist/commands/backup-cli.js +54 -0
  39. package/dist/commands/config-cli.js +45 -159
  40. package/dist/commands/env/env-binding.js +95 -0
  41. package/dist/commands/env/env-cli.js +8 -65
  42. package/dist/commands/env/secret.js +8 -5
  43. package/dist/commands/health/checks.js +130 -83
  44. package/dist/commands/health/html-report.js +4 -0
  45. package/dist/commands/health/improve-metrics.js +30 -32
  46. package/dist/commands/health/llm-usage.js +19 -19
  47. package/dist/commands/health/md-report.js +4 -0
  48. package/dist/commands/health/metrics.js +2 -1
  49. package/dist/commands/health/surfaces.js +5 -4
  50. package/dist/commands/health.js +1 -1
  51. package/dist/commands/improve/consolidate/chunking.js +2 -2
  52. package/dist/commands/improve/consolidate.js +28 -25
  53. package/dist/commands/improve/distill/promote-memory.js +5 -12
  54. package/dist/commands/improve/distill/quality-gate.js +5 -7
  55. package/dist/commands/improve/distill.js +16 -5
  56. package/dist/commands/improve/eligibility.js +22 -12
  57. package/dist/commands/improve/extract-cli.js +47 -19
  58. package/dist/commands/improve/extract.js +110 -62
  59. package/dist/commands/improve/improve-cli.js +38 -16
  60. package/dist/commands/improve/improve-result-file.js +30 -24
  61. package/dist/commands/improve/improve-strategies.js +137 -0
  62. package/dist/commands/improve/improve.js +60 -30
  63. package/dist/commands/improve/locks.js +66 -45
  64. package/dist/commands/improve/loop-stages.js +75 -33
  65. package/dist/commands/improve/memory/memory-belief.js +79 -7
  66. package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
  67. package/dist/commands/improve/preparation.js +71 -73
  68. package/dist/commands/improve/procedural.js +3 -2
  69. package/dist/commands/improve/recombine.js +2 -1
  70. package/dist/commands/improve/reflect.js +119 -214
  71. package/dist/commands/improve/shared.js +11 -5
  72. package/dist/commands/lint/base-linter.js +152 -42
  73. package/dist/commands/mv-cli.js +809 -0
  74. package/dist/commands/proposal/proposal-cli.js +18 -8
  75. package/dist/commands/proposal/propose.js +64 -69
  76. package/dist/commands/read/knowledge.js +436 -4
  77. package/dist/commands/read/remember-cli.js +39 -2
  78. package/dist/commands/read/search-cli.js +6 -1
  79. package/dist/commands/registry-cli.js +29 -14
  80. package/dist/commands/remember.js +2 -0
  81. package/dist/commands/sources/init.js +13 -14
  82. package/dist/commands/sources/migration-help.js +7 -4
  83. package/dist/commands/sources/schema-repair.js +2 -4
  84. package/dist/commands/sources/source-add.js +62 -73
  85. package/dist/commands/sources/source-manage.js +50 -46
  86. package/dist/commands/sources/stash-cli.js +41 -4
  87. package/dist/commands/tasks/default-tasks.js +12 -12
  88. package/dist/commands/tasks/tasks-cli.js +7 -3
  89. package/dist/commands/tasks/tasks.js +113 -18
  90. package/dist/commands/wiki-cli.js +9 -10
  91. package/dist/commands/workflow-cli.js +276 -12
  92. package/dist/core/asset/asset-spec.js +58 -1
  93. package/dist/core/asset/frontmatter.js +12 -2
  94. package/dist/core/common.js +5 -3
  95. package/dist/core/config/config-io.js +28 -17
  96. package/dist/core/config/config-schema.js +379 -66
  97. package/dist/core/config/config-types.js +3 -3
  98. package/dist/core/config/config-version.js +29 -0
  99. package/dist/core/config/config-walker.js +98 -27
  100. package/dist/core/config/config.js +132 -266
  101. package/dist/core/config/deep-merge.js +41 -0
  102. package/dist/core/config/engine-semantics.js +32 -0
  103. package/dist/core/errors.js +2 -2
  104. package/dist/core/extra-params.js +61 -0
  105. package/dist/core/file-lock.js +201 -56
  106. package/dist/core/improve-result.js +178 -0
  107. package/dist/core/json-schema.js +142 -0
  108. package/dist/core/maintenance-barrier.js +119 -0
  109. package/dist/core/migration-backup.js +416 -0
  110. package/dist/core/paths.js +3 -0
  111. package/dist/core/redaction.js +358 -0
  112. package/dist/core/state/migrations.js +17 -2
  113. package/dist/core/state-db.js +44 -1
  114. package/dist/indexer/db/db.js +118 -2
  115. package/dist/indexer/graph/graph-extraction.js +28 -16
  116. package/dist/indexer/index-writer-lock.js +31 -24
  117. package/dist/indexer/index-written-assets.js +15 -6
  118. package/dist/indexer/indexer.js +47 -2
  119. package/dist/indexer/passes/memory-inference.js +10 -6
  120. package/dist/indexer/passes/metadata.js +250 -0
  121. package/dist/indexer/search/db-search.js +111 -44
  122. package/dist/indexer/search/fts-query.js +41 -0
  123. package/dist/indexer/search/ranking-contributors.js +48 -0
  124. package/dist/indexer/search/ranking.js +36 -23
  125. package/dist/indexer/search/search-fields.js +11 -1
  126. package/dist/indexer/walk/matchers.js +39 -0
  127. package/dist/integrations/agent/builder-shared.js +7 -0
  128. package/dist/integrations/agent/builders.js +5 -50
  129. package/dist/integrations/agent/config.js +3 -143
  130. package/dist/integrations/agent/detect.js +17 -2
  131. package/dist/integrations/agent/engine-resolution.js +202 -0
  132. package/dist/integrations/agent/index.js +1 -2
  133. package/dist/integrations/agent/model-aliases.js +16 -2
  134. package/dist/integrations/agent/profiles.js +36 -62
  135. package/dist/integrations/agent/runner-dispatch.js +91 -4
  136. package/dist/integrations/agent/runner.js +76 -207
  137. package/dist/integrations/agent/spawn.js +141 -20
  138. package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
  139. package/dist/integrations/harnesses/aider/index.js +57 -0
  140. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  141. package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
  142. package/dist/integrations/harnesses/amazonq/index.js +58 -0
  143. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  144. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  145. package/dist/integrations/harnesses/claude/index.js +25 -25
  146. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  147. package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
  148. package/dist/integrations/harnesses/codex/index.js +62 -0
  149. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  150. package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
  151. package/dist/integrations/harnesses/copilot/index.js +59 -0
  152. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  153. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  154. package/dist/integrations/harnesses/gemini/index.js +59 -0
  155. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  156. package/dist/integrations/harnesses/index.js +27 -28
  157. package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
  158. package/dist/integrations/harnesses/opencode/index.js +15 -22
  159. package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
  160. package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
  161. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  162. package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
  163. package/dist/integrations/harnesses/openhands/index.js +57 -0
  164. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  165. package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
  166. package/dist/integrations/harnesses/pi/index.js +57 -0
  167. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  168. package/dist/integrations/harnesses/types.js +8 -32
  169. package/dist/integrations/lockfile.js +32 -21
  170. package/dist/integrations/session-logs/index.js +24 -11
  171. package/dist/llm/client.js +48 -14
  172. package/dist/llm/feature-gate.js +15 -47
  173. package/dist/llm/graph-extract.js +1 -1
  174. package/dist/llm/index-passes.js +8 -42
  175. package/dist/llm/memory-infer-impl.js +1 -1
  176. package/dist/llm/usage-persist.js +4 -0
  177. package/dist/llm/usage-telemetry.js +35 -5
  178. package/dist/output/renderers.js +3 -2
  179. package/dist/output/shapes/helpers.js +2 -1
  180. package/dist/output/shapes/passthrough.js +6 -0
  181. package/dist/output/text/helpers.js +215 -2
  182. package/dist/output/text/workflow.js +3 -1
  183. package/dist/schemas/akm-config.json +16638 -0
  184. package/dist/schemas/akm-task.json +87 -0
  185. package/dist/schemas/akm-workflow.json +372 -0
  186. package/dist/scripts/migrate-storage.js +10944 -8801
  187. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
  188. package/dist/setup/detected-engines.js +142 -0
  189. package/dist/setup/engine-config.js +89 -0
  190. package/dist/setup/setup.js +236 -132
  191. package/dist/setup/steps/connection.js +61 -32
  192. package/dist/setup/steps/platforms.js +4 -4
  193. package/dist/setup/steps.js +3 -2
  194. package/dist/storage/database.js +13 -1
  195. package/dist/storage/engines/sqlite-migrations.js +1 -0
  196. package/dist/storage/repositories/improve-runs-repository.js +5 -5
  197. package/dist/storage/repositories/task-history-repository.js +78 -0
  198. package/dist/storage/repositories/workflow-runs-repository.js +190 -1
  199. package/dist/tasks/parser.js +138 -52
  200. package/dist/tasks/runner.js +71 -75
  201. package/dist/tasks/schema.js +1 -1
  202. package/dist/tasks/validator.js +11 -6
  203. package/dist/text-import-hook.mjs +1 -1
  204. package/dist/wiki/wiki.js +9 -8
  205. package/dist/workflows/authoring/authoring.js +123 -10
  206. package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
  207. package/dist/workflows/cli.js +4 -0
  208. package/dist/workflows/concurrency-policy.js +15 -0
  209. package/dist/workflows/db.js +200 -13
  210. package/dist/workflows/exec/brief.js +478 -0
  211. package/dist/workflows/exec/frozen-judge.js +47 -0
  212. package/dist/workflows/exec/native-executor.js +1034 -0
  213. package/dist/workflows/exec/param-secrets.js +115 -0
  214. package/dist/workflows/exec/report.js +1355 -0
  215. package/dist/workflows/exec/run-workflow.js +609 -0
  216. package/dist/workflows/exec/scheduler.js +71 -0
  217. package/dist/workflows/exec/step-work.js +1212 -0
  218. package/dist/workflows/exec/unit-writer.js +23 -0
  219. package/dist/workflows/exec/watch.js +116 -0
  220. package/dist/workflows/exec/worktree.js +171 -0
  221. package/dist/workflows/ir/compile.js +375 -0
  222. package/dist/workflows/ir/freeze.js +243 -0
  223. package/dist/workflows/ir/params.js +54 -0
  224. package/dist/workflows/ir/plan-hash.js +68 -0
  225. package/dist/workflows/ir/schema.js +545 -0
  226. package/dist/workflows/parser.js +10 -1
  227. package/dist/workflows/program/expressions.js +369 -0
  228. package/dist/workflows/program/parser.js +869 -0
  229. package/dist/workflows/program/project.js +104 -0
  230. package/dist/workflows/program/schema.js +54 -0
  231. package/dist/workflows/renderer.js +82 -5
  232. package/dist/workflows/resource-limits.js +20 -0
  233. package/dist/workflows/runtime/agent-identity.js +59 -14
  234. package/dist/workflows/runtime/plan-classifier.js +187 -0
  235. package/dist/workflows/runtime/runs.js +246 -69
  236. package/dist/workflows/runtime/unit-checkin.js +45 -0
  237. package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
  238. package/dist/workflows/validate-summary.js +24 -3
  239. package/dist/workflows/validator.js +26 -1
  240. package/docs/data-and-telemetry.md +4 -3
  241. package/docs/migration/release-notes/0.6.0.md +1 -1
  242. package/docs/migration/release-notes/0.7.0.md +5 -4
  243. package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
  244. package/docs/migration/v0.8-to-v0.9.md +401 -0
  245. package/package.json +4 -2
  246. package/schemas/akm-config.json +16638 -0
  247. package/schemas/akm-task.json +87 -0
  248. package/schemas/akm-workflow.json +372 -0
  249. package/dist/commands/improve/improve-profiles.js +0 -168
  250. package/dist/core/config/config-migration.js +0 -602
  251. package/dist/core/deep-merge.js +0 -38
  252. package/dist/llm/call-ai.js +0 -62
  253. package/dist/setup/legacy-config.js +0 -106
  254. package/docs/README.md +0 -104
  255. /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
  256. /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
  257. /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
  258. /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
  259. /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
  260. /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
  261. /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
  262. /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
@@ -141,12 +141,35 @@ function buildRefTypeAlternation() {
141
141
  const types = [...getAssetTypes()].sort((a, b) => b.length - a.length);
142
142
  return types.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
143
143
  }
144
+ /**
145
+ * Body-ref boundary grammar, shared with `akm mv`'s ref-rewrite pattern —
146
+ * `src/commands/mv-cli.ts` imports these constants so the two grammars cannot
147
+ * drift. Any character-class change here retargets both the lint missing-ref
148
+ * scan and mv's inbound-xref rewriting.
149
+ *
150
+ * `REF_BOUNDARY_PREFIX_CLASS_SRC` is the character class a ref may start
151
+ * after (a ref also matches at line start): whitespace, backtick, quote,
152
+ * `(`, `[`, or `,` — the `[` admits markdown-link-style refs like
153
+ * `see [memory:foo]`, which the legacy class silently skipped, and the `,`
154
+ * admits the ref AFTER a bare comma in a no-space flow list like
155
+ * `xrefs: [memory:a,memory:b]` (valid YAML). `,` is already a slug
156
+ * TERMINATOR (excluded from `REF_SLUG_CHAR_CLASS_SRC`), so `a,b` splits
157
+ * cleanly and adding it here cannot extend any existing match; false
158
+ * positives are fenced by the type alternation — a comma only starts a match
159
+ * when a literal `<type>:` follows it.
160
+ *
161
+ * `REF_SLUG_CHAR_CLASS_SRC` is the character class a ref's `<type>:<slug>`
162
+ * token is made of; the first excluded character ends the ref.
163
+ */
164
+ export const REF_BOUNDARY_PREFIX_CLASS_SRC = "[\\s`\"'(,\\[]";
165
+ export const REF_SLUG_CHAR_CLASS_SRC = "[^\\s\"'`)\\]>,\\n]";
144
166
  // Only the TYPE alternation is registry-derived; the surrounding grammar
145
167
  // (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");
168
+ // legacy hand-written pattern, except that the boundary prefix now also
169
+ // admits `[`. Deriving the types from `getAssetTypes()` means `env`/`secret`
170
+ // (added in 0.9) are now matched, and the removed `vault` type is not — both
171
+ // follow the registry automatically.
172
+ const REF_RE = new RegExp(`(?:^|${REF_BOUNDARY_PREFIX_CLASS_SRC})((${buildRefTypeAlternation()}):${REF_SLUG_CHAR_CLASS_SRC}+)`, "gm");
150
173
  /**
151
174
  * Map from ref type to relative path pattern within stashRoot. Returns null to
152
175
  * skip (type is unresolvable by the slug walker).
@@ -185,49 +208,70 @@ export function refToRelPath(refType, refName) {
185
208
  */
186
209
  export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
187
210
  for (const root of stashRoots) {
188
- const absPath = path.join(root, relPath);
189
- if (fs.existsSync(absPath))
211
+ if (resolveRefPathInStash(relPath, refType, refName, root) !== null)
190
212
  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
- }
213
+ }
214
+ return false;
215
+ }
216
+ /**
217
+ * Resolve the on-disk primary file for a ref within a SINGLE stash root, using
218
+ * the same reachability rules (in the same order) as
219
+ * {@link refExistsInAnyStash}, which delegates here. Returns the absolute path
220
+ * of the file that makes the ref "exist" — for a multi-file skill directory
221
+ * that is its `SKILL.md` primary — or `null` when the ref does not resolve in
222
+ * this root.
223
+ *
224
+ * Extracted for SPEC-5 (`--supersedes` demotion): write commands need the
225
+ * superseded asset's actual file to mutate, and forking a second resolver
226
+ * would drift from lint's. NOT part of the akm-plugins ref-resolver contract
227
+ * (the contract pins `refToRelPath` + `refExistsInAnyStash`; this is the
228
+ * shared internal both build on).
229
+ */
230
+ export function resolveRefPathInStash(relPath, refType, refName, root) {
231
+ const absPath = path.join(root, relPath);
232
+ if (fs.existsSync(absPath))
233
+ return absPath;
234
+ // Multi-file skill layout: directory containing SKILL.md
235
+ const bareDir = absPath.replace(/\.md$/, "");
236
+ if (fs.existsSync(bareDir) && fs.existsSync(path.join(bareDir, "SKILL.md"))) {
237
+ return path.join(bareDir, "SKILL.md");
238
+ }
239
+ // .derived.md variant for memory refs
240
+ if (refType === "memory") {
241
+ const derivedPath = path.join(root, "memories", `${refName}.derived.md`);
242
+ if (fs.existsSync(derivedPath))
243
+ return derivedPath;
244
+ }
245
+ // Knowledge-specific: search subdirectories like knowledge/projects/, knowledge/tools/, etc.
246
+ if (refType === "knowledge") {
247
+ try {
248
+ const knowledgeDir = path.join(root, "knowledge");
249
+ if (fs.existsSync(knowledgeDir) && fs.statSync(knowledgeDir).isDirectory()) {
250
+ const entries = fs.readdirSync(knowledgeDir, { withFileTypes: true });
251
+ for (const entry of entries) {
252
+ if (!entry.isDirectory())
253
+ continue;
254
+ const subPath = path.join(knowledgeDir, entry.name, `${refName}.md`);
255
+ if (fs.existsSync(subPath))
256
+ return subPath;
214
257
  }
215
258
  }
216
- catch {
217
- // Ignore errors reading directory
218
- }
219
259
  }
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")))
228
- return true;
260
+ catch {
261
+ // Ignore errors reading directory
262
+ }
229
263
  }
230
- return false;
264
+ // Fallback: the refName may already encode the full stash-relative path
265
+ // (e.g. knowledge:skills/foo/references/bar where the file lives at
266
+ // <stash>/skills/foo/references/bar.md, not <stash>/knowledge/skills/...).
267
+ const directPath = path.join(root, `${refName}.md`);
268
+ if (fs.existsSync(directPath))
269
+ return directPath;
270
+ const directDir = path.join(root, refName);
271
+ if (fs.existsSync(directDir) && fs.existsSync(path.join(directDir, "SKILL.md"))) {
272
+ return path.join(directDir, "SKILL.md");
273
+ }
274
+ return null;
231
275
  }
232
276
  /**
233
277
  * Returns an array of {ref, resolvedRelPath} for every local AKM ref in the
@@ -293,6 +337,20 @@ function checkMissingRefs(body, stashRoot, extraStashRoots = []) {
293
337
  return missing;
294
338
  }
295
339
  // ── frontmatter refs ─────────────────────────────────────────────────────────
340
+ /**
341
+ * Frontmatter keys that carry cross-reference lists per the stash
342
+ * organization conventions: `xrefs:` (provenance / associative links),
343
+ * `supersededBy:` and `contradictedBy:` (belief-state correction links).
344
+ * The missing-ref check validates each of these in ADDITION to the body /
345
+ * `refs:` scan — they are the channel the conventions mandate, and a rename
346
+ * would otherwise dangle them silently.
347
+ *
348
+ * `sources:` is deliberately excluded (non-wiki `sources:` was rejected as a
349
+ * typed channel; wiki `sources:` is checked by lintWiki). `source_refs:` /
350
+ * `evidenceSources:` are excluded too — they legitimately point at
351
+ * merged-away or pruned assets (historical provenance).
352
+ */
353
+ const XREF_FRONTMATTER_KEYS = ["xrefs", "supersededBy", "contradictedBy"];
296
354
  /**
297
355
  * Return the `refs:` array from frontmatter when it is present and is an
298
356
  * array of strings; otherwise return `null` to signal the caller should
@@ -342,6 +400,22 @@ function readRefsArray(value) {
342
400
  }
343
401
  return out;
344
402
  }
403
+ /**
404
+ * Like {@link readRefsArray} but also accepts a single scalar string,
405
+ * normalizing it to a one-element list. The indexer's
406
+ * `normalizeNonEmptyStringList` treats `supersededBy: memory:x` and
407
+ * `supersededBy: [memory:x]` identically — both are live data — so the
408
+ * frontmatter xref-channel check must validate both shapes; the array-only
409
+ * reader silently skipped dangling scalar refs. Returns `null` for any other
410
+ * type (missing key, number, object) and for a blank scalar.
411
+ */
412
+ function readRefStringOrArray(value) {
413
+ if (typeof value === "string") {
414
+ const trimmed = value.trim();
415
+ return trimmed ? [trimmed] : null;
416
+ }
417
+ return readRefsArray(value);
418
+ }
345
419
  /**
346
420
  * Detect a leading nested frontmatter block in `body` (i.e. a `---\n…\n---`
347
421
  * pair that opens within the first few lines of the body). When present,
@@ -604,6 +678,42 @@ export class BaseLinter {
604
678
  fixed: false,
605
679
  });
606
680
  }
681
+ // Frontmatter xref channels (xrefs / supersededBy / contradictedBy).
682
+ // Runs regardless of the `refs:` body-scan carve-out above — that
683
+ // carve-out governs only the BODY scan (`refs: []` declares "no
684
+ // outbound refs in the body", not "skip my correction links").
685
+ // Non-ref-shaped values (URLs, `raw/<slug>`, `<placeholder>`
686
+ // templates, shell vars) fall out via checkMissingRefs' guards.
687
+ //
688
+ // Gate: runs when the file has a frontmatter block OR when an
689
+ // authoritative `refs:` list was extracted. On the task/YAML path
690
+ // (lint/index.ts) ctx.frontmatter is always null and the whole file
691
+ // IS the body (`body === raw`); the top-level YAML keys land in
692
+ // ctx.data. Without `refs:` the body scan above already catches ref
693
+ // values under these keys, so running the pass would double-report —
694
+ // skip it. With `refs:` present the body scan is suppressed
695
+ // (refSource is the refs list), so this pass is the ONLY thing that
696
+ // validates the xref keys — it must run or dangling task xrefs go
697
+ // unreported. The two cases are mutually exclusive, so no ref is
698
+ // ever double-reported. Md files without a frontmatter block and
699
+ // without `refs:` land in the skip branch with empty ctx.data, so
700
+ // nothing is lost for them either.
701
+ if (ctx.frontmatter !== null || explicitRefs !== null) {
702
+ for (const key of XREF_FRONTMATTER_KEYS) {
703
+ const values = readRefStringOrArray(ctx.data?.[key]);
704
+ if (values === null)
705
+ continue;
706
+ const missingXrefs = checkMissingRefs(values.join("\n"), ctx.stashRoot, ctx.extraStashRoots);
707
+ for (const { ref, resolvedRelPath } of missingXrefs) {
708
+ issues.push({
709
+ file: ctx.relPath,
710
+ issue: "missing-ref",
711
+ detail: `missing ref: ${ref} (frontmatter ${key}; resolved to ${resolvedRelPath})`,
712
+ fixed: false,
713
+ });
714
+ }
715
+ }
716
+ }
607
717
  }
608
718
  return issues;
609
719
  }