akm-cli 0.9.0-rc.8 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/CHANGELOG.md +1063 -44
  2. package/README.md +51 -25
  3. package/SECURITY.md +14 -1
  4. package/STABILITY.md +497 -0
  5. package/dist/akm +148 -35
  6. package/dist/{akm-migrate-storage → akm-migrate} +6 -9
  7. package/dist/assets/hints/cli-hints-full.md +223 -95
  8. package/dist/assets/hints/cli-hints-short.md +85 -22
  9. package/dist/assets/improve-strategies/default.json +1 -1
  10. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  11. package/dist/assets/prompts/memory-infer-user.md +2 -3
  12. package/dist/assets/stash-skeleton/README.md +6 -5
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
  24. package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
  25. package/dist/assets/tasks/core/extract.yml +1 -1
  26. package/dist/assets/tasks/core/version-check.yml +1 -1
  27. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  28. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  29. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  30. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  31. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  32. package/dist/assets/templates/html/health.html +1 -3
  33. package/dist/assets/workflows/workflow-template.md +32 -15
  34. package/dist/cli/invocation.js +40 -15
  35. package/dist/cli/parse-args.js +0 -22
  36. package/dist/cli/retired-commands.js +121 -0
  37. package/dist/cli/shared.js +154 -22
  38. package/dist/cli/unknown-flags.js +236 -0
  39. package/dist/cli-node.mjs +2 -1
  40. package/dist/cli.js +696 -258
  41. package/dist/commands/agent/agent-dispatch.js +14 -3
  42. package/dist/commands/agent/contribute-cli.js +73 -88
  43. package/dist/commands/completions.js +79 -22
  44. package/dist/commands/config-cli.js +17 -150
  45. package/dist/commands/env/env-cli.js +59 -143
  46. package/dist/commands/env/env.js +12 -163
  47. package/dist/commands/env/marker-path.js +6 -0
  48. package/dist/commands/env/secret-cli.js +36 -66
  49. package/dist/commands/env/secret.js +24 -57
  50. package/dist/commands/feedback-cli.js +141 -87
  51. package/dist/commands/health/accept-rate.js +58 -0
  52. package/dist/commands/health/advisories.js +3 -4
  53. package/dist/commands/health/checks.js +85 -23
  54. package/dist/commands/health/html-report.js +7 -10
  55. package/dist/commands/health/improve-metrics.js +25 -83
  56. package/dist/commands/health/md-report.js +5 -9
  57. package/dist/commands/health/metrics.js +62 -20
  58. package/dist/commands/health/renderers.js +47 -0
  59. package/dist/commands/health/report-view-model.js +4 -5
  60. package/dist/commands/health/stash-exposure.js +1 -1
  61. package/dist/commands/health/surfaces.js +3 -48
  62. package/dist/commands/health/task-runs.js +3 -67
  63. package/dist/commands/health/types-improve.js +7 -0
  64. package/dist/commands/health.js +99 -28
  65. package/dist/commands/improve/anti-collapse.js +2 -2
  66. package/dist/commands/improve/autonomy-gate.js +68 -0
  67. package/dist/commands/improve/collapse-detector.js +41 -40
  68. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  69. package/dist/commands/improve/consolidate/merge.js +4 -0
  70. package/dist/commands/improve/consolidate.js +140 -1000
  71. package/dist/commands/improve/distill/promote-memory.js +12 -12
  72. package/dist/commands/improve/distill/quality-gate.js +6 -6
  73. package/dist/commands/improve/distill.js +58 -69
  74. package/dist/commands/improve/eligibility.js +105 -57
  75. package/dist/commands/improve/extract-cli.js +14 -133
  76. package/dist/commands/improve/improve-cli.js +98 -114
  77. package/dist/commands/improve/improve-result-file.js +1 -28
  78. package/dist/commands/improve/improve-strategies.js +8 -5
  79. package/dist/commands/improve/improve.js +128 -91
  80. package/dist/commands/improve/loop-stages.js +182 -20
  81. package/dist/commands/improve/memory/derived-ref.js +45 -43
  82. package/dist/commands/improve/memory/memory-belief.js +1 -1
  83. package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
  84. package/dist/commands/improve/memory/memory-improve.js +6 -5
  85. package/dist/commands/improve/outcome-loop.js +22 -65
  86. package/dist/commands/improve/preparation.js +114 -123
  87. package/dist/commands/improve/proactive-maintenance.js +2 -5
  88. package/dist/commands/improve/reflect.js +56 -160
  89. package/dist/commands/improve/salience.js +11 -122
  90. package/dist/commands/improve/source-identity.js +10 -38
  91. package/dist/commands/lint/base-linter.js +20 -124
  92. package/dist/commands/lint/env-key-rules.js +31 -47
  93. package/dist/commands/lint/index.js +249 -43
  94. package/dist/commands/{events.js → log.js} +33 -38
  95. package/dist/commands/migrate-cli.js +92 -12
  96. package/dist/commands/migration-tool.js +46 -0
  97. package/dist/commands/observability-cli.js +70 -209
  98. package/dist/commands/proposal/drain.js +101 -29
  99. package/dist/commands/proposal/proposal-cli.js +76 -48
  100. package/dist/commands/proposal/proposal.js +54 -18
  101. package/dist/commands/proposal/propose-cli.js +88 -0
  102. package/dist/commands/proposal/propose.js +23 -15
  103. package/dist/commands/proposal/repository.js +701 -278
  104. package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
  105. package/dist/commands/proposal/validators/proposal-validators.js +55 -7
  106. package/dist/commands/proposal/validators/proposals.js +4 -7
  107. package/dist/commands/read/curate.js +34 -53
  108. package/dist/commands/read/knowledge.js +150 -95
  109. package/dist/commands/read/registry-search.js +2 -2
  110. package/dist/commands/read/remember-cli.js +42 -15
  111. package/dist/commands/read/search-cli.js +180 -78
  112. package/dist/commands/read/search.js +58 -43
  113. package/dist/commands/read/show.js +197 -141
  114. package/dist/commands/registry-cli.js +12 -51
  115. package/dist/commands/remember.js +14 -57
  116. package/dist/commands/sources/add-cli.js +100 -31
  117. package/dist/commands/sources/bundle-cli.js +166 -0
  118. package/dist/commands/sources/bundle-config-ops.js +7 -2
  119. package/dist/commands/sources/info.js +18 -5
  120. package/dist/commands/sources/init.js +12 -12
  121. package/dist/commands/sources/installed-stashes.js +382 -98
  122. package/dist/commands/sources/schema-repair.js +3 -2
  123. package/dist/commands/sources/self-update.js +131 -38
  124. package/dist/commands/sources/source-add.js +72 -17
  125. package/dist/commands/sources/source-clone.js +129 -45
  126. package/dist/commands/sources/source-manage.js +43 -23
  127. package/dist/commands/sources/sources-cli.js +57 -208
  128. package/dist/commands/sources/stash-cli.js +46 -53
  129. package/dist/commands/tasks/tasks-cli.js +91 -97
  130. package/dist/commands/tasks/tasks.js +276 -421
  131. package/dist/commands/workflow-cli.js +175 -450
  132. package/dist/core/adapter/adapters/akm-adapter.js +47 -28
  133. package/dist/core/adapter/adapters/akm-lint.js +42 -27
  134. package/dist/core/adapter/adapters/akm-metadata.js +15 -44
  135. package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
  136. package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
  137. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
  138. package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
  139. package/dist/core/adapter/adapters/index.js +6 -6
  140. package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
  141. package/dist/core/adapter/adapters/okf-adapter.js +187 -19
  142. package/dist/core/adapter/adapters/shared.js +3 -19
  143. package/dist/core/adapter/adapters/tool-dir-shared.js +8 -3
  144. package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
  145. package/dist/core/adapter/detect-adapter.js +17 -0
  146. package/dist/core/adapter/recognize-match.js +6 -4
  147. package/dist/core/adapter/validate-context.js +214 -0
  148. package/dist/core/asset/akm-markdown.js +63 -0
  149. package/dist/core/asset/asset-placement.js +20 -6
  150. package/dist/core/asset/asset-ref.js +11 -9
  151. package/dist/core/asset/frontmatter-lint.js +30 -0
  152. package/dist/core/asset/frontmatter.js +37 -9
  153. package/dist/core/asset/markdown.js +40 -51
  154. package/dist/core/asset/resolve-ref.js +89 -18
  155. package/dist/core/asset/stash-meta.js +1 -1
  156. package/dist/core/bundle-id.js +51 -0
  157. package/dist/core/common.js +152 -38
  158. package/dist/core/config/config-io.js +12 -1
  159. package/dist/core/config/config-schema.js +35 -8
  160. package/dist/core/config/config-sources.js +55 -11
  161. package/dist/core/config/config-walker.js +25 -9
  162. package/dist/core/config/config.js +9 -48
  163. package/dist/core/config/experimental.js +21 -0
  164. package/dist/core/config/schema/embedding.js +5 -1
  165. package/dist/core/config/schema/experimental.js +30 -0
  166. package/dist/core/config/schema/improve-processes.js +0 -6
  167. package/dist/core/config/schema/improve.js +21 -3
  168. package/dist/core/config/schema/index-config.js +8 -15
  169. package/dist/core/config/schema/output.js +4 -1
  170. package/dist/core/config/schema/setup.js +9 -18
  171. package/dist/core/config/schema/sources-bundles.js +49 -33
  172. package/dist/core/config/schema/workflow.js +3 -3
  173. package/dist/core/env-secret-ref.js +76 -46
  174. package/dist/core/errors.js +18 -12
  175. package/dist/core/events.js +46 -128
  176. package/dist/core/file-change.js +6 -5
  177. package/dist/core/fs-txn.js +83 -7
  178. package/dist/core/git-message.js +2 -2
  179. package/dist/core/improve-result.js +1 -100
  180. package/dist/core/lesson-lint.js +1 -17
  181. package/dist/core/logs-db.js +2 -1
  182. package/dist/core/migration-operation.js +16 -0
  183. package/dist/core/mutation-target.js +78 -0
  184. package/dist/core/parse.js +4 -1
  185. package/dist/core/paths.js +17 -20
  186. package/dist/core/recognition-util.js +12 -14
  187. package/dist/core/redaction.js +34 -0
  188. package/dist/core/standards/resolve-standards-context.js +2 -14
  189. package/dist/core/standards/resolve-stash-standards.js +2 -2
  190. package/dist/core/standards/resolve-type-conventions.js +2 -2
  191. package/dist/core/state/migrations.js +41 -18
  192. package/dist/core/state-db.js +5 -14
  193. package/dist/core/structured.js +1 -1
  194. package/dist/core/subprocess.js +6 -4
  195. package/dist/core/text-truncation.js +9 -5
  196. package/dist/core/type-presentation.js +3 -3
  197. package/dist/core/warn.js +0 -3
  198. package/dist/core/write-source.js +771 -95
  199. package/dist/indexer/bundle-identity-guard.js +3 -2
  200. package/dist/indexer/db/graph-db.js +0 -24
  201. package/dist/indexer/ensure-index.js +1 -0
  202. package/dist/indexer/graph/graph-boost.js +9 -34
  203. package/dist/indexer/graph/graph-extraction.js +8 -5
  204. package/dist/indexer/index-writer-lock.js +53 -17
  205. package/dist/indexer/index-written-assets.js +16 -22
  206. package/dist/indexer/indexer.js +497 -239
  207. package/dist/indexer/installations.js +14 -96
  208. package/dist/indexer/passes/dir-staleness.js +16 -9
  209. package/dist/indexer/passes/memory-inference.js +11 -9
  210. package/dist/indexer/passes/metadata.js +113 -47
  211. package/dist/indexer/scan/doc-to-entry.js +38 -1
  212. package/dist/indexer/scan/drain-dir.js +13 -23
  213. package/dist/indexer/search/db-search.js +99 -54
  214. package/dist/indexer/search/fts-query.js +47 -24
  215. package/dist/indexer/search/ranking-contributors.js +42 -20
  216. package/dist/indexer/search/ranking.js +18 -99
  217. package/dist/indexer/search/search-fields.js +7 -2
  218. package/dist/indexer/search/search-source.js +82 -93
  219. package/dist/indexer/usage/usage-events.js +0 -89
  220. package/dist/indexer/walk/file-context.js +2 -1
  221. package/dist/indexer/walk/matchers.js +30 -43
  222. package/dist/indexer/walk/path-resolver.js +7 -2
  223. package/dist/indexer/walk/walker.js +38 -12
  224. package/dist/integrations/agent/builders.js +0 -6
  225. package/dist/integrations/agent/config.js +2 -2
  226. package/dist/integrations/agent/detect.js +49 -19
  227. package/dist/integrations/agent/engine-fallback.js +76 -0
  228. package/dist/integrations/agent/profiles.js +14 -0
  229. package/dist/integrations/agent/prompts.js +12 -8
  230. package/dist/integrations/agent/runner-dispatch.js +4 -2
  231. package/dist/integrations/agent/runner.js +0 -1
  232. package/dist/integrations/agent/spawn.js +5 -6
  233. package/dist/integrations/github.js +1 -1
  234. package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
  235. package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
  236. package/dist/integrations/harnesses/claude/session-log.js +0 -10
  237. package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
  238. package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
  239. package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
  240. package/dist/integrations/harnesses/index.js +3 -7
  241. package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
  242. package/dist/integrations/harnesses/opencode/session-log.js +0 -15
  243. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
  244. package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
  245. package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
  246. package/dist/integrations/lockfile.js +101 -6
  247. package/dist/integrations/session-logs/index.js +3 -28
  248. package/dist/llm/client.js +136 -100
  249. package/dist/llm/embedders/remote.js +13 -5
  250. package/dist/llm/feature-gate.js +4 -12
  251. package/dist/llm/graph-extract.js +5 -11
  252. package/dist/llm/memory-infer.js +144 -1
  253. package/dist/llm/metadata-enhance.js +5 -7
  254. package/dist/llm/structured-call.js +1 -1
  255. package/dist/llm/usage-persist.js +26 -5
  256. package/dist/llm/usage-telemetry.js +25 -2
  257. package/dist/output/cli-hints.js +1 -2
  258. package/dist/output/context.js +22 -7
  259. package/dist/output/format-exempt.js +80 -0
  260. package/dist/output/generic-render.js +259 -0
  261. package/dist/output/render-registry.js +57 -0
  262. package/dist/output/renderers.js +14 -36
  263. package/dist/output/shapes/curate.js +10 -1
  264. package/dist/output/shapes/events.js +12 -7
  265. package/dist/output/shapes/helpers.js +56 -83
  266. package/dist/output/shapes/migrate.js +8 -0
  267. package/dist/output/shapes/passthrough.js +7 -41
  268. package/dist/output/shapes/proposal/producer.js +15 -7
  269. package/dist/output/shapes.js +2 -9
  270. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  271. package/dist/output/text/bundle-show.js +7 -0
  272. package/dist/output/text/command-format.js +164 -96
  273. package/dist/output/text/env.js +1 -3
  274. package/dist/output/text/events.js +8 -7
  275. package/dist/output/text/health-format.js +103 -0
  276. package/dist/output/text/health.js +7 -0
  277. package/dist/output/text/helpers.js +10 -8
  278. package/dist/output/text/lint-format.js +43 -0
  279. package/dist/output/text/{save.js → lint.js} +2 -2
  280. package/dist/output/text/migrate.js +88 -0
  281. package/dist/output/text/proposal/producer.js +4 -2
  282. package/dist/output/text/proposal-format.js +44 -72
  283. package/dist/output/text/registry-commands.js +1 -2
  284. package/dist/output/text/show-directives.js +15 -7
  285. package/dist/output/text/status-list.js +32 -0
  286. package/dist/output/text/sync.js +5 -0
  287. package/dist/output/text/workflow-format.js +24 -203
  288. package/dist/output/text/workflow.js +1 -7
  289. package/dist/output/text.js +16 -17
  290. package/dist/registry/factory.js +4 -6
  291. package/dist/registry/origin-resolve.js +16 -27
  292. package/dist/registry/providers/skills-sh.js +3 -3
  293. package/dist/registry/providers/static-index.js +13 -23
  294. package/dist/registry/resolve.js +42 -7
  295. package/dist/registry/semver.js +34 -84
  296. package/dist/runtime.js +2 -23
  297. package/dist/scripts/akm-migrate-node.js +60290 -0
  298. package/dist/scripts/akm-migrate.js +59628 -0
  299. package/dist/setup/detect.js +42 -15
  300. package/dist/setup/registry-stash-loader.js +2 -2
  301. package/dist/setup/setup.js +236 -136
  302. package/dist/setup/steps/connection.js +7 -9
  303. package/dist/setup/steps/platforms.js +9 -9
  304. package/dist/setup/steps/semantic.js +15 -3
  305. package/dist/setup/steps/sources.js +12 -13
  306. package/dist/setup/steps/stashdir.js +2 -3
  307. package/dist/setup/steps/tasks.js +237 -120
  308. package/dist/sources/freshness.js +1 -1
  309. package/dist/sources/provider-factory.js +11 -17
  310. package/dist/sources/providers/filesystem.js +2 -3
  311. package/dist/sources/providers/git-install.js +278 -34
  312. package/dist/sources/providers/git-provider.js +25 -23
  313. package/dist/sources/providers/git-stash.js +395 -106
  314. package/dist/sources/providers/git.js +2 -2
  315. package/dist/sources/providers/npm.js +16 -19
  316. package/dist/sources/providers/provider-utils.js +7 -4
  317. package/dist/sources/providers/sync-from-ref.js +3 -9
  318. package/dist/sources/providers/website.js +6 -1
  319. package/dist/sources/resolve.js +6 -5
  320. package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
  321. package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
  322. package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
  323. package/dist/sources/snapshot-fetchers/github.js +100 -0
  324. package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
  325. package/dist/sources/snapshot-fetchers/registry.js +17 -1
  326. package/dist/sources/snapshot-fetchers/robots.js +348 -0
  327. package/dist/sources/snapshot-fetchers/rss.js +282 -0
  328. package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
  329. package/dist/sources/snapshot-fetchers/website-ingest.js +566 -268
  330. package/dist/sources/snapshot-fetchers/x.js +910 -0
  331. package/dist/storage/database.js +7 -0
  332. package/dist/storage/engines/sqlite-migrations.js +23 -111
  333. package/dist/storage/managed-db.js +2 -2
  334. package/dist/storage/repositories/canaries-repository.js +1 -1
  335. package/dist/storage/repositories/events-repository.js +27 -11
  336. package/dist/storage/repositories/improve-runs-repository.js +6 -12
  337. package/dist/storage/repositories/index-connection.js +17 -6
  338. package/dist/storage/repositories/index-entries-repository.js +151 -240
  339. package/dist/storage/repositories/index-entry-mapper.js +15 -11
  340. package/dist/storage/repositories/index-fts-repository.js +5 -2
  341. package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
  342. package/dist/storage/repositories/index-meta-repository.js +2 -3
  343. package/dist/storage/repositories/index-schema.js +10 -25
  344. package/dist/storage/repositories/index-utility-repository.js +15 -28
  345. package/dist/storage/repositories/index-vec-repository.js +6 -1
  346. package/dist/storage/repositories/outcome-repository.js +119 -0
  347. package/dist/storage/repositories/proposals-repository.js +296 -59
  348. package/dist/storage/repositories/registry-cache.js +19 -0
  349. package/dist/storage/repositories/salience-repository.js +172 -0
  350. package/dist/storage/repositories/task-history-repository.js +15 -13
  351. package/dist/storage/repositories/workflow-runs-repository.js +52 -40
  352. package/dist/tasks/backends/cron.js +105 -15
  353. package/dist/tasks/backends/index.js +1 -1
  354. package/dist/tasks/backends/launchd.js +85 -38
  355. package/dist/tasks/backends/schtasks.js +135 -15
  356. package/dist/tasks/embedded.js +56 -40
  357. package/dist/tasks/parser.js +7 -157
  358. package/dist/tasks/resolve-akm-bin.js +137 -59
  359. package/dist/tasks/runner.js +79 -42
  360. package/dist/tasks/scheduler-invocation.js +220 -10
  361. package/dist/tasks/schema.js +24 -1
  362. package/dist/tasks/task-id.js +1 -3
  363. package/dist/tasks/validator.js +20 -6
  364. package/dist/workflows/authoring/authoring.js +94 -143
  365. package/dist/workflows/authoring/scope-key.js +1 -1
  366. package/dist/workflows/exec/frozen-judge.js +28 -2
  367. package/dist/workflows/exec/native-executor.js +77 -57
  368. package/dist/workflows/exec/param-secrets.js +9 -9
  369. package/dist/workflows/exec/run-workflow.js +133 -79
  370. package/dist/workflows/exec/step-work.js +219 -346
  371. package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
  372. package/dist/workflows/ir/compile.js +141 -270
  373. package/dist/workflows/ir/freeze.js +40 -30
  374. package/dist/workflows/ir/params.js +135 -11
  375. package/dist/workflows/ir/plan-hash.js +1 -1
  376. package/dist/workflows/ir/schema.js +25 -26
  377. package/dist/workflows/parser.js +872 -307
  378. package/dist/workflows/program/expressions.js +20 -208
  379. package/dist/workflows/program/schema.js +7 -10
  380. package/dist/workflows/renderer.js +95 -68
  381. package/dist/workflows/resource-limits.js +2 -0
  382. package/dist/workflows/runtime/checkin.js +3 -3
  383. package/dist/workflows/runtime/plan-classifier.js +16 -75
  384. package/dist/workflows/runtime/runs.js +186 -127
  385. package/dist/workflows/runtime/unit-checkin.js +1 -1
  386. package/dist/workflows/runtime/unit-phases.js +2 -2
  387. package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
  388. package/dist/workflows/schema.js +1 -11
  389. package/dist/workflows/validate-summary.js +30 -36
  390. package/dist/workflows/validator.js +21 -62
  391. package/docs/README.md +68 -0
  392. package/docs/migration/README.md +8 -0
  393. package/docs/migration/release-notes/0.7.0.md +11 -11
  394. package/docs/migration/release-notes/0.9.0.md +208 -27
  395. package/docs/migration/v0.7-to-v0.8.md +46 -47
  396. package/docs/migration/v0.8-to-v0.9.md +564 -208
  397. package/docs/migration/v0.9.0-troubleshooting.md +561 -0
  398. package/docs/reference/README.md +12 -0
  399. package/docs/reference/cli.md +2253 -0
  400. package/docs/reference/configuration.md +358 -0
  401. package/docs/reference/data-and-telemetry.md +105 -42
  402. package/docs/reference/workflows.md +647 -0
  403. package/package.json +22 -11
  404. package/schemas/akm-asset-envelope.json +93 -0
  405. package/schemas/akm-config.json +81 -128
  406. package/schemas/akm-workflow.json +74 -73
  407. package/dist/assets/tasks/core/backup.yml +0 -5
  408. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  409. package/dist/cli/config-migrate.js +0 -1806
  410. package/dist/cli/config-validate.js +0 -41
  411. package/dist/commands/backup-cli.js +0 -56
  412. package/dist/commands/bundle/bundle-cli.js +0 -68
  413. package/dist/commands/bundle/bundle.js +0 -219
  414. package/dist/commands/graph/graph-cli.js +0 -124
  415. package/dist/commands/graph/graph.js +0 -489
  416. package/dist/commands/improve/extract-watch.js +0 -140
  417. package/dist/commands/mv-cli.js +0 -1221
  418. package/dist/commands/sources/history.js +0 -201
  419. package/dist/commands/tasks/default-tasks.js +0 -186
  420. package/dist/core/migration-backup.js +0 -1234
  421. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
  422. package/dist/llm/memory-infer-impl.js +0 -138
  423. package/dist/migrate/legacy/config-source-migration.js +0 -223
  424. package/dist/migrate/legacy/content-migration.js +0 -305
  425. package/dist/migrate/legacy/legacy-layout.js +0 -779
  426. package/dist/migrate/legacy/legacy-paths.js +0 -25
  427. package/dist/migrate/legacy/legacy-stash-json.js +0 -72
  428. package/dist/migrate/legacy/proposal-fs-import.js +0 -168
  429. package/dist/migrate/legacy/task-target-ref-migration.js +0 -272
  430. package/dist/migrate/legacy/three-db-cutover.js +0 -841
  431. package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
  432. package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
  433. package/dist/migrate/legacy-ref-grammar.js +0 -214
  434. package/dist/output/shapes/distill.js +0 -14
  435. package/dist/output/shapes/history.js +0 -11
  436. package/dist/output/text/distill.js +0 -6
  437. package/dist/output/text/enable-disable.js +0 -8
  438. package/dist/output/text/history.js +0 -6
  439. package/dist/registry/build-index.js +0 -382
  440. package/dist/schemas/akm-config.json +0 -4704
  441. package/dist/schemas/akm-task.json +0 -87
  442. package/dist/schemas/akm-workflow.json +0 -372
  443. package/dist/scripts/migrate-storage.js +0 -3816
  444. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  445. package/dist/workflows/cli.js +0 -53
  446. package/dist/workflows/exec/brief.js +0 -481
  447. package/dist/workflows/exec/report.js +0 -1460
  448. package/dist/workflows/exec/watch.js +0 -116
  449. package/dist/workflows/program/parser.js +0 -813
  450. package/dist/workflows/program/project.js +0 -104
@@ -95,6 +95,7 @@ function recognize(c, file) {
95
95
  path: file.absPath,
96
96
  hash: hashContent(raw),
97
97
  adapterId: "website-snapshot",
98
+ ownsPresentation: true,
98
99
  type: "website",
99
100
  name,
100
101
  content: body.length > MAX_CONTENT_CHARS ? body.slice(0, MAX_CONTENT_CHARS) : body,
@@ -0,0 +1,17 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { getAdapters } from "./registry.js";
5
+ /** Select the first built-in adapter whose ordered root probe claims `root`. */
6
+ export function detectAdapterId(root, fallback = "akm") {
7
+ for (const adapter of getAdapters()) {
8
+ try {
9
+ if (adapter.looksLikeRoot?.(root) === true)
10
+ return adapter.id;
11
+ }
12
+ catch {
13
+ // An unreadable or racing probe does not claim the bundle.
14
+ }
15
+ }
16
+ return fallback;
17
+ }
@@ -1,19 +1,21 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { directoryMatcher, extensionMatcher, parentDirHintMatcher, smartMdMatcher, workflowProgramMatcher, } from "../../indexer/walk/matchers.js";
4
+ import { directoryMatcher, extensionMatcher, parentDirHintMatcher, smartMdMatcher } from "../../indexer/walk/matchers.js";
5
5
  /**
6
- * The five builtin matchers, in registration order. The array index IS the
6
+ * The four builtin matchers, in registration order. The array index IS the
7
7
  * registration index `runMatchers` uses for tie-breaking. (The `wiki` matcher
8
8
  * was removed in chunk 4 — the wiki asset-type is retired; LLM Wiki content is
9
- * served by the first-class `llm-wiki` adapter, not the akm adapter.)
9
+ * served by the first-class `llm-wiki` adapter, not the akm adapter. The YAML
10
+ * workflow-program matcher was removed by workflow-format-unification — one
11
+ * workflow format now, recognized by frontmatter `type: workflow` or
12
+ * residence under `workflows/`, both already covered by the remaining four.)
10
13
  */
11
14
  const AKM_MATCHERS = [
12
15
  extensionMatcher,
13
16
  directoryMatcher,
14
17
  parentDirHintMatcher,
15
18
  smartMdMatcher,
16
- workflowProgramMatcher,
17
19
  ];
18
20
  /**
19
21
  * Synchronous reproduction of `file-context.ts#runMatchers`'s arbitration
@@ -0,0 +1,214 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * The ONE core {@link ValidateContext} implementation — akm 0.9.0 chunk-2/lint
6
+ * follow-up ("make `validate()` genuinely load-bearing").
7
+ *
8
+ * `BundleAdapter.validate()` (`./bundle-adapter.ts`) is a REQUIRED interface
9
+ * method whose contract is explicit and normative (`./bundle-adapter.ts:96-100`
10
+ * / the format-neutral spec §12.1): the adapter MUST NOT write and MUST NOT
11
+ * read the live filesystem — `ctx` serves the run's on-disk SNAPSHOT WITH the
12
+ * caller's pending {@link FileChange}s overlaid, plus a read-only `resolveRef`
13
+ * for link/xref existence. That overlay is meant to be built ONCE, centrally —
14
+ * "one core overlay implementation, not one per adapter" (the interface's own
15
+ * words) — so every `validate()` caller shares identical overlay semantics
16
+ * rather than each hand-rolling its own (as every adapter's own test suite has
17
+ * done up to now, see e.g. `tests/core/adapter/okf-adapter.test.ts`'s
18
+ * `overlayCtx`/`diskCtx`).
19
+ *
20
+ * The two production callers wired onto this factory:
21
+ * - `akm lint`'s non-akm adapter dispatch (`commands/lint/index.ts`) — a
22
+ * plain sweep with NO pending changes, so the overlay is empty and every
23
+ * read falls straight through to disk.
24
+ * - the proposal promotion preflight (`commands/proposal/repository.ts`
25
+ * `preflightProposalPromotion`) — the one proposal's own `changes` are the
26
+ * overlay, so `validate()` sees the bundle AS IT WOULD LOOK the instant
27
+ * after the transaction commits, without that transaction ever touching
28
+ * disk.
29
+ *
30
+ * ── resolveRef ──
31
+ *
32
+ * A bare (unqualified) `conceptId` is resolved as BOTH:
33
+ * 1. the AKM placement-derived path (`typeNameFromConceptId` + `stashDirFor`
34
+ * + `assetPathForName` — the exact derivation `commands/lint/base-linter.ts
35
+ * #refToRelPath` already uses for the akm-native `missing-ref` check); and
36
+ * 2. the DIRECT component-relative spelling (`<conceptId>.md`, and the bare
37
+ * `<conceptId>` for an extensionless asset) — the form every non-akm
38
+ * adapter's OWN conceptId already IS (OKF: path − `.md`; llm-wiki: same).
39
+ * A `bundle//conceptId`-qualified ref has its bundle prefix stripped before
40
+ * resolution — mirroring `base-linter.ts#classifyConceptRef`'s existing
41
+ * behavior (the legacy resolver has never scoped-by-bundle-name either; it
42
+ * searches every configured stash root for the bare conceptId). Existence is
43
+ * checked against the PRIMARY root (with the pending-changes overlay applied)
44
+ * first, then each extra root (disk only — a pending transaction never
45
+ * targets more than one bundle).
46
+ */
47
+ import fs from "node:fs";
48
+ import path from "node:path";
49
+ import { assetPathForName, stashDirFor } from "../asset/asset-placement.js";
50
+ import { typeNameFromConceptId } from "../asset/resolve-ref.js";
51
+ function toPosix(p) {
52
+ return p.replace(/\\/g, "/");
53
+ }
54
+ /** Build the overlay map, keyed by POSIX path relative to `root`. */
55
+ function buildOverlay(root, changes) {
56
+ const overlay = new Map();
57
+ for (const change of changes) {
58
+ const relKey = toPosix(path.isAbsolute(change.path) ? path.relative(root, change.path) : change.path);
59
+ if (!relKey || relKey.startsWith(".."))
60
+ continue; // outside root — not this context's concern
61
+ if (change.op === "delete") {
62
+ overlay.set(relKey, null);
63
+ continue;
64
+ }
65
+ if (change.after !== undefined)
66
+ overlay.set(relKey, change.after);
67
+ }
68
+ return overlay;
69
+ }
70
+ function readDisk(absPath) {
71
+ try {
72
+ return fs.readFileSync(absPath, "utf8");
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ function existsOnDiskOrOverlay(relPath, root, overlay) {
79
+ const key = toPosix(relPath);
80
+ // Overlay entries are pending file contents by construction, so a present
81
+ // non-null entry is always a file.
82
+ if (overlay?.has(key))
83
+ return overlay.get(key) !== null;
84
+ try {
85
+ // `isFile()`, not `existsSync()`: a ref naming a DIRECTORY (e.g. a
86
+ // `pages/foo/` dir alongside no `pages/foo.md`) must not count as a
87
+ // resolved target — that would silently suppress a real
88
+ // `missing-ref`/`broken-xref` diagnostic.
89
+ return fs.statSync(path.join(root, relPath)).isFile();
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ /**
96
+ * Refs come from bundle CONTENT (link targets, xrefs, `sources:` entries), so
97
+ * they are untrusted input. A ref must stay inside the bundle root: reject
98
+ * absolute paths and any `..` segment before joining, so content can never
99
+ * probe for existence outside its own bundle (nor report a resolved `path`
100
+ * pointing there). Mirrors the `isWithin` containment rule the write path
101
+ * already enforces in `core/write-source.ts`.
102
+ */
103
+ function refEscapesBundle(conceptId) {
104
+ if (path.isAbsolute(conceptId) || conceptId.startsWith("/"))
105
+ return true;
106
+ return toPosix(conceptId)
107
+ .split("/")
108
+ .some((segment) => segment === "..");
109
+ }
110
+ /**
111
+ * Every on-disk relative path a bare `conceptId` might resolve to: the AKM
112
+ * placement-derived path (when the leading segment names a known placement
113
+ * stash-subdir) and the direct component-relative spellings every non-akm
114
+ * adapter's own conceptId already uses. Order doesn't matter — the caller
115
+ * checks all of them.
116
+ */
117
+ function candidateRelPaths(conceptId) {
118
+ const candidates = [];
119
+ const parts = typeNameFromConceptId(conceptId);
120
+ if (parts !== undefined) {
121
+ const typeDir = stashDirFor(parts.type);
122
+ if (typeDir !== undefined)
123
+ candidates.push(assetPathForName(parts.type, typeDir, parts.name));
124
+ }
125
+ candidates.push(`${conceptId}.md`);
126
+ candidates.push(conceptId);
127
+ return candidates;
128
+ }
129
+ /** Strip an optional `#fragment` (export selector) — never part of the on-disk identity. */
130
+ function stripFragment(ref) {
131
+ const hashIdx = ref.indexOf("#");
132
+ return hashIdx >= 0 ? ref.slice(0, hashIdx) : ref;
133
+ }
134
+ /**
135
+ * Strip an optional `bundle//` qualifier. Mirrors `base-linter.ts
136
+ * #classifyConceptRef`'s existing behavior: the legacy missing-ref checker has
137
+ * never resolved a qualifier against a NAMED bundle either — it searches every
138
+ * configured stash root for the bare conceptId. Keeping that same leniency
139
+ * here means this resolver's answers agree with today's `akm lint` for every
140
+ * ref shape that already worked.
141
+ */
142
+ function stripBundlePrefix(ref) {
143
+ const boundary = ref.indexOf("//");
144
+ return boundary >= 0 ? ref.slice(boundary + 2) : ref;
145
+ }
146
+ /**
147
+ * Build the ONE core {@link ValidateContext}: reads/lookups served from the
148
+ * on-disk snapshot rooted at `options.root` (+ `options.extraRoots` for
149
+ * cross-bundle ref existence) WITH `options.changes` overlaid on `options.root`
150
+ * — never a write, never a live-FS read beyond that snapshot.
151
+ */
152
+ export function createValidateContext(options) {
153
+ const root = options.root;
154
+ const extraRoots = options.extraRoots ?? [];
155
+ const overlay = buildOverlay(root, options.changes ?? []);
156
+ async function readFile(p) {
157
+ // An absolute candidate (e.g. a `stale-path` scan hit, which is always an
158
+ // absolute host path) bypasses the overlay — it can never name a pending
159
+ // change's stash-relative path, and reads straight from disk (the base
160
+ // checks' existing "does this literal absolute path exist" question).
161
+ if (path.isAbsolute(p))
162
+ return readDisk(p);
163
+ const key = toPosix(p);
164
+ if (overlay.has(key))
165
+ return overlay.get(key) ?? null;
166
+ return readDisk(path.join(root, p));
167
+ }
168
+ async function list(dir) {
169
+ const abs = path.isAbsolute(dir) ? dir : path.join(root, dir);
170
+ const relDir = toPosix(path.isAbsolute(dir) ? path.relative(root, dir) : dir);
171
+ const names = new Set();
172
+ try {
173
+ for (const entry of fs.readdirSync(abs))
174
+ names.add(entry);
175
+ }
176
+ catch {
177
+ // directory may not exist on disk yet — the overlay can still populate it
178
+ }
179
+ const prefix = relDir === "." || relDir === "" ? "" : `${relDir}/`;
180
+ for (const [key, value] of overlay) {
181
+ if (prefix.length > 0 && !key.startsWith(prefix))
182
+ continue;
183
+ const rest = prefix.length > 0 ? key.slice(prefix.length) : key;
184
+ if (rest.length === 0 || rest.includes("/"))
185
+ continue; // only direct children
186
+ if (value === null)
187
+ names.delete(rest);
188
+ else
189
+ names.add(rest);
190
+ }
191
+ return [...names];
192
+ }
193
+ async function resolveRef(ref) {
194
+ const conceptId = stripBundlePrefix(stripFragment(ref)).trim();
195
+ if (!conceptId)
196
+ return { exists: false };
197
+ if (refEscapesBundle(conceptId))
198
+ return { exists: false };
199
+ const candidates = candidateRelPaths(conceptId);
200
+ const roots = [
201
+ { root, usesOverlay: true },
202
+ ...extraRoots.map((r) => ({ root: r, usesOverlay: false })),
203
+ ];
204
+ for (const { root: candidateRoot, usesOverlay } of roots) {
205
+ for (const relPath of candidates) {
206
+ if (existsOnDiskOrOverlay(relPath, candidateRoot, usesOverlay ? overlay : null)) {
207
+ return { exists: true, path: path.join(candidateRoot, relPath) };
208
+ }
209
+ }
210
+ }
211
+ return { exists: false };
212
+ }
213
+ return { readFile, list, resolveRef };
214
+ }
@@ -0,0 +1,63 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { parse as parseYaml } from "yaml";
5
+ import { localDateStamp } from "../common.js";
6
+ import { UsageError } from "../errors.js";
7
+ import { serializeFrontmatter } from "./asset-serialize.js";
8
+ import { parseFrontmatterBlock, spliceFrontmatterLine } from "./frontmatter.js";
9
+ /**
10
+ * Ensure an AKM-authored Markdown concept is also a conformant OKF concept.
11
+ *
12
+ * Stamps BOTH `type` and `updated`, because both are required of a conformant
13
+ * document and this is the one chokepoint every `.md` write passes through
14
+ * (`core/write-source.ts`). Without the `updated` stamp, every asset akm
15
+ * created for you — `akm remember`, `akm import`, accepted proposals,
16
+ * authored workflows — was immediately flagged `missing-updated` by akm's own
17
+ * `akm lint`, so the tool disagreed with itself about its own output.
18
+ *
19
+ * An existing `updated` is left alone: this fills a gap, it does not
20
+ * re-stamp on every write (which would churn timestamps and manufacture
21
+ * needless diffs in git-backed bundles).
22
+ *
23
+ * Source preservation: when the type already matches and the ONLY change is
24
+ * adding `updated`, the line is spliced into the original block textually —
25
+ * round-tripping through the YAML serializer would drop user-authored
26
+ * comments and normalize formatting just to contribute one field. Only a
27
+ * document whose `type` must actually be corrected takes the re-serialize
28
+ * path (as it always has).
29
+ */
30
+ export function ensureAkmMarkdownType(content, type, now = new Date()) {
31
+ const block = parseFrontmatterBlock(content);
32
+ if (!block) {
33
+ return `---\n${serializeFrontmatter({ type, updated: localDateStamp(now) })}\n---\n${content}`;
34
+ }
35
+ let parsed;
36
+ try {
37
+ parsed = block.frontmatter.trim() ? parseYaml(block.frontmatter) : {};
38
+ }
39
+ catch {
40
+ throw new UsageError("AKM Markdown has malformed YAML frontmatter.", "INVALID_FLAG_VALUE");
41
+ }
42
+ if (parsed === null)
43
+ parsed = {};
44
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
45
+ throw new UsageError("AKM Markdown frontmatter must be a YAML mapping.", "INVALID_FLAG_VALUE");
46
+ }
47
+ const data = parsed;
48
+ const needsUpdated = !("updated" in data);
49
+ if (data.type === type) {
50
+ if (!needsUpdated)
51
+ return content;
52
+ const spliced = spliceFrontmatterLine(content, `updated: ${localDateStamp(now)}`);
53
+ if (spliced !== null)
54
+ return spliced;
55
+ // Unreachable in practice (parseFrontmatterBlock succeeded above), but a
56
+ // re-serialized document beats a non-conformant one.
57
+ }
58
+ const { type: _priorType, ...rest } = data;
59
+ const next = { type, ...rest };
60
+ if (needsUpdated)
61
+ next.updated = localDateStamp(now);
62
+ return `---\n${serializeFrontmatter(next)}\n---\n${block.content}`;
63
+ }
@@ -46,7 +46,7 @@ const workflowSpec = {
46
46
  if (lower.endsWith(ext))
47
47
  return path.join(typeRoot, name);
48
48
  }
49
- // Probe in priority order — `.md` first for back-compat — and fall back
49
+ // Probe in canonical extension priority order and fall back
50
50
  // to the markdown path so error messages keep naming the canonical file.
51
51
  for (const ext of WORKFLOW_EXTENSIONS) {
52
52
  const candidate = path.join(typeRoot, `${name}${ext}`);
@@ -60,7 +60,7 @@ const markdownSpec = {
60
60
  isRelevantFile: (fileName) => path.extname(fileName).toLowerCase() === ".md",
61
61
  toCanonicalName: (typeRoot, filePath) => {
62
62
  const rel = toPosix(path.relative(typeRoot, filePath));
63
- // Strip .md extension from canonical names (agent:code-reviewer, not agent:code-reviewer.md)
63
+ // Strip .md extension from canonical names.
64
64
  return rel.endsWith(".md") ? rel.slice(0, -3) : rel;
65
65
  },
66
66
  toAssetPath: (typeRoot, name) => {
@@ -74,7 +74,7 @@ const scriptSpec = {
74
74
  toCanonicalName: (typeRoot, filePath) => toPosix(path.relative(typeRoot, filePath)),
75
75
  toAssetPath: (typeRoot, name) => path.join(typeRoot, name),
76
76
  };
77
- const PLACEMENT_SPECS = {
77
+ const BUILTIN_PLACEMENT_SPECS = {
78
78
  skill: {
79
79
  stashDir: "skills",
80
80
  isRelevantFile: (fileName) => fileName === "SKILL.md",
@@ -89,6 +89,18 @@ const PLACEMENT_SPECS = {
89
89
  command: { stashDir: "commands", ...markdownSpec },
90
90
  agent: { stashDir: "agents", ...markdownSpec },
91
91
  knowledge: { stashDir: "knowledge", ...markdownSpec },
92
+ // R-045 / Q-18 second half (owner ruling 11, EXECUTE NOW) — `instruction` as
93
+ // a real stash-resident type, mirroring `knowledge`'s plain markdown spec.
94
+ // This is distinct from the ADAPTER-OWNED instruction docs emitted by
95
+ // format-family adapters (root CLAUDE.md/AGENTS.md via `tool-dir-shared.ts`):
96
+ // those carry `document.ownsPresentation === true` and are routed straight
97
+ // to their adapter's own projection by `rendererForIndexedEntry`
98
+ // (`src/commands/read/show.ts`), which checks `ownsPresentation` BEFORE
99
+ // ever consulting a type/renderer mapping. A stash-resident `instructions/`
100
+ // asset never sets that marker, so it always renders via the `knowledge-md`
101
+ // renderer (`TYPE_PRESENTATION.instruction`, `src/core/type-presentation.ts`)
102
+ // like any other placement type — no collision with the adapter-owned path.
103
+ instruction: { stashDir: "instructions", ...markdownSpec },
92
104
  workflow: { stashDir: "workflows", ...workflowSpec },
93
105
  script: { stashDir: "scripts", ...scriptSpec },
94
106
  memory: { stashDir: "memories", ...markdownSpec },
@@ -154,6 +166,9 @@ const PLACEMENT_SPECS = {
154
166
  // project. A plain markdown spec; see docs/architecture/specs/fact-asset-type.md.
155
167
  fact: { stashDir: "facts", ...markdownSpec },
156
168
  };
169
+ const _placementKeysSubsetOfKnownTypes = true;
170
+ void _placementKeysSubsetOfKnownTypes;
171
+ const PLACEMENT_SPECS = { ...BUILTIN_PLACEMENT_SPECS };
157
172
  /** Live placement spec for a type, or `undefined` for an unknown type. */
158
173
  export function placementSpecFor(type) {
159
174
  return PLACEMENT_SPECS[type];
@@ -174,9 +189,8 @@ export function stashDirFor(type) {
174
189
  * Reverse of {@link stashDirFor}: the placement type owning a stash subdir, or
175
190
  * `undefined` when no registered type places into it. The type→subdir map is a
176
191
  * bijection over the built-in types (each type has a distinct subdir), so this
177
- * is the well-defined inverse. Used by the Chunk-5 dual-grammar shim to reverse
178
- * a D-R2 qualified conceptId (`<stash-subdir>/<name>`) back to a legacy
179
- * `type:name` predicate so NULL-`item_ref` rows stay findable by new refs.
192
+ * is the well-defined inverse used to project a path-based conceptId onto the
193
+ * asset-type metadata required by native adapters.
180
194
  */
181
195
  export function typeForStashDir(stashDir) {
182
196
  for (const [type, spec] of Object.entries(PLACEMENT_SPECS)) {
@@ -11,16 +11,19 @@ function validateName(name) {
11
11
  throw new UsageError("Null byte in asset name.", "MISSING_REQUIRED_ARGUMENT");
12
12
  if (/^[A-Za-z]:/.test(name))
13
13
  throw new UsageError("Windows drive path in asset name.", "MISSING_REQUIRED_ARGUMENT");
14
- const normalized = path.posix.normalize(name.replace(/\\/g, "/"));
14
+ const slashName = name.replace(/\\/g, "/");
15
+ if (slashName === ".." || slashName.startsWith("../")) {
16
+ throw new UsageError("Path traversal in asset name.", "MISSING_REQUIRED_ARGUMENT");
17
+ }
18
+ if (slashName.split("/").some((seg) => seg === "." || seg === "..")) {
19
+ throw new UsageError("Asset name cannot contain relative path segments.", "MISSING_REQUIRED_ARGUMENT");
20
+ }
21
+ const normalized = path.posix.normalize(slashName);
15
22
  if (path.posix.isAbsolute(normalized))
16
23
  throw new UsageError("Absolute path in asset name.", "MISSING_REQUIRED_ARGUMENT");
17
24
  if (normalized === ".." || normalized.startsWith("../")) {
18
25
  throw new UsageError("Path traversal in asset name.", "MISSING_REQUIRED_ARGUMENT");
19
26
  }
20
- const segments = normalized.split("/");
21
- if (segments.some((seg) => seg === "." || seg === "..")) {
22
- throw new UsageError("Asset name cannot contain relative path segments.", "MISSING_REQUIRED_ARGUMENT");
23
- }
24
27
  }
25
28
  function normalizeName(name) {
26
29
  return path.posix.normalize(name.replace(/\\/g, "/"));
@@ -34,9 +37,8 @@ function normalizeName(name) {
34
37
  const BUNDLE_SLUG_RE = /^[^\s:.#/]+$/;
35
38
  /**
36
39
  * True when `s` is a legal bundle slug (spec §11.1 / D-R5 charset: non-empty,
37
- * no `:`/`.`/`#`/`/` or whitespace). Exported so the dual-grammar input dispatch
38
- * (`resolve-ref.ts`) can classify a `prefix//tail` token by whether its prefix
39
- * is a legal bundle slug — the clean legacy-vs-new-grammar discriminator.
40
+ * no `:`/`.`/`#`/`/` or whitespace). Exported for input boundaries that need
41
+ * to distinguish bundle-qualified refs from source locators.
40
42
  */
41
43
  export function isBundleSlug(s) {
42
44
  return BUNDLE_SLUG_RE.test(s);
@@ -53,7 +55,7 @@ export function isBundleSlug(s) {
53
55
  * boundary punctuation (brackets/parens/quotes/backtick/comma/angle) so a
54
56
  * leading boundary char (e.g. the `[` of a markdown link) is not absorbed into
55
57
  * the slug. The concept segment reuses the same terminator charset as the
56
- * legacy body-ref scan (whitespace/quotes/brackets/comma/nl), and admits `/`,
58
+ * body-ref scan (whitespace/quotes/brackets/comma/nl), and admits `/`,
57
59
  * `.`, and a trailing `#fragment`.
58
60
  */
59
61
  export const BUNDLE_REF_RE = /(?:^|[\s`"'(,[])([^\s:.#/`"'()[\],<>]+\/\/[^\s"'`)\]>,\n]+)/gm;
@@ -0,0 +1,30 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { isScalar, parseDocument } from "yaml";
5
+ /**
6
+ * Report a colon-bearing description only when YAML parsed it as a plain
7
+ * scalar. Quoted and block scalars may span physical lines and remain valid.
8
+ */
9
+ export function checkUnquotedDescriptionColon(frontmatterText) {
10
+ if (!frontmatterText)
11
+ return null;
12
+ const document = parseDocument(frontmatterText);
13
+ const description = document.get("description", true);
14
+ if (document.errors.length === 0 &&
15
+ isScalar(description) &&
16
+ description.type === "PLAIN" &&
17
+ typeof description.value === "string" &&
18
+ description.value.includes(":")) {
19
+ return `description value contains unquoted colon: ${description.value}`;
20
+ }
21
+ // Preserve the existing finding for malformed plain scalars that YAML cannot
22
+ // construct, while letting valid multiline quoted/block scalars pass above.
23
+ if (document.errors.length > 0) {
24
+ const line = frontmatterText.split(/\r?\n/).find((candidate) => candidate.startsWith("description:"));
25
+ const value = line?.slice("description:".length).trim();
26
+ if (value?.includes(":"))
27
+ return `description value contains unquoted colon: ${value}`;
28
+ }
29
+ return null;
30
+ }
@@ -51,15 +51,11 @@ export function parseFrontmatter(raw) {
51
51
  };
52
52
  }
53
53
  /**
54
- * Normalize YAML-parsed values to match expected AKM frontmatter types.
54
+ * Normalize YAML dates to match expected AKM frontmatter types.
55
55
  *
56
- * Two conversions:
57
- * 1. `Date` → YYYY-MM-DD string: the yaml "core" schema parses bare date
56
+ * `Date` → YYYY-MM-DD string: the yaml "core" schema parses bare date
58
57
  * scalars like `2026-06-18` as JS Date instances. AKM frontmatter treats
59
58
  * `updated:` and similar fields as plain strings.
60
- * 2. `null` → `""`: the yaml library parses empty-value keys (`key:` with no
61
- * value) as `null`, but AKM callers historically received `""` from the
62
- * hand-rolled parser. Convert to preserve backward compatibility.
63
59
  */
64
60
  function normalizeYamlValues(value) {
65
61
  if (value instanceof Date) {
@@ -68,11 +64,9 @@ function normalizeYamlValues(value) {
68
64
  const d = String(value.getUTCDate()).padStart(2, "0");
69
65
  return `${y}-${m}-${d}`;
70
66
  }
71
- if (value === null)
72
- return "";
73
67
  if (Array.isArray(value))
74
68
  return value.map(normalizeYamlValues);
75
- if (typeof value === "object") {
69
+ if (value !== null && typeof value === "object") {
76
70
  return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, normalizeYamlValues(v)]));
77
71
  }
78
72
  return value;
@@ -130,6 +124,18 @@ function parseFrontmatterLenient(frontmatter) {
130
124
  export function mutateFrontmatter(filePath, mutator) {
131
125
  const raw = fs.readFileSync(filePath, "utf8");
132
126
  const parsed = parseFrontmatter(raw);
127
+ if (parsed.frontmatter?.trim()) {
128
+ let strict;
129
+ try {
130
+ strict = yamlParse(parsed.frontmatter);
131
+ }
132
+ catch {
133
+ throw new Error(`Cannot mutate malformed YAML frontmatter in ${filePath}.`);
134
+ }
135
+ if (strict === null || typeof strict !== "object" || Array.isArray(strict)) {
136
+ throw new Error(`Cannot mutate non-mapping YAML frontmatter in ${filePath}.`);
137
+ }
138
+ }
133
139
  const nextFrontmatter = mutator(parsed);
134
140
  if (nextFrontmatter === null)
135
141
  return false;
@@ -170,6 +176,28 @@ function countLines(text) {
170
176
  return 0;
171
177
  return text.split(/\r?\n/).length - 1;
172
178
  }
179
+ /**
180
+ * Insert one `key: value` line just before the closing `---` of an existing
181
+ * frontmatter block, leaving every other byte — YAML comments, quoting, key
182
+ * order, line endings — untouched. Returns null when `raw` has no well-formed
183
+ * block, so callers can fall back to a parse-and-serialize path.
184
+ *
185
+ * This is the source-preserving way to ADD a field to user-authored
186
+ * frontmatter: round-tripping the mapping through the YAML serializer drops
187
+ * comments and normalizes formatting, which is unacceptable for a write that
188
+ * only needs to contribute one line. Shared by `ensureAkmMarkdownType`
189
+ * (stamping `updated:` on write) and lint's `--fix` for `missing-updated`.
190
+ */
191
+ export function spliceFrontmatterLine(raw, line) {
192
+ const lines = raw.split(/\r?\n/);
193
+ if (lines[0]?.trim() !== "---")
194
+ return null;
195
+ const closeIdx = lines.findIndex((l, i) => i > 0 && l.trim() === "---");
196
+ if (closeIdx === -1)
197
+ return null;
198
+ lines.splice(closeIdx, 0, line);
199
+ return lines.join("\n");
200
+ }
173
201
  /**
174
202
  * Parse a YAML scalar value (string, boolean, or number).
175
203
  *
@@ -2,6 +2,34 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { parseFrontmatter } from "./frontmatter.js";
5
+ /** Stable GitHub-style selector for a Markdown heading. */
6
+ export function markdownHeadingSlug(heading) {
7
+ return heading
8
+ .trim()
9
+ .toLowerCase()
10
+ .replace(/<[^>]*>/g, "")
11
+ .replace(/[^\p{L}\p{N}\s_-]+/gu, "-")
12
+ .replace(/[\s_]+/g, "-")
13
+ .replace(/-+/g, "-")
14
+ .replace(/^-|-$/g, "");
15
+ }
16
+ export function markdownFragmentSlugs(content) {
17
+ return uniqueHeadingSlugs(parseMarkdownToc(content).headings).filter(Boolean);
18
+ }
19
+ function uniqueHeadingSlugs(headings) {
20
+ const used = new Set();
21
+ return headings.map((heading) => {
22
+ const base = markdownHeadingSlug(heading.text);
23
+ if (!base)
24
+ return "";
25
+ let slug = base;
26
+ let suffix = 0;
27
+ while (used.has(slug))
28
+ slug = `${base}-${++suffix}`;
29
+ used.add(slug);
30
+ return slug;
31
+ });
32
+ }
5
33
  // ── Parsing ─────────────────────────────────────────────────────────────────
6
34
  export function parseMarkdownToc(content) {
7
35
  const lines = content.split(/\r?\n/);
@@ -31,61 +59,22 @@ export function parseMarkdownToc(content) {
31
59
  // ── Extraction ──────────────────────────────────────────────────────────────
32
60
  export function extractSection(content, heading) {
33
61
  const lines = content.split(/\r?\n/);
34
- const target = heading.toLowerCase();
35
- let startIdx = -1;
36
- let startLevel = 0;
37
- for (let i = 0; i < lines.length; i++) {
38
- const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
39
- if (!match)
40
- continue;
41
- const text = match[2].replace(/\s+#+\s*$/, "").trim();
42
- if (text.toLowerCase() === target && startIdx === -1) {
43
- startIdx = i;
44
- startLevel = match[1].length;
45
- }
46
- else if (startIdx !== -1 && match[1].length <= startLevel) {
47
- return {
48
- content: lines.slice(startIdx, i).join("\n"),
49
- startLine: startIdx + 1,
50
- endLine: i,
51
- };
52
- }
53
- }
54
- if (startIdx === -1)
62
+ const headings = parseMarkdownToc(content).headings;
63
+ const fragment = heading.trim();
64
+ const slugIndex = uniqueHeadingSlugs(headings).indexOf(fragment);
65
+ const exact = slugIndex < 0 ? headings.find((candidate) => candidate.text.toLowerCase() === fragment.toLowerCase()) : undefined;
66
+ const selected = slugIndex >= 0 ? headings[slugIndex] : exact;
67
+ if (!selected)
55
68
  return null;
69
+ const next = headings.find((candidate) => candidate.line > selected.line && candidate.level <= selected.level);
70
+ const startIdx = selected.line - 1;
71
+ const endIdx = next ? next.line - 1 : lines.length;
56
72
  return {
57
- content: lines.slice(startIdx).join("\n"),
58
- startLine: startIdx + 1,
59
- endLine: lines.length,
73
+ content: lines.slice(startIdx, endIdx).join("\n"),
74
+ startLine: selected.line,
75
+ endLine: endIdx,
60
76
  };
61
77
  }
62
- export function extractLineRange(content, start, end) {
63
- const lines = content.split(/\r?\n/);
64
- if (end < start)
65
- return "";
66
- const s = Math.max(1, Math.min(start, lines.length));
67
- const e = Math.min(end, lines.length);
68
- return lines.slice(s - 1, e).join("\n");
69
- }
70
- export function extractFrontmatterOnly(content) {
71
- const parsed = parseFrontmatter(content);
72
- return parsed.frontmatter;
73
- }
74
- // ── Formatting ──────────────────────────────────────────────────────────────
75
- export function formatToc(toc) {
76
- if (toc.headings.length === 0) {
77
- return `(no headings found — ${toc.totalLines} lines total)`;
78
- }
79
- const lineWidth = String(toc.totalLines).length;
80
- const parts = toc.headings.map((h) => {
81
- const lineNum = `L${String(h.line).padStart(lineWidth)}`;
82
- const indent = " ".repeat(h.level - 1);
83
- const prefix = "#".repeat(h.level);
84
- return `${lineNum} ${indent}${prefix} ${h.text}`;
85
- });
86
- parts.push(`\n${toc.totalLines} lines total`);
87
- return parts.join("\n");
88
- }
89
78
  // ── Fence stripping ──────────────────────────────────────────────────────────
90
79
  /**
91
80
  * Best-effort fence stripping. Strips `<think>` reasoning blocks emitted by