akm-cli 0.9.17-alpha.3 → 0.9.17-alpha.5

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 (346) hide show
  1. package/CHANGELOG.md +760 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/lint/base-linter.js +19 -5
  53. package/dist/commands/proposal/drain.js +251 -644
  54. package/dist/commands/proposal/proposal-cli.js +3 -18
  55. package/dist/commands/proposal/proposal-types.js +20 -41
  56. package/dist/commands/proposal/proposal.js +1 -2
  57. package/dist/commands/proposal/propose.js +134 -160
  58. package/dist/commands/proposal/repository.js +502 -1487
  59. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  60. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  61. package/dist/commands/proposal/validators/proposals.js +13 -89
  62. package/dist/commands/read/curate.js +63 -413
  63. package/dist/commands/read/search-cli.js +16 -33
  64. package/dist/commands/read/search.js +17 -23
  65. package/dist/commands/read/show.js +2 -13
  66. package/dist/commands/sources/bundle-cli.js +25 -2
  67. package/dist/commands/sources/bundle-config-ops.js +4 -0
  68. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  69. package/dist/commands/sources/info.js +2 -11
  70. package/dist/commands/sources/installed-stashes.js +197 -746
  71. package/dist/commands/sources/schema-repair.js +98 -129
  72. package/dist/commands/sources/source-add.js +62 -12
  73. package/dist/commands/sources/source-manage.js +9 -2
  74. package/dist/commands/sources/stash-cli.js +1 -1
  75. package/dist/commands/tasks/explain.js +10 -13
  76. package/dist/commands/tasks/tasks-cli.js +9 -8
  77. package/dist/commands/tasks/tasks.js +326 -930
  78. package/dist/commands/tasks/validate.js +42 -21
  79. package/dist/commands/workflow/plan.js +22 -29
  80. package/dist/commands/workflow-cli.js +4 -4
  81. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  82. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  83. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  84. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  85. package/dist/core/adapter/execution-source.js +17 -29
  86. package/dist/core/asset/asset-placement.js +4 -13
  87. package/dist/core/asset/resolve-ref.js +1 -1
  88. package/dist/core/bundle-id.js +42 -5
  89. package/dist/core/bundle-rename.js +291 -0
  90. package/dist/core/config/config-io.js +1 -2
  91. package/dist/core/config/config-schema.js +1 -33
  92. package/dist/core/config/config-walker.js +1 -1
  93. package/dist/core/config/config.js +163 -68
  94. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  95. package/dist/core/config/schema/embedding.js +20 -5
  96. package/dist/core/config/schema/engines.js +5 -0
  97. package/dist/core/config/schema/execution.js +1 -1
  98. package/dist/core/config/schema/experimental.js +1 -1
  99. package/dist/core/config/schema/improve-processes.js +21 -95
  100. package/dist/core/config/schema/improve.js +4 -42
  101. package/dist/core/config/schema/scheduler.js +12 -12
  102. package/dist/core/config/schema/search.js +6 -22
  103. package/dist/core/env-secret-ref.js +0 -1
  104. package/dist/core/errors.js +8 -9
  105. package/dist/core/file-lock.js +76 -173
  106. package/dist/core/logs-db.js +2 -2
  107. package/dist/core/paths.js +0 -27
  108. package/dist/core/redaction.js +109 -2
  109. package/dist/core/run-lock.js +2 -5
  110. package/dist/core/spawn-env.js +1 -1
  111. package/dist/core/state/migrations.js +108 -61
  112. package/dist/core/state-db-scope.js +2 -4
  113. package/dist/core/state-db.js +126 -692
  114. package/dist/core/type-presentation.js +1 -9
  115. package/dist/core/write-source.js +293 -1012
  116. package/dist/execution/input-contract.js +1 -1
  117. package/dist/execution/resolved-request.js +135 -689
  118. package/dist/execution/source.js +63 -257
  119. package/dist/execution/target-ref.js +1 -1
  120. package/dist/indexer/bundle-identity-guard.js +2 -2
  121. package/dist/indexer/db/graph-db.js +106 -46
  122. package/dist/indexer/ensure-index.js +44 -85
  123. package/dist/indexer/graph/graph-extraction.js +340 -562
  124. package/dist/indexer/graph/graph-related.js +130 -0
  125. package/dist/indexer/index-rebuild-lock.js +3 -11
  126. package/dist/indexer/index-writer-lock.js +8 -17
  127. package/dist/indexer/index-written-assets.js +139 -151
  128. package/dist/indexer/indexer.js +524 -846
  129. package/dist/indexer/materialize-embeddings.js +60 -397
  130. package/dist/indexer/passes/memory-inference.js +81 -90
  131. package/dist/indexer/passes/metadata.js +132 -200
  132. package/dist/indexer/read-preflight.js +0 -7
  133. package/dist/indexer/scan/doc-to-entry.js +1 -3
  134. package/dist/indexer/scan/drain-dir.js +1 -1
  135. package/dist/indexer/search/db-search.js +181 -590
  136. package/dist/indexer/search/fts-query.js +30 -41
  137. package/dist/indexer/search/ranking.js +28 -154
  138. package/dist/indexer/search/search-attribution.js +12 -32
  139. package/dist/indexer/search/search-fields.js +11 -15
  140. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  141. package/dist/indexer/search/search-source.js +1 -4
  142. package/dist/indexer/usage/usage-events.js +2 -7
  143. package/dist/integrations/agent/engine-fallback.js +23 -40
  144. package/dist/integrations/agent/engine-resolution.js +93 -183
  145. package/dist/integrations/agent/execution.js +507 -0
  146. package/dist/integrations/agent/model-map.js +28 -156
  147. package/dist/integrations/agent/request-lowering.js +66 -141
  148. package/dist/integrations/agent/runner-dispatch.js +143 -321
  149. package/dist/integrations/agent/runner.js +54 -14
  150. package/dist/integrations/lockfile.js +53 -101
  151. package/dist/llm/embedders/deterministic.js +2 -3
  152. package/dist/llm/embedders/profile.js +71 -0
  153. package/dist/llm/embedders/remote.js +10 -15
  154. package/dist/llm/graph-extract.js +3 -12
  155. package/dist/llm/index-passes.js +3 -5
  156. package/dist/llm/memory-infer.js +1 -2
  157. package/dist/llm/metadata-enhance.js +1 -2
  158. package/dist/llm/structured-call.js +5 -24
  159. package/dist/output/generic-render.js +23 -11
  160. package/dist/output/html-render.js +13 -10
  161. package/dist/output/render-registry.js +3 -32
  162. package/dist/output/shapes/helpers.js +2 -34
  163. package/dist/output/shapes/passthrough.js +1 -9
  164. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  165. package/dist/output/text/command-format.js +60 -23
  166. package/dist/output/text/helpers.js +1 -1
  167. package/dist/output/text/migrate.js +5 -14
  168. package/dist/output/text/proposal-format.js +1 -2
  169. package/dist/output/text/workflow-format.js +0 -32
  170. package/dist/output/text.js +2 -0
  171. package/dist/registry/factory.js +4 -19
  172. package/dist/registry/network.js +66 -220
  173. package/dist/registry/providers/index.js +0 -2
  174. package/dist/registry/providers/skills-sh.js +3 -14
  175. package/dist/registry/providers/static-index.js +24 -26
  176. package/dist/registry/resolve.js +55 -131
  177. package/dist/scripts/akm-migrate-node.js +43940 -93320
  178. package/dist/scripts/akm-migrate.js +43700 -93078
  179. package/dist/setup/registry-stash-loader.js +4 -13
  180. package/dist/setup/semantic-assets.js +3 -44
  181. package/dist/setup/setup.js +1 -1
  182. package/dist/setup/steps/tasks.js +25 -15
  183. package/dist/sources/provider-factory.js +17 -18
  184. package/dist/sources/providers/filesystem.js +2 -3
  185. package/dist/sources/providers/git-install.js +7 -1
  186. package/dist/sources/providers/git-provider.js +0 -3
  187. package/dist/sources/providers/git-stash.js +0 -17
  188. package/dist/sources/providers/npm.js +2 -4
  189. package/dist/sources/providers/provider-utils.js +5 -10
  190. package/dist/sources/providers/website.js +0 -2
  191. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  192. package/dist/sources/website-url.js +2 -2
  193. package/dist/storage/database.js +9 -35
  194. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  195. package/dist/storage/repositories/index-connection.js +34 -70
  196. package/dist/storage/repositories/index-entries-repository.js +69 -111
  197. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  198. package/dist/storage/repositories/index-entry-schema.js +83 -269
  199. package/dist/storage/repositories/index-fts-repository.js +86 -256
  200. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  201. package/dist/storage/repositories/index-meta-repository.js +6 -4
  202. package/dist/storage/repositories/index-schema.js +192 -220
  203. package/dist/storage/repositories/index-utility-repository.js +8 -29
  204. package/dist/storage/repositories/index-vec-repository.js +133 -414
  205. package/dist/storage/repositories/outcome-repository.js +2 -1
  206. package/dist/storage/repositories/proposals-repository.js +35 -0
  207. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  208. package/dist/storage/repositories/task-history-repository.js +26 -4
  209. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  210. package/dist/storage/sqlite-migrations.js +136 -0
  211. package/dist/storage/sqlite-pragmas.js +11 -9
  212. package/dist/storage/sqlite-transaction.js +170 -0
  213. package/dist/storage/state-db-integrity.js +34 -27
  214. package/dist/tasks/activation-config.js +134 -62
  215. package/dist/tasks/backends/cron.js +129 -277
  216. package/dist/tasks/backends/exec-utils.js +2 -5
  217. package/dist/tasks/backends/launchd.js +125 -745
  218. package/dist/tasks/backends/schtasks.js +101 -620
  219. package/dist/tasks/prepare/prepare-support.js +5 -15
  220. package/dist/tasks/prepare/prepare.js +0 -2
  221. package/dist/tasks/resolve-akm-bin.js +20 -79
  222. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  223. package/dist/tasks/scheduler-binding.js +18 -238
  224. package/dist/tasks/scheduler-invocation.js +52 -52
  225. package/dist/tasks/scheduler-lock.js +53 -0
  226. package/dist/tasks/scheduler-sync.js +361 -751
  227. package/dist/tasks/source/parse-task-source.js +160 -10
  228. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  229. package/dist/tasks/source/task-to-v4.js +2 -2
  230. package/dist/workflows/authoring/authoring.js +3 -12
  231. package/dist/workflows/compile.js +211 -0
  232. package/dist/workflows/concurrency-policy.js +13 -74
  233. package/dist/workflows/exec/child-invocation.js +3 -17
  234. package/dist/workflows/exec/child-workflow.js +32 -141
  235. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  236. package/dist/workflows/exec/environment.js +98 -0
  237. package/dist/workflows/exec/exec-unit.js +33 -140
  238. package/dist/workflows/exec/frozen-judge.js +7 -59
  239. package/dist/workflows/exec/native-executor.js +82 -341
  240. package/dist/workflows/exec/param-secrets.js +29 -47
  241. package/dist/workflows/exec/run-workflow.js +154 -387
  242. package/dist/workflows/exec/scheduler.js +9 -36
  243. package/dist/workflows/exec/step-work.js +127 -430
  244. package/dist/workflows/exec/unit-dispatch.js +11 -63
  245. package/dist/workflows/exec/unit-writer.js +8 -52
  246. package/dist/workflows/exec/worktree.js +39 -273
  247. package/dist/workflows/freeze/child-output-references.js +4 -15
  248. package/dist/workflows/freeze/environment.js +99 -92
  249. package/dist/workflows/freeze/freeze.js +172 -0
  250. package/dist/workflows/freeze/step-values.js +19 -21
  251. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  252. package/dist/workflows/freeze/targets/command.js +10 -33
  253. package/dist/workflows/freeze/targets/script.js +5 -12
  254. package/dist/workflows/freeze/targets/shell.js +3 -6
  255. package/dist/workflows/freeze/targets/task.js +25 -80
  256. package/dist/workflows/freeze/task-bindings.js +20 -67
  257. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  258. package/dist/workflows/ir/params.js +6 -51
  259. package/dist/workflows/ir/plan-hash.js +2 -34
  260. package/dist/workflows/parser.js +140 -43
  261. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  262. package/dist/workflows/renderer.js +36 -69
  263. package/dist/workflows/resource-limits.js +12 -120
  264. package/dist/workflows/runtime/agent-identity.js +8 -40
  265. package/dist/workflows/runtime/run-outputs.js +3 -6
  266. package/dist/workflows/runtime/run-plan.js +316 -0
  267. package/dist/workflows/runtime/runs.js +48 -200
  268. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  269. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  270. package/dist/workflows/validate-summary.js +2 -7
  271. package/docs/integration/bundling-akm.md +49 -42
  272. package/docs/migration/README.md +1 -0
  273. package/docs/migration/release-notes/0.9.17.md +41 -0
  274. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  275. package/docs/reference/cli.md +182 -125
  276. package/docs/reference/configuration.md +49 -56
  277. package/docs/reference/data-and-telemetry.md +19 -20
  278. package/docs/reference/tasks.md +86 -38
  279. package/docs/reference/workflow-schema.md +14 -18
  280. package/docs/reference/workflows.md +6 -9
  281. package/package.json +1 -1
  282. package/schemas/akm-config.json +87 -406
  283. package/dist/commands/health/advisories.js +0 -150
  284. package/dist/commands/health/metrics.js +0 -329
  285. package/dist/commands/health/surfaces.js +0 -102
  286. package/dist/commands/improve/anti-collapse.js +0 -83
  287. package/dist/commands/improve/collapse-detector.js +0 -432
  288. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  289. package/dist/commands/improve/consolidate/merge.js +0 -146
  290. package/dist/commands/improve/distill/promote-memory.js +0 -329
  291. package/dist/commands/improve/distill/quality-gate.js +0 -500
  292. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  293. package/dist/commands/improve/proposal-envelope.js +0 -31
  294. package/dist/commands/improve/run-context.js +0 -123
  295. package/dist/commands/improve/shared.js +0 -21
  296. package/dist/commands/improve/source-identity.js +0 -28
  297. package/dist/commands/improve/triage.js +0 -96
  298. package/dist/commands/proposal/drain-policies.js +0 -151
  299. package/dist/commands/sources/update-transaction.js +0 -220
  300. package/dist/core/action-contributors.js +0 -28
  301. package/dist/core/config/config-version-shim.js +0 -101
  302. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  303. package/dist/core/fs-txn.js +0 -405
  304. package/dist/core/lexical-score.js +0 -25
  305. package/dist/core/maintenance-barrier.js +0 -167
  306. package/dist/execution/executable-identity.js +0 -105
  307. package/dist/execution/guarded-source.js +0 -441
  308. package/dist/indexer/graph/graph-boost.js +0 -427
  309. package/dist/indexer/graph/graph-dedup.js +0 -95
  310. package/dist/indexer/search/name-match.js +0 -35
  311. package/dist/indexer/search/ranking-contributors.js +0 -515
  312. package/dist/indexer/walk/project-context.js +0 -192
  313. package/dist/integrations/agent/execution-cascade.js +0 -566
  314. package/dist/integrations/agent/execution-definitions.js +0 -202
  315. package/dist/integrations/agent/execution-lowering.js +0 -841
  316. package/dist/integrations/agent/execution-preparation.js +0 -98
  317. package/dist/integrations/agent/inline-execution.js +0 -74
  318. package/dist/registry/create-provider-registry.js +0 -29
  319. package/dist/registry/pinned-request-helper.js +0 -247
  320. package/dist/registry/pinned-transport.js +0 -717
  321. package/dist/sources/providers/index.js +0 -14
  322. package/dist/storage/engines/sqlite-migrations.js +0 -271
  323. package/dist/storage/repositories/canaries-repository.js +0 -107
  324. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  325. package/dist/storage/repositories/registry-cache.js +0 -113
  326. package/dist/tasks/scheduler-sync-preview.js +0 -52
  327. package/dist/workflows/freeze/resolve-steps.js +0 -86
  328. package/dist/workflows/freeze/source-freeze.js +0 -64
  329. package/dist/workflows/ir/compile.js +0 -321
  330. package/dist/workflows/ir/environment-v4.js +0 -330
  331. package/dist/workflows/ir/freeze-v4.js +0 -153
  332. package/dist/workflows/ir/schema-v4.js +0 -745
  333. package/dist/workflows/ir/schema.js +0 -354
  334. package/dist/workflows/program/schema.js +0 -78
  335. package/dist/workflows/runtime/checkin.js +0 -57
  336. package/dist/workflows/runtime/plan-classifier.js +0 -196
  337. package/dist/workflows/runtime/unit-checkin.js +0 -45
  338. package/dist/workflows/runtime/unit-phases.js +0 -20
  339. package/dist/workflows/schema.js +0 -4
  340. package/dist/workflows/source-ir/compile.js +0 -200
  341. package/dist/workflows/source-ir/program.js +0 -50
  342. package/dist/workflows/source-ir/result.js +0 -26
  343. package/dist/workflows/source-ir/schema.js +0 -786
  344. package/dist/workflows/source-ir/triggers.js +0 -79
  345. package/dist/workflows/source-ir/uses.js +0 -40
  346. package/dist/workflows/validator.js +0 -60
@@ -0,0 +1,130 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Find graph files that share entities with the given file.
6
+ *
7
+ * Implementation: SQL self-join on graph_file_entities, scoped by stash_root,
8
+ * grouped by file_path, ordered by shared-entity count desc. Touches ~50-200
9
+ * rows instead of loading the entire snapshot into memory. Cold-call latency
10
+ * drops from ~30-60ms (full snapshot parse) to ~2-5ms on typical stashes.
11
+ *
12
+ * #624-P1: the graph tables are keyed on (stash_root, file_path, body_hash) —
13
+ * NOT entries.id — so candidates are identified by file_path (the unique index
14
+ * idx_graph_files_path guarantees one graph_files row per path).
15
+ *
16
+ * The returned `ref` field carries the canonical indexed `concept_id`, never a
17
+ * value re-derived from presentation fields. It is undefined
18
+ * when the graph row has no matching entry with current indexed provenance.
19
+ */
20
+ export function listRelatedPathsForFile(stashRoot, filePath, limit = 5, db) {
21
+ if (!db) {
22
+ // Fallback: opening a transient DB here is not currently a use case (all
23
+ // callers pass a handle), so degrade to empty rather than reopening.
24
+ return [];
25
+ }
26
+ // Confirm the target file has a graph row; without it there is nothing to
27
+ // relate. (Identity is file_path within the stash — one row per path.)
28
+ const row = db
29
+ .prepare("SELECT 1 AS present FROM graph_files WHERE stash_root = ? AND file_path = ? LIMIT 1")
30
+ .get(stashRoot, filePath);
31
+ if (row === undefined)
32
+ return [];
33
+ const effectiveLimit = Math.max(1, limit);
34
+ // Shared-entity count per candidate file_path. The target's entities are the
35
+ // rows for `filePath`; candidates are any OTHER file_path in the stash that
36
+ // shares a normalized entity.
37
+ const candidateRows = db
38
+ .prepare(`SELECT gf.file_path AS file_path,
39
+ gf.file_type AS file_type,
40
+ COUNT(*) AS shared
41
+ FROM graph_file_entities target
42
+ JOIN graph_file_entities e
43
+ ON e.stash_root = target.stash_root
44
+ AND e.entity_norm = target.entity_norm
45
+ AND e.file_path != target.file_path
46
+ JOIN graph_files gf
47
+ ON gf.stash_root = e.stash_root
48
+ AND gf.file_path = e.file_path
49
+ AND gf.body_hash = e.body_hash
50
+ WHERE target.file_path = ?
51
+ AND target.stash_root = ?
52
+ GROUP BY gf.file_path
53
+ ORDER BY shared DESC, gf.file_path ASC
54
+ LIMIT ?`)
55
+ .all(filePath, stashRoot, effectiveLimit);
56
+ if (candidateRows.length === 0)
57
+ return [];
58
+ const candidatePaths = candidateRows.map((r) => r.file_path);
59
+ const placeholders = candidatePaths.map(() => "?").join(",");
60
+ // Pull the shared entity names (joined by normalized casing) for display, in
61
+ // a fixed order so identical calls return identical results.
62
+ const sharedRows = db
63
+ .prepare(`SELECT e.file_path AS file_path, e.entity AS entity
64
+ FROM graph_file_entities e
65
+ JOIN graph_file_entities target
66
+ ON target.stash_root = e.stash_root
67
+ AND target.entity_norm = e.entity_norm
68
+ WHERE e.file_path IN (${placeholders})
69
+ AND e.stash_root = ?
70
+ AND target.file_path = ?
71
+ AND target.stash_root = ?
72
+ ORDER BY e.file_path, e.entity`)
73
+ .all(...candidatePaths, stashRoot, filePath, stashRoot);
74
+ const sharedByPath = new Map();
75
+ for (const row of sharedRows) {
76
+ let bucket = sharedByPath.get(row.file_path);
77
+ if (!bucket) {
78
+ bucket = new Set();
79
+ sharedByPath.set(row.file_path, bucket);
80
+ }
81
+ bucket.add(row.entity);
82
+ }
83
+ // Relation count for each candidate (relations where either endpoint
84
+ // matches one of the shared entities).
85
+ const relationCountByPath = new Map();
86
+ const relationRows = db
87
+ .prepare(`SELECT file_path, from_entity, to_entity
88
+ FROM graph_file_relations
89
+ WHERE file_path IN (${placeholders})
90
+ AND stash_root = ?`)
91
+ .all(...candidatePaths, stashRoot);
92
+ for (const row of relationRows) {
93
+ const shared = sharedByPath.get(row.file_path);
94
+ if (!shared)
95
+ continue;
96
+ if (shared.has(row.from_entity) || shared.has(row.to_entity)) {
97
+ relationCountByPath.set(row.file_path, (relationCountByPath.get(row.file_path) ?? 0) + 1);
98
+ }
99
+ }
100
+ // This related list is scoped to one source root, so the user-facing ref is
101
+ // the indexed bundle-less conceptId.
102
+ const refByPath = new Map();
103
+ try {
104
+ const entryRows = db
105
+ .prepare(`SELECT file_path, concept_id FROM entries
106
+ WHERE file_path IN (${placeholders})
107
+ ORDER BY file_path, concept_id`)
108
+ .all(...candidatePaths);
109
+ // Several entries can index one file; the first concept id wins.
110
+ for (const row of entryRows) {
111
+ if (!refByPath.has(row.file_path))
112
+ refByPath.set(row.file_path, row.concept_id);
113
+ }
114
+ }
115
+ catch {
116
+ /* ignore — refs are best-effort */
117
+ }
118
+ return candidateRows.map((row) => {
119
+ const sharedSet = sharedByPath.get(row.file_path) ?? new Set();
120
+ const sharedEntities = [...sharedSet].sort((a, b) => a.localeCompare(b));
121
+ const ref = refByPath.get(row.file_path);
122
+ return {
123
+ ...(ref ? { ref } : {}),
124
+ path: row.file_path,
125
+ type: row.file_type,
126
+ sharedEntities,
127
+ relationCount: relationCountByPath.get(row.file_path) ?? 0,
128
+ };
129
+ });
130
+ }
@@ -20,7 +20,6 @@
20
20
  * no-stale-age-window rationale.
21
21
  */
22
22
  import { releaseLock } from "../core/file-lock.js";
23
- import { tryWithMaintenanceStartBarrier, withMaintenanceStartBarrier } from "../core/maintenance-barrier.js";
24
23
  import { getIndexRebuildLockPath } from "../core/paths.js";
25
24
  import { formatLockHolderPid, tryAcquireRunLock } from "../core/run-lock.js";
26
25
  import { warn, warnVerbose } from "../core/warn.js";
@@ -49,21 +48,14 @@ export function tryAcquireIndexRebuildLock(skipIfLocked) {
49
48
  `(${info.reason}); that process is no longer running.`);
50
49
  },
51
50
  });
51
+ const result = acquire();
52
+ if (result.state === "acquired")
53
+ return result;
52
54
  if (skipIfLocked) {
53
- const result = tryWithMaintenanceStartBarrier(acquire);
54
- if (!result) {
55
- warn("[index] maintenance barrier held; skipping (--skip-if-locked)");
56
- return { state: "skipped", holder: { pid: null, startedAt: null, launcherPid: null } };
57
- }
58
- if (result.state === "acquired")
59
- return result;
60
55
  warn(`[index] another index run holds the lock (PID ${formatLockHolderPid(result.holder)}, started ${result.holder.startedAt}); ` +
61
56
  "skipping (--skip-if-locked)");
62
57
  return { state: "skipped", holder: result.holder };
63
58
  }
64
- const result = withMaintenanceStartBarrier(acquire);
65
- if (result.state === "acquired")
66
- return result;
67
59
  warn(`[index] another index run is active (pid ${formatLockHolderPid(result.holder)}, started ${result.holder.startedAt}); ` +
68
60
  "this run will contend with it — pass --skip-if-locked for scheduled runs");
69
61
  return { state: "contended", holder: result.holder };
@@ -25,8 +25,8 @@
25
25
  import { AsyncLocalStorage } from "node:async_hooks";
26
26
  import fs from "node:fs";
27
27
  import path from "node:path";
28
+ import { TransientError } from "../core/errors.js";
28
29
  import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../core/file-lock.js";
29
- import { tryAcquireMaintenanceBarrier } from "../core/maintenance-barrier.js";
30
30
  import { getDbPath, getIndexWriterLockPath } from "../core/paths.js";
31
31
  import { warn } from "../core/warn.js";
32
32
  import { sleepSync } from "../runtime.js";
@@ -92,21 +92,12 @@ function createLease(lockPath, ownership) {
92
92
  }
93
93
  function tryAcquireAssetMutationLease(lockPath, purpose) {
94
94
  while (true) {
95
- const releaseBarrier = tryAcquireMaintenanceBarrier();
96
- if (!releaseBarrier)
95
+ const ownership = tryAcquireLockSync(lockPath, buildPayload(purpose));
96
+ if (ownership)
97
+ return createLease(lockPath, ownership);
98
+ const probe = probeLock(lockPath);
99
+ if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
97
100
  return undefined;
98
- try {
99
- const ownership = tryAcquireLockSync(lockPath, buildPayload(purpose));
100
- if (ownership)
101
- return createLease(lockPath, ownership);
102
- // No `staleAfterMs`: only a verifiably dead holder is ever reclaimed.
103
- const probe = probeLock(lockPath);
104
- if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
105
- return undefined;
106
- }
107
- finally {
108
- releaseBarrier();
109
- }
110
101
  }
111
102
  }
112
103
  export async function acquireAssetMutationLease(options) {
@@ -129,7 +120,7 @@ export async function acquireAssetMutationLease(options) {
129
120
  // attempt, so a caller with maxWaitMs:0 still gets one chance at a free lock
130
121
  // instead of throwing before it ever tries.
131
122
  if (maxWaitMs >= 0 && Date.now() - startedAt >= maxWaitMs) {
132
- throw new Error(`timed out waiting for asset mutation lease for ${options.purpose}`);
123
+ throw new TransientError(`timed out waiting for asset mutation lease for ${options.purpose}`, "ASSET_MUTATION_LEASE_HELD");
133
124
  }
134
125
  const waitedMs = Date.now() - startedAt;
135
126
  if (waitedMs - lastWaitNoticeMs >= 15000) {
@@ -180,7 +171,7 @@ export function withAssetMutationLeaseSync(purpose, run) {
180
171
  if (!lease) {
181
172
  const waitedMs = Date.now() - startedAt;
182
173
  if (waitedMs >= maxWaitMs) {
183
- throw new Error(`timed out waiting for asset mutation lease for ${purpose}`);
174
+ throw new TransientError(`timed out waiting for asset mutation lease for ${purpose}`, "ASSET_MUTATION_LEASE_HELD");
184
175
  }
185
176
  // #956: the sync path used to wait up to 10 minutes with zero
186
177
  // progress feedback. Mirror the async path's 15s onWait cadence, but
@@ -33,9 +33,8 @@ import { closeDatabase, openExistingDatabase } from "../storage/repositories/ind
33
33
  import { deleteEntriesByIds, getEntryCount, upsertEntry } from "../storage/repositories/index-entries-repository.js";
34
34
  import { deriveEntryProvenance, deriveInstallations } from "./installations.js";
35
35
  import { generateEmbeddingsForDb, publishTargetedEmbeddingMeta } from "./materialize-embeddings.js";
36
- import { getMarkdownFragmentContent, hasMarkdownFragmentContent, setMarkdownFragmentContent, } from "./passes/metadata.js";
36
+ import { withFileSize } from "./passes/metadata.js";
37
37
  import { drainDirDocuments } from "./scan/drain-dir.js";
38
- import { buildSearchText } from "./search/search-fields.js";
39
38
  import { buildFileContext } from "./walk/file-context.js";
40
39
  /**
41
40
  * Busy-timeout (ms) for write-path index upserts. A real write — unlike the
@@ -66,161 +65,150 @@ export const WRITE_PATH_INDEX_BUSY_TIMEOUT_MS = 5_000;
66
65
  */
67
66
  export async function indexWrittenAssets(stashDir, filePaths, options = {}) {
68
67
  try {
69
- return await (async () => {
70
- // #956: a live `akm index` rebuild holds index.db under one long
71
- // transaction (persistDirRecords), which this fast path's own 5s
72
- // busy_timeout would just contend with pointlessly. Interactive
73
- // commands (remember, import, extract session assets) must never wait
74
- // on it — skip the inline upsert/embedding entirely; the write itself
75
- // (file + commit) has already succeeded by the time this runs, and the
76
- // rebuild in progress will pick up the change on its own.
77
- const rebuildProbe = probeLock(getIndexRebuildLockPath());
78
- if (rebuildProbe.state === "held") {
79
- // #956: name the launcher pid alongside the holder pid when known —
80
- // every process listing and task log shows the launcher's pid, not
81
- // the bun/node child's.
82
- const holderLabel = formatLockHolderPid({
83
- pid: rebuildProbe.holderPid,
84
- launcherPid: rebuildProbe.launcherPid ?? null,
85
- });
86
- warn(`index rebuild in progress (pid ${holderLabel}); the next index pass will index ${filePaths.join(", ")}`);
87
- return true;
88
- }
89
- const dbPath = getDbPath();
90
- // `true` here means "the index is in the state the caller expects" — and
91
- // `acceptProposal` advances its journal to `index-finalized` on the
92
- // strength of it. Only a genuinely ABSENT index earns that answer: an
93
- // index we cannot read has NOT been updated, so it falls through to
94
- // `openExistingDatabase` and surfaces as the honest `false` (#791).
95
- if (isPathAbsent(dbPath))
96
- return true;
97
- // The full walk never descends into dot-directories (for example `.meta/`)
98
- // — mirror that dot-segment skip here so this fast path indexes exactly
99
- // what a full run would. Sensitive/
100
- // infra abstention is the adapter's job now (see the `akmAdapter` note
101
- // below), not a path pre-filter.
102
- const files = filePaths.filter((f) => {
103
- const rel = path.relative(stashDir, f);
104
- return !rel.split(/[\\/]+/).some((segment) => segment.startsWith("."));
68
+ // #956: a live `akm index` rebuild holds index.db under one long
69
+ // transaction (persistDirRecords), which this fast path's own 5s
70
+ // busy_timeout would just contend with pointlessly. Interactive
71
+ // commands (remember, import, extract session assets) must never wait
72
+ // on it — skip the inline upsert/embedding entirely; the write itself
73
+ // (file + commit) has already succeeded by the time this runs, and the
74
+ // rebuild in progress will pick up the change on its own.
75
+ const rebuildProbe = probeLock(getIndexRebuildLockPath());
76
+ if (rebuildProbe.state === "held") {
77
+ // #956: name the launcher pid alongside the holder pid when known —
78
+ // every process listing and task log shows the launcher's pid, not
79
+ // the bun/node child's.
80
+ const holderLabel = formatLockHolderPid({
81
+ pid: rebuildProbe.holderPid,
82
+ launcherPid: rebuildProbe.launcherPid ?? null,
105
83
  });
106
- if (files.length === 0)
107
- return true;
108
- // Generate metadata BEFORE opening the DB so the write window stays
109
- // short. One drain call per file keeps the entry↔path pairing exact and
110
- // reuses the full-index recognize engine (F4a M-core-2 item 5): broken
111
- // workflows drop, valid workflow docs are cached for the side-table upsert.
112
- const component = deriveInstallations([
113
- { path: stashDir, writable: true, ...(options.bundleId ? { registryId: options.bundleId } : {}) },
114
- ])[0]?.components[0];
115
- if (!component)
116
- throw new Error(`Could not derive bundle provenance for ${stashDir}`);
117
- const pairs = [];
118
- const unindexable = new Set();
119
- const rejectedConceptIds = new Set();
120
- for (const file of files) {
121
- if (!fs.existsSync(file)) {
122
- let authoredDanglingSymlink = false;
123
- try {
124
- authoredDanglingSymlink = fs.lstatSync(file).isSymbolicLink();
125
- }
126
- catch {
127
- // A genuinely absent path has no source identity to preflight.
128
- }
129
- if (!authoredDanglingSymlink) {
130
- unindexable.add(file);
131
- continue;
132
- }
84
+ warn(`index rebuild in progress (pid ${holderLabel}); the next index pass will index ${filePaths.join(", ")}`);
85
+ return true;
86
+ }
87
+ const dbPath = getDbPath();
88
+ // `true` here means "the index is in the state the caller expects" — and
89
+ // `acceptProposal` advances its journal to `index-finalized` on the
90
+ // strength of it. Only a genuinely ABSENT index earns that answer: an
91
+ // index we cannot read has NOT been updated, so it falls through to
92
+ // `openExistingDatabase` and surfaces as the honest `false` (#791).
93
+ if (isPathAbsent(dbPath))
94
+ return true;
95
+ // The full walk never descends into dot-directories (for example `.meta/`)
96
+ // — mirror that dot-segment skip here so this fast path indexes exactly
97
+ // what a full run would. Sensitive/
98
+ // infra abstention is the adapter's job now (see the `akmAdapter` note
99
+ // below), not a path pre-filter.
100
+ const files = filePaths.filter((f) => {
101
+ const rel = path.relative(stashDir, f);
102
+ return !rel.split(/[\\/]+/).some((segment) => segment.startsWith("."));
103
+ });
104
+ if (files.length === 0)
105
+ return true;
106
+ // Generate metadata BEFORE opening the DB so the write window stays
107
+ // short. One drain call per file keeps the entry↔path pairing exact and
108
+ // reuses the full-index recognize engine (F4a M-core-2 item 5): broken
109
+ // workflows drop, valid workflow docs are cached for the side-table upsert.
110
+ const component = deriveInstallations([
111
+ { path: stashDir, writable: true, ...(options.bundleId ? { registryId: options.bundleId } : {}) },
112
+ ])[0]?.components[0];
113
+ if (!component)
114
+ throw new Error(`Could not derive bundle provenance for ${stashDir}`);
115
+ const pairs = [];
116
+ const unindexable = new Set();
117
+ const rejectedConceptIds = new Set();
118
+ for (const file of files) {
119
+ if (!fs.existsSync(file)) {
120
+ let authoredDanglingSymlink = false;
121
+ try {
122
+ authoredDanglingSymlink = fs.lstatSync(file).isSymbolicLink();
123
+ }
124
+ catch {
125
+ // A genuinely absent path has no source identity to preflight.
133
126
  }
134
- const ctx = buildFileContext(stashDir, file);
135
- // Hardcoded `akmAdapter` on purpose (owner ruling 2026-07-21): this
136
- // write-path fast path only ever runs for assets a first-class akm
137
- // mutation command just wrote into a managed akm source, so the akm
138
- // adapter is always the right recognizer here — no per-component
139
- // dispatch needed.
140
- const drained = drainDirDocuments(akmAdapter, component, [ctx]);
141
- for (const rejectedPath of drained.rejectedPaths)
142
- unindexable.add(rejectedPath);
143
- for (const conceptId of drained.rejectedConceptIds)
144
- rejectedConceptIds.add(conceptId);
145
- const entry = drained.entries[0];
146
- // A broken workflow drains to zero entries and is treated as
147
- // unindexable; valid peer sources have already compiled to source IR.
148
- const conceptId = drained.conceptIdByFile.get(ctx.absPath);
149
- if (entry && conceptId)
150
- pairs.push({ file, entry, conceptId, contentHash: drained.hashByFile.get(ctx.absPath) });
151
- else
127
+ if (!authoredDanglingSymlink) {
152
128
  unindexable.add(file);
153
- }
154
- const db = openExistingDatabase(dbPath);
155
- try {
156
- db.exec(`PRAGMA busy_timeout = ${WRITE_PATH_INDEX_BUSY_TIMEOUT_MS}`);
157
- if (getEntryCount(db) === 0)
158
- return true;
159
- const targetEntryIds = new Set();
160
- let mutated = false;
161
- db.transaction(() => {
162
- const unindexableEntryIds = new Set();
163
- for (const file of unindexable) {
164
- const rows = db
165
- .prepare(`SELECT id FROM entries
166
- WHERE file_path = ?
167
- AND bundle_id = ?
168
- AND adapter_id = ?`)
169
- .all(file, component.id, component.adapter);
170
- for (const row of rows)
171
- unindexableEntryIds.add(row.id);
172
- }
173
- for (const conceptId of rejectedConceptIds) {
174
- const itemRef = `${component.id}//${conceptId}`;
175
- const rows = db
176
- .prepare(`SELECT id FROM entries
177
- WHERE bundle_id = ? AND adapter_id = ?
178
- AND type = 'workflow'
179
- AND (concept_id = ? OR item_ref = ?)`)
180
- .all(component.id, component.adapter, conceptId, itemRef);
181
- for (const row of rows)
182
- unindexableEntryIds.add(row.id);
183
- }
184
- deleteEntriesByIds(db, [...unindexableEntryIds]);
185
- mutated ||= unindexableEntryIds.size > 0;
186
- for (const { file, entry, conceptId, contentHash } of pairs) {
187
- let entryWithSize = entry;
188
- try {
189
- entryWithSize = { ...entry, fileSize: fs.statSync(file).size };
190
- if (hasMarkdownFragmentContent(entry)) {
191
- setMarkdownFragmentContent(entryWithSize, getMarkdownFragmentContent(entry));
192
- }
193
- }
194
- catch {
195
- // stat raced a delete — index without the size, like the full walk does.
196
- }
197
- // Real provenance (F4a M-core-2 item 5): populate item_ref/content_hash
198
- // via the SAME derivation the full-index writer uses, so a write-path
199
- // row is never a NULL-item_ref straggler.
200
- const provenance = deriveEntryProvenance({ bundleId: component.id, componentId: component.id, adapterId: component.adapter }, entry.type, entry.name, conceptId);
201
- // A materialized file has one current owner. If a targeted write is
202
- // the ownership handoff (for example an explicitly named bundle
203
- // replacing an earlier path-derived bootstrap identity), remove the
204
- // superseded physical-path row before publishing the canonical ref.
205
- const supersededIds = db
206
- .prepare("SELECT id FROM entries WHERE file_path = ? AND item_ref <> ?")
207
- .all(file, provenance.itemRef);
208
- deleteEntriesByIds(db, supersededIds.map((row) => row.id));
209
- targetEntryIds.add(upsertEntry(db, file, entryWithSize, buildSearchText(entry), provenance, contentHash));
210
- mutated = true;
211
- }
212
- })();
213
- if (mutated) {
214
- const config = loadConfig();
215
- await generateEmbeddingsForDb(db, config, () => { }, undefined, [...targetEntryIds]);
216
- publishTargetedEmbeddingMeta(db, config);
129
+ continue;
217
130
  }
218
131
  }
219
- finally {
220
- closeDatabase(db);
132
+ const ctx = buildFileContext(stashDir, file);
133
+ // Hardcoded `akmAdapter` on purpose (owner ruling 2026-07-21): this
134
+ // write-path fast path only ever runs for assets a first-class akm
135
+ // mutation command just wrote into a managed akm source, so the akm
136
+ // adapter is always the right recognizer here — no per-component
137
+ // dispatch needed.
138
+ const drained = drainDirDocuments(akmAdapter, component, [ctx]);
139
+ for (const rejectedPath of drained.rejectedPaths)
140
+ unindexable.add(rejectedPath);
141
+ for (const conceptId of drained.rejectedConceptIds)
142
+ rejectedConceptIds.add(conceptId);
143
+ const entry = drained.entries[0];
144
+ // A broken workflow drains to zero entries and is treated as
145
+ // unindexable; valid peer sources have already compiled to source IR.
146
+ const conceptId = drained.conceptIdByFile.get(ctx.absPath);
147
+ if (entry && conceptId)
148
+ pairs.push({ file, entry, conceptId, contentHash: drained.hashByFile.get(ctx.absPath) });
149
+ else
150
+ unindexable.add(file);
151
+ }
152
+ const db = openExistingDatabase(dbPath);
153
+ try {
154
+ db.exec(`PRAGMA busy_timeout = ${WRITE_PATH_INDEX_BUSY_TIMEOUT_MS}`);
155
+ if (getEntryCount(db) === 0)
156
+ return true;
157
+ const targetEntryIds = new Set();
158
+ let mutated = false;
159
+ db.transaction(() => {
160
+ const unindexableEntryIds = new Set();
161
+ for (const file of unindexable) {
162
+ const rows = db
163
+ .prepare(`SELECT id FROM entries
164
+ WHERE file_path = ?
165
+ AND bundle_id = ?
166
+ AND adapter_id = ?`)
167
+ .all(file, component.id, component.adapter);
168
+ for (const row of rows)
169
+ unindexableEntryIds.add(row.id);
170
+ }
171
+ for (const conceptId of rejectedConceptIds) {
172
+ const itemRef = `${component.id}//${conceptId}`;
173
+ const rows = db
174
+ .prepare(`SELECT id FROM entries
175
+ WHERE bundle_id = ? AND adapter_id = ?
176
+ AND type = 'workflow'
177
+ AND (concept_id = ? OR item_ref = ?)`)
178
+ .all(component.id, component.adapter, conceptId, itemRef);
179
+ for (const row of rows)
180
+ unindexableEntryIds.add(row.id);
181
+ }
182
+ deleteEntriesByIds(db, [...unindexableEntryIds]);
183
+ mutated ||= unindexableEntryIds.size > 0;
184
+ for (const { file, entry, conceptId, contentHash } of pairs) {
185
+ const entryWithSize = withFileSize(entry, file);
186
+ // Real provenance (F4a M-core-2 item 5): populate item_ref/content_hash
187
+ // via the SAME derivation the full-index writer uses, so a write-path
188
+ // row is never a NULL-item_ref straggler.
189
+ const provenance = deriveEntryProvenance({ bundleId: component.id, componentId: component.id, adapterId: component.adapter }, entry.type, entry.name, conceptId);
190
+ // A materialized file has one current owner. If a targeted write is
191
+ // the ownership handoff (for example an explicitly named bundle
192
+ // replacing an earlier path-derived bootstrap identity), remove the
193
+ // superseded physical-path row before publishing the canonical ref.
194
+ const supersededIds = db
195
+ .prepare("SELECT id FROM entries WHERE file_path = ? AND item_ref <> ?")
196
+ .all(file, provenance.itemRef);
197
+ deleteEntriesByIds(db, supersededIds.map((row) => row.id));
198
+ targetEntryIds.add(upsertEntry(db, file, entryWithSize, provenance, contentHash));
199
+ mutated = true;
200
+ }
201
+ })();
202
+ if (mutated) {
203
+ const config = loadConfig();
204
+ await generateEmbeddingsForDb(db, config, () => { }, undefined, [...targetEntryIds]);
205
+ publishTargetedEmbeddingMeta(db, config);
221
206
  }
222
- return true;
223
- })();
207
+ }
208
+ finally {
209
+ closeDatabase(db);
210
+ }
211
+ return true;
224
212
  }
225
213
  catch (error) {
226
214
  // A permission fault is the one failure the next full index will NOT heal,