akm-cli 0.9.16 → 0.9.17-alpha.10

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 (403) hide show
  1. package/CHANGELOG.md +2101 -0
  2. package/STABILITY.md +11 -10
  3. package/dist/akm +124 -193
  4. package/dist/akm-migrate +38 -19
  5. package/dist/assets/hints/cli-hints-full.md +6 -7
  6. package/dist/assets/improve-strategies/catchup.json +0 -3
  7. package/dist/assets/improve-strategies/consolidate.json +0 -1
  8. package/dist/assets/improve-strategies/default.json +1 -2
  9. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  10. package/dist/assets/improve-strategies/quick.json +1 -2
  11. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  12. package/dist/assets/improve-strategies/thorough.json +0 -3
  13. package/dist/assets/prompts/consolidate-pair.md +20 -0
  14. package/dist/assets/prompts/consolidate-system.md +4 -11
  15. package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
  17. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  18. package/dist/assets/templates/html/health.html +3 -5
  19. package/dist/cli/retired-commands.js +1 -1
  20. package/dist/cli/shared.js +6 -2
  21. package/dist/cli/unknown-flags.js +24 -1
  22. package/dist/cli.js +68 -10
  23. package/dist/commands/agent/agent-dispatch.js +1 -1
  24. package/dist/commands/command/command-execution.js +24 -62
  25. package/dist/commands/feedback-cli.js +0 -1
  26. package/dist/commands/health/accept-rate.js +6 -0
  27. package/dist/commands/health/archive-usage.js +92 -0
  28. package/dist/commands/health/checks.js +83 -74
  29. package/dist/commands/health/config-skew.js +38 -0
  30. package/dist/commands/health/data-dir-usage.js +25 -13
  31. package/dist/commands/health/egress.js +54 -0
  32. package/dist/commands/health/html-report.js +1 -42
  33. package/dist/commands/health/improve-metrics.js +136 -591
  34. package/dist/commands/health/md-report.js +1 -6
  35. package/dist/commands/health/plugin-staleness.js +53 -3
  36. package/dist/commands/health/renderers.js +12 -4
  37. package/dist/commands/health/report-view-model.js +14 -120
  38. package/dist/commands/health/types-improve.js +4 -19
  39. package/dist/commands/health/windows.js +64 -74
  40. package/dist/commands/health.js +145 -143
  41. package/dist/commands/improve/consolidate/chunking.js +26 -117
  42. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  43. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  44. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  45. package/dist/commands/improve/consolidate.js +589 -1127
  46. package/dist/commands/improve/content-hash.js +16 -24
  47. package/dist/commands/improve/distill/content-repair.js +18 -100
  48. package/dist/commands/improve/distill-guards.js +20 -81
  49. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  50. package/dist/commands/improve/distill.js +608 -1041
  51. package/dist/commands/improve/eligibility.js +126 -390
  52. package/dist/commands/improve/execution.js +8 -10
  53. package/dist/commands/improve/extract-prompt.js +1 -2
  54. package/dist/commands/improve/extract.js +487 -1046
  55. package/dist/commands/improve/feedback-valence.js +0 -25
  56. package/dist/commands/improve/improve-cli.js +75 -169
  57. package/dist/commands/improve/improve-result-file.js +10 -66
  58. package/dist/commands/improve/improve-strategies.js +52 -4
  59. package/dist/commands/improve/improve-usage-report.js +18 -64
  60. package/dist/commands/improve/improve.js +480 -1074
  61. package/dist/commands/improve/ledger.js +119 -0
  62. package/dist/commands/improve/locks.js +2 -8
  63. package/dist/commands/improve/loop-stages.js +415 -1073
  64. package/dist/commands/improve/memory/derived-ref.js +12 -77
  65. package/dist/commands/improve/memory/memory-belief.js +16 -118
  66. package/dist/commands/improve/memory/memory-improve.js +266 -14
  67. package/dist/commands/improve/outcome-loop.js +28 -156
  68. package/dist/commands/improve/planner.js +5 -15
  69. package/dist/commands/improve/preparation.js +779 -2319
  70. package/dist/commands/improve/proactive-maintenance.js +34 -101
  71. package/dist/commands/improve/reflect-noise.js +104 -280
  72. package/dist/commands/improve/reflect.js +642 -1353
  73. package/dist/commands/improve/retrieval-gate.js +127 -0
  74. package/dist/commands/improve/retrieval-scope.js +92 -0
  75. package/dist/commands/improve/salience.js +41 -240
  76. package/dist/commands/improve/session-asset.js +19 -100
  77. package/dist/commands/improve/stage.js +322 -0
  78. package/dist/commands/lint/base-linter.js +37 -15
  79. package/dist/commands/proposal/drain.js +261 -578
  80. package/dist/commands/proposal/proposal-cli.js +19 -20
  81. package/dist/commands/proposal/proposal-types.js +31 -24
  82. package/dist/commands/proposal/proposal.js +38 -8
  83. package/dist/commands/proposal/propose.js +134 -160
  84. package/dist/commands/proposal/repository.js +1097 -1394
  85. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  86. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  87. package/dist/commands/proposal/validators/proposals.js +22 -89
  88. package/dist/commands/read/curate.js +105 -462
  89. package/dist/commands/read/knowledge.js +3 -2
  90. package/dist/commands/read/search-cli.js +16 -33
  91. package/dist/commands/read/search.js +17 -23
  92. package/dist/commands/read/show.js +57 -108
  93. package/dist/commands/sources/bundle-cli.js +25 -2
  94. package/dist/commands/sources/bundle-config-ops.js +4 -0
  95. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  96. package/dist/commands/sources/info.js +127 -29
  97. package/dist/commands/sources/installed-stashes.js +197 -746
  98. package/dist/commands/sources/schema-repair.js +98 -129
  99. package/dist/commands/sources/source-add.js +62 -12
  100. package/dist/commands/sources/source-manage.js +9 -2
  101. package/dist/commands/sources/stash-cli.js +24 -4
  102. package/dist/commands/tasks/explain.js +10 -13
  103. package/dist/commands/tasks/tasks-cli.js +12 -13
  104. package/dist/commands/tasks/tasks.js +350 -936
  105. package/dist/commands/tasks/validate.js +26 -24
  106. package/dist/commands/workflow/plan.js +22 -29
  107. package/dist/commands/workflow-cli.js +4 -4
  108. package/dist/core/adapter/adapters/akm-adapter.js +2 -1
  109. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  110. package/dist/core/adapter/adapters/akm-metadata.js +42 -12
  111. package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
  112. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  113. package/dist/core/adapter/execution-source.js +17 -29
  114. package/dist/core/asset/asset-placement.js +4 -13
  115. package/dist/core/asset/frontmatter.js +106 -1
  116. package/dist/core/asset/resolve-ref.js +1 -1
  117. package/dist/core/bundle-id.js +42 -5
  118. package/dist/core/bundle-rename.js +285 -0
  119. package/dist/core/config/config-io.js +1 -2
  120. package/dist/core/config/config-schema.js +9 -34
  121. package/dist/core/config/config-walker.js +1 -1
  122. package/dist/core/config/config.js +184 -111
  123. package/dist/core/config/engine-semantics.js +0 -2
  124. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  125. package/dist/core/config/schema/embedding.js +20 -5
  126. package/dist/core/config/schema/engines.js +5 -0
  127. package/dist/core/config/schema/execution.js +1 -1
  128. package/dist/core/config/schema/experimental.js +1 -1
  129. package/dist/core/config/schema/improve-processes.js +54 -125
  130. package/dist/core/config/schema/improve.js +4 -42
  131. package/dist/core/config/schema/index-config.js +9 -48
  132. package/dist/core/config/schema/scheduler.js +12 -12
  133. package/dist/core/config/schema/search.js +6 -22
  134. package/dist/core/env-secret-ref.js +0 -1
  135. package/dist/core/errors.js +8 -9
  136. package/dist/core/file-change.js +13 -5
  137. package/dist/core/file-lock.js +76 -173
  138. package/dist/core/improve-result.js +35 -7
  139. package/dist/core/improve-types.js +0 -1
  140. package/dist/core/logs-db.js +2 -2
  141. package/dist/core/loopback.js +7 -12
  142. package/dist/core/non-task-input.js +20 -0
  143. package/dist/core/parse.js +13 -16
  144. package/dist/core/paths.js +0 -24
  145. package/dist/core/redaction.js +109 -2
  146. package/dist/core/run-lock.js +2 -5
  147. package/dist/core/spawn-env.js +1 -1
  148. package/dist/core/state/migrations.js +123 -61
  149. package/dist/core/state-db-scope.js +2 -4
  150. package/dist/core/state-db.js +126 -692
  151. package/dist/core/time.js +0 -20
  152. package/dist/core/type-presentation.js +1 -9
  153. package/dist/core/write-source.js +294 -1005
  154. package/dist/execution/input-contract.js +1 -1
  155. package/dist/execution/resolved-request.js +135 -689
  156. package/dist/execution/source.js +63 -257
  157. package/dist/execution/target-ref.js +1 -1
  158. package/dist/indexer/bundle-identity-guard.js +2 -2
  159. package/dist/indexer/db/llm-cache.js +2 -2
  160. package/dist/indexer/ensure-index.js +77 -73
  161. package/dist/indexer/index-rebuild-lock.js +3 -11
  162. package/dist/indexer/index-writer-lock.js +8 -17
  163. package/dist/indexer/index-written-assets.js +141 -154
  164. package/dist/indexer/indexer.js +400 -1124
  165. package/dist/indexer/links/declared-links.js +90 -0
  166. package/dist/indexer/materialize-embeddings.js +60 -397
  167. package/dist/indexer/passes/memory-inference.js +96 -90
  168. package/dist/indexer/passes/metadata.js +132 -219
  169. package/dist/indexer/read-preflight.js +0 -7
  170. package/dist/indexer/scan/doc-to-entry.js +2 -3
  171. package/dist/indexer/scan/drain-dir.js +1 -1
  172. package/dist/indexer/search/db-search.js +190 -590
  173. package/dist/indexer/search/fts-query.js +30 -41
  174. package/dist/indexer/search/ranking.js +28 -154
  175. package/dist/indexer/search/search-attribution.js +12 -32
  176. package/dist/indexer/search/search-fields.js +11 -15
  177. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  178. package/dist/indexer/search/search-source.js +1 -4
  179. package/dist/indexer/usage/usage-events.js +36 -7
  180. package/dist/indexer/walk/walker.js +3 -4
  181. package/dist/integrations/agent/engine-fallback.js +23 -40
  182. package/dist/integrations/agent/engine-resolution.js +93 -183
  183. package/dist/integrations/agent/execution.js +507 -0
  184. package/dist/integrations/agent/model-map.js +28 -156
  185. package/dist/integrations/agent/request-lowering.js +66 -141
  186. package/dist/integrations/agent/runner-dispatch.js +143 -321
  187. package/dist/integrations/agent/runner.js +54 -14
  188. package/dist/integrations/lockfile.js +53 -101
  189. package/dist/llm/client.js +18 -6
  190. package/dist/llm/embedders/deterministic.js +2 -3
  191. package/dist/llm/embedders/profile.js +71 -0
  192. package/dist/llm/embedders/remote.js +11 -17
  193. package/dist/llm/feature-gate.js +0 -8
  194. package/dist/llm/index-passes.js +3 -5
  195. package/dist/llm/memory-infer.js +1 -2
  196. package/dist/llm/structured-call.js +5 -24
  197. package/dist/output/generic-render.js +23 -11
  198. package/dist/output/html-render.js +13 -10
  199. package/dist/output/render-registry.js +3 -32
  200. package/dist/output/shapes/helpers.js +25 -38
  201. package/dist/output/shapes/passthrough.js +1 -9
  202. package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
  203. package/dist/output/text/command-format.js +69 -31
  204. package/dist/output/text/helpers.js +1 -1
  205. package/dist/output/text/migrate.js +5 -14
  206. package/dist/output/text/proposal-format.js +48 -3
  207. package/dist/output/text/show-format.js +13 -17
  208. package/dist/output/text/workflow-format.js +0 -32
  209. package/dist/output/text.js +2 -0
  210. package/dist/registry/factory.js +4 -19
  211. package/dist/registry/network.js +66 -220
  212. package/dist/registry/providers/index.js +0 -2
  213. package/dist/registry/providers/skills-sh.js +3 -14
  214. package/dist/registry/providers/static-index.js +24 -26
  215. package/dist/registry/resolve.js +55 -131
  216. package/dist/scripts/akm-migrate-node.js +42948 -92369
  217. package/dist/scripts/akm-migrate.js +42935 -92354
  218. package/dist/setup/registry-stash-loader.js +4 -13
  219. package/dist/setup/semantic-assets.js +3 -44
  220. package/dist/setup/setup.js +1 -1
  221. package/dist/setup/steps/connection.js +5 -6
  222. package/dist/setup/steps/platforms.js +2 -2
  223. package/dist/setup/steps/tasks.js +25 -15
  224. package/dist/sources/provider-factory.js +17 -18
  225. package/dist/sources/providers/filesystem.js +2 -3
  226. package/dist/sources/providers/git-install.js +7 -1
  227. package/dist/sources/providers/git-provider.js +0 -3
  228. package/dist/sources/providers/git-stash.js +83 -21
  229. package/dist/sources/providers/npm.js +2 -4
  230. package/dist/sources/providers/provider-utils.js +5 -10
  231. package/dist/sources/providers/website.js +0 -2
  232. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  233. package/dist/sources/website-url.js +2 -2
  234. package/dist/storage/database.js +9 -35
  235. package/dist/storage/repositories/improve-ledger-repository.js +209 -0
  236. package/dist/storage/repositories/index-connection.js +39 -72
  237. package/dist/storage/repositories/index-entries-repository.js +131 -129
  238. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  239. package/dist/storage/repositories/index-entry-schema.js +101 -268
  240. package/dist/storage/repositories/index-fts-repository.js +86 -256
  241. package/dist/storage/repositories/index-links-repository.js +143 -0
  242. package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
  243. package/dist/storage/repositories/index-meta-repository.js +6 -4
  244. package/dist/storage/repositories/index-schema.js +257 -325
  245. package/dist/storage/repositories/index-utility-repository.js +8 -29
  246. package/dist/storage/repositories/index-vec-repository.js +133 -414
  247. package/dist/storage/repositories/outcome-repository.js +2 -1
  248. package/dist/storage/repositories/proposals-repository.js +104 -1
  249. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  250. package/dist/storage/repositories/salience-repository.js +1 -19
  251. package/dist/storage/repositories/task-history-repository.js +26 -4
  252. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  253. package/dist/storage/sqlite-migrations.js +136 -0
  254. package/dist/storage/sqlite-pragmas.js +11 -9
  255. package/dist/storage/sqlite-transaction.js +170 -0
  256. package/dist/storage/state-db-integrity.js +130 -0
  257. package/dist/tasks/activation-config.js +134 -62
  258. package/dist/tasks/backends/cron.js +191 -302
  259. package/dist/tasks/backends/exec-utils.js +2 -5
  260. package/dist/tasks/backends/launchd.js +141 -748
  261. package/dist/tasks/backends/schtasks.js +119 -623
  262. package/dist/tasks/prepare/prepare-support.js +5 -15
  263. package/dist/tasks/prepare/prepare.js +0 -2
  264. package/dist/tasks/resolve-akm-bin.js +20 -79
  265. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  266. package/dist/tasks/run/load-task.js +1 -1
  267. package/dist/tasks/scheduler-binding.js +20 -238
  268. package/dist/tasks/scheduler-invocation.js +136 -244
  269. package/dist/tasks/scheduler-lock.js +53 -0
  270. package/dist/tasks/scheduler-sync.js +368 -679
  271. package/dist/tasks/source/parse-task-source.js +55 -9
  272. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  273. package/dist/tasks/source/task-to-v4.js +464 -88
  274. package/dist/workflows/authoring/authoring.js +3 -12
  275. package/dist/workflows/compile.js +211 -0
  276. package/dist/workflows/concurrency-policy.js +13 -74
  277. package/dist/workflows/exec/child-invocation.js +3 -17
  278. package/dist/workflows/exec/child-workflow.js +32 -141
  279. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  280. package/dist/workflows/exec/environment.js +98 -0
  281. package/dist/workflows/exec/exec-unit.js +33 -140
  282. package/dist/workflows/exec/frozen-judge.js +7 -59
  283. package/dist/workflows/exec/native-executor.js +82 -341
  284. package/dist/workflows/exec/param-secrets.js +29 -47
  285. package/dist/workflows/exec/run-workflow.js +154 -387
  286. package/dist/workflows/exec/scheduler.js +9 -36
  287. package/dist/workflows/exec/step-work.js +127 -430
  288. package/dist/workflows/exec/unit-dispatch.js +11 -63
  289. package/dist/workflows/exec/unit-writer.js +8 -52
  290. package/dist/workflows/exec/worktree.js +39 -273
  291. package/dist/workflows/freeze/child-output-references.js +4 -15
  292. package/dist/workflows/freeze/environment.js +99 -92
  293. package/dist/workflows/freeze/freeze.js +172 -0
  294. package/dist/workflows/freeze/step-values.js +19 -21
  295. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  296. package/dist/workflows/freeze/targets/command.js +10 -33
  297. package/dist/workflows/freeze/targets/script.js +5 -12
  298. package/dist/workflows/freeze/targets/shell.js +3 -6
  299. package/dist/workflows/freeze/targets/task.js +25 -80
  300. package/dist/workflows/freeze/task-bindings.js +20 -67
  301. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  302. package/dist/workflows/ir/params.js +6 -51
  303. package/dist/workflows/ir/plan-hash.js +2 -34
  304. package/dist/workflows/parser.js +140 -43
  305. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  306. package/dist/workflows/renderer.js +36 -69
  307. package/dist/workflows/resource-limits.js +12 -120
  308. package/dist/workflows/runtime/agent-identity.js +8 -40
  309. package/dist/workflows/runtime/run-outputs.js +3 -6
  310. package/dist/workflows/runtime/run-plan.js +316 -0
  311. package/dist/workflows/runtime/runs.js +48 -200
  312. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  313. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  314. package/dist/workflows/validate-summary.js +2 -7
  315. package/docs/integration/bundling-akm.md +49 -42
  316. package/docs/migration/README.md +1 -0
  317. package/docs/migration/release-notes/0.9.17.md +43 -0
  318. package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
  319. package/docs/reference/cli.md +232 -135
  320. package/docs/reference/configuration.md +71 -57
  321. package/docs/reference/data-and-telemetry.md +20 -21
  322. package/docs/reference/tasks.md +105 -39
  323. package/docs/reference/workflow-schema.md +14 -18
  324. package/docs/reference/workflows.md +6 -9
  325. package/package.json +1 -1
  326. package/schemas/akm-config.json +115 -738
  327. package/schemas/akm-workflow.json +1 -0
  328. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  329. package/dist/assets/prompts/contradiction-judge.md +0 -33
  330. package/dist/assets/prompts/graph-extract-system.md +0 -1
  331. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  332. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  333. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  334. package/dist/commands/health/advisories.js +0 -150
  335. package/dist/commands/health/metrics.js +0 -329
  336. package/dist/commands/health/surfaces.js +0 -102
  337. package/dist/commands/improve/anti-collapse.js +0 -83
  338. package/dist/commands/improve/collapse-detector.js +0 -432
  339. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  340. package/dist/commands/improve/consolidate/merge.js +0 -149
  341. package/dist/commands/improve/distill/promote-memory.js +0 -291
  342. package/dist/commands/improve/distill/quality-gate.js +0 -337
  343. package/dist/commands/improve/eval-cases.js +0 -52
  344. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  345. package/dist/commands/improve/proposal-envelope.js +0 -31
  346. package/dist/commands/improve/run-context.js +0 -123
  347. package/dist/commands/improve/shared.js +0 -31
  348. package/dist/commands/improve/source-identity.js +0 -28
  349. package/dist/commands/improve/triage.js +0 -96
  350. package/dist/commands/proposal/drain-policies.js +0 -151
  351. package/dist/commands/sources/update-transaction.js +0 -220
  352. package/dist/core/action-contributors.js +0 -28
  353. package/dist/core/config/config-version-shim.js +0 -101
  354. package/dist/core/fs-txn.js +0 -405
  355. package/dist/core/lexical-score.js +0 -25
  356. package/dist/core/maintenance-barrier.js +0 -167
  357. package/dist/execution/executable-identity.js +0 -105
  358. package/dist/execution/guarded-source.js +0 -427
  359. package/dist/indexer/db/graph-db.js +0 -444
  360. package/dist/indexer/graph/graph-boost.js +0 -427
  361. package/dist/indexer/graph/graph-dedup.js +0 -95
  362. package/dist/indexer/graph/graph-extraction.js +0 -1108
  363. package/dist/indexer/search/name-match.js +0 -35
  364. package/dist/indexer/search/ranking-contributors.js +0 -515
  365. package/dist/indexer/search/ranking-types.js +0 -4
  366. package/dist/indexer/walk/project-context.js +0 -192
  367. package/dist/integrations/agent/execution-cascade.js +0 -566
  368. package/dist/integrations/agent/execution-definitions.js +0 -202
  369. package/dist/integrations/agent/execution-lowering.js +0 -841
  370. package/dist/integrations/agent/execution-preparation.js +0 -98
  371. package/dist/integrations/agent/inline-execution.js +0 -74
  372. package/dist/llm/graph-extract.js +0 -728
  373. package/dist/llm/metadata-enhance.js +0 -96
  374. package/dist/registry/create-provider-registry.js +0 -29
  375. package/dist/registry/pinned-request-helper.js +0 -247
  376. package/dist/registry/pinned-transport.js +0 -717
  377. package/dist/sources/providers/index.js +0 -14
  378. package/dist/storage/engines/sqlite-migrations.js +0 -271
  379. package/dist/storage/repositories/canaries-repository.js +0 -107
  380. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  381. package/dist/storage/repositories/registry-cache.js +0 -113
  382. package/dist/tasks/scheduler-sync-preview.js +0 -52
  383. package/dist/tasks/source/task-to-v3.js +0 -507
  384. package/dist/workflows/freeze/resolve-steps.js +0 -86
  385. package/dist/workflows/freeze/source-freeze.js +0 -64
  386. package/dist/workflows/ir/compile.js +0 -321
  387. package/dist/workflows/ir/environment-v4.js +0 -330
  388. package/dist/workflows/ir/freeze-v4.js +0 -153
  389. package/dist/workflows/ir/schema-v4.js +0 -745
  390. package/dist/workflows/ir/schema.js +0 -354
  391. package/dist/workflows/program/schema.js +0 -77
  392. package/dist/workflows/runtime/checkin.js +0 -57
  393. package/dist/workflows/runtime/plan-classifier.js +0 -196
  394. package/dist/workflows/runtime/unit-checkin.js +0 -45
  395. package/dist/workflows/runtime/unit-phases.js +0 -20
  396. package/dist/workflows/schema.js +0 -4
  397. package/dist/workflows/source-ir/compile.js +0 -200
  398. package/dist/workflows/source-ir/program.js +0 -50
  399. package/dist/workflows/source-ir/result.js +0 -26
  400. package/dist/workflows/source-ir/schema.js +0 -786
  401. package/dist/workflows/source-ir/triggers.js +0 -79
  402. package/dist/workflows/source-ir/uses.js +0 -40
  403. package/dist/workflows/validator.js +0 -60
@@ -1,61 +1,22 @@
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
- /**
5
- * The single, keyed-on-ref implementation of "is this a derived memory?" and
6
- * "which parent does it derive from?" (R12).
7
- *
8
- * Two divergent copies previously lived side by side — the CONSUMER
9
- * (`memory-improve.ts`, keyed on the memory name) and the PRODUCER
10
- * (`memory-contradiction-detect.ts`, keyed on the file path). The producer's
11
- * copy was strictly narrower: it ignored `derivedFrom` entirely and matched
12
- * `source:` through a separate parser. That let the producer and consumer
13
- * disagree on a memory's parent — the exact defect plan §6 calls out
14
- * ("producer/consumer cannot disagree").
15
- *
16
- * Both sides now share this one impl, keyed on the memory NAME (the stash-
17
- * relative path without the `.md` extension, e.g. `nested/foo.derived`). The
18
- * producer converts its file path to a name via `toMemoryRef` before calling
19
- * in. Adopting it on the producer side is an INTENTIONAL widening, pinned by
20
- * `tests/commands/improve/derived-ref.test.ts`:
21
- * - `derivedFrom`-keyed families now resolve a parent (and so participate in
22
- * contradiction detection); and
23
- * - `source:` is parsed through `parseRefInput` so current bundle-qualified
24
- * refs resolve consistently.
25
- *
26
- * Resolution order (source → derivedFrom → `.derived` suffix) matches the
27
- * consumer's prior behaviour exactly, so the consumer side is a pure move.
28
- */
4
+ /** The one implementation of "is this a derived memory, and what is its parent?" (R12), keyed on the memory name. */
29
5
  import { conceptIdFromTypeName, parseRefInput } from "../../../core/asset/resolve-ref.js";
30
6
  import { asNonEmptyString } from "../../../core/common.js";
31
7
  import { DERIVED_SUFFIX } from "../../../core/recognition-util.js";
32
8
  /**
33
- * Parse a belief edge (`supersededBy` / `contradictedBy` / `currentBeliefRefs`)
34
- * to the bare memory name the identity channel keys on.
35
- *
36
- * Accepts BOTH spellings, because both reach this function:
37
- * - `memory:<name>` — the internal identity spelling, and what belief edges
38
- * written before 0.9.0 carry on disk;
39
- * - `memories/<name>` / `<bundle>//memories/<name>` — the conceptId grammar
40
- * the current writers emit (`akm remember --supersedes` and `akm import
41
- * --supersedes` go through `writeSupersededEdge` with the write result's
42
- * `ref`, which is fully qualified).
43
- *
44
- * Accepting only the first spelling silently reduced every edge written by the
45
- * current code path to nothing, so a superseded memory read back as active.
46
- * Both normalize to the bare name; the caller re-mints the internal
47
- * `memory:<name>` form via {@link memoryIdentityRef}, so there is still exactly
48
- * one identity spelling downstream.
9
+ * A belief edge (`supersededBy` / `contradictedBy` / `currentBeliefRefs`) as a
10
+ * bare memory name. Both spellings occur: `memory:<name>` (pre-0.9 edges) and
11
+ * `[bundle//]memories/<name>` (what `--supersedes` writes today); accepting only
12
+ * the first once read every superseded memory back as active.
49
13
  */
50
14
  export function parseMemoryName(value) {
51
15
  if (!value)
52
16
  return undefined;
53
17
  const trimmed = value.trim();
54
- const MEMORY_PREFIX = "memory:";
55
- if (trimmed.startsWith(MEMORY_PREFIX)) {
56
- const name = trimmed.slice(MEMORY_PREFIX.length);
57
- return name.length > 0 ? name : undefined;
58
- }
18
+ if (trimmed.startsWith("memory:"))
19
+ return trimmed.slice("memory:".length) || undefined;
59
20
  try {
60
21
  const parsed = parseRefInput(trimmed);
61
22
  return parsed.type === "memory" && parsed.name.length > 0 ? parsed.name : undefined;
@@ -64,10 +25,7 @@ export function parseMemoryName(value) {
64
25
  return undefined;
65
26
  }
66
27
  }
67
- /**
68
- * Parse a current `source:` backref and return its canonical memory conceptId,
69
- * or `undefined` when it is empty, invalid, or not a memory ref.
70
- */
28
+ /** A `source:` backref as a memory conceptId, or `undefined` when it is not a memory ref. */
71
29
  export function parseMemoryRef(value) {
72
30
  if (!value)
73
31
  return undefined;
@@ -79,37 +37,15 @@ export function parseMemoryRef(value) {
79
37
  return undefined;
80
38
  }
81
39
  }
82
- /**
83
- * Format a bare memory name into the belief-edge IDENTITY channel ref
84
- * `memory:<name>` — the ONE implementation of that spelling.
85
- *
86
- * Belief-edge / identity
87
- * channel (`contradictedBy` / `supersededBy` / `currentBeliefRefs`, and a derived
88
- * memory's own `record.ref`) uses `memory:<name>` and is compared against a
89
- * derived memory's identity ref. This channel is separate from `source:` asset
90
- * refs, which use `memories/<name>`. Both `memory-improve.ts` (`refArray`) and
91
- * `memory-contradiction-detect.ts` (`toMemoryRef`) emit through here so the
92
- * identity spelling has one implementation.
93
- */
40
+ /** The belief-edge identity spelling, `memory:<name>` (separate from `source:` asset refs). */
94
41
  export function memoryIdentityRef(name) {
95
42
  return `memory:${name}`;
96
43
  }
97
- /**
98
- * True when the named memory is a derived/inferred child — either it carries
99
- * `inferred: true` in its frontmatter or its name ends with the structural
100
- * `.derived` suffix.
101
- */
44
+ /** Inferred (`inferred: true`) or named with the `.derived` suffix. */
102
45
  export function isDerivedMemory(name, frontmatter) {
103
46
  return frontmatter.inferred === true || name.endsWith(DERIVED_SUFFIX);
104
47
  }
105
- /**
106
- * Resolve the parent (source) memory ref for a derived memory as a canonical
107
- * `memories/<name>` conceptId, or `undefined` when none
108
- * can be determined. Precedence:
109
- * 1. `frontmatter.source` (normalised through {@link parseMemoryRef});
110
- * 2. `frontmatter.derivedFrom` (a bare memory name → `memories/<name>`);
111
- * 3. the `.derived` name suffix, stripped → `memories/<name>`.
112
- */
48
+ /** A derived memory's parent conceptId: from `source`, else `derivedFrom`, else the name minus `.derived`. */
113
49
  export function resolveParentRef(name, frontmatter) {
114
50
  const fromSource = parseMemoryRef(asNonEmptyString(frontmatter.source));
115
51
  if (fromSource)
@@ -117,8 +53,7 @@ export function resolveParentRef(name, frontmatter) {
117
53
  const derivedFrom = asNonEmptyString(frontmatter.derivedFrom);
118
54
  if (derivedFrom)
119
55
  return conceptIdFromTypeName("memory", derivedFrom);
120
- if (name.endsWith(DERIVED_SUFFIX)) {
56
+ if (name.endsWith(DERIVED_SUFFIX))
121
57
  return conceptIdFromTypeName("memory", name.slice(0, -DERIVED_SUFFIX.length));
122
- }
123
58
  return undefined;
124
59
  }
@@ -2,141 +2,39 @@
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
  /**
5
- * Shared memory belief-state machinery (C-3 / #382).
6
- *
7
- * Extracted from `memory-improve.ts` so both `akmConsolidate` and
8
- * `analyzeMemoryCleanup` can emit `MemoryBeliefTransitionLogRecord` entries
9
- * through a unified state-transition model.
10
- *
11
- * # Design
12
- *
13
- * The 4-state belief lifecycle (active → superseded | contradicted → archived)
14
- * was previously encoded only in `memory-improve.ts`. `akmConsolidate` used a
15
- * flat merge/delete/promote model with no belief states, causing the two engines
16
- * to diverge. This module:
17
- *
18
- * 1. Re-exports the belief-state types from `memory-improve.ts` so callers
19
- * can import from one canonical location.
20
- * 2. Provides `writeContradictEdge` — a shared primitive that both engines
21
- * use when an LLM (or heuristic) identifies a contradiction between two
22
- * memories. This is the bridge between `akmConsolidate`'s LLM-detected
23
- * contradictions and `resolveFamilyContradictions`' SCC resolver.
24
- *
25
- * # References
26
- *
27
- * - Zep / Graphiti (arXiv:2501.13956 §3) — unified belief-revision pipeline
28
- * - MemOS (arXiv:2507.03724) — formal archive/merge/transition with shared state model
5
+ * Belief-state edge writer (#382): append a `supersededBy` edge to an asset's
6
+ * frontmatter and demote its `beliefState`, metadata only. Idempotent (a
7
+ * present edge AND demotion is a no-op; an edge without its demotion is
8
+ * repaired) and never weakens a stronger demotion — severity is
9
+ * superseded > contradicted > archived. (alpha.4 removed belief weights from
10
+ * ranking: search no longer reads `beliefState` at all, and `--belief
11
+ * current|historical` is the only reader, opt-in.)
12
+ * The SCC resolver in memory-improve.ts is a state-transition writer (it
13
+ * replaces and clears edges) and deliberately does not use this (#885).
29
14
  */
30
15
  import { mutateFrontmatter } from "../../../core/asset/frontmatter.js";
31
- // ── Shared edge-list reader ───────────────────────────────────────────────────
32
16
  /**
33
- * Read a frontmatter edge value (`supersededBy` / `contradictedBy`) as a
34
- * string list, promoting a scalar string to a one-element list.
35
- *
36
- * Scalar edges are LIVE data: the indexer's `normalizeNonEmptyStringList`
37
- * accepts them and lint deliberately never flags them. An Array.isArray-only
38
- * read treats a scalar as "no existing edges" and silently destroys the edge
39
- * on the next merge — mirror `mergeXrefsIntoContent`'s scalar promotion
40
- * instead.
17
+ * An edge value as a list. A scalar string is live data (the indexer accepts
18
+ * it), so it is promoted rather than dropped on the next write.
41
19
  */
42
20
  function readEdgeList(value) {
43
- if (Array.isArray(value)) {
21
+ if (Array.isArray(value))
44
22
  return value.filter((v) => typeof v === "string" && v.trim().length > 0);
45
- }
46
23
  if (typeof value === "string" && value.trim())
47
24
  return [value.trim()];
48
25
  return [];
49
26
  }
50
- // ── Contradiction edge writer ─────────────────────────────────────────────────
51
- /**
52
- * Write `contradictedBy` and `beliefState: contradicted` edges to a memory
53
- * file's frontmatter (C-3 / #382).
54
- *
55
- * The shared primitive for APPENDING one contradiction edge. Used by
56
- * `memory-contradiction-detect.ts`'s automated contradiction pass.
57
- *
58
- * NOT used by `persistBeliefStateTransition` in `memory-improve.ts`, and that
59
- * is deliberate (#885): the SCC resolver is a state-TRANSITION writer, not an
60
- * edge appender. It replaces `contradictedBy` wholesale from recomputed
61
- * `currentBeliefRefs`, deletes the key when a memory transitions away from
62
- * `contradicted`, and moves a memory to any target state including back to
63
- * active. An append-only primitive that never weakens a demotion cannot
64
- * express those, and routing it through this would break the resolver's
65
- * ability to clear an edge.
66
- *
67
- * Idempotent: if the `contradictedByRef` is already in `contradictedBy` AND
68
- * the file already carries the demotion state, the file is not rewritten. The
69
- * guard is state-aware like its sibling {@link writeSupersededEdge}: an edge
70
- * present WITHOUT the demotion (e.g. a hand-written `contradictedBy:` line,
71
- * or a beliefState lost to a partial edit) is repaired, not skipped — an
72
- * edge-only guard would make such a file a permanent no-op while
73
- * contradiction writers report the contradiction as applied.
74
- *
75
- * Never weakens a stronger demotion: `archived` ranks BELOW `contradicted`
76
- * (see `BELIEF_STATE_SCORE_CEILINGS` in
77
- * src/indexer/search/ranking-contributors.ts), so contradicting an archived
78
- * memory keeps `archived` and only appends the edge.
79
- *
80
- * @param filePath - Absolute path to the memory markdown file.
81
- * @param contradictedByRef - The ref that contradicts this memory.
82
- * @returns `true` when the file was rewritten, `false` when the edge and the
83
- * demotion were already present (the idempotent no-op). Callers count
84
- * edges written from this.
85
- */
86
- export function writeContradictEdge(filePath, contradictedByRef) {
87
- return mutateFrontmatter(filePath, (parsed) => {
88
- const existing = readEdgeList(parsed.data.contradictedBy);
89
- const currentState = parsed.data.beliefState;
90
- const nextState = currentState === "archived" ? currentState : "contradicted";
91
- if (existing.includes(contradictedByRef) && currentState === nextState) {
92
- return null; // Already written — idempotent.
93
- }
94
- const nextContradictedBy = [...new Set([...existing, contradictedByRef])].sort();
95
- return {
96
- ...parsed.data,
97
- contradictedBy: nextContradictedBy,
98
- beliefState: nextState,
99
- };
100
- });
101
- }
102
- // ── Supersession edge writer ─────────────────────────────────────────────────
103
- /**
104
- * Write `supersededBy` and `beliefState: superseded` edges to an asset file's
105
- * frontmatter (SPEC-5, stash-conventions-code-spec.md).
106
- *
107
- * Sibling of {@link writeContradictEdge}: the shared primitive for the
108
- * conventions' corrections pattern — when a new asset supersedes an old one,
109
- * the old asset gets a METADATA-ONLY demotion edit (every other frontmatter
110
- * key and the body are preserved) so the ranker's beliefStateBoost demotes the
111
- * stale incumbent and `--belief current` hides it. Used by
112
- * `akm remember --supersedes` / `akm import --supersedes`.
113
- *
114
- * Idempotent: if `supersededByRef` is already in `supersededBy` and the file
115
- * is already demoted, the file is not rewritten. Multiple corrections
116
- * sorted-set-append their refs.
117
- *
118
- * Never WEAKENS an existing demotion: `contradicted` and `archived` rank
119
- * BELOW `superseded` (severity order deprecated > superseded > contradicted >
120
- * archived — see `BELIEF_STATE_SCORE_CEILINGS` in
121
- * src/indexer/search/ranking-contributors.ts), so superseding an already
122
- * contradicted/archived asset keeps the stronger state and only appends the
123
- * `supersededBy` edge.
124
- *
125
- * @param filePath - Absolute path to the asset markdown file.
126
- * @param supersededByRef - The ref of the correction that supersedes this asset.
127
- */
27
+ /** Mark an asset superseded by a correction (`akm remember|import --supersedes`). */
128
28
  export function writeSupersededEdge(filePath, supersededByRef) {
129
29
  mutateFrontmatter(filePath, (parsed) => {
130
30
  const existing = readEdgeList(parsed.data.supersededBy);
131
31
  const currentState = parsed.data.beliefState;
132
32
  const nextState = currentState === "contradicted" || currentState === "archived" ? currentState : "superseded";
133
- if (existing.includes(supersededByRef) && currentState === nextState) {
134
- return null; // Already written — idempotent.
135
- }
136
- const nextSupersededBy = [...new Set([...existing, supersededByRef])].sort();
33
+ if (existing.includes(supersededByRef) && currentState === nextState)
34
+ return null;
137
35
  return {
138
36
  ...parsed.data,
139
- supersededBy: nextSupersededBy,
37
+ supersededBy: [...new Set([...existing, supersededByRef])].sort(),
140
38
  beliefState: nextState,
141
39
  };
142
40
  });
@@ -7,11 +7,13 @@ import { assembleAsset } from "../../../core/asset/asset-serialize.js";
7
7
  import { mutateFrontmatter, parseFrontmatter } from "../../../core/asset/frontmatter.js";
8
8
  import { MEMORY_ARCHIVE_REL } from "../../../core/asset/memory-archive.js";
9
9
  import { conceptIdFromTypeName } from "../../../core/asset/resolve-ref.js";
10
- import { asNonEmptyString, groupBy, stringArray } from "../../../core/common.js";
10
+ import { asNonEmptyString, groupBy, stringArray, toPosix } from "../../../core/common.js";
11
11
  import { DERIVED_SUFFIX } from "../../../core/recognition-util.js";
12
12
  import { warn } from "../../../core/warn.js";
13
13
  import { recordWrittenPath } from "../../../core/write-provenance.js";
14
14
  import { walkMarkdownFiles } from "../../../indexer/walk/walker.js";
15
+ import { checkGitPathSafety, isGitBackedStash } from "../../../sources/providers/git-stash.js";
16
+ import { contentHash } from "../content-hash.js";
15
17
  import { isDerivedMemory, memoryIdentityRef, parseMemoryName, resolveParentRef } from "./derived-ref.js";
16
18
  export function analyzeMemoryCleanup(stashDir, options = {}) {
17
19
  const records = collectDerivedMemories(stashDir, options.parentRef);
@@ -462,17 +464,80 @@ function stronglyConnectedComponents(refs, edges) {
462
464
  }
463
465
  return { components, componentIndexByRef };
464
466
  }
465
- function archiveCleanupCandidate(stashDir, candidate, filePath) {
467
+ /**
468
+ * The `.derived` twin of a non-derived memory file, if one exists on disk:
469
+ * `<name>.derived.md` beside it, the naming convention
470
+ * `indexer/passes/memory-inference.ts`'s `derivedChildPath` writes.
471
+ * `undefined` for a knowledge or lesson ref (no such twin exists), or for a
472
+ * memory that is already itself `.derived` (it has no further twin).
473
+ *
474
+ * Used by `akm proposal accept` (alpha.9) to take a retired or promoted
475
+ * memory's derived child along when it archives the memory.
476
+ */
477
+ export function derivedTwinPath(filePath, refType) {
478
+ if (refType !== "memory" || filePath.endsWith(`${DERIVED_SUFFIX}.md`))
479
+ return undefined;
480
+ const twin = `${filePath.slice(0, -3)}${DERIVED_SUFFIX}.md`;
481
+ return fs.existsSync(twin) ? twin : undefined;
482
+ }
483
+ /**
484
+ * True for a retire-proposal-caused archive (alpha.9: the consolidate pair
485
+ * pass, or O1's promotion retirement) — distinguished from a memory-cleanup
486
+ * family-prune candidate by carrying a `proposalId`. The two paths differ in
487
+ * how `previousBeliefState` is derived (below) and in which extra tombstone
488
+ * fields apply.
489
+ */
490
+ function isRetireCandidate(candidate) {
491
+ return candidate.proposalId !== undefined;
492
+ }
493
+ /**
494
+ * The tombstone's `previousBeliefState`. Memory cleanup's own family-prune
495
+ * candidates keep their original reason-based inference (unchanged, so
496
+ * existing behavior is not disturbed by this generalization). A
497
+ * retire-proposal candidate has no such reason vocabulary to infer from, so
498
+ * it reads the asset's ACTUAL frontmatter `beliefState` instead — more
499
+ * correct, and available because every retire path already has the file on
500
+ * disk right before the move.
501
+ */
502
+ function resolvePreviousBeliefState(candidate, filePath) {
503
+ if (!isRetireCandidate(candidate))
504
+ return priorBeliefStateForArchive(candidate);
505
+ try {
506
+ return resolveBeliefState(parseFrontmatter(fs.readFileSync(filePath, "utf8")).data);
507
+ }
508
+ catch {
509
+ return "active";
510
+ }
511
+ }
512
+ /**
513
+ * Move `filePath` into the recoverable cleanup archive
514
+ * (`.akm/memory-cleanup/archive/<stamp>-<ref>/`) with a `cleanup.md`
515
+ * tombstone, journaling both ends (`recordWrittenPath`) so a LATER sync
516
+ * commits the move — `akm sync`, or the batched auto-sync an `akm improve`
517
+ * run does at its own end (`docs/architecture/improvement.md`, "Auto-sync").
518
+ * This call does not itself commit anything: a standalone `akm proposal
519
+ * accept` (the only way a retire proposal is ever accepted — triage never
520
+ * auto-accepts one) leaves the move journaled but uncommitted until
521
+ * something later reads that journal, unless the write target's `kind` is
522
+ * `"git"`, in which case the caller's own `commitWriteTargetBoundary` commits
523
+ * (and maybe pushes) immediately as part of the SAME accept.
524
+ *
525
+ * Generalized in alpha.9 to cover any memory, knowledge or lesson file in a
526
+ * writable bundle — not only `.derived` memories — so `akm proposal accept`
527
+ * can archive a consolidate pair-pass `retire` proposal's target, or (O1) an
528
+ * accepted promotion's source memory, through the same one encoding memory
529
+ * cleanup already used (D27: never two coexisting encodings). A
530
+ * retire-proposal candidate (one carrying `proposalId`) additionally stamps
531
+ * `proposalId`, `successorRefs` and `retiredAt` on the tombstone.
532
+ */
533
+ export function archiveCleanupCandidate(stashDir, candidate, filePath) {
466
534
  const archivedAt = new Date().toISOString();
535
+ const previousBeliefState = resolvePreviousBeliefState(candidate, filePath);
467
536
  const originalPath = path.relative(stashDir, filePath).replace(/\\/g, "/");
468
537
  const archiveDir = createArchiveDir(stashDir, candidate.ref, archivedAt);
469
538
  const archivedPath = path.join(archiveDir, originalPath);
470
539
  fs.mkdirSync(path.dirname(archivedPath), { recursive: true });
471
- fs.renameSync(filePath, archivedPath);
472
- // #652: an archive is a delete + a create. BOTH ends are journaled so the
473
- // sync stages the removal of the original alongside the archived copy.
474
- recordWrittenPath(filePath);
475
- recordWrittenPath(archivedPath);
540
+ const retiring = isRetireCandidate(candidate);
476
541
  const archiveRef = path.relative(stashDir, archivedPath).replace(/\\/g, "/");
477
542
  const auditPath = path.join(archiveDir, "cleanup.md");
478
543
  const auditRef = path.relative(stashDir, auditPath).replace(/\\/g, "/");
@@ -481,29 +546,216 @@ function archiveCleanupCandidate(stashDir, candidate, filePath) {
481
546
  kind: "memory-cleanup-archive",
482
547
  archivedAt,
483
548
  beliefState: "archived",
484
- previousBeliefState: priorBeliefStateForArchive(candidate),
549
+ previousBeliefState,
485
550
  ref: candidate.ref,
486
- parentRef: candidate.parentRef,
551
+ ...(candidate.parentRef ? { parentRef: candidate.parentRef } : {}),
487
552
  reason: candidate.reason,
488
553
  ...(candidate.survivorRef ? { survivorRef: candidate.survivorRef } : {}),
489
554
  originalPath,
490
555
  archivedPath: archiveRef,
556
+ ...(retiring ? { proposalId: candidate.proposalId, retiredAt: archivedAt } : {}),
557
+ ...(retiring && candidate.successorRefs && candidate.successorRefs.length > 0
558
+ ? { successorRefs: candidate.successorRefs }
559
+ : {}),
491
560
  }, "Archived derived memory for recoverable cleanup.\n");
561
+ // 4c (third review round): write the tombstone BEFORE moving the file — a
562
+ // crash in between used to leave a file already at archivedPath with no
563
+ // cleanup.md to explain it (unrecoverable: revert refuses on a missing
564
+ // tombstone, and nothing else knows this archive dir exists). Reordered,
565
+ // a crash here instead leaves, at worst, a tombstone describing a move
566
+ // that has not happened yet, with the file still at its original
567
+ // location — the ordinary "nothing archived yet" state every caller
568
+ // already handles.
492
569
  fs.writeFileSync(auditPath, auditAsset, "utf8");
493
570
  recordWrittenPath(auditPath);
571
+ fs.renameSync(filePath, archivedPath);
572
+ // #652: an archive is a delete + a create. BOTH ends are journaled so the
573
+ // sync stages the removal of the original alongside the archived copy.
574
+ recordWrittenPath(filePath);
575
+ recordWrittenPath(archivedPath);
494
576
  return {
495
577
  ref: candidate.ref,
496
- parentRef: candidate.parentRef,
578
+ ...(candidate.parentRef ? { parentRef: candidate.parentRef } : {}),
497
579
  reason: candidate.reason,
498
580
  beliefState: "archived",
499
- previousBeliefState: priorBeliefStateForArchive(candidate),
581
+ previousBeliefState,
500
582
  ...(candidate.survivorRef ? { survivorRef: candidate.survivorRef } : {}),
501
583
  originalPath,
502
584
  archivedPath: archiveRef,
503
585
  auditPath: auditRef,
504
586
  archivedAt,
587
+ ...(retiring ? { proposalId: candidate.proposalId, retiredAt: archivedAt } : {}),
588
+ ...(retiring && candidate.successorRefs && candidate.successorRefs.length > 0
589
+ ? { successorRefs: candidate.successorRefs }
590
+ : {}),
505
591
  };
506
592
  }
593
+ /**
594
+ * How long a retirement's archived bytes stay on disk after `retiredAt`
595
+ * before the purge sweep deletes them. Git history keeps the bytes (D27;
596
+ * plan §5.4 "Purge").
597
+ */
598
+ export const RETIRE_GRACE_DAYS = 30;
599
+ const RETIRE_GRACE_MS = RETIRE_GRACE_DAYS * 24 * 60 * 60 * 1000;
600
+ /** The one file every archive dir keeps forever — never deleted by the purge sweep. */
601
+ const TOMBSTONE_FILENAME = "cleanup.md";
602
+ const EMPTY_ARCHIVE_PURGE_RESULT = { purgedDirs: 0, purgedFiles: 0 };
603
+ /**
604
+ * The purge sweep (0.9.17-alpha.9 plan §5.4, §8 step 8): deterministic, no
605
+ * LLM, run once at improve-run start. Deletes the archived asset bytes —
606
+ * never `cleanup.md` — of every retirement whose tombstone `retiredAt` is
607
+ * more than {@link RETIRE_GRACE_DAYS} old AND whose archived files are all
608
+ * git-tracked and clean at the time of the sweep (see below). Git history
609
+ * keeps the bytes (D27); the tombstone, and ref resolution through it
610
+ * (`core/asset/memory-archive.ts`), are unaffected — only the tombstone's
611
+ * own `originalPath` file(s) are removed.
612
+ *
613
+ * Git-backed bundles only: a bundle with no `.git` of its own has no history
614
+ * to fall back on, so its archive is left untouched (`akm health` reports
615
+ * its size instead — see `health/archive-usage.ts`). Every deleted path is
616
+ * journaled (`recordWrittenPath`) so the end-of-run sync commits the
617
+ * removal, the same way it commits the archive move itself (#652).
618
+ *
619
+ * `.git` presence is necessary but NOT sufficient: `proposal accept` only
620
+ * commits for a `kind: "git"` write target (`core/write-source.ts`
621
+ * `commitWriteTargetBoundary`), and improve's own auto-sync stages only the
622
+ * paths its own run wrote. A filesystem-kind bundle that merely happens to
623
+ * have a `.git` directory (e.g. the owner committing by hand, or an old
624
+ * repo that was never configured as a git source) can carry retirements
625
+ * that were archived but never committed — deleting those would lose the
626
+ * only surviving copy. So every archived file under a directory past grace
627
+ * is checked against `git ls-files` (tracked), `git status --porcelain
628
+ * -uall` (clean), and `git ls-files -v` (verifiable — an assume-unchanged
629
+ * or skip-worktree file hides its own edits from `git status`, so it is
630
+ * never trusted as clean either) — each computed ONCE per sweep, not per
631
+ * directory; a directory with even one untracked, modified, or
632
+ * unverifiable file (tombstone included) is left whole for a later sweep.
633
+ * If any of those three git calls itself fails (a broken submodule can fail
634
+ * `git status` while `git ls-files` still succeeds, or git can be missing
635
+ * from `PATH` entirely), the whole sweep purges nothing and warns once —
636
+ * an empty result from a FAILED check is never treated the same as a
637
+ * verified-empty one.
638
+ *
639
+ * A memory-cleanup family-prune archive (not a retire proposal's) carries no
640
+ * `retiredAt` in its tombstone at all, so it is never a candidate here —
641
+ * this sweep only ever touches retirements, never that older archive class.
642
+ */
643
+ export function purgeGracedArchive(stashDir, now = new Date()) {
644
+ if (!isGitBackedStash(stashDir))
645
+ return EMPTY_ARCHIVE_PURGE_RESULT;
646
+ const archiveRoot = path.join(stashDir, MEMORY_ARCHIVE_REL);
647
+ let entries;
648
+ try {
649
+ entries = fs.readdirSync(archiveRoot, { withFileTypes: true });
650
+ }
651
+ catch {
652
+ return EMPTY_ARCHIVE_PURGE_RESULT; // no archive yet
653
+ }
654
+ const cutoffMs = now.getTime() - RETIRE_GRACE_MS;
655
+ // One git inspection per sweep, not per directory. All three sets are
656
+ // repo-relative POSIX paths, matched below against each archived file's
657
+ // own repo-relative path — a file is safe to delete only if it is in
658
+ // `tracked`, NOT in `dirty`, and NOT in `unverifiable`.
659
+ //
660
+ // Each of the three git calls can itself fail independently — a broken
661
+ // submodule can make `git status` exit nonzero while `git ls-files`
662
+ // succeeds, or vice versa (round-3 review, probes G8/G9). `[]` from a
663
+ // failed call is indistinguishable from a genuinely empty result once it
664
+ // is in a Set, so this checks `ok` FIRST: any failure purges nothing this
665
+ // sweep rather than silently trusting whichever check happened to
666
+ // succeed — a `dirty`/`unverifiable` set that came back empty ONLY
667
+ // because the call failed must never read as "nothing to protect".
668
+ // G10: assume-unchanged / skip-worktree files never show up as dirty even
669
+ // when genuinely modified — `checkGitPathSafety` treats them the same as
670
+ // "not tracked" below, so such a file (and its whole retirement) is left
671
+ // for a later sweep.
672
+ const gitSafety = checkGitPathSafety(stashDir, MEMORY_ARCHIVE_REL);
673
+ if (!gitSafety.ok) {
674
+ warn(`[improve] archive purge: skipped this sweep — could not determine the archive's git state at ${stashDir} ` +
675
+ "(git status/ls-files failed); nothing was purged.");
676
+ return EMPTY_ARCHIVE_PURGE_RESULT;
677
+ }
678
+ let purgedDirs = 0;
679
+ let purgedFiles = 0;
680
+ for (const entry of entries) {
681
+ // `Dirent.isDirectory()` reflects `lstat`, so it is false for a symlink
682
+ // even when the symlink points at a directory — a symlinked
683
+ // `archive/<name>` is skipped here, never followed (N1). Everything
684
+ // below only ever joins path components onto `archiveRoot` through
685
+ // `entry.name`/`readdirSync` results, so a purge can never reach
686
+ // outside `.akm/memory-cleanup/archive/`.
687
+ if (!entry.isDirectory())
688
+ continue;
689
+ const dir = path.join(archiveRoot, entry.name);
690
+ let data;
691
+ try {
692
+ data = parseFrontmatter(fs.readFileSync(path.join(dir, TOMBSTONE_FILENAME), "utf8")).data;
693
+ }
694
+ catch {
695
+ continue; // not a tombstone dir, or unreadable — never guess
696
+ }
697
+ const retiredAt = data.retiredAt;
698
+ if (typeof retiredAt !== "string")
699
+ continue; // family-prune archive, not a retirement — out of scope
700
+ const retiredMs = Date.parse(retiredAt);
701
+ if (!Number.isFinite(retiredMs) || retiredMs >= cutoffMs)
702
+ continue; // "more than" the grace period — exactly at it is not enough
703
+ const allFiles = listFilesRecursive(dir); // tombstone included — the whole entry must be a clean, committed unit
704
+ const isSafeToPurge = allFiles.every((filePath) => gitSafety.isSafe(toPosix(path.relative(stashDir, filePath))));
705
+ if (!isSafeToPurge)
706
+ continue; // untracked, modified, or unverifiable entry — skip the whole directory this sweep (B1, G10)
707
+ let children;
708
+ try {
709
+ children = fs.readdirSync(dir);
710
+ }
711
+ catch {
712
+ continue;
713
+ }
714
+ let purgedAnyInThisDir = false;
715
+ for (const child of children) {
716
+ if (child === TOMBSTONE_FILENAME)
717
+ continue;
718
+ const childPath = path.join(dir, child);
719
+ // The archived original path may be nested (e.g. `memories/sub/foo.md`
720
+ // under this dir) — the journal (like git) tracks FILES, so every leaf
721
+ // under childPath is recorded individually, not the directory itself.
722
+ const filesUnderChild = listFilesRecursive(childPath);
723
+ try {
724
+ fs.rmSync(childPath, { recursive: true, force: true });
725
+ for (const filePath of filesUnderChild)
726
+ recordWrittenPath(filePath);
727
+ purgedFiles += filesUnderChild.length;
728
+ purgedAnyInThisDir = purgedAnyInThisDir || filesUnderChild.length > 0;
729
+ }
730
+ catch {
731
+ // Best-effort: a locked or already-gone entry is skipped, not fatal to the run.
732
+ }
733
+ }
734
+ if (purgedAnyInThisDir)
735
+ purgedDirs++;
736
+ }
737
+ return { purgedDirs, purgedFiles };
738
+ }
739
+ /** Every file under `target` (itself included if it's a file), for individual journaling before a recursive delete. */
740
+ function listFilesRecursive(target) {
741
+ let stat;
742
+ try {
743
+ stat = fs.lstatSync(target);
744
+ }
745
+ catch {
746
+ return [];
747
+ }
748
+ if (!stat.isDirectory())
749
+ return stat.isFile() ? [target] : [];
750
+ let children;
751
+ try {
752
+ children = fs.readdirSync(target);
753
+ }
754
+ catch {
755
+ return [];
756
+ }
757
+ return children.flatMap((child) => listFilesRecursive(path.join(target, child)));
758
+ }
507
759
  function persistBeliefStateTransition(filePath, transition) {
508
760
  mutateFrontmatter(filePath, (parsed) => {
509
761
  const nextFrontmatter = {
@@ -676,7 +928,7 @@ function resolveBeliefState(frontmatter) {
676
928
  // firstExistingRef's byRef map), so they are NORMALIZED to `memory:<name>` here.
677
929
  // On disk they arrive in either spelling — `memory:<name>` from pre-0.9.0
678
930
  // writes, or the `[<bundle>//]memories/<name>` conceptId that
679
- // `writeSupersededEdge`/`writeContradictEdge` persist today — and
931
+ // `writeSupersededEdge` persists today — and
680
932
  // `parseMemoryName` accepts both. Reading only the first spelling silently
681
933
  // dropped every edge the current write path produces.
682
934
  function refArray(value) {
@@ -697,13 +949,13 @@ function refArray(value) {
697
949
  return [...refs].sort();
698
950
  }
699
951
  function buildFingerprint(title, description, tags, searchHints, body) {
700
- return JSON.stringify({
952
+ return contentHash(JSON.stringify({
701
953
  title: normalizeSignal(title),
702
954
  description: normalizeSignal(description),
703
955
  tags: normalizeList(tags),
704
956
  searchHints: normalizeList(searchHints),
705
957
  body: normalizeBody(body),
706
- });
958
+ }));
707
959
  }
708
960
  function normalizeBody(value) {
709
961
  return value