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,102 +1,38 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { bundleRefToString, isBundleSlug, parseBundleRef } from "../core/asset/asset-ref.js";
5
- import { cloneExecutionJson, cloneExecutionJsonObject, } from "./json.js";
6
- import { assertSnapshotKeys, snapshotStrictRecord } from "./record.js";
4
+ import { cloneExecutionJson, cloneExecutionJsonObject } from "./json.js";
7
5
  export const EXECUTION_SOURCE_SCHEMA_VERSION = 1;
8
6
  /** Current internal adapter identifiers are lowercase kebab-case registry keys. */
9
7
  export const EXECUTION_ADAPTER_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
10
- const CONTROL_OR_LINE_SEPARATOR_PATTERN = /[\p{Cc}\p{Zl}\p{Zp}]/u;
11
- const FORMAT_CHARACTER_PATTERN = /\p{Cf}/u;
12
- const WINDOWS_DRIVE_PREFIX_PATTERN = /^[A-Za-z]:/;
13
- const RESERVED_EXTENSION_OWNERS = new Set(["__proto__", "constructor", "prototype", "tostring"]);
14
- const renderedSourceBrand = Symbol("akm.adapter-rendered-execution-source");
15
- const renderedSourceInstances = new WeakSet();
16
8
  function requireRecord(value, path) {
17
- return snapshotStrictRecord(value, path);
18
- }
19
- function assertOnlyKeys(value, allowed, path) {
20
- assertSnapshotKeys(value, allowed, path);
21
- }
22
- function validateExtensionOwner(owner) {
23
- const normalized = owner.toLowerCase();
24
- if (!EXECUTION_ADAPTER_ID_PATTERN.test(owner) || RESERVED_EXTENSION_OWNERS.has(normalized)) {
25
- throw new TypeError(`extension owner must be a canonical, non-reserved adapter identifier: ${owner}`);
26
- }
27
- }
28
- function frozenNullPrototypeMap(entries) {
29
- const out = Object.create(null);
30
- for (const [key, value] of entries) {
31
- Object.defineProperty(out, key, { value, enumerable: true, configurable: false, writable: false });
9
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
10
+ throw new TypeError(`${path} must be an object`);
32
11
  }
33
- return Object.freeze(out);
34
- }
35
- function cloneExtensionEntry(value, index) {
36
- const path = `extension entry ${index}`;
37
- const cloned = cloneExecutionJson(value, path);
38
- if (!Array.isArray(cloned) || cloned.length !== 2) {
39
- throw new TypeError(`${path} must be a two-element [owner, values] array`);
40
- }
41
- const [owner, values] = cloned;
42
- if (typeof owner !== "string")
43
- throw new TypeError(`${path} owner must be a string`);
44
- if (values === null || Array.isArray(values) || typeof values !== "object") {
45
- throw new TypeError(`${path} values must be a JSON object`);
46
- }
47
- return [owner, values];
12
+ return value;
48
13
  }
49
14
  export function createAdapterExtensions(first, second, ...rest) {
50
- const rawEntries = typeof first === "string"
15
+ const entries = typeof first === "string"
51
16
  ? [[first, second]]
52
17
  : [first, ...(second === undefined ? [] : [second]), ...rest];
53
- const seen = new Set();
54
- const cloned = [];
55
- for (const [index, rawEntry] of rawEntries.entries()) {
56
- const [owner, values] = cloneExtensionEntry(rawEntry, index);
57
- validateExtensionOwner(owner);
58
- if (seen.has(owner))
59
- throw new TypeError(`duplicate extension owner: ${owner}`);
60
- seen.add(owner);
61
- cloned.push([owner, cloneExecutionJsonObject(values, `extensions.${owner}`)]);
62
- }
63
- return frozenNullPrototypeMap(cloned);
18
+ const out = {};
19
+ for (const [owner, values] of entries)
20
+ out[owner] = cloneExecutionJsonObject(values, `extensions.${owner}`);
21
+ return Object.freeze(out);
64
22
  }
65
23
  export function cloneAdapterExtensions(value, path) {
66
- const record = requireRecord(value, path);
67
- const entries = Object.entries(record).map(([owner, fields]) => [owner, cloneExecutionJsonObject(fields, `${path}.${owner}`)]);
68
- const [first, ...rest] = entries;
69
- return first ? createAdapterExtensions(first, ...rest) : frozenNullPrototypeMap([]);
70
- }
71
- function requireCanonicalString(value, path) {
72
- if (typeof value !== "string" ||
73
- value.length === 0 ||
74
- !isWellFormedUnicode(value) ||
75
- value.normalize("NFC") !== value) {
76
- throw new TypeError(`${path} must be a non-empty NFC string`);
77
- }
78
- if (hasUnsafeIdentityCharacter(value)) {
79
- throw new TypeError(`${path} must not contain Unicode control or dangerous format characters`);
24
+ const out = {};
25
+ for (const [owner, fields] of Object.entries(requireRecord(value, path))) {
26
+ out[owner] = cloneExecutionJsonObject(fields, `${path}.${owner}`);
80
27
  }
81
- return value;
28
+ return Object.freeze(out);
82
29
  }
83
- /**
84
- * Validate a selector/layer identifier that may cross the durable execution
85
- * boundary. Keep this shared with the cascade planner so resume cannot admit a
86
- * selector the live planner would reject.
87
- */
30
+ /** A selector or layer identifier that may cross the durable execution boundary. */
88
31
  export function requireStableExecutionSelector(value, path) {
89
- let selector;
90
- try {
91
- selector = requireCanonicalString(value, path);
32
+ if (typeof value !== "string" || value.length === 0 || value.trim() !== value) {
33
+ throw new TypeError(`${path} must be a non-empty stable identifier`);
92
34
  }
93
- catch (cause) {
94
- throw new TypeError(`${path} must be a non-empty stable NFC identifier`, { cause });
95
- }
96
- if (selector.length > 512 || selector.trim() !== selector) {
97
- throw new TypeError(`${path} must be a non-empty stable NFC identifier`);
98
- }
99
- return selector;
35
+ return value;
100
36
  }
101
37
  export function isPortableExecutionAgentSelector(value) {
102
38
  return /^(?:[^/]+\/\/)?agents\/[^/]/.test(value);
@@ -104,118 +40,61 @@ export function isPortableExecutionAgentSelector(value) {
104
40
  export function executionPersonaMatchesSelector(selector, personaRef) {
105
41
  return selector.includes("//") ? selector === personaRef : personaRef.endsWith(`//${selector}`);
106
42
  }
107
- function hasUnsafeIdentityCharacter(value) {
108
- for (const character of value) {
109
- if (CONTROL_OR_LINE_SEPARATOR_PATTERN.test(character))
110
- return true;
111
- // Join controls are meaningful in emoji and several writing systems. All
112
- // other format controls are rejected because they can conceal identity.
113
- if (FORMAT_CHARACTER_PATTERN.test(character) && character !== "\u200C" && character !== "\u200D")
114
- return true;
115
- }
116
- return false;
117
- }
118
- function isWellFormedUnicode(value) {
119
- for (let index = 0; index < value.length; index += 1) {
120
- const code = value.charCodeAt(index);
121
- if (code >= 0xd800 && code <= 0xdbff) {
122
- const next = value.charCodeAt(index + 1);
123
- if (!(next >= 0xdc00 && next <= 0xdfff))
124
- return false;
125
- index += 1;
126
- }
127
- else if (code >= 0xdc00 && code <= 0xdfff) {
128
- return false;
129
- }
130
- }
131
- return true;
132
- }
133
- function validateCanonicalIdentity(input, path) {
134
- assertOnlyKeys(input, ["ref", "bundle", "adapter", "file", "hash"], path);
135
- const ref = requireCanonicalString(input.ref, `${path}.ref`);
136
- const bundle = requireCanonicalString(input.bundle, `${path}.bundle`);
137
- const adapter = requireCanonicalString(input.adapter, `${path}.adapter`);
138
- const file = requireCanonicalString(input.file, `${path}.file`);
139
- const hash = requireCanonicalString(input.hash, `${path}.hash`);
140
- let parsed;
141
- try {
142
- parsed = parseBundleRef(ref);
143
- }
144
- catch (cause) {
145
- throw new TypeError(`${path}.ref is not a canonical bundle ref`, { cause });
146
- }
147
- if (parsed.bundle === undefined ||
148
- parsed.bundle !== bundle ||
149
- parsed.fragment !== undefined ||
150
- !isBundleSlug(bundle) ||
151
- bundleRefToString(parsed) !== ref) {
152
- throw new TypeError(`${path}.ref must round-trip as the same fully-qualified bundle ref without a fragment`);
153
- }
154
- if (!EXECUTION_ADAPTER_ID_PATTERN.test(adapter)) {
155
- throw new TypeError(`${path}.adapter must use the current lowercase kebab-case adapter identifier grammar`);
156
- }
157
- const segments = file.split("/");
158
- if (file.startsWith("/") ||
159
- WINDOWS_DRIVE_PREFIX_PATTERN.test(file) ||
160
- file.includes("\\") ||
161
- segments.some((segment) => !segment || segment === "." || segment === "..")) {
162
- throw new TypeError(`${path}.file must be a normalized relative POSIX path`);
43
+ const IDENTITY_KEYS = ["ref", "bundle", "adapter", "file", "hash"];
44
+ function identityFrom(value, path) {
45
+ const record = requireRecord(value, path);
46
+ const out = {};
47
+ for (const key of IDENTITY_KEYS) {
48
+ const field = record[key];
49
+ if (typeof field !== "string" || field.length === 0)
50
+ throw new TypeError(`${path}.${key} must be a non-empty string`);
51
+ out[key] = field;
163
52
  }
164
- if (!/^[a-f0-9]{64}$/.test(hash))
165
- throw new TypeError(`${path}.hash must be a SHA-256 hex digest`);
166
- return { ref, bundle, adapter, file, hash };
53
+ return Object.freeze(out);
167
54
  }
168
- /** Validate, clone, and freeze an identity produced by an adapter renderer. */
55
+ /** Copy and freeze an identity produced by an adapter renderer. */
169
56
  export function createExecutionSourceIdentity(input) {
170
- return Object.freeze(validateCanonicalIdentity(requireRecord(input, "execution source identity"), "execution source identity"));
57
+ return identityFrom(input, "execution source identity");
171
58
  }
172
- /** Validate and freeze a source identity read back from a frozen request. */
59
+ /** Read a source identity back from a frozen request. */
173
60
  export function decodeExecutionSourceIdentity(value, path = "execution source identity") {
174
- return Object.freeze(validateCanonicalIdentity(requireRecord(value, path), path));
61
+ return identityFrom(value, path);
175
62
  }
63
+ /** Type-check the common frontmatter defaults; unknown keys are ignored. */
176
64
  export function cloneUnresolvedExecutionDefaults(input, path = "execution source defaults") {
177
65
  const record = requireRecord(input, path);
178
- assertOnlyKeys(record, [
179
- "agent",
180
- "engine",
181
- "model",
182
- "inference",
183
- "outputSchema",
184
- "tools",
185
- "timeout",
186
- "workspace",
187
- "environment",
188
- "runtime",
189
- ], path);
190
- const json = { ...cloneExecutionJsonObject(record, path) };
66
+ const present = (key) => Object.hasOwn(record, key) && record[key] !== undefined;
67
+ const out = {};
191
68
  for (const key of ["agent", "engine", "model", "workspace"]) {
192
- const value = json[key];
193
- if (value !== undefined && value !== null && typeof value !== "string") {
69
+ if (!present(key))
70
+ continue;
71
+ const value = record[key];
72
+ if (value !== null && typeof value !== "string")
194
73
  throw new TypeError(`${path}.${key} must be a string or null`);
195
- }
74
+ out[key] = value;
196
75
  }
197
- const timeout = json.timeout;
198
- if (timeout !== undefined && timeout !== null && typeof timeout !== "string" && typeof timeout !== "number") {
199
- throw new TypeError(`${path}.timeout must be a string, number, or null`);
76
+ if (present("timeout")) {
77
+ const timeout = record.timeout;
78
+ if (timeout !== null && typeof timeout !== "string" && typeof timeout !== "number") {
79
+ throw new TypeError(`${path}.timeout must be a string, number, or null`);
80
+ }
81
+ out.timeout = timeout;
200
82
  }
201
83
  for (const key of ["inference", "outputSchema", "runtime"]) {
202
- const value = json[key];
203
- if (value !== undefined && value !== null && (Array.isArray(value) || typeof value !== "object")) {
204
- throw new TypeError(`${path}.${key} must be an object or null`);
205
- }
84
+ if (!present(key))
85
+ continue;
86
+ out[key] = record[key] === null ? null : cloneExecutionJsonObject(record[key], `${path}.${key}`);
206
87
  }
207
- const environment = json.environment;
208
- if (environment !== undefined && environment !== null) {
209
- if (Array.isArray(environment) || typeof environment !== "object") {
210
- throw new TypeError(`${path}.environment must be an object or null`);
211
- }
212
- if (Object.values(environment).some((value) => typeof value !== "string")) {
88
+ if (present("environment")) {
89
+ const environment = record.environment === null ? null : cloneExecutionJsonObject(record.environment, `${path}.environment`);
90
+ if (environment !== null && Object.values(environment).some((value) => typeof value !== "string")) {
213
91
  throw new TypeError(`${path}.environment values must be strings`);
214
92
  }
93
+ out.environment = environment;
215
94
  }
216
- if (Object.hasOwn(json, "tools"))
217
- json.tools = cloneToolSelection(json.tools, `${path}.tools`);
218
- return Object.freeze(json);
95
+ if (present("tools"))
96
+ out.tools = cloneToolSelection(record.tools, `${path}.tools`);
97
+ return Object.freeze(out);
219
98
  }
220
99
  export function cloneToolSelection(value, path = "tools") {
221
100
  if (value === null || typeof value === "string")
@@ -229,92 +108,19 @@ export function cloneToolSelection(value, path = "tools") {
229
108
  return cloneExecutionJsonObject(cloned, path);
230
109
  }
231
110
  export function createAdapterRenderedExecutionSource(input) {
232
- const record = requireRecord(input, "adapter-rendered execution source");
233
- assertOnlyKeys(record, ["kind", "content", "identity", "defaults", "extensions"], "adapter-rendered execution source");
234
- const kind = record.kind;
235
- const content = record.content;
111
+ const { kind, content } = input;
236
112
  if (kind !== "command" && kind !== "persona")
237
113
  throw new TypeError("execution source kind is invalid");
238
114
  if (typeof content !== "string")
239
115
  throw new TypeError("execution source content must be a string");
240
- const defaults = Object.hasOwn(record, "defaults") ? record.defaults : {};
241
- if (defaults === undefined) {
242
- throw new TypeError("execution source defaults must be omitted or be an object");
243
- }
244
- const source = {
116
+ return Object.freeze({
245
117
  schemaVersion: EXECUTION_SOURCE_SCHEMA_VERSION,
246
118
  kind,
247
119
  content,
248
- defaults: cloneUnresolvedExecutionDefaults(defaults),
249
- identity: createExecutionSourceIdentity(record.identity),
250
- };
251
- if (Object.hasOwn(record, "extensions")) {
252
- source.extensions = cloneAdapterExtensions(record.extensions, "execution source extensions");
253
- }
254
- Object.defineProperty(source, renderedSourceBrand, { value: true, enumerable: false });
255
- const frozen = Object.freeze(source);
256
- renderedSourceInstances.add(frozen);
257
- return frozen;
258
- }
259
- function isAdapterRenderedSource(value, kind) {
260
- if (typeof value !== "object" || value === null)
261
- return false;
262
- if (!renderedSourceInstances.has(value))
263
- return false;
264
- if (Object.getPrototypeOf(value) !== Object.prototype || !Object.isFrozen(value))
265
- return false;
266
- if (!Object.hasOwn(value, renderedSourceBrand))
267
- return false;
268
- const required = new Set(["schemaVersion", "kind", "content", "defaults", "identity"]);
269
- const optional = new Set(["extensions"]);
270
- for (const key of Reflect.ownKeys(value)) {
271
- if (key === renderedSourceBrand)
272
- continue;
273
- if (typeof key !== "string" || (!required.delete(key) && !optional.has(key)))
274
- return false;
275
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
276
- if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
277
- return false;
278
- }
279
- if (required.size > 0)
280
- return false;
281
- const brand = Object.getOwnPropertyDescriptor(value, renderedSourceBrand);
282
- const source = value;
283
- if (brand?.value !== true ||
284
- brand.enumerable !== false ||
285
- brand.configurable !== false ||
286
- brand.writable !== false ||
287
- source.schemaVersion !== EXECUTION_SOURCE_SCHEMA_VERSION ||
288
- source.kind !== kind ||
289
- typeof source.content !== "string" ||
290
- typeof source.defaults !== "object" ||
291
- source.defaults === null ||
292
- !Object.isFrozen(source.defaults) ||
293
- typeof source.identity !== "object" ||
294
- source.identity === null ||
295
- Object.getPrototypeOf(source.identity) !== Object.prototype ||
296
- !Object.isFrozen(source.identity)) {
297
- return false;
298
- }
299
- if (Object.hasOwn(source, "extensions")) {
300
- if (typeof source.extensions !== "object" ||
301
- source.extensions === null ||
302
- Object.getPrototypeOf(source.extensions) !== null ||
303
- !Object.isFrozen(source.extensions)) {
304
- return false;
305
- }
306
- }
307
- try {
308
- decodeExecutionSourceIdentity(source.identity);
309
- }
310
- catch {
311
- return false;
312
- }
313
- return true;
314
- }
315
- export function isAdapterRenderedCommandSource(value) {
316
- return isAdapterRenderedSource(value, "command");
317
- }
318
- export function isAdapterRenderedPersonaSource(value) {
319
- return isAdapterRenderedSource(value, "persona");
120
+ defaults: cloneUnresolvedExecutionDefaults(input.defaults ?? {}),
121
+ identity: createExecutionSourceIdentity(input.identity),
122
+ ...(input.extensions
123
+ ? { extensions: cloneAdapterExtensions(input.extensions, "execution source extensions") }
124
+ : {}),
125
+ });
320
126
  }
@@ -23,7 +23,7 @@
23
23
  * Explicit non-goals (binding, spec §4.1): no GitHub locator grammar, no
24
24
  * `akm/command` builtin special case, no resolution, no filesystem access, no
25
25
  * guessing. Callers layer builtin detection on top (see
26
- * `classifyWorkflowSourceUses` in src/workflows/source-ir/uses.ts).
26
+ * `classifyWorkflowStepUses` in src/workflows/source-semantics.ts).
27
27
  */
28
28
  import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
29
29
  import { UsageError } from "../core/errors.js";
@@ -34,7 +34,7 @@ import { rethrowIfTestIsolationError } from "../core/errors.js";
34
34
  import { getDbPath } from "../core/paths.js";
35
35
  import { warn } from "../core/warn.js";
36
36
  import { closeDatabase, openReadonlyExistingDatabase } from "../storage/repositories/index-connection.js";
37
- import { isCanonicalIndexGeneration } from "../storage/repositories/index-entry-schema.js";
37
+ import { hasCurrentEntriesTable } from "../storage/repositories/index-entry-schema.js";
38
38
  let guardSettled = false;
39
39
  /** TEST-ONLY: re-arm the once-per-process guard between cases. */
40
40
  export function resetBundleIdentityGuardForTests() {
@@ -49,7 +49,7 @@ function indexedBundlePaths(dbPath) {
49
49
  db = openReadonlyExistingDatabase(dbPath);
50
50
  if (!db)
51
51
  return undefined;
52
- if (!isCanonicalIndexGeneration(db))
52
+ if (!hasCurrentEntriesTable(db))
53
53
  return undefined;
54
54
  return db
55
55
  .prepare("SELECT DISTINCT bundle_id AS bundleId, file_path AS filePath FROM entries " +
@@ -27,10 +27,10 @@ import { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry, } from "../../s
27
27
  * already hashed the body (e.g. to reuse it elsewhere)
28
28
  * should pass this to avoid the redundant work.
29
29
  * @param cacheVariant - Namespace token for the cache row so different passes'
30
- * rows do not collide (e.g. memory-inference vs graph).
30
+ * rows do not collide (e.g. memory-inference vs metadata-enhance).
31
31
  * @param hooks - Optional event sink for telemetry (see {@link WithLlmCacheHooks}).
32
32
  */
33
- export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant = "", hooks) {
33
+ export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant, hooks) {
34
34
  const bodyHash = precomputedHash ?? computeBodyHash(body);
35
35
  if (!reEnrich) {
36
36
  const cacheHit = bestEffort(() => {
@@ -21,12 +21,15 @@
21
21
  */
22
22
  import fs from "node:fs";
23
23
  import path from "node:path";
24
+ import { hashContent } from "../core/adapter/adapters/shared.js";
24
25
  import { placementSpecList } from "../core/asset/asset-placement.js";
26
+ import { ConfigError } from "../core/errors.js";
25
27
  import { classifyPathAccess } from "../core/path-access.js";
26
28
  import { getDbPath } from "../core/paths.js";
29
+ import { warnVerbose } from "../core/warn.js";
27
30
  import { assertIndexPathReadable, closeDatabase, openExistingDatabase } from "../storage/repositories/index-connection.js";
28
- import { getEntryCount, getIndexedFilePaths } from "../storage/repositories/index-entries-repository.js";
29
- import { isCanonicalIndexGeneration } from "../storage/repositories/index-entry-schema.js";
31
+ import { getEntryCount, getIndexedFileHashes, getIndexedFilePaths, } from "../storage/repositories/index-entries-repository.js";
32
+ import { hasCurrentEntriesTable } from "../storage/repositories/index-entry-schema.js";
30
33
  import { getMeta } from "../storage/repositories/index-meta-repository.js";
31
34
  import { warnOnBundleRenameDrift } from "./bundle-identity-guard.js";
32
35
  function getIndexableFiles(root, spec) {
@@ -77,11 +80,20 @@ function getIndexableFiles(root, spec) {
77
80
  * millisecond-truncated), so the mtime test alone silently misses
78
81
  * additions made within ~a millisecond of the previous build.
79
82
  *
83
+ * A file whose mtime is newer than `builtAt` is NOT automatically stale
84
+ * (R6): `indexWrittenAssets` upserts a fresh `content_hash` without bumping
85
+ * `builtAt`, so a file `ensureIndex` itself just incrementally re-indexed
86
+ * (for example a proposal triage just promoted into `knowledge/`) would
87
+ * otherwise keep tripping this check on every subsequent call, forcing the
88
+ * full rescan the write-path fast path exists to avoid. Only files newer
89
+ * than `builtAt` are hashed here, so this stays cheap — the common case is
90
+ * zero or a handful of such files.
91
+ *
80
92
  * `getIndexableFiles` applies each asset type's own relevance filter, so
81
93
  * non-indexed companion files (e.g. `package.json` next to a knowledge doc) are
82
94
  * never considered and do not produce false "new file" positives.
83
95
  */
84
- function hasNewerIndexableFiles(stashDir, builtAt, indexedPaths) {
96
+ function hasNewerIndexableFiles(stashDir, builtAt, indexedPaths, indexedHashes) {
85
97
  const builtAtMs = builtAt ? new Date(builtAt).getTime() : Number.NaN;
86
98
  const builtAtUsable = Number.isFinite(builtAtMs);
87
99
  for (const spec of placementSpecList()) {
@@ -92,62 +104,42 @@ function hasNewerIndexableFiles(stashDir, builtAt, indexedPaths) {
92
104
  return true;
93
105
  if (!builtAtUsable)
94
106
  return true;
107
+ let mtimeMs;
95
108
  try {
96
- if (fs.statSync(file).mtimeMs > builtAtMs)
97
- return true;
109
+ mtimeMs = fs.statSync(file).mtimeMs;
98
110
  }
99
111
  catch {
100
112
  return true;
101
113
  }
114
+ if (mtimeMs <= builtAtMs)
115
+ continue;
116
+ // Newer than the last full build — only stale if its current content
117
+ // actually differs from what is indexed. No stored hash means the row
118
+ // predates content-hash tracking, so fall back to the conservative
119
+ // mtime-stale answer.
120
+ const indexedHash = indexedHashes.get(file);
121
+ if (indexedHash === undefined)
122
+ return true;
123
+ let currentHash;
124
+ try {
125
+ currentHash = hashContent(fs.readFileSync(file, "utf8"));
126
+ }
127
+ catch {
128
+ return true;
129
+ }
130
+ if (currentHash !== indexedHash)
131
+ return true;
102
132
  }
103
133
  }
104
134
  return false;
105
135
  }
106
136
  /**
107
137
  * Check whether the local index is stale relative to the given stash directory.
108
- * Returns `true` when the index is missing, empty, or was built against a
109
- * different primary stash dir.
138
+ * Returns `true` when the index is missing, empty, was built against a
139
+ * different primary stash dir, or an indexable file is newer than it.
110
140
  */
111
141
  export function isIndexStale(stashDir) {
112
- const dbPath = getDbPath();
113
- // Raises on an index we cannot READ rather than calling it stale — "stale"
114
- // sends us into an inline reindex that will fail anyway, and whose failure is
115
- // reported as the misleading "proceeding with existing index" (#791).
116
- assertIndexPathReadable(dbPath);
117
- if (classifyPathAccess(dbPath).access === "absent")
118
- return true;
119
- let db;
120
- try {
121
- db = openExistingDatabase(dbPath);
122
- if (!isCanonicalIndexGeneration(db))
123
- return true;
124
- const entryCount = getEntryCount(db);
125
- if (entryCount === 0)
126
- return true;
127
- const builtAt = getMeta(db, "builtAt");
128
- if (hasNewerIndexableFiles(stashDir, builtAt, getIndexedFilePaths(db)))
129
- return true;
130
- const storedStashDir = getMeta(db, "stashDir");
131
- if (storedStashDir !== stashDir) {
132
- // Check if the incoming stashDir appears in the stored stashDirs array
133
- try {
134
- const storedDirs = JSON.parse(getMeta(db, "stashDirs") ?? "[]");
135
- if (!storedDirs.includes(stashDir))
136
- return true;
137
- }
138
- catch {
139
- return true;
140
- }
141
- }
142
- return false;
143
- }
144
- catch {
145
- return true;
146
- }
147
- finally {
148
- if (db)
149
- closeDatabase(db);
150
- }
142
+ return !indexCanServeStash(stashDir, { requireFresh: true });
151
143
  }
152
144
  /**
153
145
  * Whether the existing index can serve queries for `stashDir` *right now* —
@@ -157,20 +149,27 @@ export function isIndexStale(stashDir) {
157
149
  * content-stale, so read paths serve it as-is. When it is false the existing
158
150
  * index has nothing relevant to return (no DB, no `entries` table, zero rows,
159
151
  * or built for a different stash), so those cases must rebuild inline.
152
+ * `requireFresh` also demands that no indexable file is newer than the index.
160
153
  */
161
- function indexCanServeStash(stashDir) {
162
- // Same rule as isIndexStale: unreadable is an error, not "cannot serve" (#791).
154
+ function indexCanServeStash(stashDir, options = {}) {
163
155
  const dbPath = getDbPath();
156
+ // Raises on an index we cannot READ rather than calling it unusable — that
157
+ // sends us into an inline reindex that will fail anyway, and whose failure
158
+ // is reported as the misleading "proceeding with existing index" (#791).
164
159
  assertIndexPathReadable(dbPath);
165
160
  if (classifyPathAccess(dbPath).access === "absent")
166
161
  return false;
167
162
  let db;
168
163
  try {
169
164
  db = openExistingDatabase(dbPath);
170
- if (!isCanonicalIndexGeneration(db))
165
+ if (!hasCurrentEntriesTable(db))
171
166
  return false;
172
167
  if (getEntryCount(db) === 0)
173
168
  return false;
169
+ if (options.requireFresh &&
170
+ hasNewerIndexableFiles(stashDir, getMeta(db, "builtAt"), getIndexedFilePaths(db), getIndexedFileHashes(db))) {
171
+ return false;
172
+ }
174
173
  const storedStashDir = getMeta(db, "stashDir");
175
174
  if (storedStashDir === stashDir)
176
175
  return true;
@@ -182,7 +181,10 @@ function indexCanServeStash(stashDir) {
182
181
  return false;
183
182
  }
184
183
  }
185
- catch {
184
+ catch (error) {
185
+ // A newer layout's refusal names the remedy; a rebuild would refuse too.
186
+ if (error instanceof ConfigError)
187
+ throw error;
186
188
  // No `entries` table (or otherwise unreadable) — cannot serve.
187
189
  return false;
188
190
  }
@@ -191,16 +193,6 @@ function indexCanServeStash(stashDir) {
191
193
  closeDatabase(db);
192
194
  }
193
195
  }
194
- async function runInlineReindex(stashDir, options = {}) {
195
- const { akmIndex } = await import("./indexer.js");
196
- await akmIndex({
197
- stashDir,
198
- implicit: true,
199
- ...(options.signal ? { signal: options.signal } : {}),
200
- ...(options.hydrateSources === false ? { hydrateSources: false } : {}),
201
- });
202
- return true;
203
- }
204
196
  /**
205
197
  * Ensure the local index exists and can serve the caller.
206
198
  *
@@ -221,20 +213,32 @@ export async function ensureIndex(stashDir, options = {}) {
221
213
  // §11.5: warn (once) if the configured bundle ids drifted from the persisted
222
214
  // index prefixes (hand-renamed bundle key) BEFORE any rebuild could re-mint.
223
215
  warnOnBundleRenameDrift();
224
- if (options.mode === "blocking") {
225
- // Blocking callers (improve's planning preflight) are a sanctioned
226
- // materialization point — hydrate cache-backed sources as usual.
227
- if (!isIndexStale(stashDir))
228
- return false;
229
- return runInlineReindex(stashDir, { ...(options.signal ? { signal: options.signal } : {}) });
230
- }
231
- // Background = the READ path (`show` auto-index): query time must never clone/
232
- // pull/fetch (spec §14.3 / D11). Build from already-materialized content only;
233
- // absent source caches are skipped with a warning, not fetched.
234
- if (indexCanServeStash(stashDir))
216
+ const blocking = options.mode === "blocking";
217
+ if (indexCanServeStash(stashDir, { requireFresh: blocking }))
235
218
  return false;
236
- return runInlineReindex(stashDir, {
219
+ const { akmIndex } = await import("./indexer.js");
220
+ const startedMs = Date.now();
221
+ const response = await akmIndex({
222
+ stashDir,
223
+ implicit: true,
237
224
  ...(options.signal ? { signal: options.signal } : {}),
238
- hydrateSources: false,
225
+ // Blocking callers (improve's planning preflight) are a sanctioned
226
+ // materialization point and hydrate cache-backed sources as usual. The
227
+ // background READ path (`show` auto-index) must never clone/pull/fetch at
228
+ // query time (spec §14.3 / D11): it builds from already-materialized
229
+ // content, and absent source caches are skipped with a warning.
230
+ ...(blocking ? {} : { hydrateSources: false }),
239
231
  });
232
+ // R6: the implicit reindex's cost was previously discarded entirely
233
+ // (`await akmIndex(...)` and nothing else), making a 27-minute blocking
234
+ // rebuild invisible to both the operator and the improve result. Fall back
235
+ // to a wall-clock measurement when the response carries no `timing` block.
236
+ const durationMs = response.timing?.totalMs ?? Date.now() - startedMs;
237
+ const timing = response.timing;
238
+ warnVerbose(`[ensure-index] implicit reindex completed in ${durationMs}ms` +
239
+ (timing
240
+ ? ` (walk=${timing.walkMs}ms llm=${timing.llmMs}ms embed=${timing.embedMs}ms finalize=${timing.finalizeMs}ms)`
241
+ : ""));
242
+ options.onReindexTiming?.({ durationMs, timing });
243
+ return true;
240
244
  }