akm-cli 0.9.0-beta.5 → 0.9.0-beta.50

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 (207) hide show
  1. package/CHANGELOG.md +709 -0
  2. package/dist/assets/profiles/default.json +9 -4
  3. package/dist/assets/profiles/frequent.json +1 -1
  4. package/dist/assets/profiles/memory-focus.json +1 -1
  5. package/dist/assets/profiles/quick.json +1 -1
  6. package/dist/assets/profiles/synthesize.json +15 -0
  7. package/dist/assets/profiles/thorough.json +1 -1
  8. package/dist/assets/prompts/consolidate-system.md +23 -0
  9. package/dist/assets/prompts/contradiction-judge.md +33 -0
  10. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  11. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  12. package/dist/assets/prompts/extract-session.md +6 -2
  13. package/dist/assets/prompts/graph-extract-system.md +1 -0
  14. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  15. package/dist/assets/prompts/memory-infer-system.md +1 -0
  16. package/dist/assets/prompts/memory-infer-user.md +5 -0
  17. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  18. package/dist/assets/prompts/procedural-system.md +44 -0
  19. package/dist/assets/prompts/recombine-system.md +40 -0
  20. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  21. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  24. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  25. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  26. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  31. package/dist/assets/templates/html/health.html +281 -111
  32. package/dist/assets/wiki/ingest-workflow-template.md +17 -10
  33. package/dist/cli/shared.js +28 -0
  34. package/dist/cli.js +15 -5
  35. package/dist/commands/agent/agent-dispatch.js +2 -2
  36. package/dist/commands/agent/agent-support.js +0 -7
  37. package/dist/commands/agent/contribute-cli.js +17 -4
  38. package/dist/commands/env/env-cli.js +16 -24
  39. package/dist/commands/env/secret-cli.js +12 -20
  40. package/dist/commands/feedback-cli.js +15 -6
  41. package/dist/commands/graph/graph-cli.js +5 -13
  42. package/dist/commands/graph/graph.js +76 -72
  43. package/dist/commands/health/checks.js +48 -0
  44. package/dist/commands/health/html-report.js +422 -80
  45. package/dist/commands/health.js +386 -9
  46. package/dist/commands/improve/calibration.js +161 -0
  47. package/dist/commands/improve/consolidate/chunking.js +141 -0
  48. package/dist/commands/improve/consolidate/eligibility.js +81 -0
  49. package/dist/commands/improve/consolidate/merge.js +145 -0
  50. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  51. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  52. package/dist/commands/improve/consolidate.js +635 -660
  53. package/dist/commands/improve/dedup.js +482 -0
  54. package/dist/commands/improve/distill.js +159 -69
  55. package/dist/commands/improve/eligibility.js +434 -0
  56. package/dist/commands/improve/encoding-salience.js +205 -0
  57. package/dist/commands/improve/extract-cli.js +124 -2
  58. package/dist/commands/improve/extract-prompt.js +39 -2
  59. package/dist/commands/improve/extract-watch.js +140 -0
  60. package/dist/commands/improve/extract.js +389 -40
  61. package/dist/commands/improve/feedback-valence.js +54 -0
  62. package/dist/commands/improve/homeostatic.js +467 -0
  63. package/dist/commands/improve/improve-auto-accept.js +109 -6
  64. package/dist/commands/improve/improve-cli.js +35 -60
  65. package/dist/commands/improve/improve-profiles.js +14 -0
  66. package/dist/commands/improve/improve-result-file.js +5 -23
  67. package/dist/commands/improve/improve-session.js +58 -0
  68. package/dist/commands/improve/improve.js +485 -2498
  69. package/dist/commands/improve/locks.js +154 -0
  70. package/dist/commands/improve/loop-stages.js +1083 -0
  71. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  72. package/dist/commands/improve/outcome-loop.js +256 -0
  73. package/dist/commands/improve/preparation.js +1966 -0
  74. package/dist/commands/improve/proactive-maintenance.js +115 -0
  75. package/dist/commands/improve/procedural.js +418 -0
  76. package/dist/commands/improve/recombine.js +813 -0
  77. package/dist/commands/improve/reflect-noise.js +0 -0
  78. package/dist/commands/improve/reflect.js +183 -40
  79. package/dist/commands/improve/salience.js +438 -0
  80. package/dist/commands/improve/triage.js +93 -0
  81. package/dist/commands/lint/agent-linter.js +19 -24
  82. package/dist/commands/lint/base-linter.js +173 -60
  83. package/dist/commands/lint/command-linter.js +19 -24
  84. package/dist/commands/lint/env-key-rules.js +34 -1
  85. package/dist/commands/lint/fact-linter.js +39 -0
  86. package/dist/commands/lint/index.js +31 -13
  87. package/dist/commands/lint/memory-linter.js +1 -1
  88. package/dist/commands/lint/registry.js +7 -2
  89. package/dist/commands/lint/task-linter.js +3 -3
  90. package/dist/commands/lint/workflow-linter.js +26 -1
  91. package/dist/commands/proposal/drain-policies.js +5 -0
  92. package/dist/commands/proposal/drain.js +43 -50
  93. package/dist/commands/proposal/proposal-cli.js +21 -31
  94. package/dist/commands/proposal/proposal.js +5 -0
  95. package/dist/commands/proposal/propose.js +7 -2
  96. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  97. package/dist/commands/proposal/validators/proposals.js +189 -63
  98. package/dist/commands/read/curate.js +414 -94
  99. package/dist/commands/read/knowledge.js +2 -2
  100. package/dist/commands/read/search-cli.js +7 -0
  101. package/dist/commands/read/search.js +1 -0
  102. package/dist/commands/read/show.js +67 -2
  103. package/dist/commands/sources/init.js +36 -9
  104. package/dist/commands/sources/installed-stashes.js +5 -1
  105. package/dist/commands/sources/schema-repair.js +13 -1
  106. package/dist/commands/sources/self-update.js +2 -2
  107. package/dist/commands/sources/stash-cli.js +28 -40
  108. package/dist/commands/sources/stash-skeleton.js +23 -8
  109. package/dist/commands/tasks/tasks-cli.js +19 -27
  110. package/dist/commands/tasks/tasks.js +1 -1
  111. package/dist/commands/wiki-cli.js +21 -35
  112. package/dist/core/asset/asset-registry.js +2 -0
  113. package/dist/core/asset/asset-spec.js +14 -0
  114. package/dist/core/asset/frontmatter.js +166 -167
  115. package/dist/core/asset/markdown.js +8 -0
  116. package/dist/core/authoring-rules.js +92 -0
  117. package/dist/core/common.js +0 -5
  118. package/dist/core/config/config-schema.js +340 -56
  119. package/dist/core/config/config-types.js +3 -3
  120. package/dist/core/config/config.js +28 -7
  121. package/dist/core/events.js +3 -7
  122. package/dist/core/improve-types.js +11 -8
  123. package/dist/core/logs-db.js +10 -66
  124. package/dist/core/parse.js +36 -16
  125. package/dist/core/paths.js +3 -0
  126. package/dist/core/standards/resolve-standards-context.js +87 -0
  127. package/dist/core/standards/resolve-stash-standards.js +99 -0
  128. package/dist/core/standards/resolve-type-conventions.js +66 -0
  129. package/dist/core/state/migrations.js +714 -0
  130. package/dist/core/state-db.js +525 -474
  131. package/dist/indexer/db/db.js +439 -247
  132. package/dist/indexer/db/graph-db.js +129 -86
  133. package/dist/indexer/ensure-index.js +152 -17
  134. package/dist/indexer/graph/graph-boost.js +51 -41
  135. package/dist/indexer/graph/graph-extraction.js +218 -4
  136. package/dist/indexer/index-writer-lock.js +99 -0
  137. package/dist/indexer/indexer.js +123 -221
  138. package/dist/indexer/passes/dir-staleness.js +114 -0
  139. package/dist/indexer/passes/memory-inference.js +10 -3
  140. package/dist/indexer/passes/staleness-detect.js +2 -5
  141. package/dist/indexer/search/db-search.js +15 -4
  142. package/dist/indexer/search/ranking-contributors.js +22 -0
  143. package/dist/indexer/search/ranking.js +4 -0
  144. package/dist/indexer/search/search-source.js +10 -24
  145. package/dist/indexer/search/semantic-status.js +4 -0
  146. package/dist/indexer/walk/matchers.js +9 -0
  147. package/dist/integrations/agent/config.js +6 -53
  148. package/dist/integrations/agent/index.js +2 -18
  149. package/dist/integrations/agent/prompts.js +74 -8
  150. package/dist/integrations/agent/runner-dispatch.js +59 -0
  151. package/dist/integrations/harnesses/claude/session-log.js +11 -1
  152. package/dist/integrations/harnesses/index.js +2 -3
  153. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  154. package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
  155. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
  156. package/dist/integrations/session-logs/index.js +16 -0
  157. package/dist/llm/client.js +45 -15
  158. package/dist/llm/embedder.js +42 -3
  159. package/dist/llm/embedders/deterministic.js +66 -0
  160. package/dist/llm/embedders/local.js +66 -2
  161. package/dist/llm/feature-gate.js +8 -4
  162. package/dist/llm/graph-extract.js +67 -44
  163. package/dist/llm/memory-infer.js +38 -30
  164. package/dist/llm/metadata-enhance.js +44 -31
  165. package/dist/llm/structured-call.js +49 -0
  166. package/dist/output/context.js +5 -5
  167. package/dist/output/renderers.js +73 -1
  168. package/dist/output/shapes/curate.js +14 -2
  169. package/dist/output/shapes/passthrough.js +0 -1
  170. package/dist/output/text/helpers.js +16 -1
  171. package/dist/registry/providers/skills-sh.js +21 -147
  172. package/dist/registry/providers/static-index.js +15 -157
  173. package/dist/registry/resolve.js +22 -9
  174. package/dist/runtime.js +25 -1
  175. package/dist/scripts/migrate-storage.js +2136 -1596
  176. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +682 -433
  177. package/dist/setup/setup.js +29 -8
  178. package/dist/sources/providers/filesystem.js +0 -1
  179. package/dist/sources/providers/git-install.js +206 -0
  180. package/dist/sources/providers/git-provider.js +234 -0
  181. package/dist/sources/providers/git-stash.js +248 -0
  182. package/dist/sources/providers/git.js +10 -661
  183. package/dist/sources/providers/npm.js +2 -6
  184. package/dist/sources/providers/sync-from-ref.js +9 -1
  185. package/dist/sources/providers/tar-utils.js +16 -8
  186. package/dist/sources/providers/website.js +2 -3
  187. package/dist/sources/website-ingest.js +51 -9
  188. package/dist/sources/wiki-fetchers/registry.js +53 -0
  189. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  190. package/dist/storage/database.js +45 -10
  191. package/dist/storage/managed-db.js +82 -0
  192. package/dist/storage/repositories/registry-cache.js +92 -0
  193. package/dist/storage/sqlite-pragmas.js +146 -0
  194. package/dist/tasks/backends/cron.js +1 -1
  195. package/dist/tasks/backends/launchd.js +1 -1
  196. package/dist/tasks/backends/schtasks.js +1 -1
  197. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  198. package/dist/tasks/runner.js +5 -13
  199. package/dist/wiki/wiki.js +37 -0
  200. package/dist/workflows/db.js +3 -4
  201. package/dist/workflows/runtime/runs.js +1 -117
  202. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  203. package/dist/workflows/validate-summary.js +2 -7
  204. package/docs/data-and-telemetry.md +1 -0
  205. package/package.json +9 -7
  206. package/dist/commands/db-cli.js +0 -23
  207. package/dist/indexer/db/db-backup.js +0 -376
@@ -0,0 +1,92 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { rethrowIfTestIsolationError } from "../../core/errors.js";
5
+ import { closeDatabase, getRegistryIndexCache, openIndexDatabase, upsertRegistryIndexCache } from "../../indexer/db/db.js";
6
+ /**
7
+ * RAII-style lifecycle helper for the registry cache DB. Opens the DB (treating
8
+ * a failed open exactly like the legacy fall-through: the bun-test isolation
9
+ * guard is re-thrown, any other failure yields `db = undefined`), runs `fn`,
10
+ * and guarantees the DB is closed in a `finally` after `fn` has fully settled
11
+ * (the await is required: the callbacks are async, and closing before they
12
+ * settle would tear the DB down mid-write).
13
+ */
14
+ export async function withRegistryCacheDb(fn) {
15
+ let db;
16
+ try {
17
+ db = openIndexDatabase();
18
+ }
19
+ catch (err) {
20
+ // Never mask the bun-test isolation guard as "DB unavailable".
21
+ rethrowIfTestIsolationError(err);
22
+ db = undefined;
23
+ }
24
+ try {
25
+ return await fn(db);
26
+ }
27
+ finally {
28
+ if (db) {
29
+ try {
30
+ closeDatabase(db);
31
+ }
32
+ catch {
33
+ /* ignore */
34
+ }
35
+ }
36
+ }
37
+ }
38
+ /**
39
+ * Shared registry index-cache fetch template. Opens the cache DB, returns a
40
+ * fresh cache hit when present, otherwise fetches live (writing the result back
41
+ * to the cache best-effort), and falls back to a stale cache row when the fetch
42
+ * fails. Behaviour-preserving extraction of the logic previously duplicated in
43
+ * `skills-sh.ts` (`fetchSkills`) and `static-index.ts` (`loadIndex`).
44
+ */
45
+ export async function fetchCachedJson(opts) {
46
+ const { cacheKey, ttlMs, parseCache, fetchFresh } = opts;
47
+ return withRegistryCacheDb(async (db) => {
48
+ // ── Step 1: Try DB cache (index.db) ─────────────────────────────────────
49
+ let dbCacheResult;
50
+ try {
51
+ if (db) {
52
+ dbCacheResult = getRegistryIndexCache(db, cacheKey, ttlMs);
53
+ }
54
+ }
55
+ catch (err) {
56
+ // Never mask the bun-test isolation guard as "DB unavailable" — see
57
+ // rethrowIfTestIsolationError in src/core/errors.ts. Without this, a
58
+ // leaky test silently gets a cold cache instead of the loud
59
+ // TEST_ISOLATION_MISSING failure the guard intends.
60
+ rethrowIfTestIsolationError(err);
61
+ // index.db read failed (pre-migration install or test env) — fall through
62
+ }
63
+ if (dbCacheResult) {
64
+ const cached = parseCache(dbCacheResult.indexJson, { stale: false });
65
+ if (cached !== undefined) {
66
+ return cached;
67
+ }
68
+ }
69
+ // ── Step 2: Fetch fresh ──────────────────────────────────────────────────
70
+ try {
71
+ const { value, cacheJson, cacheOpts } = await fetchFresh(db);
72
+ if (db) {
73
+ try {
74
+ upsertRegistryIndexCache(db, cacheKey, cacheJson, cacheOpts);
75
+ }
76
+ catch {
77
+ /* best-effort */
78
+ }
79
+ }
80
+ return value;
81
+ }
82
+ catch (err) {
83
+ // Fetch failed — use stale DB cache if available.
84
+ if (dbCacheResult) {
85
+ const stale = parseCache(dbCacheResult.indexJson, { stale: true });
86
+ if (stale !== undefined)
87
+ return stale;
88
+ }
89
+ throw err;
90
+ }
91
+ });
92
+ }
@@ -0,0 +1,146 @@
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
+ * Shared SQLite PRAGMA application + journal-mode resolution (#628).
6
+ *
7
+ * Every AKM SQLite opener applies the same opening PRAGMAs: a journal mode, a
8
+ * 30 s busy_timeout, and (for most) foreign_keys = ON. Historically each opener
9
+ * hard-coded `PRAGMA journal_mode = WAL`. WAL is impossible on network
10
+ * filesystems (NFS/SMB) because its `-shm` shared-memory wal-index cannot be
11
+ * mmap'd over a network mount, so AKM could not run with its data dir on a
12
+ * network share.
13
+ *
14
+ * This module centralises that PRAGMA block behind {@link applyStandardPragmas}
15
+ * and makes the journal mode configurable via the `AKM_SQLITE_JOURNAL_MODE`
16
+ * env var (WAL | DELETE | TRUNCATE; default WAL = unchanged behaviour). When the
17
+ * mode is the WAL default and the data directory is detected to live on a
18
+ * network filesystem, it auto-falls-back to DELETE with a one-line warning.
19
+ *
20
+ * Boundary note: this is a PLAIN module, not a runtime-boundary file. It only
21
+ * does pure string work plus `db.exec()` (allowed everywhere). The single
22
+ * runtime primitive it needs — a filesystem-type probe (statfs) — lives in
23
+ * src/runtime.ts and is injected here, keeping the network-FS classifier a
24
+ * pure, unit-testable function.
25
+ *
26
+ * @module storage/sqlite-pragmas
27
+ */
28
+ import { warn } from "../core/warn.js";
29
+ import { statfsType } from "../runtime.js";
30
+ const VALID_MODES = new Set(["WAL", "DELETE", "TRUNCATE"]);
31
+ // One-shot warning guards so a misconfigured env var or a network-FS fallback
32
+ // each emit AT MOST ONCE per process rather than on every db open.
33
+ let warnedInvalid = false;
34
+ let warnedNetworkFallback = false;
35
+ /**
36
+ * Resolve a raw `AKM_SQLITE_JOURNAL_MODE` value to a canonical {@link JournalMode}.
37
+ *
38
+ * PURE and unit-testable: the raw string is passed in (not read from
39
+ * `process.env` here). Trims + uppercases; an empty/undefined value yields the
40
+ * WAL default; a recognised value yields its canonical form; any other
41
+ * non-empty value warns once and falls back to WAL. Never throws.
42
+ */
43
+ export function resolveJournalMode(raw) {
44
+ if (raw === undefined)
45
+ return "WAL";
46
+ const normalized = raw.trim().toUpperCase();
47
+ if (normalized === "")
48
+ return "WAL";
49
+ if (VALID_MODES.has(normalized)) {
50
+ return normalized;
51
+ }
52
+ warnInvalidJournalModeOnce(raw);
53
+ return "WAL";
54
+ }
55
+ /**
56
+ * The single env-reading seam: resolve the configured journal mode from
57
+ * `process.env.AKM_SQLITE_JOURNAL_MODE`. Read at call time (per open) so tests
58
+ * that set the env per-case see the right value and we avoid stale-env flakes.
59
+ */
60
+ export function resolveConfiguredJournalMode() {
61
+ return resolveJournalMode(process.env.AKM_SQLITE_JOURNAL_MODE);
62
+ }
63
+ function warnInvalidJournalModeOnce(raw) {
64
+ if (warnedInvalid)
65
+ return;
66
+ warnedInvalid = true;
67
+ warn(`[akm] invalid AKM_SQLITE_JOURNAL_MODE=${JSON.stringify(raw)} — using WAL (valid: WAL, DELETE, TRUNCATE)`);
68
+ }
69
+ // Known Linux f_type magic numbers for network filesystems. node's statfs
70
+ // normalises `type` to this numeric f_type magic on all platforms.
71
+ const FS_MAGIC_NFS = 0x6969; // 26985 — NFS
72
+ const FS_MAGIC_SMB = 0x517b; // 20859 — older SMB_SUPER_MAGIC
73
+ const FS_MAGIC_CIFS = 0xff534d42; // 4283649346 — SMB/CIFS
74
+ const FS_MAGIC_SMB2 = 0xfe534d42; // 4267272514 — SMB2
75
+ const FS_MAGIC_FUSE = 0x65735546; // 1702057286 — FUSE (sshfs + many network FUSE mounts)
76
+ const NETWORK_FS_MAGICS = new Set([
77
+ FS_MAGIC_NFS,
78
+ FS_MAGIC_SMB,
79
+ FS_MAGIC_CIFS,
80
+ FS_MAGIC_SMB2,
81
+ // FUSE is a judgment call: it backs BOTH network mounts (sshfs) and local
82
+ // mounts (some encrypted/overlay FS). Treating it as network falls back to
83
+ // DELETE — conservative-but-safe (DELETE works everywhere; the only cost is
84
+ // losing WAL concurrency). An operator can always force WAL via the env var.
85
+ FS_MAGIC_FUSE,
86
+ ]);
87
+ /**
88
+ * PURE classifier: is `fsType` a known network-filesystem magic number?
89
+ * Returns false for `undefined` (probe failed/unsupported) and for local
90
+ * magics (ext4 0xEF53, btrfs, xfs, tmpfs, apfs, …). Unit-testable with
91
+ * injected magic numbers — no real mount required.
92
+ */
93
+ export function isNetworkFilesystem(fsType) {
94
+ if (fsType === undefined)
95
+ return false;
96
+ return NETWORK_FS_MAGICS.has(fsType);
97
+ }
98
+ /**
99
+ * Apply AKM's standard opening PRAGMAs to `db`, in order:
100
+ * 1. `journal_mode` = the configured mode (with WAL→DELETE network-FS fallback)
101
+ * 2. `busy_timeout = 30000`
102
+ * 3. `foreign_keys = ON` (unless `opts.foreignKeys === false`)
103
+ * 4. `synchronous = FULL` (only in a rollback-journal mode — DELETE/TRUNCATE)
104
+ *
105
+ * Returns the effective {@link JournalMode} so callers/tests can assert it.
106
+ *
107
+ * `synchronous = FULL` is set explicitly only in DELETE/TRUNCATE so durability
108
+ * intent is clear: rollback journals need FULL for crash-durability across
109
+ * power loss, whereas WAL is durable at NORMAL. SQLite's default synchronous is
110
+ * already FULL when unset, so this never changes WAL-default behaviour — the
111
+ * WAL path emits no `synchronous` pragma, exactly as before.
112
+ */
113
+ export function applyStandardPragmas(db, opts = {}) {
114
+ let mode = resolveConfiguredJournalMode();
115
+ // Network-FS fallback only fires for the WAL default and only when we have a
116
+ // directory to probe. An explicitly-requested DELETE/TRUNCATE is never
117
+ // overridden, and a failed/unsupported probe (undefined) keeps WAL.
118
+ if (mode === "WAL" && opts.dataDir) {
119
+ const probe = opts.fsTypeProbe ?? statfsType;
120
+ if (isNetworkFilesystem(probe(opts.dataDir))) {
121
+ mode = "DELETE";
122
+ warnNetworkFallbackOnce(opts.dataDir);
123
+ }
124
+ }
125
+ // PRAGMAs must run before any DDL or DML. busy_timeout is applied FIRST so a
126
+ // journal-mode change that must reclaim a leftover `-wal` file (WAL→DELETE on
127
+ // reopen of an unclean WAL db, e.g. after a crash) can wait out a transient
128
+ // lock instead of failing immediately with SQLITE_BUSY. For the WAL default
129
+ // this is a no-op (WAL→WAL changes nothing), so byte-identical behaviour is
130
+ // preserved.
131
+ db.exec("PRAGMA busy_timeout = 30000");
132
+ db.exec(`PRAGMA journal_mode = ${mode}`);
133
+ if (opts.foreignKeys !== false) {
134
+ db.exec("PRAGMA foreign_keys = ON");
135
+ }
136
+ if (mode !== "WAL") {
137
+ db.exec("PRAGMA synchronous = FULL");
138
+ }
139
+ return mode;
140
+ }
141
+ function warnNetworkFallbackOnce(dataDir) {
142
+ if (warnedNetworkFallback)
143
+ return;
144
+ warnedNetworkFallback = true;
145
+ warn(`[akm] network filesystem detected at ${dataDir} — WAL unsupported, using DELETE journal mode`);
146
+ }
@@ -29,7 +29,7 @@ import fs from "node:fs";
29
29
  import path from "node:path";
30
30
  import { ConfigError } from "../../core/errors.js";
31
31
  import { getTaskLogDir } from "../../core/paths.js";
32
- import { resolveAkmInvocation } from "../resolveAkmBin.js";
32
+ import { resolveAkmInvocation } from "../resolve-akm-bin.js";
33
33
  import { parseSchedule, translateToCron } from "../schedule.js";
34
34
  const BEGIN = (id) => `# akm:task ${id} BEGIN`;
35
35
  const END = (id) => `# akm:task ${id} END`;
@@ -27,7 +27,7 @@ import path from "node:path";
27
27
  import launchdTemplate from "../../assets/backends/launchd-template.xml" with { type: "text" };
28
28
  import { ConfigError } from "../../core/errors.js";
29
29
  import { getTaskLogDir } from "../../core/paths.js";
30
- import { resolveAkmInvocation } from "../resolveAkmBin.js";
30
+ import { resolveAkmInvocation } from "../resolve-akm-bin.js";
31
31
  import { parseSchedule, translateToLaunchd } from "../schedule.js";
32
32
  import { escapeXml, nodeExec, nodeFs } from "./exec-utils.js";
33
33
  export const LAUNCHD_LABEL_PREFIX = "com.akm.task.";
@@ -35,7 +35,7 @@ import path from "node:path";
35
35
  import schtasksTemplate from "../../assets/backends/schtasks-template.xml" with { type: "text" };
36
36
  import { ConfigError } from "../../core/errors.js";
37
37
  import { getTaskLogDir } from "../../core/paths.js";
38
- import { resolveAkmInvocation } from "../resolveAkmBin.js";
38
+ import { resolveAkmInvocation } from "../resolve-akm-bin.js";
39
39
  import { parseSchedule, translateToSchtasks } from "../schedule.js";
40
40
  import { escapeXml, nodeExec, nodeFs } from "./exec-utils.js";
41
41
  export const DEFAULT_FOLDER_PREFIX = "\\akm\\";
@@ -44,8 +44,8 @@ export function resolveAkmInvocation(options = {}) {
44
44
  /**
45
45
  * From the URL of a module inside `src/tasks/` figure out the CLI entry.
46
46
  *
47
- * • dev `…/src/tasks/resolveAkmBin.ts` → `…/src/cli.ts`
48
- * • build `…/dist/tasks/resolveAkmBin.js` → `…/dist/cli.js`
47
+ * • dev `…/src/tasks/resolve-akm-bin.ts` → `…/src/cli.ts`
48
+ * • build `…/dist/tasks/resolve-akm-bin.js` → `…/dist/cli.js`
49
49
  */
50
50
  function resolveCliEntry(moduleUrl) {
51
51
  let modulePath;
@@ -33,7 +33,7 @@ import { loadConfig } from "../core/config/config.js";
33
33
  import { NotFoundError, rethrowIfTestIsolationError } from "../core/errors.js";
34
34
  import { buildTaskRunId, insertTaskLogLines, openLogsDatabase, } from "../core/logs-db.js";
35
35
  import { getTaskLogDir } from "../core/paths.js";
36
- import { getTaskHistory, openStateDatabase, queryTaskHistory, upsertTaskHistory } from "../core/state-db.js";
36
+ import { getTaskHistory, queryTaskHistory, upsertTaskHistory, withStateDb } from "../core/state-db.js";
37
37
  import { error } from "../core/warn.js";
38
38
  import { requireAgentProfile, runAgent } from "../integrations/agent/index.js";
39
39
  import { resolveProcessAgentProfile } from "../integrations/agent/config.js";
@@ -460,8 +460,7 @@ function persistRunLog(input) {
460
460
  // ── history ─────────────────────────────────────────────────────────────────
461
461
  function appendHistory(result) {
462
462
  try {
463
- const db = openStateDatabase();
464
- try {
463
+ withStateDb((db) => {
465
464
  upsertTaskHistory(db, {
466
465
  task_id: result.id,
467
466
  status: result.status,
@@ -477,10 +476,7 @@ function appendHistory(result) {
477
476
  profile: result.target.kind === "prompt" ? result.target.profile : undefined,
478
477
  }),
479
478
  });
480
- }
481
- finally {
482
- db.close();
483
- }
479
+ });
484
480
  }
485
481
  catch (err) {
486
482
  rethrowIfTestIsolationError(err);
@@ -488,8 +484,7 @@ function appendHistory(result) {
488
484
  }
489
485
  }
490
486
  export function readTaskHistory(options = {}) {
491
- const db = openStateDatabase();
492
- try {
487
+ return withStateDb((db) => {
493
488
  let rows;
494
489
  if (options.id) {
495
490
  const row = getTaskHistory(db, options.id);
@@ -503,10 +498,7 @@ export function readTaskHistory(options = {}) {
503
498
  return rows.slice(0, options.limit);
504
499
  }
505
500
  return rows;
506
- }
507
- finally {
508
- db.close();
509
- }
501
+ });
510
502
  }
511
503
  /**
512
504
  * Convert a `TaskHistoryRow` from state.db back to a `TaskRunResult` shape
package/dist/wiki/wiki.js CHANGED
@@ -224,6 +224,43 @@ function readSchemaDescription(wikiDir) {
224
224
  return undefined;
225
225
  }
226
226
  }
227
+ /**
228
+ * Load a wiki's `schema.md` body and frontmatter.
229
+ *
230
+ * Unlike {@link readSchemaDescription} (which returns only the frontmatter
231
+ * `description` for `listWikis` summaries), this returns the markdown **body**
232
+ * — everything after the closing `---` of the frontmatter — which is where the
233
+ * page contract, operations, and hard rules live. The body is the rulebook
234
+ * injected into the write-time prompt for wiki-page edits.
235
+ *
236
+ * Swallow-and-degrade like the existing reader: a missing file, an unresolvable
237
+ * wiki dir, or malformed content yields `{ body: "", frontmatter: {} }`. Never
238
+ * throws.
239
+ */
240
+ export function loadWikiSchema(stashRoot, name) {
241
+ const empty = { body: "", frontmatter: {} };
242
+ let wikiDir;
243
+ try {
244
+ wikiDir = resolveWikiDir(stashRoot, name);
245
+ }
246
+ catch {
247
+ return empty;
248
+ }
249
+ let raw;
250
+ try {
251
+ raw = fs.readFileSync(path.join(wikiDir, SCHEMA_MD), "utf8");
252
+ }
253
+ catch {
254
+ return empty;
255
+ }
256
+ try {
257
+ const parsed = parseFrontmatter(raw);
258
+ return { body: parsed.content, frontmatter: parsed.data };
259
+ }
260
+ catch {
261
+ return empty;
262
+ }
263
+ }
227
264
  function toIsoDate(ms) {
228
265
  return new Date(ms).toISOString();
229
266
  }
@@ -6,6 +6,7 @@ import path from "node:path";
6
6
  import { getWorkflowDbPath } from "../core/paths.js";
7
7
  import { openDatabase } from "../storage/database.js";
8
8
  import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
9
+ import { applyStandardPragmas } from "../storage/sqlite-pragmas.js";
9
10
  /**
10
11
  * workflow.db — Durable SQLite database for workflow run state.
11
12
  *
@@ -46,12 +47,10 @@ export function openWorkflowDatabase(dbPath = getWorkflowDbPath()) {
46
47
  fs.mkdirSync(dir, { recursive: true });
47
48
  }
48
49
  const db = openDatabase(dbPath);
49
- db.exec("PRAGMA journal_mode = WAL");
50
50
  // #589: 30 s busy timeout, matching index.db / state.db. Without it the
51
51
  // default is 0 ms, so any concurrent writer fails immediately with
52
- // SQLITE_BUSY.
53
- db.exec("PRAGMA busy_timeout = 30000");
54
- db.exec("PRAGMA foreign_keys = ON");
52
+ // SQLITE_BUSY. #628: journal_mode is configurable via AKM_SQLITE_JOURNAL_MODE.
53
+ applyStandardPragmas(db, { dataDir: dir });
55
54
  ensureBaseSchema(db);
56
55
  runMigrations(db);
57
56
  return db;
@@ -2,23 +2,16 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { randomUUID } from "node:crypto";
5
- import fs from "node:fs";
6
5
  import { parseAssetRef } from "../../core/asset/asset-ref.js";
7
6
  import { loadConfig } from "../../core/config/config.js";
8
7
  import { NotFoundError, UsageError } from "../../core/errors.js";
9
8
  import { appendEvent } from "../../core/events.js";
10
- import { getDbPath } from "../../core/paths.js";
11
- import { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
12
- import { resolveSourceEntries } from "../../indexer/search/search-source.js";
13
- import { resolveSourcesForOrigin } from "../../registry/origin-resolve.js";
14
- import { resolveAssetPath } from "../../sources/resolve.js";
15
9
  import { withWorkflowRunsRepo, } from "../../storage/repositories/workflow-runs-repository.js";
16
- import { formatWorkflowErrors } from "../authoring/authoring.js";
17
10
  import { getCurrentWorkflowScopeKey } from "../authoring/scope-key.js";
18
- import { parseWorkflow } from "../parser.js";
19
11
  import { validateStepSummary } from "../validate-summary.js";
20
12
  import { resolveAgentIdentity } from "./agent-identity.js";
21
13
  import { evaluateCheckin } from "./checkin.js";
14
+ import { loadWorkflowAsset, resolveWorkflowEntryId } from "./workflow-asset-loader.js";
22
15
  export async function startWorkflowRun(ref, params = {}, options) {
23
16
  const asset = await loadWorkflowAsset(ref);
24
17
  return withWorkflowRunsRepo(async (repo) => {
@@ -299,115 +292,6 @@ async function resolveRunSpecifier(repo, specifier, params) {
299
292
  const started = await startWorkflowRun(ref, params ?? {});
300
293
  return { run: readWorkflowRun(repo, started.run.id), autoStarted: true };
301
294
  }
302
- async function loadWorkflowAsset(ref) {
303
- const parsed = parseAssetRef(ref);
304
- if (parsed.type !== "workflow") {
305
- throw new UsageError(`Expected a workflow ref (workflow:<name>), got "${ref}".`);
306
- }
307
- const config = loadConfig();
308
- const allSources = resolveSourceEntries(undefined, config);
309
- const searchSources = resolveSourcesForOrigin(parsed.origin, allSources);
310
- let assetPath;
311
- let sourcePath;
312
- for (const source of searchSources) {
313
- try {
314
- assetPath = await resolveAssetPath(source.path, "workflow", parsed.name);
315
- sourcePath = source.path;
316
- break;
317
- }
318
- catch {
319
- /* continue */
320
- }
321
- }
322
- if (!assetPath) {
323
- throw new NotFoundError(`Workflow not found for ref: workflow:${parsed.name}`);
324
- }
325
- const resolvedSourcePath = sourcePath ?? config.stashDir ?? assetPath;
326
- const fullRef = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
327
- const cached = readWorkflowDocumentFromIndex(resolvedSourcePath, fullRef);
328
- const document = cached ?? loadWorkflowDocumentFromDisk(assetPath);
329
- return projectAsset(document, fullRef, assetPath, resolvedSourcePath);
330
- }
331
- function loadWorkflowDocumentFromDisk(assetPath) {
332
- const content = fs.readFileSync(assetPath, "utf8");
333
- const result = parseWorkflow(content, { path: assetPath });
334
- if (!result.ok) {
335
- throw new UsageError(formatWorkflowErrors(assetPath, result.errors));
336
- }
337
- return result.document;
338
- }
339
- function readWorkflowDocumentFromIndex(sourcePath, ref) {
340
- const dbPath = getDbPath();
341
- if (!fs.existsSync(dbPath))
342
- return null;
343
- const db = openExistingDatabase(dbPath);
344
- try {
345
- const parsed = parseAssetRef(ref);
346
- const entryKey = `${sourcePath}:${parsed.type}:${parsed.name}`;
347
- const row = db
348
- .prepare(`SELECT wd.document_json AS document_json
349
- FROM workflow_documents wd
350
- JOIN entries e ON e.id = wd.entry_id
351
- WHERE e.entry_type = 'workflow' AND e.entry_key = ?
352
- LIMIT 1`)
353
- .get(entryKey);
354
- if (!row)
355
- return null;
356
- try {
357
- return JSON.parse(row.document_json);
358
- }
359
- catch {
360
- return null;
361
- }
362
- }
363
- finally {
364
- closeDatabase(db);
365
- }
366
- }
367
- function projectAsset(doc, ref, assetPath, sourcePath) {
368
- return {
369
- ref,
370
- path: assetPath,
371
- sourcePath,
372
- title: doc.title,
373
- ...(doc.parameters
374
- ? {
375
- parameters: doc.parameters.map((p) => ({
376
- name: p.name,
377
- ...(p.description ? { description: p.description } : {}),
378
- })),
379
- }
380
- : {}),
381
- steps: doc.steps.map((s) => ({
382
- id: s.id,
383
- title: s.title,
384
- instructions: s.instructions.text,
385
- ...(s.completionCriteria ? { completionCriteria: s.completionCriteria.map((c) => c.text) } : {}),
386
- sequenceIndex: s.sequenceIndex,
387
- })),
388
- };
389
- }
390
- function resolveWorkflowEntryId(sourcePath, ref) {
391
- const dbPath = getDbPath();
392
- if (!fs.existsSync(dbPath))
393
- return null;
394
- const db = openExistingDatabase(dbPath);
395
- try {
396
- const parsed = parseAssetRef(ref);
397
- const entryKey = `${sourcePath}:${parsed.type}:${parsed.name}`;
398
- const row = db
399
- .prepare(`SELECT id
400
- FROM entries
401
- WHERE entry_type = 'workflow'
402
- AND entry_key = ?
403
- LIMIT 1`)
404
- .get(entryKey);
405
- return row?.id ?? null;
406
- }
407
- finally {
408
- closeDatabase(db);
409
- }
410
- }
411
295
  function readWorkflowRun(repo, runId) {
412
296
  const run = repo.getRunById(runId);
413
297
  if (!run) {
@@ -0,0 +1,125 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import fs from "node:fs";
5
+ import { parseAssetRef } from "../../core/asset/asset-ref.js";
6
+ import { loadConfig } from "../../core/config/config.js";
7
+ import { NotFoundError, UsageError } from "../../core/errors.js";
8
+ import { getDbPath } from "../../core/paths.js";
9
+ import { resolveSourceEntries } from "../../indexer/search/search-source.js";
10
+ import { resolveSourcesForOrigin } from "../../registry/origin-resolve.js";
11
+ import { resolveAssetPath } from "../../sources/resolve.js";
12
+ import { withIndexDb } from "../../storage/repositories/index-db.js";
13
+ import { formatWorkflowErrors } from "../authoring/authoring.js";
14
+ import { parseWorkflow } from "../parser.js";
15
+ /**
16
+ * Resolve a `workflow:<name>` ref to a fully-projected {@link WorkflowAsset}.
17
+ *
18
+ * Prefers the parsed document cached in `index.db` (fast path) and falls back to
19
+ * reading + parsing the source file from disk. Pure loading/parsing concern —
20
+ * extracted from the run repository so run orchestration no longer owns asset
21
+ * resolution.
22
+ */
23
+ export async function loadWorkflowAsset(ref) {
24
+ const parsed = parseAssetRef(ref);
25
+ if (parsed.type !== "workflow") {
26
+ throw new UsageError(`Expected a workflow ref (workflow:<name>), got "${ref}".`);
27
+ }
28
+ const config = loadConfig();
29
+ const allSources = resolveSourceEntries(undefined, config);
30
+ const searchSources = resolveSourcesForOrigin(parsed.origin, allSources);
31
+ let assetPath;
32
+ let sourcePath;
33
+ for (const source of searchSources) {
34
+ try {
35
+ assetPath = await resolveAssetPath(source.path, "workflow", parsed.name);
36
+ sourcePath = source.path;
37
+ break;
38
+ }
39
+ catch {
40
+ /* continue */
41
+ }
42
+ }
43
+ if (!assetPath) {
44
+ throw new NotFoundError(`Workflow not found for ref: workflow:${parsed.name}`);
45
+ }
46
+ const resolvedSourcePath = sourcePath ?? config.stashDir ?? assetPath;
47
+ const fullRef = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
48
+ const cached = readWorkflowDocumentFromIndex(resolvedSourcePath, fullRef);
49
+ const document = cached ?? loadWorkflowDocumentFromDisk(assetPath);
50
+ return projectAsset(document, fullRef, assetPath, resolvedSourcePath);
51
+ }
52
+ /**
53
+ * Resolve the `entries.id` for an indexed workflow, or null when the index
54
+ * database does not yet exist or has no matching entry.
55
+ */
56
+ export function resolveWorkflowEntryId(sourcePath, ref) {
57
+ if (!fs.existsSync(getDbPath()))
58
+ return null;
59
+ const parsed = parseAssetRef(ref);
60
+ const entryKey = `${sourcePath}:${parsed.type}:${parsed.name}`;
61
+ return withIndexDb((db) => {
62
+ const row = db
63
+ .prepare(`SELECT id
64
+ FROM entries
65
+ WHERE entry_type = 'workflow'
66
+ AND entry_key = ?
67
+ LIMIT 1`)
68
+ .get(entryKey);
69
+ return row?.id ?? null;
70
+ });
71
+ }
72
+ function loadWorkflowDocumentFromDisk(assetPath) {
73
+ const content = fs.readFileSync(assetPath, "utf8");
74
+ const result = parseWorkflow(content, { path: assetPath });
75
+ if (!result.ok) {
76
+ throw new UsageError(formatWorkflowErrors(assetPath, result.errors));
77
+ }
78
+ return result.document;
79
+ }
80
+ function readWorkflowDocumentFromIndex(sourcePath, ref) {
81
+ if (!fs.existsSync(getDbPath()))
82
+ return null;
83
+ const parsed = parseAssetRef(ref);
84
+ const entryKey = `${sourcePath}:${parsed.type}:${parsed.name}`;
85
+ return withIndexDb((db) => {
86
+ const row = db
87
+ .prepare(`SELECT wd.document_json AS document_json
88
+ FROM workflow_documents wd
89
+ JOIN entries e ON e.id = wd.entry_id
90
+ WHERE e.entry_type = 'workflow' AND e.entry_key = ?
91
+ LIMIT 1`)
92
+ .get(entryKey);
93
+ if (!row)
94
+ return null;
95
+ try {
96
+ return JSON.parse(row.document_json);
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ });
102
+ }
103
+ function projectAsset(doc, ref, assetPath, sourcePath) {
104
+ return {
105
+ ref,
106
+ path: assetPath,
107
+ sourcePath,
108
+ title: doc.title,
109
+ ...(doc.parameters
110
+ ? {
111
+ parameters: doc.parameters.map((p) => ({
112
+ name: p.name,
113
+ ...(p.description ? { description: p.description } : {}),
114
+ })),
115
+ }
116
+ : {}),
117
+ steps: doc.steps.map((s) => ({
118
+ id: s.id,
119
+ title: s.title,
120
+ instructions: s.instructions.text,
121
+ ...(s.completionCriteria ? { completionCriteria: s.completionCriteria.map((c) => c.text) } : {}),
122
+ sequenceIndex: s.sequenceIndex,
123
+ })),
124
+ };
125
+ }