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
@@ -34,7 +34,7 @@
34
34
  * grammar to bridge any more.
35
35
  */
36
36
  import { NotFoundError, UsageError } from "../errors.js";
37
- import { stashDirFor, typeForStashDir } from "./asset-placement.js";
37
+ import { deriveCanonicalAssetNameFromStashRoot, placementSpecFor, stashDirFor, typeForStashDir, } from "./asset-placement.js";
38
38
  import { isBundleSlug, parseBundleRef } from "./asset-ref.js";
39
39
  /**
40
40
  * Resolve a maybe-short input ref to a fully-qualified {@link ResolvedRef}
@@ -82,10 +82,10 @@ function notFound(conceptId, triedForms, ctx) {
82
82
  return new NotFoundError(`No bundle contains concept "${conceptId}"${scope} (tried ${forms}).`, "ASSET_NOT_FOUND");
83
83
  }
84
84
  /**
85
- * D-R2 conceptId derivation from a legacy `type`/`name` pair
85
+ * D-R2 conceptId derivation from an asset `type`/`name` pair
86
86
  * (`stashDirFor(type)/name`; bare name for a foreign type with no placement
87
87
  * stash-subdir). Kept self-contained so {@link displayRef} — a PERMANENT display
88
- * rule does not depend on the transient legacy shims.
88
+ * rule remains independent of input parsing.
89
89
  *
90
90
  * Exported (Chunk-8 WI-8.5c) as the ONE conceptId derivation the improve
91
91
  * correlation sites (`eligibility.ts` candidate refs, `salience.ts`
@@ -97,6 +97,19 @@ export function conceptIdFromTypeName(type, name) {
97
97
  const stashDir = stashDirFor(type);
98
98
  return stashDir !== undefined ? `${stashDir}/${name}` : name;
99
99
  }
100
+ /**
101
+ * User-facing conceptId for a file on disk, derived through the placement
102
+ * spec's canonical-name rule — the ONE way a diagnostic should spell a ref it
103
+ * expects the user to paste into `akm show`. (The dangerous-env-key lint used
104
+ * to hand-build `env:<base>` colon refs the parser rejects; both its emission
105
+ * sites now route through here.) For a type with no placement spec — which no
106
+ * built-in caller passes — falls back to the raw name so the output is still
107
+ * informative rather than empty.
108
+ */
109
+ export function conceptIdForStashFile(type, stashRoot, filePath) {
110
+ const name = deriveCanonicalAssetNameFromStashRoot(type, stashRoot, filePath);
111
+ return name === undefined ? filePath : conceptIdFromTypeName(type, name);
112
+ }
100
113
  /**
101
114
  * Build the USER-FACING / envelope ref string for an indexed item, applying the
102
115
  * Chunk-5 flip F4b output-spelling rule (orchestrator decision; ref-grammar
@@ -116,19 +129,23 @@ export function conceptIdFromTypeName(type, name) {
116
129
  * derived slug bundle id, never the retired `origin//type:name` spelling.
117
130
  */
118
131
  export function displayRef(item, defaultBundleId) {
119
- const conceptId = item.conceptId ?? conceptIdFromTypeName(item.type, item.name);
120
- const { bundleId } = item;
121
- // Default/primary bundle → SHORT conceptId (the flip). `"local"`/`"stash"` are
122
- // the primary-stash origin sentinels (never real bundle slugs they name the
123
- // workspace's own stash, exactly where the pre-0.9.0 output was un-qualified),
124
- // so they display short too.
125
- if (bundleId === undefined || bundleId === defaultBundleId || bundleId === "local" || bundleId === "stash")
132
+ return displayRefForConceptId(item.conceptId ?? conceptIdFromTypeName(item.type, item.name), item.bundleId, defaultBundleId);
133
+ }
134
+ /**
135
+ * The F4b output-spelling flip itself, for a caller that already holds the
136
+ * conceptId (no `type`/`name` derivation needed — e.g. lint findings built
137
+ * from {@link conceptIdForStashFile}). {@link displayRef} delegates here, so
138
+ * the short-default / qualified-secondary rule still has exactly one home.
139
+ */
140
+ export function displayRefForConceptId(conceptId, bundleId, defaultBundleId) {
141
+ // Default/primary bundle → SHORT conceptId (the flip).
142
+ if (bundleId === undefined || bundleId === defaultBundleId)
126
143
  return conceptId;
127
144
  // Non-default bundle → the new fully-qualified `bundle//conceptId` grammar.
128
145
  return `${bundleId}//${conceptId}`;
129
146
  }
130
147
  /**
131
- * Split a qualified conceptId (`<stash-subdir>/<name>`) back to its legacy
148
+ * Split a qualified conceptId (`<stash-subdir>/<name>`) into its asset
132
149
  * `type`/`name`, or `undefined` when the leading segment is not a known stash
133
150
  * subdir (a bare-name conceptId from a foreign type — no legacy predicate
134
151
  * applies). The PERMANENT D-R2 reverse table: the input boundary uses it to map
@@ -144,6 +161,54 @@ export function typeNameFromConceptId(conceptId) {
144
161
  return undefined;
145
162
  return { type, name: conceptId.slice(slash + 1) };
146
163
  }
164
+ /**
165
+ * D11 — the opaque-adapter-conceptId fallback. `typeNameFromConceptId` only
166
+ * ever answers for the PLACEMENT_SPECS stash-resident subset (D-R2); a
167
+ * conceptId whose leading segment is NOT a registered placement stashDir is
168
+ * still perfectly legal DATA per D11 — an OKF item (`tables/customers`), a
169
+ * website page, a wiki pageKind, an adapter `instruction` doc, … — and the
170
+ * ref-consuming commands must accept it rather than treat "not an AKM
171
+ * placement dir" as "malformed ref". This function draws the line: it accepts
172
+ * any well-formed `<segment>/<rest>` conceptId (so the shape is still
173
+ * anchored — a bare no-slash name stays the caller's job to pre-qualify with a
174
+ * default type, the existing env/secret/`akm mv` "bare name" convenience), and
175
+ * REJECTS anything shaped like the retired `type:name` colon grammar (Q-02):
176
+ * a `:` in the leading segment is that grammar smuggled through a conceptId
177
+ * string (e.g. `script:db/migrate/run.sh`, `workflow:release/train`), not a
178
+ * real adapter directory name, so it is refused rather than silently
179
+ * reinterpreted as opaque data.
180
+ *
181
+ * `name` deliberately carries the FULL original conceptId, not just the tail.
182
+ * This is what makes the pair round-trip through the UNCHANGED
183
+ * {@link conceptIdFromTypeName} (`stashDirFor(type)/name`, bare `name` when
184
+ * `type` has no placement stashDir): since an opaque `type` never owns a
185
+ * placement stashDir, `conceptIdFromTypeName(type, name)` falls to its bare-
186
+ * `name` branch and returns `name` verbatim — the original conceptId,
187
+ * unchanged — instead of losing the leading segment. `type` itself carries
188
+ * the leading segment (informational/behavioral use: `.type === "lesson"`
189
+ * checks correctly miss for opaque data) UNLESS that segment happens to
190
+ * collide with a real PLACEMENT_SPECS type key that merely uses a different
191
+ * stashDir spelling (e.g. a foreign top-level dir literally named "skill",
192
+ * singular) — that pathological case would make `stashDirFor` succeed on the
193
+ * "type" and corrupt the round-trip, so it falls back to the full conceptId as
194
+ * `type` too (never a placement key, since a placement key never contains
195
+ * `/`).
196
+ *
197
+ * This intentionally does NOT collapse the KNOWN_TYPES/PLACEMENT_SPECS split:
198
+ * `typeNameFromConceptId` (PLACEMENT_SPECS only) is untouched, and an opaque
199
+ * `type` returned here is never a {@link KnownType} — it is a passthrough
200
+ * label, not a claim that AKM recognizes or owns the concept.
201
+ */
202
+ function opaqueRefParts(conceptId, allowRoot = false) {
203
+ const slash = conceptId.indexOf("/");
204
+ if (slash <= 0)
205
+ return allowRoot && !conceptId.includes(":") ? { type: conceptId, name: conceptId } : undefined;
206
+ const segment = conceptId.slice(0, slash);
207
+ if (segment.includes(":"))
208
+ return undefined; // retired `type:name` grammar, not opaque data (Q-02).
209
+ const type = placementSpecFor(segment) === undefined ? segment : conceptId;
210
+ return { type, name: conceptId };
211
+ }
147
212
  /**
148
213
  * Parse a RAW user / CLI / API ref string in the 0.9.0 `[bundle//]conceptId`
149
214
  * grammar, returning it in today's {@link AssetRef} value-object shape
@@ -154,9 +219,14 @@ export function typeNameFromConceptId(conceptId) {
154
219
  *
155
220
  * Mapping (new grammar → {@link AssetRef}):
156
221
  * - `conceptId` → `type`/`name` via {@link typeNameFromConceptId} (the D-R2
157
- * static stash-subdir table). A conceptId whose leading segment is not a
158
- * known stash subdir has no legacy `type` predicate — the same outcome an
159
- * unknown asset type produces today (a not-found).
222
+ * static stash-subdir table) when the leading segment is a known AKM
223
+ * placement stashDir, else via {@link opaqueRefParts} (D11 — the ref-parser
224
+ * seam accepts opaque adapter conceptIds, e.g. OKF items, website pages,
225
+ * wiki pageKinds, adapter `instruction` docs) when it is merely a well-
226
+ * formed but foreign `<segment>/<rest>` shape. A conceptId that is neither
227
+ * — no slash at all, or a retired colon-grammar shape smuggled through —
228
+ * has no type predicate: the same not-found outcome a genuine typo
229
+ * produces today.
160
230
  * - `bundle` → `origin`. A new-grammar bundle slug is a registryId-shaped
161
231
  * id by construction, so it flows straight into the legacy origin channel
162
232
  * that `resolveSourcesForOrigin` matches on `registryId`. The SHORT form (no
@@ -168,11 +238,11 @@ export function parseRefInput(raw) {
168
238
  if (ref.fragment !== undefined) {
169
239
  throw new UsageError(`Export fragment "#${ref.fragment}" is not accepted here — drop it from "${raw.trim()}".`, "INVALID_FLAG_VALUE");
170
240
  }
171
- const legacy = typeNameFromConceptId(ref.conceptId);
172
- if (legacy === undefined) {
241
+ const parts = typeNameFromConceptId(ref.conceptId) ?? opaqueRefParts(ref.conceptId, ref.bundle !== undefined);
242
+ if (parts === undefined) {
173
243
  throw new NotFoundError(`Unrecognized asset ref "${raw.trim()}": conceptId "${ref.conceptId}" has no known asset-type prefix.`, "ASSET_NOT_FOUND");
174
244
  }
175
- return { type: legacy.type, name: legacy.name, origin: ref.bundle };
245
+ return { type: parts.type, name: parts.name, origin: ref.bundle };
176
246
  }
177
247
  /**
178
248
  * Parse a CLI/API ref that MAY be qualified by a NON-slug origin — a registry
@@ -215,7 +285,8 @@ export function isFullRefInput(raw) {
215
285
  if (!trimmed)
216
286
  return false;
217
287
  try {
218
- return typeNameFromConceptId(parseBundleRef(trimmed).conceptId) !== undefined;
288
+ const parsed = parseBundleRef(trimmed);
289
+ return parsed.bundle !== undefined || typeNameFromConceptId(parsed.conceptId) !== undefined;
219
290
  }
220
291
  catch {
221
292
  return false;
@@ -7,7 +7,7 @@
7
7
  * A stash may carry an optional, human-authored `.meta/` directory at its
8
8
  * root holding orientation docs for the stash as a whole: purpose, key
9
9
  * assets, conventions, maintainer info. Because `.meta/` is a dot-directory,
10
- * the indexer's walker already skips it (see `src/indexer/walker.ts`), so
10
+ * the indexer's walker already skips it (see `src/indexer/walk/walker.ts`), so
11
11
  * these files never pollute the search corpus. They are surfaced on demand
12
12
  * via `akm show [<origin>//]meta[:<name>]`, which direct-reads the file
13
13
  * rather than going through the index.
@@ -0,0 +1,51 @@
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 crypto from "node:crypto";
5
+ import path from "node:path";
6
+ import { isBundleSlug } from "./asset/asset-ref.js";
7
+ /** Deterministic, filesystem-safe bundle slug from a source path. */
8
+ export function slugForPath(sourcePath) {
9
+ const resolved = path.resolve(sourcePath);
10
+ const base = path
11
+ .basename(resolved)
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9]+/g, "-")
14
+ .replace(/^-+|-+$/g, "");
15
+ if (base.length > 0)
16
+ return base;
17
+ return `bundle-${shortHash(resolved)}`;
18
+ }
19
+ /** Derive one batch-unique bundle id. */
20
+ export function deriveBundleId(registryId, sourcePath, usedIds) {
21
+ const preferred = registryId && registryId.length > 0 && isBundleSlug(registryId) ? registryId : slugForPath(sourcePath);
22
+ const id = ensureUniqueId(preferred, sourcePath, usedIds);
23
+ usedIds.add(id);
24
+ return id;
25
+ }
26
+ /** Derive an ordered batch while reserving every explicit configured bundle id. */
27
+ export function deriveBundleIds(sources) {
28
+ const usedIds = new Set();
29
+ const reservedIds = new Set(sources.flatMap((source) => (source.registryId && isBundleSlug(source.registryId) ? [source.registryId] : [])));
30
+ return sources.map((source) => {
31
+ const id = source.registryId && isBundleSlug(source.registryId)
32
+ ? deriveBundleId(source.registryId, source.path, usedIds)
33
+ : deriveBundleId(undefined, source.path, new Set([...usedIds, ...reservedIds]));
34
+ usedIds.add(id);
35
+ return id;
36
+ });
37
+ }
38
+ function ensureUniqueId(preferred, sourcePath, used) {
39
+ if (!used.has(preferred))
40
+ return preferred;
41
+ const suffixed = `${preferred}-${shortHash(path.resolve(sourcePath))}`;
42
+ if (!used.has(suffixed))
43
+ return suffixed;
44
+ let n = 2;
45
+ while (used.has(`${suffixed}-${n}`))
46
+ n++;
47
+ return `${suffixed}-${n}`;
48
+ }
49
+ function shortHash(input) {
50
+ return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
51
+ }
@@ -80,60 +80,105 @@ export function readTextFileWithLimit(filePath, maxBytes, label = "File") {
80
80
  * 2. fsync the parent directory after rename, so the directory entry change
81
81
  * is durable too. Some filesystems (FAT, certain FUSE mounts) don't
82
82
  * support directory fsync; we ignore EINVAL/ENOTSUP so atomic writes
83
- * don't fail on exotic mounts.
83
+ * don't fail on those mounts. Windows does not support opening a
84
+ * directory for fsync, so the directory-sync step is skipped there.
84
85
  */
85
86
  export function writeFileAtomic(target, content, mode) {
86
87
  const tmp = `${target}.tmp.${process.pid}.${crypto.randomBytes(8).toString("hex")}`;
87
- const fd = fs.openSync(tmp, "w", mode ?? 0o600);
88
+ const data = typeof content === "string" ? Buffer.from(content) : content;
89
+ const fileMode = mode ?? 0o600;
90
+ let fd;
91
+ let tempOwned = false;
92
+ let renamed = false;
93
+ let failed = false;
94
+ let failure;
88
95
  try {
89
- fs.writeSync(fd, typeof content === "string" ? Buffer.from(content) : content);
90
- try {
91
- fs.fdatasyncSync(fd);
92
- }
93
- catch {
94
- // Best-effort: some pseudo-filesystems lack fdatasync. Fall through
95
- // to closeSync the rename below still preserves atomicity even if
96
- // the data isn't durable, and the calling code's retry will recover.
96
+ fd = fs.openSync(tmp, "wx", fileMode);
97
+ tempOwned = true;
98
+ if (process.platform !== "win32")
99
+ fs.fchmodSync(fd, fileMode);
100
+ let offset = 0;
101
+ while (offset < data.byteLength) {
102
+ const written = fs.writeSync(fd, data, offset, data.byteLength - offset);
103
+ if (written <= 0)
104
+ throw new Error(`Could not make progress writing atomic temp file ${tmp}.`);
105
+ offset += written;
97
106
  }
107
+ fs.fdatasyncSync(fd);
108
+ fs.closeSync(fd);
109
+ fd = undefined;
110
+ fs.renameSync(tmp, target);
111
+ renamed = true;
112
+ }
113
+ catch (error) {
114
+ failed = true;
115
+ failure = error;
98
116
  }
99
117
  finally {
100
- fs.closeSync(fd);
118
+ if (fd !== undefined) {
119
+ try {
120
+ fs.closeSync(fd);
121
+ }
122
+ catch (error) {
123
+ if (!failed) {
124
+ failed = true;
125
+ failure = error;
126
+ }
127
+ }
128
+ }
129
+ if (tempOwned && !renamed) {
130
+ try {
131
+ fs.unlinkSync(tmp);
132
+ }
133
+ catch (error) {
134
+ if (!failed && !hasErrnoCode(error, "ENOENT")) {
135
+ failed = true;
136
+ failure = error;
137
+ }
138
+ }
139
+ }
101
140
  }
102
- fs.renameSync(tmp, target);
103
- try {
104
- const dirFd = fs.openSync(path.dirname(target), "r");
141
+ if (failed)
142
+ throw failure;
143
+ if (process.platform !== "win32") {
144
+ let dirFd;
105
145
  try {
106
- fs.fsyncSync(dirFd);
146
+ dirFd = fs.openSync(path.dirname(target), "r");
147
+ }
148
+ catch (error) {
149
+ if (hasErrnoCode(error, "EINVAL") || hasErrnoCode(error, "ENOTSUP"))
150
+ return;
151
+ throw error;
152
+ }
153
+ try {
154
+ try {
155
+ fs.fsyncSync(dirFd);
156
+ }
157
+ catch (error) {
158
+ if (!hasErrnoCode(error, "EINVAL") && !hasErrnoCode(error, "ENOTSUP"))
159
+ throw error;
160
+ }
107
161
  }
108
162
  finally {
109
163
  fs.closeSync(dirFd);
110
164
  }
111
165
  }
112
- catch {
113
- // Directory fsync is unsupported on FAT, some FUSE mounts, and Windows
114
- // (where directories cannot be opened for read like POSIX). Silently
115
- // ignore so writeFileAtomic remains portable.
116
- }
117
166
  }
118
167
  /**
119
168
  * Resolve the stash directory using a three-level fallback chain:
120
- * 1. AKM_STASH_DIR environment variable (override for CI/scripts)
121
- * 2. stashDir field in config.json
169
+ * 1. AKM_BUNDLE_DIR environment variable (override for CI/scripts)
170
+ * 2. The configured default bundle path
122
171
  * 3. Platform default (~/akm or ~/Documents/akm on Windows)
123
172
  *
124
- * Pure read: never writes to disk. The legacy `readOnly` option is accepted
125
- * (and ignored) for one release cycle so older callers continue to compile;
126
- * it can be removed in the next minor bump.
127
- *
128
173
  * Throws if no valid stash directory is found.
129
174
  */
130
- export function resolveStashDir(_options, env = process.env) {
175
+ export function resolveStashDir(env = process.env) {
131
176
  // 1. Env var override (for CI, scripts, testing)
132
- const envDir = env.AKM_STASH_DIR?.trim();
177
+ const envDir = env.AKM_BUNDLE_DIR?.trim();
133
178
  if (envDir) {
134
179
  return validateStashDir(envDir);
135
180
  }
136
- // 2. Config file stashDir field
181
+ // 2. Configured default bundle path
137
182
  const configStashDir = readStashDirFromConfig();
138
183
  if (configStashDir)
139
184
  return validateStashDir(configStashDir);
@@ -142,7 +187,7 @@ export function resolveStashDir(_options, env = process.env) {
142
187
  if (isValidDirectory(defaultDir)) {
143
188
  return defaultDir;
144
189
  }
145
- throw new ConfigError(`No stash directory found. Run "akm init" to create one at ${defaultDir}.`, "STASH_DIR_NOT_FOUND");
190
+ throw new ConfigError(`No bundle directory found. Run "akm bundle create" to create one at ${defaultDir}.`, "STASH_DIR_NOT_FOUND");
146
191
  }
147
192
  function validateStashDir(raw) {
148
193
  const stashDir = path.resolve(raw);
@@ -151,10 +196,10 @@ function validateStashDir(raw) {
151
196
  stat = fs.statSync(stashDir);
152
197
  }
153
198
  catch {
154
- throw new ConfigError(`Unable to read stash directory at "${stashDir}".`, "STASH_DIR_UNREADABLE");
199
+ throw new ConfigError(`Unable to read bundle directory at "${stashDir}".`, "STASH_DIR_UNREADABLE");
155
200
  }
156
201
  if (!stat.isDirectory()) {
157
- throw new ConfigError(`Stash path must point to a directory: "${stashDir}".`, "STASH_DIR_NOT_A_DIRECTORY");
202
+ throw new ConfigError(`Bundle path must point to a directory: "${stashDir}".`, "STASH_DIR_NOT_A_DIRECTORY");
158
203
  }
159
204
  return stashDir;
160
205
  }
@@ -172,7 +217,7 @@ function isValidDirectory(dir) {
172
217
  *
173
218
  * Reads ONLY the 0.9.0 `bundles`/`defaultBundle` shape. A config still carrying
174
219
  * the retired `stashDir`/`sources`/`installed` keys (with no usable bundles
175
- * path) is an unmigrated config: this refuses it with the same `akm migrate
220
+ * path) is an unmigrated config: this refuses it with the same `akm-migrate
176
221
  * apply` hint the schema hard-reject uses (config-schema.ts), rather than
177
222
  * silently honouring the retired key — so every `resolveStashDir` caller gets
178
223
  * the coherent migrate posture instead of split-brain success.
@@ -197,14 +242,37 @@ function readStashDirFromConfig() {
197
242
  typeof bundles[defaultBundle] === "object" &&
198
243
  typeof bundles[defaultBundle].path === "string" &&
199
244
  bundles[defaultBundle].path.trim()) {
200
- return bundles[defaultBundle].path.trim();
245
+ const bundle = bundles[defaultBundle];
246
+ const bundlePath = bundle.path.trim();
247
+ if (bundle.components !== undefined) {
248
+ if (typeof bundle.components !== "object" || bundle.components === null) {
249
+ throw new ConfigError("A bundle components map must contain exactly one component.", "INVALID_CONFIG_FILE");
250
+ }
251
+ const components = Object.values(bundle.components);
252
+ if (components.length !== 1) {
253
+ throw new ConfigError("A bundle components map must contain exactly one component.", "INVALID_CONFIG_FILE");
254
+ }
255
+ const component = components[0];
256
+ if (typeof component === "object" && component !== null) {
257
+ const componentConfig = component;
258
+ if (typeof componentConfig.root !== "string")
259
+ return bundlePath;
260
+ const bundleRoot = path.resolve(bundlePath);
261
+ const componentRoot = path.resolve(bundleRoot, componentConfig.root);
262
+ if (!isWithin(componentRoot, bundleRoot)) {
263
+ throw new ConfigError(`Component root "${componentConfig.root}" escapes bundle "${defaultBundle}".`, "INVALID_CONFIG_FILE");
264
+ }
265
+ return componentRoot;
266
+ }
267
+ }
268
+ return bundlePath;
201
269
  }
202
270
  // Retired pre-cutover shape with no usable bundles path: refuse with the
203
271
  // migrate hint (matches the schema's hard-reject, config-schema.ts) instead
204
272
  // of silently resolving the old key.
205
273
  for (const key of ["stashDir", "sources", "installed"]) {
206
274
  if (key in raw && raw[key] !== undefined) {
207
- throw new ConfigError(`${key} is the retired pre-cutover source shape; run \`akm migrate apply\` to convert it to bundles`, "INVALID_CONFIG_FILE");
275
+ throw new ConfigError(`${key} is the retired pre-cutover source shape; run \`akm-migrate apply\` to convert it to bundles`, "INVALID_CONFIG_FILE");
208
276
  }
209
277
  }
210
278
  }
@@ -308,6 +376,8 @@ export async function fetchWithTimeout(url, opts, timeoutMs = 30_000, signal) {
308
376
  }
309
377
  }
310
378
  try {
379
+ if (controller.signal.aborted)
380
+ throw controller.signal.reason ?? new Error(`Request aborted: ${url}`);
311
381
  return await fetch(url, { ...opts, signal: controller.signal });
312
382
  }
313
383
  catch (err) {
@@ -337,11 +407,12 @@ export async function fetchWithRetry(url, init, options) {
337
407
  const timeout = options?.timeout ?? 30_000;
338
408
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
339
409
  try {
340
- const response = await fetchWithTimeout(url, init, timeout);
410
+ const response = await fetchWithTimeout(url, init, timeout, init?.signal ?? undefined);
341
411
  if (attempt < maxRetries && shouldRetry(response.status)) {
342
412
  const retryAfter = parseRetryAfter(response);
343
413
  const delay = retryAfter ?? baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
344
- await new Promise((r) => setTimeout(r, delay));
414
+ await response.body?.cancel().catch(() => undefined);
415
+ await abortableDelay(delay, init?.signal);
345
416
  continue;
346
417
  }
347
418
  return response;
@@ -349,12 +420,41 @@ export async function fetchWithRetry(url, init, options) {
349
420
  catch (err) {
350
421
  if (attempt >= maxRetries)
351
422
  throw err;
423
+ // A caller-supplied abort is terminal: never keep retrying past it.
424
+ if (init?.signal?.aborted)
425
+ throw err;
352
426
  const delay = baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
353
- await new Promise((r) => setTimeout(r, delay));
427
+ await abortableDelay(delay, init?.signal);
354
428
  }
355
429
  }
356
430
  throw new Error("fetchWithRetry: unreachable");
357
431
  }
432
+ /**
433
+ * Sleep, but wake immediately if `signal` aborts.
434
+ *
435
+ * A server-supplied `Retry-After` is honored verbatim and can be arbitrarily
436
+ * large. Sleeping it out with a bare `setTimeout` ignored the caller's abort
437
+ * signal entirely, so a single `429` could park an operation far past any
438
+ * deadline its caller believed it had imposed — the request timeout bounds
439
+ * only the request, never the wait between attempts.
440
+ */
441
+ function abortableDelay(ms, signal) {
442
+ if (!signal)
443
+ return new Promise((resolve) => setTimeout(resolve, ms));
444
+ if (signal.aborted)
445
+ return Promise.reject(signal.reason ?? new Error("Aborted"));
446
+ return new Promise((resolve, reject) => {
447
+ const onAbort = () => {
448
+ clearTimeout(timer);
449
+ reject(signal.reason ?? new Error("Aborted"));
450
+ };
451
+ const timer = setTimeout(() => {
452
+ signal.removeEventListener("abort", onAbort);
453
+ resolve();
454
+ }, ms);
455
+ signal.addEventListener("abort", onAbort, { once: true });
456
+ });
457
+ }
358
458
  function shouldRetry(status) {
359
459
  return status === 429 || status >= 500;
360
460
  }
@@ -560,6 +660,20 @@ export function toErrorMessage(error) {
560
660
  export function todayIso() {
561
661
  return new Date().toISOString().slice(0, 10);
562
662
  }
663
+ /**
664
+ * `YYYY-MM-DD` in LOCAL time — deliberately not {@link todayIso}, which is
665
+ * UTC and can differ near midnight. This is the spelling the `updated:`
666
+ * frontmatter stampers share (`core/asset/akm-markdown.ts` on write,
667
+ * `commands/lint/base-linter.ts` on `--fix`), so the field's format has one
668
+ * definition even though the two stampers pick different instants (now vs
669
+ * file mtime).
670
+ */
671
+ export function localDateStamp(d) {
672
+ const y = d.getFullYear();
673
+ const m = String(d.getMonth() + 1).padStart(2, "0");
674
+ const day = String(d.getDate()).padStart(2, "0");
675
+ return `${y}-${m}-${day}`;
676
+ }
563
677
  /**
564
678
  * Return a filesystem-safe timestamp string derived from the current instant.
565
679
  * Colons and dots are replaced with hyphens so the result is safe as a
@@ -156,7 +156,18 @@ function pruneOldBackups(backupDir) {
156
156
  export function getConfigLockPath() {
157
157
  return path.join(getConfigDir(), "config.json.lck");
158
158
  }
159
- const CONFIG_LOCK_MAX_RETRIES = 10;
159
+ // Total wait budget for a CONTENDED lock: retries × delay. An abandoned lock
160
+ // is not covered by this budget — it is reclaimed by the stale-lock probe
161
+ // below — so this only bounds how long a caller queues behind live writers.
162
+ //
163
+ // 10 × 50ms (500ms) was too thin: N concurrent `akm config set` processes
164
+ // serialize on this lock, so the last one in line waits for all N-1 holders.
165
+ // On a loaded machine (CI runs four test shards in parallel) eight contenders
166
+ // exhausted the budget and one exited with "Timed out waiting for config
167
+ // lock", which is a spurious failure — the lock was healthy and simply busy.
168
+ // 2s gives real concurrent use headroom while still failing promptly against a
169
+ // genuinely wedged (but non-stale) holder.
170
+ const CONFIG_LOCK_MAX_RETRIES = 40;
160
171
  const CONFIG_LOCK_RETRY_DELAY_MS = 50;
161
172
  /**
162
173
  * Block the current thread for `ms` without busy-spinning (H8). Delegates to
@@ -46,6 +46,7 @@ import { z } from "zod";
46
46
  import { BUILTIN_IMPROVE_STRATEGY_NAMES, IMPROVE_PROCESS_ENGINE_CAPABILITIES } from "./engine-semantics.js";
47
47
  import { EmbeddingConnectionConfigSchema } from "./schema/embedding.js";
48
48
  import { EnginesSchema } from "./schema/engines.js";
49
+ import { ExperimentalConfigSchema } from "./schema/experimental.js";
49
50
  import { FeedbackConfigSchema } from "./schema/feedback.js";
50
51
  import { ImproveConfigSchema } from "./schema/improve.js";
51
52
  import { IndexConfigSchema } from "./schema/index-config.js";
@@ -58,6 +59,7 @@ import { WorkflowConfigSchema } from "./schema/workflow.js";
58
59
  // ── Section re-exports (keep every `./config-schema` import path working) ────
59
60
  export { EmbeddingConnectionConfigSchema } from "./schema/embedding.js";
60
61
  export { EngineConfigSchema, EnginesSchema, LlmConnectionConfigSchema, LlmProfileConfigSchema } from "./schema/engines.js";
62
+ export { ExperimentalConfigSchema } from "./schema/experimental.js";
61
63
  export { FEEDBACK_FAILURE_MODES, FeedbackConfigSchema } from "./schema/feedback.js";
62
64
  export { ImproveConfigSchema } from "./schema/improve.js";
63
65
  export { ConsolidateProcessConfigSchema, DistillProcessConfigSchema, ExtractProcessConfigSchema, GraphExtractionProcessConfigSchema, ImproveProcessConfigSchema, ImproveProfileConfigSchema, MemoryInferenceProcessConfigSchema, ProactiveMaintenanceProcessConfigSchema, ReflectProcessConfigSchema, TriageProcessConfigSchema, ValidationProcessConfigSchema, } from "./schema/improve-processes.js";
@@ -65,8 +67,8 @@ export { IndexConfigSchema, IndexPassConfigSchema } from "./schema/index-config.
65
67
  export { OutputConfigSchema } from "./schema/output.js";
66
68
  export { CURRENT_CONFIG_VERSION, LlmInvocationOverridesSchema } from "./schema/primitives.js";
67
69
  export { SearchConfigSchema } from "./schema/search.js";
68
- export { SetupConfigSchema, SetupTaskSchedulesSchema } from "./schema/setup.js";
69
- export { BundleConfigEntrySchema, BundlesConfigSchema, InstalledStashEntrySchema, RegistryConfigEntrySchema, SourceConfigEntrySchema, } from "./schema/sources-bundles.js";
70
+ export { SetupConfigSchema } from "./schema/setup.js";
71
+ export { BundleConfigEntrySchema, BundlesConfigSchema, RegistryConfigEntrySchema, SourceConfigEntrySchema, } from "./schema/sources-bundles.js";
70
72
  export { WorkflowConfigSchema } from "./schema/workflow.js";
71
73
  // ── Defaults ───────────────────────────────────────────────────────────────
72
74
  export const DefaultsSchema = z
@@ -100,7 +102,7 @@ export const AkmConfigShape = {
100
102
  // custom profile's name for the default builder) — unknown keys are inert.
101
103
  // Precedence: profile modelAliases > this table > built-in aliases.
102
104
  modelAliases: GlobalModelAliasesSchema.optional(),
103
- semanticSearchMode: z.enum(["off", "auto"]).default("auto"),
105
+ semanticSearchMode: z.enum(["off", "auto"]).default("off"),
104
106
  embedding: EmbeddingConnectionConfigSchema.optional(),
105
107
  index: IndexConfigSchema.optional(),
106
108
  registries: z.array(RegistryConfigEntrySchema).optional(),
@@ -109,13 +111,10 @@ export const AkmConfigShape = {
109
111
  // trio is hard-rejected at load (see the top-level superRefine). The migrator
110
112
  // ({@link migrateConfigSourcesToBundles}) converts a pre-cutover config to this
111
113
  // shape before validation. `defaultBundle` names the primary bundle (spec
112
- // §11.1 short-ref resolution / D-R4). `SourceConfigEntrySchema` /
113
- // `InstalledStashEntrySchema` remain EXPORTED (the migrator + transitional
114
- // readers consume them) but are no longer top-level config fields.
114
+ // §11.1 short-ref resolution / D-R4).
115
115
  bundles: BundlesConfigSchema.optional(),
116
116
  defaultBundle: nonEmptyString.optional(),
117
117
  output: OutputConfigSchema.optional(),
118
- writable: z.boolean().optional(),
119
118
  defaultWriteTarget: nonEmptyString.optional(),
120
119
  search: SearchConfigSchema.optional(),
121
120
  feedback: FeedbackConfigSchema.optional(),
@@ -123,8 +122,20 @@ export const AkmConfigShape = {
123
122
  improve: ImproveConfigSchema.optional(),
124
123
  workflow: WorkflowConfigSchema.optional(),
125
124
  setup: SetupConfigSchema.optional(),
125
+ // D8 — explicit opt-ins for behaviour outside the stability contract. Every
126
+ // key defaults to OFF; see `src/core/config/experimental.ts` for the readers.
127
+ experimental: ExperimentalConfigSchema.optional(),
126
128
  };
127
129
  export const AkmConfigBaseSchema = z.object(AkmConfigShape).passthrough();
130
+ /**
131
+ * Per-key overrides for the retired `stashDir`/`sources`/`installed` source-
132
+ * shape rejection below. Keys without an entry fall back to the shared
133
+ * "retired pre-cutover source shape" message, which already names the
134
+ * replacement (bundles) and the migration command.
135
+ */
136
+ const RETIRED_SOURCE_SHAPE_KEY_MESSAGES = {
137
+ stashDir: "stashDir is retired in 0.9; the stash path now comes from `bundles`. Run `akm-migrate apply` to convert a pre-0.9 config, or see `akm config path --all` / `akm info`.",
138
+ };
128
139
  export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) => {
129
140
  const raw = config;
130
141
  for (const key of ["profiles", "llm", "agent", "features", "stashes"]) {
@@ -157,10 +168,18 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
157
168
  ctx.addIssue({
158
169
  code: z.ZodIssueCode.custom,
159
170
  path: [key],
160
- message: `${key} is the retired pre-cutover source shape; run \`akm migrate apply\` to convert it to bundles`,
171
+ message: RETIRED_SOURCE_SHAPE_KEY_MESSAGES[key] ??
172
+ `${key} is the retired pre-cutover source shape; run \`akm-migrate apply\` to convert it to bundles`,
161
173
  });
162
174
  }
163
175
  }
176
+ if ("writable" in raw) {
177
+ ctx.addIssue({
178
+ code: z.ZodIssueCode.custom,
179
+ path: ["writable"],
180
+ message: "top-level writable is not supported; configure bundles.<id>.writable instead",
181
+ });
182
+ }
164
183
  // `defaultBundle`, when present, must name a configured bundle.
165
184
  if (config.defaultBundle !== undefined) {
166
185
  if (config.bundles === undefined) {
@@ -215,6 +234,14 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
215
234
  message: "llmEngine must name an LLM engine",
216
235
  });
217
236
  }
237
+ const workflowJudge = config.workflow?.judgeEngine;
238
+ if (workflowJudge && !config.engines?.[workflowJudge]) {
239
+ ctx.addIssue({
240
+ code: z.ZodIssueCode.custom,
241
+ path: ["workflow", "judgeEngine"],
242
+ message: "judgeEngine does not name a configured engine",
243
+ });
244
+ }
218
245
  const defaultStrategy = config.defaults?.improveStrategy;
219
246
  if (defaultStrategy &&
220
247
  !BUILTIN_IMPROVE_STRATEGY_NAMES.includes(defaultStrategy) &&