akm-cli 0.9.0-rc.2 → 0.9.0-rc.3

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 (88) hide show
  1. package/CHANGELOG.md +34 -8
  2. package/dist/assets/improve-strategies/catchup.json +3 -1
  3. package/dist/assets/improve-strategies/consolidate.json +3 -1
  4. package/dist/assets/improve-strategies/frequent.json +3 -1
  5. package/dist/assets/improve-strategies/graph-refresh.json +3 -1
  6. package/dist/assets/improve-strategies/memory-focus.json +4 -1
  7. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
  8. package/dist/assets/improve-strategies/quick.json +3 -1
  9. package/dist/assets/improve-strategies/recombine-only.json +2 -0
  10. package/dist/assets/improve-strategies/reflect-distill.json +1 -0
  11. package/dist/assets/improve-strategies/synthesize.json +2 -0
  12. package/dist/assets/tasks/core/backup.yml +2 -2
  13. package/dist/cli/config-migrate.js +554 -26
  14. package/dist/cli.js +3 -1
  15. package/dist/commands/backup-cli.js +6 -4
  16. package/dist/commands/config-cli.js +10 -3
  17. package/dist/commands/feedback-cli.js +24 -7
  18. package/dist/commands/health/checks.js +94 -15
  19. package/dist/commands/health/surfaces.js +2 -1
  20. package/dist/commands/improve/anti-collapse.js +3 -3
  21. package/dist/commands/improve/collapse-detector.js +5 -5
  22. package/dist/commands/improve/consolidate.js +123 -66
  23. package/dist/commands/improve/distill/promote-memory.js +8 -6
  24. package/dist/commands/improve/distill/quality-gate.js +1 -1
  25. package/dist/commands/improve/distill-guards.js +1 -1
  26. package/dist/commands/improve/distill-promotion-policy.js +32 -26
  27. package/dist/commands/improve/distill.js +52 -36
  28. package/dist/commands/improve/eligibility.js +9 -8
  29. package/dist/commands/improve/extract-prompt.js +2 -2
  30. package/dist/commands/improve/extract.js +43 -11
  31. package/dist/commands/improve/improve-auto-accept.js +14 -28
  32. package/dist/commands/improve/improve-cli.js +4 -2
  33. package/dist/commands/improve/improve-strategies.js +2 -1
  34. package/dist/commands/improve/improve.js +49 -8
  35. package/dist/commands/improve/loop-stages.js +11 -6
  36. package/dist/commands/improve/preparation.js +51 -17
  37. package/dist/commands/improve/procedural.js +10 -6
  38. package/dist/commands/improve/recombine.js +31 -7
  39. package/dist/commands/improve/reflect.js +24 -14
  40. package/dist/commands/improve/salience.js +5 -1
  41. package/dist/commands/improve/source-identity.js +43 -0
  42. package/dist/commands/migrate-cli.js +36 -0
  43. package/dist/commands/mv-cli.js +647 -258
  44. package/dist/commands/proposal/drain.js +16 -4
  45. package/dist/commands/proposal/proposal-cli.js +3 -2
  46. package/dist/commands/proposal/proposal.js +16 -55
  47. package/dist/commands/proposal/repository.js +664 -58
  48. package/dist/commands/read/curate.js +4 -2
  49. package/dist/commands/read/knowledge.js +4 -1
  50. package/dist/commands/read/search.js +6 -2
  51. package/dist/commands/read/show.js +8 -7
  52. package/dist/commands/sources/self-update.js +156 -112
  53. package/dist/commands/sources/sources-cli.js +7 -2
  54. package/dist/core/common.js +36 -3
  55. package/dist/core/config/config-io.js +2 -2
  56. package/dist/core/config/config-schema.js +15 -3
  57. package/dist/core/config/config.js +14 -13
  58. package/dist/core/file-lock.js +2 -2
  59. package/dist/core/migration-backup.js +816 -171
  60. package/dist/core/migration-operation.js +44 -0
  61. package/dist/core/state/migrations.js +4 -7
  62. package/dist/core/state-db.js +22 -7
  63. package/dist/core/write-source.js +86 -18
  64. package/dist/indexer/db/db.js +109 -53
  65. package/dist/indexer/index-writer-lock.js +38 -37
  66. package/dist/indexer/index-written-assets.js +73 -56
  67. package/dist/indexer/indexer.js +5 -1
  68. package/dist/indexer/search/search-source.js +2 -2
  69. package/dist/indexer/usage/usage-events.js +8 -2
  70. package/dist/integrations/agent/engine-resolution.js +14 -7
  71. package/dist/scripts/migrate-storage.js +867 -990
  72. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +405 -566
  73. package/dist/setup/detected-engines.js +7 -13
  74. package/dist/setup/engine-config.js +14 -4
  75. package/dist/setup/setup.js +1 -1
  76. package/dist/setup/steps/connection.js +1 -1
  77. package/dist/setup/steps/tasks.js +1 -1
  78. package/dist/sources/providers/git-stash.js +58 -21
  79. package/dist/sources/providers/git.js +1 -1
  80. package/dist/storage/engines/sqlite-migrations.js +138 -3
  81. package/dist/storage/repositories/events-repository.js +24 -0
  82. package/dist/storage/repositories/proposals-repository.js +14 -0
  83. package/dist/tasks/embedded.js +2 -0
  84. package/dist/workflows/db.js +24 -13
  85. package/dist/workflows/validator.js +24 -2
  86. package/docs/migration/release-notes/0.9.0.md +24 -7
  87. package/docs/migration/v0.8-to-v0.9.md +124 -19
  88. package/package.json +1 -1
@@ -7558,21 +7558,169 @@ var init_markdown = __esm(() => {
7558
7558
  init_frontmatter();
7559
7559
  });
7560
7560
 
7561
- // src/storage/database.ts
7561
+ // src/core/paths.ts
7562
+ import path from "node:path";
7563
+ function isUnderBunTest(env) {
7564
+ return env.BUN_TEST === "1" || env.NODE_ENV === "test";
7565
+ }
7566
+ function isTransientStashPath(p) {
7567
+ return p.startsWith("/tmp/") || p === "/tmp" || p.startsWith("/var/tmp/") || p === "/var/tmp" || p.startsWith("/private/tmp/") || p.startsWith("/private/var/folders/") || p.startsWith("/var/folders/");
7568
+ }
7569
+ function testIsolationError() {
7570
+ return new ConfigError("Refusing to resolve data directory under bun test: neither XDG_DATA_HOME nor AKM_DATA_DIR is set. " + "This guards against tests writing into the developer's real ~/.local/share/akm. " + "Set XDG_DATA_HOME (or AKM_DATA_DIR) to a mktemp-d directory in this test's env block.", "TEST_ISOLATION_MISSING");
7571
+ }
7572
+ function getConfigDir(env = process.env, platform = process.platform) {
7573
+ const override = env.AKM_CONFIG_DIR?.trim();
7574
+ if (override)
7575
+ return override;
7576
+ if (platform === "win32") {
7577
+ const appData = env.APPDATA?.trim();
7578
+ if (appData)
7579
+ return path.join(appData, "akm");
7580
+ } else {
7581
+ const xdgConfigHome2 = env.XDG_CONFIG_HOME?.trim();
7582
+ if (xdgConfigHome2)
7583
+ return path.join(xdgConfigHome2, "akm");
7584
+ }
7585
+ const stashOverride = env.AKM_STASH_DIR?.trim();
7586
+ if (stashOverride && isTransientStashPath(stashOverride)) {
7587
+ return path.join(stashOverride, ".akm");
7588
+ }
7589
+ if (platform === "win32") {
7590
+ const appData = env.APPDATA?.trim();
7591
+ if (appData)
7592
+ return path.join(appData, "akm");
7593
+ const userProfile = env.USERPROFILE?.trim();
7594
+ if (!userProfile) {
7595
+ throw new ConfigError("Unable to determine config directory. Set APPDATA or USERPROFILE.", "CONFIG_DIR_UNRESOLVABLE");
7596
+ }
7597
+ return path.join(userProfile, "AppData", "Roaming", "akm");
7598
+ }
7599
+ const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
7600
+ if (xdgConfigHome)
7601
+ return path.join(xdgConfigHome, "akm");
7602
+ const home = env.HOME?.trim();
7603
+ if (!home) {
7604
+ throw new ConfigError("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.", "CONFIG_DIR_UNRESOLVABLE");
7605
+ }
7606
+ return path.join(home, ".config", "akm");
7607
+ }
7608
+ function getConfigPath(env = process.env) {
7609
+ return path.join(getConfigDir(env), "config.json");
7610
+ }
7611
+ function getDataDir(env = process.env, platform = process.platform) {
7612
+ const override = env.AKM_DATA_DIR?.trim();
7613
+ if (override)
7614
+ return override;
7615
+ if (isUnderBunTest(env) && !env.XDG_DATA_HOME?.trim()) {
7616
+ throw testIsolationError();
7617
+ }
7618
+ if (platform === "win32") {
7619
+ const localAppData = env.LOCALAPPDATA?.trim();
7620
+ if (localAppData)
7621
+ return path.join(localAppData, "akm", "data");
7622
+ const userProfile = env.USERPROFILE?.trim();
7623
+ if (userProfile)
7624
+ return path.join(userProfile, "AppData", "Local", "akm", "data");
7625
+ const appData = env.APPDATA?.trim();
7626
+ if (!appData) {
7627
+ throw new ConfigError("Unable to determine data directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
7628
+ }
7629
+ return path.join(appData, "..", "Local", "akm", "data");
7630
+ }
7631
+ const xdgDataHome = env.XDG_DATA_HOME?.trim();
7632
+ if (xdgDataHome)
7633
+ return path.join(xdgDataHome, "akm");
7634
+ const home = env.HOME?.trim();
7635
+ if (!home)
7636
+ return path.join("/tmp", "akm-data");
7637
+ return path.join(home, ".local", "share", "akm");
7638
+ }
7639
+ function getDbPath(env = process.env) {
7640
+ return path.join(getDataDir(env), "index.db");
7641
+ }
7642
+ function getMaintenanceBarrierPath() {
7643
+ return path.join(getDataDir(), "maintenance.barrier.lock");
7644
+ }
7645
+ var init_paths = __esm(() => {
7646
+ init_common();
7647
+ init_errors();
7648
+ });
7649
+
7650
+ // src/core/migration-operation.ts
7651
+ import crypto from "node:crypto";
7652
+ import fs2 from "node:fs";
7653
+ import path2 from "node:path";
7654
+ function installationId() {
7655
+ return crypto.createHash("sha256").update(path2.resolve(path2.dirname(getConfigPath()))).update("\x00").update(path2.resolve(getDataDir())).digest("hex").slice(0, 24);
7656
+ }
7657
+ function getMigrationOperationRoot() {
7658
+ return path2.join(getDataDir(), "backups", "migrations", installationId());
7659
+ }
7660
+ function getMigrationRestoreJournalPath() {
7661
+ return path2.join(getMigrationOperationRoot(), "restore-active.json");
7662
+ }
7663
+ function getMigrationApplyJournalPath() {
7664
+ return path2.join(getMigrationOperationRoot(), "apply-active.json");
7665
+ }
7666
+ function assertNoPendingMigrationOperation() {
7667
+ for (const [kind, journalPath] of [
7668
+ ["restore", getMigrationRestoreJournalPath()],
7669
+ ["migration apply", getMigrationApplyJournalPath()]
7670
+ ]) {
7671
+ if (fs2.existsSync(journalPath)) {
7672
+ throw new ConfigError(`AKM ${kind} recovery is pending at ${journalPath}; refusing canonical config/database access until recovery completes.`, "INVALID_CONFIG_FILE");
7673
+ }
7674
+ }
7675
+ const hook = afterPendingCheckHook;
7676
+ afterPendingCheckHook = undefined;
7677
+ hook?.();
7678
+ }
7679
+ var afterPendingCheckHook;
7680
+ var init_migration_operation = __esm(() => {
7681
+ init_errors();
7682
+ init_paths();
7683
+ });
7684
+
7685
+ // src/runtime.ts
7686
+ import { createHash } from "node:crypto";
7687
+ import { createWriteStream, statfsSync } from "node:fs";
7562
7688
  import { createRequire as createRequire2 } from "node:module";
7689
+ function sha256Hex(data) {
7690
+ return createHash("sha256").update(data).digest("hex");
7691
+ }
7692
+ function statfsType(path3) {
7693
+ try {
7694
+ return statfsSync(path3).type;
7695
+ } catch {
7696
+ return;
7697
+ }
7698
+ }
7699
+ function bunGlobal() {
7700
+ return globalThis.Bun;
7701
+ }
7702
+ var isBun, nodeRequire, mainPath;
7703
+ var init_runtime = __esm(() => {
7704
+ isBun = !!process.versions?.bun;
7705
+ nodeRequire = createRequire2(import.meta.url);
7706
+ mainPath = isBun ? bunGlobal().main : process.argv[1];
7707
+ });
7708
+
7709
+ // src/storage/database.ts
7710
+ import { createRequire as createRequire3 } from "node:module";
7563
7711
  function selectProvider() {
7564
7712
  const provider = PROVIDERS.find((p) => p.supported());
7565
7713
  if (!provider) {
7566
- throw new Error(`No storage provider supports the current runtime (${isBun ? "Bun" : "Node"}).`);
7714
+ throw new Error(`No storage provider supports the current runtime (${isBun2 ? "Bun" : "Node"}).`);
7567
7715
  }
7568
7716
  return provider;
7569
7717
  }
7570
- function openDatabase(path, opts) {
7571
- return selectProvider().open(path, opts);
7718
+ function openDatabase(path3, opts) {
7719
+ return selectProvider().open(path3, opts);
7572
7720
  }
7573
- function openBunDatabase(path, opts) {
7721
+ function openBunDatabase(path3, opts) {
7574
7722
  const { Database: BunDatabase } = loadBunSqlite();
7575
- const db = opts ? new BunDatabase(path, bunOptions(opts)) : new BunDatabase(path);
7723
+ const db = opts ? new BunDatabase(path3, bunOptions(opts)) : new BunDatabase(path3);
7576
7724
  return db;
7577
7725
  }
7578
7726
  function bunOptions(opts) {
@@ -7585,7 +7733,7 @@ function bunOptions(opts) {
7585
7733
  }
7586
7734
  function loadBunSqlite() {
7587
7735
  if (!bunSqliteModule) {
7588
- bunSqliteModule = nodeRequire("bun:sqlite");
7736
+ bunSqliteModule = nodeRequire2("bun:sqlite");
7589
7737
  }
7590
7738
  return bunSqliteModule;
7591
7739
  }
@@ -7593,7 +7741,7 @@ function loadBetterSqlite3() {
7593
7741
  if (!betterSqlite3Ctor) {
7594
7742
  let mod;
7595
7743
  try {
7596
- mod = nodeRequire("better-sqlite3");
7744
+ mod = nodeRequire2("better-sqlite3");
7597
7745
  } catch (err) {
7598
7746
  throw new Error(`akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.
7599
7747
  ` + ` • Reinstall akm with a working C/C++ build toolchain so its optional
@@ -7605,14 +7753,14 @@ function loadBetterSqlite3() {
7605
7753
  }
7606
7754
  return betterSqlite3Ctor;
7607
7755
  }
7608
- function openNodeDatabase(path, opts) {
7756
+ function openNodeDatabase(path3, opts) {
7609
7757
  const BetterSqlite3 = loadBetterSqlite3();
7610
7758
  const options = {};
7611
7759
  if (opts?.readonly !== undefined)
7612
7760
  options.readonly = opts.readonly;
7613
7761
  if (opts?.create === false)
7614
7762
  options.fileMustExist = true;
7615
- const db = opts ? new BetterSqlite3(path, options) : new BetterSqlite3(path);
7763
+ const db = opts ? new BetterSqlite3(path3, options) : new BetterSqlite3(path3);
7616
7764
  return {
7617
7765
  prepare: db.prepare.bind(db),
7618
7766
  exec: db.exec.bind(db),
@@ -7624,83 +7772,52 @@ function openNodeDatabase(path, opts) {
7624
7772
  close: db.close.bind(db)
7625
7773
  };
7626
7774
  }
7627
- var isBun, nodeRequire, bunSqliteProvider, nodeSqliteProvider, PROVIDERS, bunSqliteModule, betterSqlite3Ctor;
7775
+ var isBun2, nodeRequire2, bunSqliteProvider, nodeSqliteProvider, PROVIDERS, bunSqliteModule, betterSqlite3Ctor;
7628
7776
  var init_database = __esm(() => {
7629
- isBun = !!process.versions?.bun;
7630
- nodeRequire = createRequire2(import.meta.url);
7777
+ isBun2 = !!process.versions?.bun;
7778
+ nodeRequire2 = createRequire3(import.meta.url);
7631
7779
  bunSqliteProvider = {
7632
7780
  name: "bun:sqlite",
7633
- supported: () => isBun,
7781
+ supported: () => isBun2,
7634
7782
  open: openBunDatabase
7635
7783
  };
7636
7784
  nodeSqliteProvider = {
7637
7785
  name: "better-sqlite3",
7638
- supported: () => !isBun,
7786
+ supported: () => !isBun2,
7639
7787
  open: openNodeDatabase
7640
7788
  };
7641
7789
  PROVIDERS = [bunSqliteProvider, nodeSqliteProvider];
7642
7790
  });
7643
7791
 
7644
- // src/runtime.ts
7645
- import { createHash } from "node:crypto";
7646
- import { createWriteStream, statfsSync } from "node:fs";
7647
- import { createRequire as createRequire3 } from "node:module";
7648
- function sha256Hex(data) {
7649
- return createHash("sha256").update(data).digest("hex");
7650
- }
7651
- function statfsType(path) {
7652
- try {
7653
- return statfsSync(path).type;
7654
- } catch {
7655
- return;
7656
- }
7657
- }
7658
- function sleepSync(ms) {
7659
- if (isBun2) {
7660
- bunGlobal().sleepSync(ms);
7661
- return;
7662
- }
7663
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
7664
- }
7665
- function bunGlobal() {
7666
- return globalThis.Bun;
7667
- }
7668
- var isBun2, nodeRequire2, mainPath;
7669
- var init_runtime = __esm(() => {
7670
- isBun2 = !!process.versions?.bun;
7671
- nodeRequire2 = createRequire3(import.meta.url);
7672
- mainPath = isBun2 ? bunGlobal().main : process.argv[1];
7673
- });
7674
-
7675
7792
  // src/core/file-lock.ts
7676
7793
  import { randomUUID } from "node:crypto";
7677
- import fs2 from "node:fs";
7678
- import path from "node:path";
7794
+ import fs3 from "node:fs";
7795
+ import path3 from "node:path";
7679
7796
  function readLockSnapshot(lockPath) {
7680
7797
  let fd;
7681
7798
  try {
7682
- fd = fs2.openSync(lockPath, "r");
7799
+ fd = fs3.openSync(lockPath, "r");
7683
7800
  } catch (err) {
7684
7801
  if (err.code === "ENOENT")
7685
7802
  return;
7686
7803
  throw err;
7687
7804
  }
7688
7805
  try {
7689
- const rawContent = fs2.readFileSync(fd, "utf8");
7690
- const stat = fs2.fstatSync(fd);
7806
+ const rawContent = readTextFileDescriptorWithLimit(fd, MAX_LOCK_METADATA_BYTES, "Lock metadata", lockPath);
7807
+ const stat = fs3.fstatSync(fd);
7691
7808
  return {
7692
7809
  rawContent,
7693
7810
  identity: { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeMs: stat.mtimeMs }
7694
7811
  };
7695
7812
  } finally {
7696
- fs2.closeSync(fd);
7813
+ fs3.closeSync(fd);
7697
7814
  }
7698
7815
  }
7699
7816
  function sameIdentity(left, right) {
7700
7817
  return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs;
7701
7818
  }
7702
7819
  function operationMutexPath(lockPath) {
7703
- return path.join(path.dirname(lockPath), `.${path.basename(lockPath)}.operations.sensitive`);
7820
+ return path3.join(path3.dirname(lockPath), `.${path3.basename(lockPath)}.operations.sensitive`);
7704
7821
  }
7705
7822
  function withLockOperationMutex(lockPath, run) {
7706
7823
  const db = openDatabase(operationMutexPath(lockPath));
@@ -7732,7 +7849,7 @@ function withLockOperationMutex(lockPath, run) {
7732
7849
  }
7733
7850
  function tryAcquireLockRaw(lockPath, payload) {
7734
7851
  try {
7735
- fs2.writeFileSync(lockPath, payload, { flag: "wx" });
7852
+ fs3.writeFileSync(lockPath, payload, { flag: "wx" });
7736
7853
  } catch (err) {
7737
7854
  if (err.code === "EEXIST")
7738
7855
  return;
@@ -7753,7 +7870,7 @@ function tryAcquireLockRaw(lockPath, payload) {
7753
7870
  }
7754
7871
  function releaseLockRaw(lockPath) {
7755
7872
  try {
7756
- fs2.unlinkSync(lockPath);
7873
+ fs3.unlinkSync(lockPath);
7757
7874
  } catch {}
7758
7875
  }
7759
7876
  function tryAcquireLockSync(lockPath, payload) {
@@ -7802,7 +7919,7 @@ function reclaimStaleLock(lockPath, probe, options) {
7802
7919
  }
7803
7920
  const quarantinePath = `${lockPath}.stale-${process.pid}-${randomUUID()}`;
7804
7921
  try {
7805
- fs2.renameSync(lockPath, quarantinePath);
7922
+ fs3.renameSync(lockPath, quarantinePath);
7806
7923
  } catch (err) {
7807
7924
  if (err.code === "ENOENT")
7808
7925
  return false;
@@ -7816,7 +7933,7 @@ function reclaimStaleLock(lockPath, probe, options) {
7816
7933
  }
7817
7934
  if (!quarantined || quarantined.rawContent !== expectedContent || !sameIdentity(quarantined.identity, expectedIdentity)) {
7818
7935
  try {
7819
- fs2.linkSync(quarantinePath, lockPath);
7936
+ fs3.linkSync(quarantinePath, lockPath);
7820
7937
  } catch (err) {
7821
7938
  if (err.code !== "EEXIST")
7822
7939
  throw err;
@@ -7826,7 +7943,7 @@ function reclaimStaleLock(lockPath, probe, options) {
7826
7943
  }
7827
7944
  options?.afterQuarantineVerified?.();
7828
7945
  try {
7829
- fs2.unlinkSync(quarantinePath);
7946
+ fs3.unlinkSync(quarantinePath);
7830
7947
  return true;
7831
7948
  } catch (err) {
7832
7949
  if (err.code === "ENOENT")
@@ -7837,7 +7954,7 @@ function reclaimStaleLock(lockPath, probe, options) {
7837
7954
  }
7838
7955
  function releaseLock(ownership) {
7839
7956
  const { lockPath } = ownership;
7840
- if (!fs2.existsSync(lockPath) && !fs2.existsSync(operationMutexPath(lockPath)))
7957
+ if (!fs3.existsSync(lockPath) && !fs3.existsSync(operationMutexPath(lockPath)))
7841
7958
  return;
7842
7959
  withLockOperationMutex(lockPath, () => {
7843
7960
  let current;
@@ -7872,245 +7989,7 @@ var init_file_lock = __esm(() => {
7872
7989
  init_common();
7873
7990
  });
7874
7991
 
7875
- // src/core/paths.ts
7876
- import path2 from "node:path";
7877
- function isUnderBunTest(env) {
7878
- return env.BUN_TEST === "1" || env.NODE_ENV === "test";
7879
- }
7880
- function isTransientStashPath(p) {
7881
- return p.startsWith("/tmp/") || p === "/tmp" || p.startsWith("/var/tmp/") || p === "/var/tmp" || p.startsWith("/private/tmp/") || p.startsWith("/private/var/folders/") || p.startsWith("/var/folders/");
7882
- }
7883
- function testIsolationError() {
7884
- return new ConfigError("Refusing to resolve data directory under bun test: neither XDG_DATA_HOME nor AKM_DATA_DIR is set. " + "This guards against tests writing into the developer's real ~/.local/share/akm. " + "Set XDG_DATA_HOME (or AKM_DATA_DIR) to a mktemp-d directory in this test's env block.", "TEST_ISOLATION_MISSING");
7885
- }
7886
- function getConfigDir(env = process.env, platform = process.platform) {
7887
- const override = env.AKM_CONFIG_DIR?.trim();
7888
- if (override)
7889
- return override;
7890
- if (platform === "win32") {
7891
- const appData = env.APPDATA?.trim();
7892
- if (appData)
7893
- return path2.join(appData, "akm");
7894
- } else {
7895
- const xdgConfigHome2 = env.XDG_CONFIG_HOME?.trim();
7896
- if (xdgConfigHome2)
7897
- return path2.join(xdgConfigHome2, "akm");
7898
- }
7899
- const stashOverride = env.AKM_STASH_DIR?.trim();
7900
- if (stashOverride && isTransientStashPath(stashOverride)) {
7901
- return path2.join(stashOverride, ".akm");
7902
- }
7903
- if (platform === "win32") {
7904
- const appData = env.APPDATA?.trim();
7905
- if (appData)
7906
- return path2.join(appData, "akm");
7907
- const userProfile = env.USERPROFILE?.trim();
7908
- if (!userProfile) {
7909
- throw new ConfigError("Unable to determine config directory. Set APPDATA or USERPROFILE.", "CONFIG_DIR_UNRESOLVABLE");
7910
- }
7911
- return path2.join(userProfile, "AppData", "Roaming", "akm");
7912
- }
7913
- const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
7914
- if (xdgConfigHome)
7915
- return path2.join(xdgConfigHome, "akm");
7916
- const home = env.HOME?.trim();
7917
- if (!home) {
7918
- throw new ConfigError("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.", "CONFIG_DIR_UNRESOLVABLE");
7919
- }
7920
- return path2.join(home, ".config", "akm");
7921
- }
7922
- function getConfigPath(env = process.env) {
7923
- return path2.join(getConfigDir(env), "config.json");
7924
- }
7925
- function getCacheDir(env = process.env) {
7926
- const override = env.AKM_CACHE_DIR?.trim();
7927
- if (override)
7928
- return override;
7929
- if (IS_WINDOWS) {
7930
- const localAppData = env.LOCALAPPDATA?.trim();
7931
- if (localAppData)
7932
- return path2.join(localAppData, "akm");
7933
- const userProfile = env.USERPROFILE?.trim();
7934
- if (userProfile)
7935
- return path2.join(userProfile, "AppData", "Local", "akm");
7936
- const appData = env.APPDATA?.trim();
7937
- if (appData) {
7938
- return path2.join(appData, "..", "Local", "akm");
7939
- }
7940
- } else {
7941
- const xdgCacheHome = env.XDG_CACHE_HOME?.trim();
7942
- if (xdgCacheHome)
7943
- return path2.join(xdgCacheHome, "akm");
7944
- }
7945
- const stashOverride = env.AKM_STASH_DIR?.trim();
7946
- if (stashOverride && isTransientStashPath(stashOverride)) {
7947
- return path2.join(stashOverride, ".akm", "cache");
7948
- }
7949
- if (IS_WINDOWS) {
7950
- throw new ConfigError("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
7951
- }
7952
- const home = env.HOME?.trim();
7953
- if (!home)
7954
- return path2.join("/tmp", "akm-cache");
7955
- return path2.join(home, ".cache", "akm");
7956
- }
7957
- function getDataDir(env = process.env, platform = process.platform) {
7958
- const override = env.AKM_DATA_DIR?.trim();
7959
- if (override)
7960
- return override;
7961
- if (isUnderBunTest(env) && !env.XDG_DATA_HOME?.trim()) {
7962
- throw testIsolationError();
7963
- }
7964
- if (platform === "win32") {
7965
- const localAppData = env.LOCALAPPDATA?.trim();
7966
- if (localAppData)
7967
- return path2.join(localAppData, "akm", "data");
7968
- const userProfile = env.USERPROFILE?.trim();
7969
- if (userProfile)
7970
- return path2.join(userProfile, "AppData", "Local", "akm", "data");
7971
- const appData = env.APPDATA?.trim();
7972
- if (!appData) {
7973
- throw new ConfigError("Unable to determine data directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
7974
- }
7975
- return path2.join(appData, "..", "Local", "akm", "data");
7976
- }
7977
- const xdgDataHome = env.XDG_DATA_HOME?.trim();
7978
- if (xdgDataHome)
7979
- return path2.join(xdgDataHome, "akm");
7980
- const home = env.HOME?.trim();
7981
- if (!home)
7982
- return path2.join("/tmp", "akm-data");
7983
- return path2.join(home, ".local", "share", "akm");
7984
- }
7985
- function getDbPath(env = process.env) {
7986
- return path2.join(getDataDir(env), "index.db");
7987
- }
7988
- function getMaintenanceBarrierPath() {
7989
- return path2.join(getDataDir(), "maintenance.barrier.lock");
7990
- }
7991
- function getWorkflowDbPath() {
7992
- return path2.join(getDataDir(), "workflow.db");
7993
- }
7994
- function getStateDbPathInDataDir() {
7995
- return path2.join(getDataDir(), "state.db");
7996
- }
7997
- var init_paths = __esm(() => {
7998
- init_common();
7999
- init_errors();
8000
- });
8001
-
8002
7992
  // src/core/config/config-io.ts
8003
- import fs3 from "node:fs";
8004
- import path3 from "node:path";
8005
- function parseConfigText(text, sourcePath) {
8006
- const stripped = stripJsonComments(text);
8007
- const where = sourcePath ? ` at ${sourcePath}` : "";
8008
- let parsed;
8009
- try {
8010
- parsed = JSON.parse(stripped);
8011
- } catch (err) {
8012
- const detail = err instanceof Error ? err.message : String(err);
8013
- throw new ConfigError(`Failed to parse config JSON${where}: ${detail}`, "INVALID_CONFIG_FILE", "Edit the file to fix the JSON syntax error. Comments (// and /* */) are allowed; trailing commas are not.");
8014
- }
8015
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
8016
- throw new ConfigError(`Config file${where} must contain a JSON object at the root, got ${describeJsonRoot(parsed)}.`, "INVALID_CONFIG_FILE");
8017
- }
8018
- return parsed;
8019
- }
8020
- function describeJsonRoot(value) {
8021
- if (value === null)
8022
- return "null";
8023
- if (Array.isArray(value))
8024
- return "an array";
8025
- if (typeof value === "string")
8026
- return "a string";
8027
- if (typeof value === "number")
8028
- return "a number";
8029
- if (typeof value === "boolean")
8030
- return "a boolean";
8031
- return typeof value;
8032
- }
8033
- function getConfigLockPath() {
8034
- return path3.join(getConfigDir(), "config.json.lck");
8035
- }
8036
- function sleepSyncMs(ms) {
8037
- sleepSync(ms);
8038
- }
8039
- function acquireConfigLock() {
8040
- const lockPath = getConfigLockPath();
8041
- try {
8042
- fs3.mkdirSync(path3.dirname(lockPath), { recursive: true });
8043
- } catch {}
8044
- for (let attempt = 0;attempt < CONFIG_LOCK_MAX_RETRIES; attempt++) {
8045
- try {
8046
- const ownership = tryAcquireLockSync(lockPath, createLockPayload());
8047
- if (ownership) {
8048
- return () => releaseLock(ownership);
8049
- }
8050
- } catch (error) {
8051
- throw new ConfigError(`Unable to acquire config lock at ${lockPath}: ${error instanceof Error ? error.message : String(error)}`, "INVALID_CONFIG_FILE");
8052
- }
8053
- const probe = probeLock(lockPath);
8054
- if (probe.state === "stale" && reclaimStaleLock(lockPath, probe)) {
8055
- continue;
8056
- }
8057
- if (attempt < CONFIG_LOCK_MAX_RETRIES - 1) {
8058
- sleepSyncMs(CONFIG_LOCK_RETRY_DELAY_MS);
8059
- }
8060
- }
8061
- throw new ConfigError(`Timed out waiting for config lock at ${lockPath}. Another AKM process may be updating config.`, "INVALID_CONFIG_FILE");
8062
- }
8063
- function withConfigLock(fn) {
8064
- const release = acquireConfigLock();
8065
- try {
8066
- return fn();
8067
- } finally {
8068
- release();
8069
- }
8070
- }
8071
- function stripJsonComments(text) {
8072
- let result = "";
8073
- let i = 0;
8074
- let inString = false;
8075
- while (i < text.length) {
8076
- if (inString) {
8077
- if (text[i] === "\\") {
8078
- result += text[i] + (text[i + 1] ?? "");
8079
- i += 2;
8080
- continue;
8081
- }
8082
- if (text[i] === '"') {
8083
- inString = false;
8084
- }
8085
- result += text[i];
8086
- i++;
8087
- continue;
8088
- }
8089
- if (text[i] === '"') {
8090
- inString = true;
8091
- result += text[i];
8092
- i++;
8093
- continue;
8094
- }
8095
- if (text[i] === "/" && text[i + 1] === "/") {
8096
- while (i < text.length && text[i] !== `
8097
- `)
8098
- i++;
8099
- continue;
8100
- }
8101
- if (text[i] === "/" && text[i + 1] === "*") {
8102
- i += 2;
8103
- while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
8104
- i++;
8105
- i += 2;
8106
- continue;
8107
- }
8108
- result += text[i];
8109
- i++;
8110
- }
8111
- return result;
8112
- }
8113
- var CONFIG_LOCK_MAX_RETRIES = 10, CONFIG_LOCK_RETRY_DELAY_MS = 50;
8114
7993
  var init_config_io = __esm(() => {
8115
7994
  init_runtime();
8116
7995
  init_common();
@@ -14467,6 +14346,15 @@ var init_config_schema = __esm(() => {
14467
14346
  message: "feedbackDistillation was removed in 0.8.0 — use processes.distill.enabled instead. " + "It now controls both the orchestration gate and the LLM-call gate."
14468
14347
  });
14469
14348
  }
14349
+ for (const [name, process2] of Object.entries(val)) {
14350
+ if (!(name in IMPROVE_PROCESS_ENGINE_CAPABILITIES) && process2 !== null && typeof process2 === "object" && process2.enabled === true) {
14351
+ ctx.addIssue({
14352
+ code: exports_external.ZodIssueCode.custom,
14353
+ path: [name],
14354
+ message: `Unknown enabled improve process "${name}"`
14355
+ });
14356
+ }
14357
+ }
14470
14358
  });
14471
14359
  ImproveProfileConfigSchema = exports_external.object({
14472
14360
  engine: engineName.optional(),
@@ -14892,331 +14780,10 @@ var init_config_schema = __esm(() => {
14892
14780
  code: exports_external.ZodIssueCode.custom,
14893
14781
  path: ["defaultWriteTarget"],
14894
14782
  message: `defaultWriteTarget "${config.defaultWriteTarget}" does not match any configured source name: ${knownNames.map((n) => `"${n}"`).join(", ")}.`
14895
- });
14896
- }
14897
- }
14898
- });
14899
- });
14900
-
14901
- // src/core/maintenance-barrier.ts
14902
- import { AsyncLocalStorage } from "node:async_hooks";
14903
- import { randomUUID as randomUUID2 } from "node:crypto";
14904
- import fs6 from "node:fs";
14905
- import path6 from "node:path";
14906
- function tryAcquireMaintenanceBarrier() {
14907
- const lockPath = getMaintenanceBarrierPath();
14908
- fs6.mkdirSync(path6.dirname(lockPath), { recursive: true });
14909
- for (let attempt = 0;attempt < 2; attempt += 1) {
14910
- const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: "maintenance-start" }));
14911
- if (ownership) {
14912
- return () => releaseLock(ownership);
14913
- }
14914
- const probe = probeLock(lockPath);
14915
- if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
14916
- return;
14917
- }
14918
- return;
14919
- }
14920
- function acquireMaintenanceBarrier() {
14921
- const release = tryAcquireMaintenanceBarrier();
14922
- if (release)
14923
- return release;
14924
- throw new ConfigError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry after it completes.`, "INVALID_CONFIG_FILE");
14925
- }
14926
- function withMaintenanceStartBarrierSyncWait(run) {
14927
- if (heldBarrierContext.getStore()?.active)
14928
- return run();
14929
- const deadline = Date.now() + 5000;
14930
- let release = tryAcquireMaintenanceBarrier();
14931
- while (!release && Date.now() < deadline) {
14932
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
14933
- release = tryAcquireMaintenanceBarrier();
14934
- }
14935
- if (!release)
14936
- release = acquireMaintenanceBarrier();
14937
- const ownership = { active: true };
14938
- try {
14939
- return heldBarrierContext.run(ownership, run);
14940
- } finally {
14941
- ownership.active = false;
14942
- release();
14943
- }
14944
- }
14945
- function acquireMaintenanceActivitySync(name) {
14946
- return withMaintenanceStartBarrierSyncWait(() => {
14947
- const directory = path6.join(path6.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
14948
- fs6.mkdirSync(directory, { recursive: true, mode: 448 });
14949
- const lockPath = path6.join(directory, `${name}-${process.pid}-${randomUUID2()}.lock`);
14950
- const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: name }));
14951
- if (!ownership) {
14952
- throw new ConfigError(`Could not register AKM maintenance activity at ${lockPath}.`, "INVALID_CONFIG_FILE");
14953
- }
14954
- return () => releaseLock(ownership);
14955
- });
14956
- }
14957
- var heldBarrierContext;
14958
- var init_maintenance_barrier = __esm(() => {
14959
- init_errors();
14960
- init_file_lock();
14961
- init_paths();
14962
- heldBarrierContext = new AsyncLocalStorage;
14963
- });
14964
-
14965
- // src/core/migration-backup.ts
14966
- import crypto from "node:crypto";
14967
- import fs7 from "node:fs";
14968
- import path7 from "node:path";
14969
- function getMigrationBackupDir() {
14970
- return path7.join(getCacheDir(), "migration-backups", MIGRATION_BACKUP_VERSION);
14971
- }
14972
- function migrationBackupLockPath() {
14973
- return path7.join(getCacheDir(), "migration-backups", `${MIGRATION_BACKUP_VERSION}.lock`);
14974
- }
14975
- function expectedSourcePaths() {
14976
- return {
14977
- "config.json": getConfigPath(),
14978
- "state.db": getStateDbPathInDataDir(),
14979
- "workflow.db": getWorkflowDbPath()
14980
- };
14981
- }
14982
- function sha256File(filePath) {
14983
- return crypto.createHash("sha256").update(fs7.readFileSync(filePath)).digest("hex");
14984
- }
14985
- function ownerOnlyMode(filePath, directory) {
14986
- if (process.platform === "win32")
14987
- return true;
14988
- const mode = fs7.statSync(filePath).mode & 511;
14989
- return mode === (directory ? 448 : 384);
14990
- }
14991
- function fsyncDirectory(directory) {
14992
- try {
14993
- const fd = fs7.openSync(directory, "r");
14994
- try {
14995
- fs7.fsyncSync(fd);
14996
- } finally {
14997
- fs7.closeSync(fd);
14998
- }
14999
- } catch (error) {
15000
- const code = error.code;
15001
- if (code !== "EINVAL" && code !== "ENOTSUP" && code !== "EISDIR" && code !== "EPERM")
15002
- throw error;
15003
- }
15004
- }
15005
- function parseManifest(bundlePath) {
15006
- const manifestPath = path7.join(bundlePath, "manifest.json");
15007
- let value;
15008
- try {
15009
- value = JSON.parse(fs7.readFileSync(manifestPath, "utf8"));
15010
- } catch (error) {
15011
- throw new ConfigError(`Migration backup at ${bundlePath} is incomplete or unreadable: ${error instanceof Error ? error.message : String(error)}. Remove it only after preserving any recoverable files, then retry backup creation.`, "INVALID_CONFIG_FILE");
15012
- }
15013
- if (!value || typeof value !== "object" || Array.isArray(value)) {
15014
- throw new ConfigError(`Migration backup manifest at ${manifestPath} is invalid.`, "INVALID_CONFIG_FILE");
15015
- }
15016
- const manifest = value;
15017
- if (manifest.version !== MIGRATION_BACKUP_VERSION || typeof manifest.createdAt !== "string") {
15018
- throw new ConfigError(`Migration backup manifest at ${manifestPath} has an unsupported version.`, "INVALID_CONFIG_FILE");
15019
- }
15020
- const expected = expectedSourcePaths();
15021
- for (const name of ARTIFACT_NAMES) {
15022
- const artifact = manifest.artifacts?.[name];
15023
- if (!artifact || artifact.sourcePath !== expected[name] || typeof artifact.present !== "boolean" || !Number.isSafeInteger(artifact.byteSize) || artifact.byteSize < 0 || typeof artifact.createdAt !== "string" || (artifact.present ? typeof artifact.sha256 !== "string" || artifact.sha256.length !== 64 : artifact.sha256 !== null)) {
15024
- throw new ConfigError(`Migration backup manifest has an invalid ${name} entry.`, "INVALID_CONFIG_FILE");
15025
- }
15026
- }
15027
- return manifest;
15028
- }
15029
- function verifyMigrationBackup(bundlePath = getMigrationBackupDir()) {
15030
- if (!fs7.existsSync(bundlePath) || !fs7.statSync(bundlePath).isDirectory()) {
15031
- throw new ConfigError(`Migration backup does not exist at ${bundlePath}.`, "INVALID_CONFIG_FILE");
15032
- }
15033
- if (!ownerOnlyMode(bundlePath, true)) {
15034
- throw new ConfigError(`Migration backup directory ${bundlePath} must have mode 0700.`, "INVALID_CONFIG_FILE");
15035
- }
15036
- const manifest = parseManifest(bundlePath);
15037
- const expectedFiles = new Set(["manifest.json"]);
15038
- for (const name of ARTIFACT_NAMES) {
15039
- const artifact = manifest.artifacts[name];
15040
- const artifactPath = path7.join(bundlePath, name);
15041
- if (!artifact.present) {
15042
- if (fs7.existsSync(artifactPath)) {
15043
- throw new ConfigError(`Migration backup contains ${name}, but its manifest records it absent.`, "INVALID_CONFIG_FILE");
15044
- }
15045
- continue;
15046
- }
15047
- expectedFiles.add(name);
15048
- if (!fs7.existsSync(artifactPath) || !fs7.statSync(artifactPath).isFile()) {
15049
- throw new ConfigError(`Migration backup is missing ${artifactPath}.`, "INVALID_CONFIG_FILE");
15050
- }
15051
- if (!ownerOnlyMode(artifactPath, false)) {
15052
- throw new ConfigError(`Migration backup artifact ${artifactPath} must have mode 0600.`, "INVALID_CONFIG_FILE");
15053
- }
15054
- const stat = fs7.statSync(artifactPath);
15055
- if (stat.size !== artifact.byteSize || sha256File(artifactPath) !== artifact.sha256) {
15056
- throw new ConfigError(`Migration backup artifact ${artifactPath} failed checksum verification.`, "INVALID_CONFIG_FILE");
15057
- }
15058
- }
15059
- if (!ownerOnlyMode(path7.join(bundlePath, "manifest.json"), false)) {
15060
- throw new ConfigError(`Migration backup manifest must have mode 0600.`, "INVALID_CONFIG_FILE");
15061
- }
15062
- const extras = fs7.readdirSync(bundlePath).filter((name) => !expectedFiles.has(name));
15063
- if (extras.length > 0) {
15064
- throw new ConfigError(`Migration backup contains unexpected files: ${extras.join(", ")}.`, "INVALID_CONFIG_FILE");
15065
- }
15066
- return manifest;
15067
- }
15068
- function acquireMigrationBackupLock() {
15069
- const lockPath = migrationBackupLockPath();
15070
- fs7.mkdirSync(path7.dirname(lockPath), { recursive: true, mode: 448 });
15071
- fs7.chmodSync(path7.dirname(lockPath), 448);
15072
- for (let attempt = 0;attempt < 50; attempt += 1) {
15073
- const ownership = tryAcquireLockSync(lockPath, createLockPayload());
15074
- if (ownership)
15075
- return () => releaseLock(ownership);
15076
- const probe = probeLock(lockPath);
15077
- if (probe.state === "stale" && reclaimStaleLock(lockPath, probe)) {
15078
- continue;
15079
- }
15080
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
15081
- }
15082
- throw new ConfigError(`Timed out waiting for migration backup lock at ${lockPath}.`, "INVALID_CONFIG_FILE");
15083
- }
15084
- function assertNotAlreadyCutOver() {
15085
- const configPath = getConfigPath();
15086
- let text;
15087
- try {
15088
- text = fs7.readFileSync(configPath, "utf8");
15089
- } catch (error) {
15090
- if (error.code === "ENOENT")
15091
- return;
15092
- throw error;
15093
- }
15094
- const raw = parseConfigText(text, configPath);
15095
- if (raw.configVersion === CURRENT_CONFIG_VERSION) {
15096
- throw new ConfigError(`Refusing to create a pre-0.9 migration backup from an existing ${CURRENT_CONFIG_VERSION} config at ${configPath}. Restore the original migration bundle or pre-cutover config before retrying.`, "INVALID_CONFIG_FILE");
15097
- }
15098
- }
15099
- function copyRegularArtifact(source, destination) {
15100
- const stat = fs7.statSync(source);
15101
- if (!stat.isFile())
15102
- throw new ConfigError(`Backup source is not a regular file: ${source}`, "INVALID_CONFIG_FILE");
15103
- writeFileAtomic(destination, fs7.readFileSync(source), 384);
15104
- fs7.chmodSync(destination, 384);
15105
- }
15106
- function sqliteQuote(value) {
15107
- return `'${value.replaceAll("'", "''")}'`;
15108
- }
15109
- function backupSqlite(source, destination) {
15110
- const stat = fs7.statSync(source);
15111
- if (!stat.isFile())
15112
- throw new ConfigError(`SQLite backup source is not a regular file: ${source}`, "INVALID_CONFIG_FILE");
15113
- const resolvedSource = path7.resolve(source);
15114
- const activityName = resolvedSource === path7.resolve(getWorkflowDbPath()) ? "workflow-db" : resolvedSource === path7.resolve(getStateDbPathInDataDir()) ? "state-db" : undefined;
15115
- const releaseActivity = activityName ? acquireMaintenanceActivitySync(activityName) : undefined;
15116
- let db;
15117
- try {
15118
- db = openDatabase(source);
15119
- db.exec("PRAGMA busy_timeout = 10000");
15120
- db.exec("PRAGMA wal_checkpoint(FULL)");
15121
- db.exec(`VACUUM INTO ${sqliteQuote(destination)}`);
15122
- } finally {
15123
- try {
15124
- db?.close();
15125
- } finally {
15126
- releaseActivity?.();
15127
- }
15128
- }
15129
- fs7.chmodSync(destination, 384);
15130
- const fd = fs7.openSync(destination, "r");
15131
- try {
15132
- fs7.fsyncSync(fd);
15133
- } finally {
15134
- fs7.closeSync(fd);
15135
- }
15136
- }
15137
- function createMigrationBackupUnlocked() {
15138
- const bundlePath = getMigrationBackupDir();
15139
- if (fs7.existsSync(bundlePath)) {
15140
- return { path: bundlePath, created: false, manifest: verifyMigrationBackup(bundlePath) };
15141
- }
15142
- assertNotAlreadyCutOver();
15143
- const parent = path7.dirname(bundlePath);
15144
- fs7.mkdirSync(parent, { recursive: true, mode: 448 });
15145
- fs7.chmodSync(parent, 448);
15146
- const temporary = path7.join(parent, `.0.9.0.tmp.${process.pid}.${crypto.randomBytes(8).toString("hex")}`);
15147
- fs7.mkdirSync(temporary, { mode: 448 });
15148
- const createdAt = new Date().toISOString();
15149
- const sources = expectedSourcePaths();
15150
- const artifacts = {};
15151
- try {
15152
- for (const name of ARTIFACT_NAMES) {
15153
- const sourcePath = sources[name];
15154
- const destination = path7.join(temporary, name);
15155
- let present = true;
15156
- try {
15157
- fs7.statSync(sourcePath);
15158
- } catch (error) {
15159
- if (error.code === "ENOENT")
15160
- present = false;
15161
- else
15162
- throw error;
14783
+ });
15163
14784
  }
15164
- if (present) {
15165
- if (name === "config.json")
15166
- copyRegularArtifact(sourcePath, destination);
15167
- else
15168
- backupSqlite(sourcePath, destination);
15169
- }
15170
- const byteSize = present ? fs7.statSync(destination).size : 0;
15171
- artifacts[name] = {
15172
- sourcePath,
15173
- present,
15174
- byteSize,
15175
- sha256: present ? sha256File(destination) : null,
15176
- createdAt
15177
- };
15178
- }
15179
- const manifest = { version: MIGRATION_BACKUP_VERSION, createdAt, artifacts };
15180
- writeFileAtomic(path7.join(temporary, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
15181
- `, 384);
15182
- fs7.chmodSync(path7.join(temporary, "manifest.json"), 384);
15183
- fsyncDirectory(temporary);
15184
- fs7.renameSync(temporary, bundlePath);
15185
- fsyncDirectory(parent);
15186
- return { path: bundlePath, created: true, manifest: verifyMigrationBackup(bundlePath) };
15187
- } catch (error) {
15188
- fs7.rmSync(temporary, { recursive: true, force: true });
15189
- if (fs7.existsSync(bundlePath)) {
15190
- throw new ConfigError(`Migration backup creation raced with another writer and left ${bundlePath}. Verify or preserve it before retrying.`, "INVALID_CONFIG_FILE");
15191
14785
  }
15192
- throw error;
15193
- }
15194
- }
15195
- function withMigrationBackupLock(fn) {
15196
- const release = acquireMigrationBackupLock();
15197
- try {
15198
- return fn();
15199
- } finally {
15200
- release();
15201
- }
15202
- }
15203
- function createMigrationBackup() {
15204
- return withConfigLock(() => withMigrationBackupLock(createMigrationBackupUnlocked));
15205
- }
15206
- function ensureMigrationBackup() {
15207
- return createMigrationBackup();
15208
- }
15209
- var MIGRATION_BACKUP_VERSION = "0.9.0", ARTIFACT_NAMES;
15210
- var init_migration_backup = __esm(() => {
15211
- init_database();
15212
- init_common();
15213
- init_config_io();
15214
- init_config_schema();
15215
- init_errors();
15216
- init_file_lock();
15217
- init_maintenance_barrier();
15218
- init_paths();
15219
- ARTIFACT_NAMES = ["config.json", "state.db", "workflow.db"];
14786
+ });
15220
14787
  });
15221
14788
 
15222
14789
  // src/core/config/deep-merge.ts
@@ -15396,7 +14963,7 @@ var init_engine_resolution = __esm(() => {
15396
14963
  });
15397
14964
 
15398
14965
  // src/core/warn.ts
15399
- import fs8 from "node:fs";
14966
+ import fs6 from "node:fs";
15400
14967
  function isVerbose() {
15401
14968
  const env = process.env.AKM_VERBOSE?.trim().toLowerCase();
15402
14969
  if (env === "1" || env === "true" || env === "yes" || env === "on")
@@ -15411,7 +14978,7 @@ function appendToLogFile(level, args) {
15411
14978
  const ts = new Date().toISOString();
15412
14979
  const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
15413
14980
  try {
15414
- fs8.appendFileSync(logFilePath, `[${ts}] [${level}] ${msg}
14981
+ fs6.appendFileSync(logFilePath, `[${ts}] [${level}] ${msg}
15415
14982
  `);
15416
14983
  } catch (e) {
15417
14984
  process.stderr.write(`[akm:warn] log-file write failed (${logFilePath}): ${e}
@@ -15456,11 +15023,11 @@ var init_warn = () => {};
15456
15023
  var init_config_sources = () => {};
15457
15024
 
15458
15025
  // src/core/config/config.ts
15459
- import path8 from "node:path";
15026
+ import path6 from "node:path";
15460
15027
  var PROJECT_CONFIG_RELATIVE_PATH, INDEX_RESERVED_KEYS, PROJECT_CONFIG_DEPRECATION_WARNED;
15461
15028
  var init_config = __esm(() => {
15462
15029
  init_errors();
15463
- init_migration_backup();
15030
+ init_migration_operation();
15464
15031
  init_config_io();
15465
15032
  init_config_schema();
15466
15033
  init_deep_merge();
@@ -15471,7 +15038,7 @@ var init_config = __esm(() => {
15471
15038
  init_config_types();
15472
15039
  init_config_schema();
15473
15040
  init_config_sources();
15474
- PROJECT_CONFIG_RELATIVE_PATH = path8.join(".akm", "config.json");
15041
+ PROJECT_CONFIG_RELATIVE_PATH = path6.join(".akm", "config.json");
15475
15042
  INDEX_RESERVED_KEYS = new Set(["metadataEnhance", "stalenessDetection", "indexBodyOpening"]);
15476
15043
  PROJECT_CONFIG_DEPRECATION_WARNED = new Set;
15477
15044
  });
@@ -15494,11 +15061,11 @@ var init_metadata_contributors = __esm(() => {
15494
15061
  });
15495
15062
 
15496
15063
  // src/indexer/passes/metadata.ts
15497
- import fs9 from "node:fs";
15064
+ import fs7 from "node:fs";
15498
15065
  function extractDescriptionFromComments(filePath) {
15499
15066
  let content;
15500
15067
  try {
15501
- content = fs9.readFileSync(filePath, "utf8");
15068
+ content = fs7.readFileSync(filePath, "utf8");
15502
15069
  } catch {
15503
15070
  return null;
15504
15071
  }
@@ -15548,7 +15115,18 @@ var init_metadata = __esm(() => {
15548
15115
  });
15549
15116
 
15550
15117
  // src/core/asset/asset-ref.ts
15551
- import path9 from "node:path";
15118
+ import path7 from "node:path";
15119
+ function makeAssetRef(type, name, origin) {
15120
+ validateName(name);
15121
+ const normalized = normalizeName(name);
15122
+ const asset = `${type}:${normalized}`;
15123
+ if (!origin)
15124
+ return asset;
15125
+ return `${origin}//${asset}`;
15126
+ }
15127
+ function refToString(ref) {
15128
+ return makeAssetRef(ref.type, ref.name, ref.origin);
15129
+ }
15552
15130
  function parseAssetRef(ref) {
15553
15131
  const trimmed = ref.trim();
15554
15132
  if (!trimmed)
@@ -15586,8 +15164,8 @@ function validateName(name) {
15586
15164
  throw new UsageError("Null byte in asset name.", "MISSING_REQUIRED_ARGUMENT");
15587
15165
  if (/^[A-Za-z]:/.test(name))
15588
15166
  throw new UsageError("Windows drive path in asset name.", "MISSING_REQUIRED_ARGUMENT");
15589
- const normalized = path9.posix.normalize(name.replace(/\\/g, "/"));
15590
- if (path9.posix.isAbsolute(normalized))
15167
+ const normalized = path7.posix.normalize(name.replace(/\\/g, "/"));
15168
+ if (path7.posix.isAbsolute(normalized))
15591
15169
  throw new UsageError("Absolute path in asset name.", "MISSING_REQUIRED_ARGUMENT");
15592
15170
  if (normalized === ".." || normalized.startsWith("../")) {
15593
15171
  throw new UsageError("Path traversal in asset name.", "MISSING_REQUIRED_ARGUMENT");
@@ -15598,7 +15176,7 @@ function validateName(name) {
15598
15176
  }
15599
15177
  }
15600
15178
  function normalizeName(name) {
15601
- return path9.posix.normalize(name.replace(/\\/g, "/"));
15179
+ return path7.posix.normalize(name.replace(/\\/g, "/"));
15602
15180
  }
15603
15181
  var TYPE_ALIASES;
15604
15182
  var init_asset_ref = __esm(() => {
@@ -15631,10 +15209,30 @@ var WORKFLOW_SCHEMA_VERSION = 1;
15631
15209
  // src/workflows/validator.ts
15632
15210
  function runSemanticChecks(draft, frontmatterData, frontmatterEndLine, errors3) {
15633
15211
  checkFrontmatterKeys(frontmatterData, frontmatterEndLine, errors3);
15212
+ checkXrefs(frontmatterData.xrefs, frontmatterEndLine, errors3);
15634
15213
  checkStepIdFormat(draft, errors3);
15635
15214
  checkDuplicateStepIds(draft, errors3);
15636
15215
  checkResourceLimits(draft, errors3);
15637
15216
  }
15217
+ function checkXrefs(value, line, errors3) {
15218
+ if (value === undefined)
15219
+ return;
15220
+ if (!Array.isArray(value)) {
15221
+ errors3.push({ line, message: 'Workflow frontmatter "xrefs" must be an array of canonical asset refs.' });
15222
+ return;
15223
+ }
15224
+ for (const ref of value) {
15225
+ try {
15226
+ if (typeof ref !== "string" || refToString(parseAssetRef(ref)) !== ref)
15227
+ throw new Error("non-canonical ref");
15228
+ } catch {
15229
+ errors3.push({
15230
+ line,
15231
+ message: `Workflow frontmatter "xrefs" contains an invalid or non-canonical ref: ${String(ref)}.`
15232
+ });
15233
+ }
15234
+ }
15235
+ }
15638
15236
  function checkResourceLimits(draft, errors3) {
15639
15237
  if (draft.steps.length > WORKFLOW_MAX_STEPS) {
15640
15238
  errors3.push({ line: 1, message: `Workflow must contain at most ${WORKFLOW_MAX_STEPS} steps.` });
@@ -15657,7 +15255,7 @@ function checkFrontmatterKeys(data, fmEndLine, errors3) {
15657
15255
  continue;
15658
15256
  errors3.push({
15659
15257
  line: fmEndLine,
15660
- message: `Workflow frontmatter "${key}" is not supported. Use only: description, tags, params, name, updated, when_to_use.`
15258
+ message: `Workflow frontmatter "${key}" is not supported. Use only: description, tags, params, name, updated, when_to_use, xrefs.`
15661
15259
  });
15662
15260
  }
15663
15261
  }
@@ -15687,9 +15285,10 @@ function checkDuplicateStepIds(draft, errors3) {
15687
15285
  }
15688
15286
  var STEP_ID_REGEX, ALLOWED_FRONTMATTER_KEYS;
15689
15287
  var init_validator = __esm(() => {
15288
+ init_asset_ref();
15690
15289
  init_resource_limits();
15691
15290
  STEP_ID_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
15692
- ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params", "name", "updated", "when_to_use"]);
15291
+ ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params", "name", "updated", "when_to_use", "xrefs"]);
15693
15292
  });
15694
15293
 
15695
15294
  // src/workflows/parser.ts
@@ -15701,7 +15300,7 @@ function parseWorkflow(markdown, source) {
15701
15300
  };
15702
15301
  }
15703
15302
  const errors3 = [];
15704
- const path10 = source.path;
15303
+ const path8 = source.path;
15705
15304
  const lines = markdown.split(/\r?\n/);
15706
15305
  const totalLines = lines.length;
15707
15306
  const fmBlock = parseFrontmatterBlock(markdown);
@@ -15709,7 +15308,7 @@ function parseWorkflow(markdown, source) {
15709
15308
  const fmData = readFrontmatter(fmBlock?.frontmatter, errors3);
15710
15309
  const description = readDescription(fmData);
15711
15310
  const tags = readTags(fmData, errors3, frontmatterEndLine);
15712
- const parameters = readParameters(fmData, errors3, frontmatterEndLine, path10);
15311
+ const parameters = readParameters(fmData, errors3, frontmatterEndLine, path8);
15713
15312
  const toc = parseMarkdownToc(markdown);
15714
15313
  const { title, titleLine } = extractTitle(toc.headings, errors3);
15715
15314
  for (const h of toc.headings) {
@@ -15726,7 +15325,7 @@ function parseWorkflow(markdown, source) {
15726
15325
  });
15727
15326
  }
15728
15327
  }
15729
- const steps = extractSteps(toc.headings, lines, totalLines, path10, errors3);
15328
+ const steps = extractSteps(toc.headings, lines, totalLines, path8, errors3);
15730
15329
  if (steps.length === 0 && titleLine > 0) {
15731
15330
  errors3.push({
15732
15331
  line: titleLine,
@@ -15740,7 +15339,7 @@ function parseWorkflow(markdown, source) {
15740
15339
  ...tags ? { tags } : {},
15741
15340
  ...parameters ? { parameters } : {},
15742
15341
  steps,
15743
- source: { path: path10, lineCount: totalLines }
15342
+ source: { path: path8, lineCount: totalLines }
15744
15343
  };
15745
15344
  runSemanticChecks(draft, fmData, frontmatterEndLine, errors3);
15746
15345
  if (errors3.length > 0) {
@@ -15775,7 +15374,7 @@ function extractTitle(headings, errors3) {
15775
15374
  }
15776
15375
  return { title, titleLine: first.line };
15777
15376
  }
15778
- function extractSteps(headings, lines, totalLines, path10, errors3) {
15377
+ function extractSteps(headings, lines, totalLines, path8, errors3) {
15779
15378
  const steps = [];
15780
15379
  let sequenceIndex = 0;
15781
15380
  for (let i = 0;i < headings.length; i++) {
@@ -15791,11 +15390,11 @@ function extractSteps(headings, lines, totalLines, path10, errors3) {
15791
15390
  continue;
15792
15391
  }
15793
15392
  const stepEnd = Math.min(findNextSiblingOrParentLine(headings, i, 2) - 1, totalLines);
15794
- const stepSource = { path: path10, start: h.line, end: stepEnd };
15393
+ const stepSource = { path: path8, start: h.line, end: stepEnd };
15795
15394
  const subsections = collectSubsections(headings, i, stepEnd);
15796
15395
  const stepIdSearchEnd = subsections.length > 0 ? subsections[0].headingLine - 1 : stepEnd;
15797
15396
  const stepId = scanStepId(lines, h.line + 1, stepIdSearchEnd, stepTitle, errors3);
15798
- const { instructions, completionCriteria } = collectStepBody(subsections, lines, path10, stepTitle, errors3);
15397
+ const { instructions, completionCriteria } = collectStepBody(subsections, lines, path8, stepTitle, errors3);
15799
15398
  if (!stepId)
15800
15399
  continue;
15801
15400
  if (!instructions) {
@@ -15835,7 +15434,7 @@ function collectSubsections(headings, stepIndex, stepEnd) {
15835
15434
  }
15836
15435
  return subs;
15837
15436
  }
15838
- function collectStepBody(subsections, lines, path10, stepTitle, errors3) {
15437
+ function collectStepBody(subsections, lines, path8, stepTitle, errors3) {
15839
15438
  let instructions;
15840
15439
  let completionCriteria;
15841
15440
  for (const sub of subsections) {
@@ -15857,7 +15456,7 @@ function collectStepBody(subsections, lines, path10, stepTitle, errors3) {
15857
15456
  }
15858
15457
  instructions = {
15859
15458
  text,
15860
- source: { path: path10, start: sub.bodyStart, end: sub.bodyEnd }
15459
+ source: { path: path8, start: sub.bodyStart, end: sub.bodyEnd }
15861
15460
  };
15862
15461
  continue;
15863
15462
  }
@@ -15869,7 +15468,7 @@ function collectStepBody(subsections, lines, path10, stepTitle, errors3) {
15869
15468
  });
15870
15469
  continue;
15871
15470
  }
15872
- const items = collectBullets(lines, sub.bodyStart, sub.bodyEnd, path10);
15471
+ const items = collectBullets(lines, sub.bodyStart, sub.bodyEnd, path8);
15873
15472
  if (items.length === 0) {
15874
15473
  errors3.push({
15875
15474
  line: sub.headingLine,
@@ -15918,7 +15517,7 @@ function scanStepId(lines, startLineInclusive, endLineInclusive, stepTitle, erro
15918
15517
  }
15919
15518
  return foundId;
15920
15519
  }
15921
- function collectBullets(lines, startLineInclusive, endLineInclusive, path10) {
15520
+ function collectBullets(lines, startLineInclusive, endLineInclusive, path8) {
15922
15521
  const items = [];
15923
15522
  for (let lineNum = startLineInclusive;lineNum <= endLineInclusive; lineNum++) {
15924
15523
  const trimmed = (lines[lineNum - 1] ?? "").trim();
@@ -15929,7 +15528,7 @@ function collectBullets(lines, startLineInclusive, endLineInclusive, path10) {
15929
15528
  continue;
15930
15529
  items.push({
15931
15530
  text: match[1].trim(),
15932
- source: { path: path10, start: lineNum, end: lineNum }
15531
+ source: { path: path8, start: lineNum, end: lineNum }
15933
15532
  });
15934
15533
  }
15935
15534
  return items;
@@ -15998,7 +15597,7 @@ function readTags(data, errors3, fmEndLine) {
15998
15597
  }
15999
15598
  return v.map((tag) => tag.trim());
16000
15599
  }
16001
- function readParameters(data, errors3, fmEndLine, path10) {
15600
+ function readParameters(data, errors3, fmEndLine, path8) {
16002
15601
  const v = data.params;
16003
15602
  if (v === undefined || v === null)
16004
15603
  return;
@@ -16032,7 +15631,7 @@ function readParameters(data, errors3, fmEndLine, path10) {
16032
15631
  out.push({
16033
15632
  name: trimmedName,
16034
15633
  description: desc.trim(),
16035
- source: { path: path10, start: 1, end: fmEndLine }
15634
+ source: { path: path8, start: 1, end: fmEndLine }
16036
15635
  });
16037
15636
  }
16038
15637
  return out.length > 0 ? out : undefined;
@@ -16105,9 +15704,9 @@ function parseWorkflowProgram(yamlText, source) {
16105
15704
  } catch (cause) {
16106
15705
  return { ok: false, errors: [{ line: 1, message: `YAML expansion failed: ${describeError(cause)}` }] };
16107
15706
  }
16108
- const lineAt = (path10) => {
16109
- for (let depth = path10.length;depth >= 0; depth--) {
16110
- const node = depth === 0 ? doc.contents : doc.getIn(path10.slice(0, depth), true);
15707
+ const lineAt = (path8) => {
15708
+ for (let depth = path8.length;depth >= 0; depth--) {
15709
+ const node = depth === 0 ? doc.contents : doc.getIn(path8.slice(0, depth), true);
16111
15710
  const range = node?.range;
16112
15711
  if (range)
16113
15712
  return Math.max(1, lineCounter2.linePos(range[0]).line);
@@ -16119,10 +15718,10 @@ function parseWorkflowProgram(yamlText, source) {
16119
15718
  errors: errors3,
16120
15719
  lineAt,
16121
15720
  lineAtOffset: (offset) => Math.max(1, lineCounter2.linePos(offset).line),
16122
- nodeAt: (path10) => path10.length === 0 ? doc.contents : doc.getIn(path10, true),
16123
- refAt: (path10) => {
16124
- for (let depth = path10.length;depth >= 0; depth--) {
16125
- const node = depth === 0 ? doc.contents : doc.getIn(path10.slice(0, depth), true);
15721
+ nodeAt: (path8) => path8.length === 0 ? doc.contents : doc.getIn(path8, true),
15722
+ refAt: (path8) => {
15723
+ for (let depth = path8.length;depth >= 0; depth--) {
15724
+ const node = depth === 0 ? doc.contents : doc.getIn(path8.slice(0, depth), true);
16126
15725
  const range = node?.range;
16127
15726
  if (range) {
16128
15727
  const start = Math.max(1, lineCounter2.linePos(range[0]).line);
@@ -16132,9 +15731,9 @@ function parseWorkflowProgram(yamlText, source) {
16132
15731
  }
16133
15732
  return { path: source.path, start: 1, end: 1 };
16134
15733
  },
16135
- err: (path10, message) => errors3.push({ line: lineAt(path10), message }),
15734
+ err: (path8, message) => errors3.push({ line: lineAt(path8), message }),
16136
15735
  errAtLine: (line, message) => errors3.push({ line, message }),
16137
- checkTemplates: (text, path10, label) => checkTemplates(ctx, text, path10, label)
15736
+ checkTemplates: (text, path8, label) => checkTemplates(ctx, text, path8, label)
16138
15737
  };
16139
15738
  if (!isPlainRecord(root)) {
16140
15739
  return {
@@ -16214,32 +15813,32 @@ function parseParams(ctx, raw) {
16214
15813
  function parseDefaults(ctx, raw) {
16215
15814
  if (raw === undefined)
16216
15815
  return;
16217
- const path10 = ["defaults"];
15816
+ const path8 = ["defaults"];
16218
15817
  if (!isPlainRecord(raw)) {
16219
- ctx.err(path10, `"defaults" must be a mapping with any of: ${DEFAULTS_KEYS.join(", ")}.`);
15818
+ ctx.err(path8, `"defaults" must be a mapping with any of: ${DEFAULTS_KEYS.join(", ")}.`);
16220
15819
  return;
16221
15820
  }
16222
- checkUnknownKeys(ctx, raw, path10, DEFAULTS_KEYS, `"defaults"`);
15821
+ checkUnknownKeys(ctx, raw, path8, DEFAULTS_KEYS, `"defaults"`);
16223
15822
  const defaults = {};
16224
15823
  if (raw.engine !== undefined) {
16225
15824
  if (typeof raw.engine === "string" && raw.engine.trim() !== "")
16226
15825
  defaults.engine = raw.engine.trim();
16227
15826
  else
16228
- ctx.err([...path10, "engine"], `"defaults.engine" must be a non-empty engine name.`);
15827
+ ctx.err([...path8, "engine"], `"defaults.engine" must be a non-empty engine name.`);
16229
15828
  }
16230
15829
  if (raw.model !== undefined) {
16231
15830
  if (typeof raw.model === "string" && raw.model.trim() !== "")
16232
15831
  defaults.model = raw.model.trim();
16233
15832
  else
16234
- ctx.err([...path10, "model"], `"defaults.model" must be a non-empty string (a model alias or exact id).`);
15833
+ ctx.err([...path8, "model"], `"defaults.model" must be a non-empty string (a model alias or exact id).`);
16235
15834
  }
16236
- const timeoutMs = parseTimeoutField(ctx, raw.timeout, [...path10, "timeout"], `"defaults.timeout"`);
15835
+ const timeoutMs = parseTimeoutField(ctx, raw.timeout, [...path8, "timeout"], `"defaults.timeout"`);
16237
15836
  if (timeoutMs !== undefined)
16238
15837
  defaults.timeoutMs = timeoutMs;
16239
- const onError = parseEnumField(ctx, raw.on_error, [...path10, "on_error"], `"defaults.on_error"`, PROGRAM_ON_ERROR);
15838
+ const onError = parseEnumField(ctx, raw.on_error, [...path8, "on_error"], `"defaults.on_error"`, PROGRAM_ON_ERROR);
16240
15839
  if (onError !== undefined)
16241
15840
  defaults.onError = onError;
16242
- const llm = parseLlmOverrides(ctx, raw.llm, [...path10, "llm"], `"defaults.llm"`);
15841
+ const llm = parseLlmOverrides(ctx, raw.llm, [...path8, "llm"], `"defaults.llm"`);
16243
15842
  if (llm !== undefined)
16244
15843
  defaults.llm = llm;
16245
15844
  return Object.keys(defaults).length > 0 ? defaults : undefined;
@@ -16247,25 +15846,25 @@ function parseDefaults(ctx, raw) {
16247
15846
  function parseBudget(ctx, raw) {
16248
15847
  if (raw === undefined)
16249
15848
  return;
16250
- const path10 = ["budget"];
15849
+ const path8 = ["budget"];
16251
15850
  if (!isPlainRecord(raw)) {
16252
- ctx.err(path10, `"budget" must be a mapping with any of: ${BUDGET_KEYS.join(", ")}.`);
15851
+ ctx.err(path8, `"budget" must be a mapping with any of: ${BUDGET_KEYS.join(", ")}.`);
16253
15852
  return;
16254
15853
  }
16255
- checkUnknownKeys(ctx, raw, path10, BUDGET_KEYS, `"budget"`);
15854
+ checkUnknownKeys(ctx, raw, path8, BUDGET_KEYS, `"budget"`);
16256
15855
  const budget = {};
16257
15856
  if (raw.max_tokens !== undefined) {
16258
15857
  if (typeof raw.max_tokens === "number" && Number.isInteger(raw.max_tokens) && raw.max_tokens >= 1) {
16259
15858
  budget.maxTokens = raw.max_tokens;
16260
15859
  } else {
16261
- ctx.err([...path10, "max_tokens"], `"budget.max_tokens" must be an integer >= 1.`);
15860
+ ctx.err([...path8, "max_tokens"], `"budget.max_tokens" must be an integer >= 1.`);
16262
15861
  }
16263
15862
  }
16264
15863
  if (raw.max_units !== undefined) {
16265
15864
  if (typeof raw.max_units === "number" && Number.isInteger(raw.max_units) && raw.max_units >= 1 && raw.max_units <= WORKFLOW_MAX_MAP_EXPANSION) {
16266
15865
  budget.maxUnits = raw.max_units;
16267
15866
  } else {
16268
- ctx.err([...path10, "max_units"], `"budget.max_units" must be an integer from 1 through ${WORKFLOW_MAX_MAP_EXPANSION}.`);
15867
+ ctx.err([...path8, "max_units"], `"budget.max_units" must be an integer from 1 through ${WORKFLOW_MAX_MAP_EXPANSION}.`);
16269
15868
  }
16270
15869
  }
16271
15870
  return Object.keys(budget).length > 0 ? budget : undefined;
@@ -16288,23 +15887,23 @@ function parseSteps(ctx, raw) {
16288
15887
  const seenIds = new Map;
16289
15888
  const routeChecks = [];
16290
15889
  raw.forEach((rawStep, index) => {
16291
- const path10 = ["steps", index];
15890
+ const path8 = ["steps", index];
16292
15891
  if (!isPlainRecord(rawStep)) {
16293
- ctx.err(path10, `Step ${index + 1} must be a mapping with an "id" and exactly one of "unit", "map", or "route".`);
15892
+ ctx.err(path8, `Step ${index + 1} must be a mapping with an "id" and exactly one of "unit", "map", or "route".`);
16294
15893
  return;
16295
15894
  }
16296
15895
  const label = typeof rawStep.id === "string" && rawStep.id !== "" ? `Step "${rawStep.id}"` : `Step ${index + 1}`;
16297
- checkUnknownKeys(ctx, rawStep, path10, STEP_KEYS, label);
15896
+ checkUnknownKeys(ctx, rawStep, path8, STEP_KEYS, label);
16298
15897
  let id = "";
16299
15898
  if (typeof rawStep.id !== "string" || rawStep.id === "") {
16300
- ctx.err([...path10, "id"], `${label} requires a non-empty string "id".`);
15899
+ ctx.err([...path8, "id"], `${label} requires a non-empty string "id".`);
16301
15900
  } else if (!PROGRAM_STEP_ID_PATTERN.test(rawStep.id)) {
16302
- ctx.err([...path10, "id"], `${label} has an invalid id "${rawStep.id}". A step id cannot be referenced from \${{ }} expressions ` + `unless it matches [A-Za-z_][A-Za-z0-9_-]* (a letter or underscore first, then letters, digits, ` + `underscores, or dashes; no dots, no leading digit) — otherwise \${{ steps.${rawStep.id}.output }} ` + `cannot be written.`);
15901
+ ctx.err([...path8, "id"], `${label} has an invalid id "${rawStep.id}". A step id cannot be referenced from \${{ }} expressions ` + `unless it matches [A-Za-z_][A-Za-z0-9_-]* (a letter or underscore first, then letters, digits, ` + `underscores, or dashes; no dots, no leading digit) — otherwise \${{ steps.${rawStep.id}.output }} ` + `cannot be written.`);
16303
15902
  } else {
16304
15903
  id = rawStep.id;
16305
15904
  const firstIndex = seenIds.get(id);
16306
15905
  if (firstIndex !== undefined) {
16307
- ctx.err([...path10, "id"], `Duplicate step id "${id}" (first used by step ${firstIndex + 1}). Step ids must be unique.`);
15906
+ ctx.err([...path8, "id"], `Duplicate step id "${id}" (first used by step ${firstIndex + 1}). Step ids must be unique.`);
16308
15907
  } else {
16309
15908
  seenIds.set(id, index);
16310
15909
  }
@@ -16313,22 +15912,22 @@ function parseSteps(ctx, raw) {
16313
15912
  if (rawStep.title !== undefined) {
16314
15913
  if (typeof rawStep.title === "string" && rawStep.title.trim() !== "") {
16315
15914
  title = rawStep.title.trim();
16316
- ctx.checkTemplates(rawStep.title, [...path10, "title"], `${label} "title"`);
15915
+ ctx.checkTemplates(rawStep.title, [...path8, "title"], `${label} "title"`);
16317
15916
  } else {
16318
- ctx.err([...path10, "title"], `${label} "title" must be a non-empty string.`);
15917
+ ctx.err([...path8, "title"], `${label} "title" must be a non-empty string.`);
16319
15918
  }
16320
15919
  }
16321
15920
  const declaredKinds = STEP_KINDS.filter((kind) => rawStep[kind] !== undefined);
16322
15921
  if (declaredKinds.length !== 1) {
16323
15922
  const found = declaredKinds.length === 0 ? "found none" : `found ${declaredKinds.join(" + ")}`;
16324
- ctx.err(path10, `${label} must declare exactly one of "unit", "map", or "route" (${found}).`);
16325
- }
16326
- const unit = rawStep.unit !== undefined ? parseUnit(ctx, rawStep.unit, [...path10, "unit"], label) : undefined;
16327
- const map = rawStep.map !== undefined ? parseMap(ctx, rawStep.map, [...path10, "map"], label) : undefined;
16328
- const route = rawStep.route !== undefined ? parseRoute(ctx, rawStep.route, [...path10, "route"], label, index, routeChecks) : undefined;
16329
- const output = parseSchemaObject(ctx, rawStep.output, [...path10, "output"], `${label} "output"`);
16330
- const gate = rawStep.gate !== undefined ? parseGate(ctx, rawStep.gate, [...path10, "gate"], label) : undefined;
16331
- const step = { id, source: ctx.refAt(path10) };
15923
+ ctx.err(path8, `${label} must declare exactly one of "unit", "map", or "route" (${found}).`);
15924
+ }
15925
+ const unit = rawStep.unit !== undefined ? parseUnit(ctx, rawStep.unit, [...path8, "unit"], label) : undefined;
15926
+ const map = rawStep.map !== undefined ? parseMap(ctx, rawStep.map, [...path8, "map"], label) : undefined;
15927
+ const route = rawStep.route !== undefined ? parseRoute(ctx, rawStep.route, [...path8, "route"], label, index, routeChecks) : undefined;
15928
+ const output = parseSchemaObject(ctx, rawStep.output, [...path8, "output"], `${label} "output"`);
15929
+ const gate = rawStep.gate !== undefined ? parseGate(ctx, rawStep.gate, [...path8, "gate"], label) : undefined;
15930
+ const step = { id, source: ctx.refAt(path8) };
16332
15931
  if (title !== undefined)
16333
15932
  step.title = title;
16334
15933
  if (declaredKinds.length === 1) {
@@ -16362,86 +15961,86 @@ function parseSteps(ctx, raw) {
16362
15961
  }
16363
15962
  return steps;
16364
15963
  }
16365
- function parseUnit(ctx, raw, path10, stepLabel) {
15964
+ function parseUnit(ctx, raw, path8, stepLabel) {
16366
15965
  if (!isPlainRecord(raw)) {
16367
- ctx.err(path10, `${stepLabel} "unit" must be a mapping with an "instructions" key.`);
15966
+ ctx.err(path8, `${stepLabel} "unit" must be a mapping with an "instructions" key.`);
16368
15967
  return;
16369
15968
  }
16370
- checkUnknownKeys(ctx, raw, path10, UNIT_KEYS, `${stepLabel} "unit"`);
16371
- const unit = { instructions: "", source: ctx.refAt(path10) };
15969
+ checkUnknownKeys(ctx, raw, path8, UNIT_KEYS, `${stepLabel} "unit"`);
15970
+ const unit = { instructions: "", source: ctx.refAt(path8) };
16372
15971
  if (typeof raw.instructions === "string" && raw.instructions.trim() !== "") {
16373
15972
  unit.instructions = raw.instructions;
16374
- ctx.checkTemplates(raw.instructions, [...path10, "instructions"], `${stepLabel} "instructions"`);
15973
+ ctx.checkTemplates(raw.instructions, [...path8, "instructions"], `${stepLabel} "instructions"`);
16375
15974
  if (utf8Bytes(raw.instructions) > WORKFLOW_MAX_INSTRUCTION_BYTES) {
16376
- ctx.err([...path10, "instructions"], `${stepLabel} "instructions" exceeds the 256 KiB resource limit.`);
15975
+ ctx.err([...path8, "instructions"], `${stepLabel} "instructions" exceeds the 256 KiB resource limit.`);
16377
15976
  }
16378
15977
  } else {
16379
- ctx.err([...path10, "instructions"], `${stepLabel} "unit" requires non-empty string "instructions".`);
15978
+ ctx.err([...path8, "instructions"], `${stepLabel} "unit" requires non-empty string "instructions".`);
16380
15979
  }
16381
15980
  if (raw.engine !== undefined) {
16382
15981
  if (typeof raw.engine === "string" && raw.engine.trim() !== "")
16383
15982
  unit.engine = raw.engine.trim();
16384
15983
  else
16385
- ctx.err([...path10, "engine"], `${stepLabel} "engine" must be a non-empty engine name.`);
15984
+ ctx.err([...path8, "engine"], `${stepLabel} "engine" must be a non-empty engine name.`);
16386
15985
  }
16387
15986
  if (raw.model !== undefined) {
16388
15987
  if (typeof raw.model === "string" && raw.model.trim() !== "")
16389
15988
  unit.model = raw.model.trim();
16390
15989
  else
16391
- ctx.err([...path10, "model"], `${stepLabel} "model" must be a non-empty string (a model alias or exact id).`);
15990
+ ctx.err([...path8, "model"], `${stepLabel} "model" must be a non-empty string (a model alias or exact id).`);
16392
15991
  }
16393
- const llm = parseLlmOverrides(ctx, raw.llm, [...path10, "llm"], `${stepLabel} "llm"`);
15992
+ const llm = parseLlmOverrides(ctx, raw.llm, [...path8, "llm"], `${stepLabel} "llm"`);
16394
15993
  if (llm !== undefined)
16395
15994
  unit.llm = llm;
16396
- const timeoutMs = parseTimeoutField(ctx, raw.timeout, [...path10, "timeout"], `${stepLabel} "timeout"`);
15995
+ const timeoutMs = parseTimeoutField(ctx, raw.timeout, [...path8, "timeout"], `${stepLabel} "timeout"`);
16397
15996
  if (timeoutMs !== undefined)
16398
15997
  unit.timeoutMs = timeoutMs;
16399
- const retry = parseRetry(ctx, raw.retry, [...path10, "retry"], stepLabel);
15998
+ const retry = parseRetry(ctx, raw.retry, [...path8, "retry"], stepLabel);
16400
15999
  if (retry !== undefined)
16401
16000
  unit.retry = retry;
16402
- const onError = parseEnumField(ctx, raw.on_error, [...path10, "on_error"], `${stepLabel} "on_error"`, PROGRAM_ON_ERROR);
16001
+ const onError = parseEnumField(ctx, raw.on_error, [...path8, "on_error"], `${stepLabel} "on_error"`, PROGRAM_ON_ERROR);
16403
16002
  if (onError !== undefined)
16404
16003
  unit.onError = onError;
16405
- const output = parseSchemaObject(ctx, raw.output, [...path10, "output"], `${stepLabel} unit "output"`);
16004
+ const output = parseSchemaObject(ctx, raw.output, [...path8, "output"], `${stepLabel} unit "output"`);
16406
16005
  if (output !== undefined)
16407
16006
  unit.output = output;
16408
16007
  if (raw.env !== undefined) {
16409
16008
  if (Array.isArray(raw.env) && raw.env.every((entry) => typeof entry === "string" && entry.trim() !== "")) {
16410
16009
  unit.env = raw.env.map((entry) => entry.trim());
16411
16010
  } else {
16412
- ctx.err([...path10, "env"], `${stepLabel} "env" must be a list of non-empty env asset refs.`);
16011
+ ctx.err([...path8, "env"], `${stepLabel} "env" must be a list of non-empty env asset refs.`);
16413
16012
  }
16414
16013
  }
16415
- const isolation = parseEnumField(ctx, raw.isolation, [...path10, "isolation"], `${stepLabel} "isolation"`, PROGRAM_ISOLATION_KINDS);
16014
+ const isolation = parseEnumField(ctx, raw.isolation, [...path8, "isolation"], `${stepLabel} "isolation"`, PROGRAM_ISOLATION_KINDS);
16416
16015
  if (isolation !== undefined)
16417
16016
  unit.isolation = isolation;
16418
16017
  return unit;
16419
16018
  }
16420
- function parseMap(ctx, raw, path10, stepLabel) {
16019
+ function parseMap(ctx, raw, path8, stepLabel) {
16421
16020
  if (!isPlainRecord(raw)) {
16422
- ctx.err(path10, `${stepLabel} "map" must be a mapping with "over" and "unit" keys.`);
16021
+ ctx.err(path8, `${stepLabel} "map" must be a mapping with "over" and "unit" keys.`);
16423
16022
  return;
16424
16023
  }
16425
- checkUnknownKeys(ctx, raw, path10, MAP_KEYS, `${stepLabel} "map"`);
16024
+ checkUnknownKeys(ctx, raw, path8, MAP_KEYS, `${stepLabel} "map"`);
16426
16025
  let over = "";
16427
16026
  if (typeof raw.over === "string" && raw.over.trim() !== "") {
16428
16027
  over = raw.over.trim();
16429
- ctx.checkTemplates(raw.over, [...path10, "over"], `${stepLabel} "over"`);
16028
+ ctx.checkTemplates(raw.over, [...path8, "over"], `${stepLabel} "over"`);
16430
16029
  } else {
16431
- ctx.err([...path10, "over"], `${stepLabel} "map" requires "over": a \${{ … }} expression naming the item list (e.g. \${{ steps.discover.output.files }}).`);
16030
+ ctx.err([...path8, "over"], `${stepLabel} "map" requires "over": a \${{ … }} expression naming the item list (e.g. \${{ steps.discover.output.files }}).`);
16432
16031
  }
16433
16032
  let concurrency;
16434
16033
  if (raw.concurrency !== undefined) {
16435
16034
  if (typeof raw.concurrency === "number" && Number.isInteger(raw.concurrency) && raw.concurrency > 0) {
16436
16035
  concurrency = raw.concurrency;
16437
16036
  } else {
16438
- ctx.err([...path10, "concurrency"], `${stepLabel} "concurrency" must be a positive integer.`);
16037
+ ctx.err([...path8, "concurrency"], `${stepLabel} "concurrency" must be a positive integer.`);
16439
16038
  }
16440
16039
  }
16441
- const reducer = parseEnumField(ctx, raw.reducer, [...path10, "reducer"], `${stepLabel} "reducer"`, PROGRAM_REDUCERS);
16442
- const unit = raw.unit !== undefined ? parseUnit(ctx, raw.unit, [...path10, "unit"], stepLabel) : undefined;
16040
+ const reducer = parseEnumField(ctx, raw.reducer, [...path8, "reducer"], `${stepLabel} "reducer"`, PROGRAM_REDUCERS);
16041
+ const unit = raw.unit !== undefined ? parseUnit(ctx, raw.unit, [...path8, "unit"], stepLabel) : undefined;
16443
16042
  if (raw.unit === undefined) {
16444
- ctx.err(path10, `${stepLabel} "map" requires a nested "unit" to fan out.`);
16043
+ ctx.err(path8, `${stepLabel} "map" requires a nested "unit" to fan out.`);
16445
16044
  }
16446
16045
  if (unit === undefined)
16447
16046
  return;
@@ -16452,21 +16051,21 @@ function parseMap(ctx, raw, path10, stepLabel) {
16452
16051
  map.reducer = reducer;
16453
16052
  return map;
16454
16053
  }
16455
- function parseRoute(ctx, raw, path10, stepLabel, stepIndex, routeChecks) {
16054
+ function parseRoute(ctx, raw, path8, stepLabel, stepIndex, routeChecks) {
16456
16055
  if (!isPlainRecord(raw)) {
16457
- ctx.err(path10, `${stepLabel} "route" must be a mapping with "input" and "when" keys.`);
16056
+ ctx.err(path8, `${stepLabel} "route" must be a mapping with "input" and "when" keys.`);
16458
16057
  return;
16459
16058
  }
16460
- checkUnknownKeys(ctx, raw, path10, ROUTE_KEYS, `${stepLabel} "route"`);
16059
+ checkUnknownKeys(ctx, raw, path8, ROUTE_KEYS, `${stepLabel} "route"`);
16461
16060
  let input = "";
16462
16061
  if (typeof raw.input === "string" && raw.input.trim() !== "") {
16463
16062
  input = raw.input.trim();
16464
- ctx.checkTemplates(raw.input, [...path10, "input"], `${stepLabel} "route.input"`);
16063
+ ctx.checkTemplates(raw.input, [...path8, "input"], `${stepLabel} "route.input"`);
16465
16064
  } else {
16466
- ctx.err([...path10, "input"], `${stepLabel} "route" requires "input": a \${{ … }} expression naming the value to route on.`);
16065
+ ctx.err([...path8, "input"], `${stepLabel} "route" requires "input": a \${{ … }} expression naming the value to route on.`);
16467
16066
  }
16468
16067
  const check = { stepIndex, stepLabel, branches: [] };
16469
- const whenPath = [...path10, "when"];
16068
+ const whenPath = [...path8, "when"];
16470
16069
  const whenNode = ctx.nodeAt(whenPath);
16471
16070
  if (raw.when === undefined || !isPlainRecord(raw.when)) {
16472
16071
  ctx.err(whenPath, `${stepLabel} "route" requires "when": a mapping of match value to target step id (e.g. when: { pass: ship }).`);
@@ -16519,9 +16118,9 @@ function parseRoute(ctx, raw, path10, stepLabel, stepIndex, routeChecks) {
16519
16118
  if (raw.default !== undefined) {
16520
16119
  if (typeof raw.default === "string" && raw.default.trim() !== "") {
16521
16120
  defaultStepId = raw.default.trim();
16522
- check.defaultTarget = { stepId: defaultStepId, line: ctx.lineAt([...path10, "default"]) };
16121
+ check.defaultTarget = { stepId: defaultStepId, line: ctx.lineAt([...path8, "default"]) };
16523
16122
  } else {
16524
- ctx.err([...path10, "default"], `${stepLabel} "route.default" must be a step id string.`);
16123
+ ctx.err([...path8, "default"], `${stepLabel} "route.default" must be a step id string.`);
16525
16124
  }
16526
16125
  }
16527
16126
  routeChecks.push(check);
@@ -16530,48 +16129,48 @@ function parseRoute(ctx, raw, path10, stepLabel, stepIndex, routeChecks) {
16530
16129
  route.defaultStepId = defaultStepId;
16531
16130
  return route;
16532
16131
  }
16533
- function parseGate(ctx, raw, path10, stepLabel) {
16132
+ function parseGate(ctx, raw, path8, stepLabel) {
16534
16133
  if (!isPlainRecord(raw)) {
16535
- ctx.err(path10, `${stepLabel} "gate" must be a mapping with a "criteria" list.`);
16134
+ ctx.err(path8, `${stepLabel} "gate" must be a mapping with a "criteria" list.`);
16536
16135
  return;
16537
16136
  }
16538
- checkUnknownKeys(ctx, raw, path10, GATE_KEYS, `${stepLabel} "gate"`);
16137
+ checkUnknownKeys(ctx, raw, path8, GATE_KEYS, `${stepLabel} "gate"`);
16539
16138
  const gate = { criteria: [] };
16540
16139
  if (Array.isArray(raw.criteria) && raw.criteria.length > 0 && raw.criteria.every((c) => typeof c === "string" && c.trim() !== "")) {
16541
16140
  gate.criteria = raw.criteria.map((c) => c.trim());
16542
16141
  for (const [i, c] of raw.criteria.entries()) {
16543
- ctx.checkTemplates(c, [...path10, "criteria", i], `${stepLabel} gate criterion ${i + 1}`);
16142
+ ctx.checkTemplates(c, [...path8, "criteria", i], `${stepLabel} gate criterion ${i + 1}`);
16544
16143
  }
16545
16144
  } else {
16546
- ctx.err([...path10, "criteria"], `${stepLabel} "gate" requires "criteria": a non-empty list of criterion strings.`);
16145
+ ctx.err([...path8, "criteria"], `${stepLabel} "gate" requires "criteria": a non-empty list of criterion strings.`);
16547
16146
  }
16548
16147
  if (raw.max_loops !== undefined) {
16549
16148
  if (typeof raw.max_loops === "number" && Number.isInteger(raw.max_loops) && raw.max_loops >= 1) {
16550
16149
  gate.maxLoops = raw.max_loops;
16551
16150
  } else {
16552
- ctx.err([...path10, "max_loops"], `${stepLabel} "gate.max_loops" must be an integer >= 1.`);
16151
+ ctx.err([...path8, "max_loops"], `${stepLabel} "gate.max_loops" must be an integer >= 1.`);
16553
16152
  }
16554
16153
  }
16555
16154
  if (raw.required !== undefined) {
16556
16155
  if (typeof raw.required === "boolean") {
16557
16156
  gate.required = raw.required;
16558
16157
  } else {
16559
- ctx.err([...path10, "required"], `${stepLabel} "gate.required" must be a boolean (true or false).`);
16158
+ ctx.err([...path8, "required"], `${stepLabel} "gate.required" must be a boolean (true or false).`);
16560
16159
  }
16561
16160
  }
16562
16161
  return gate;
16563
16162
  }
16564
- function parseRetry(ctx, raw, path10, stepLabel) {
16163
+ function parseRetry(ctx, raw, path8, stepLabel) {
16565
16164
  if (raw === undefined)
16566
16165
  return;
16567
16166
  if (!isPlainRecord(raw)) {
16568
- ctx.err(path10, `${stepLabel} "retry" must be a mapping: { max: <n>, on: [<failure_reason>, …] }.`);
16167
+ ctx.err(path8, `${stepLabel} "retry" must be a mapping: { max: <n>, on: [<failure_reason>, …] }.`);
16569
16168
  return;
16570
16169
  }
16571
- checkUnknownKeys(ctx, raw, path10, RETRY_KEYS, `${stepLabel} "retry"`);
16170
+ checkUnknownKeys(ctx, raw, path8, RETRY_KEYS, `${stepLabel} "retry"`);
16572
16171
  let ok = true;
16573
16172
  if (!(typeof raw.max === "number" && Number.isInteger(raw.max) && raw.max >= 0)) {
16574
- ctx.err([...path10, "max"], `${stepLabel} "retry.max" is required and must be a non-negative integer.`);
16173
+ ctx.err([...path8, "max"], `${stepLabel} "retry.max" is required and must be a non-negative integer.`);
16575
16174
  ok = false;
16576
16175
  }
16577
16176
  const on = [];
@@ -16580,27 +16179,27 @@ function parseRetry(ctx, raw, path10, stepLabel) {
16580
16179
  if (typeof reason === "string" && PROGRAM_RETRY_REASONS.includes(reason)) {
16581
16180
  on.push(reason);
16582
16181
  } else {
16583
- ctx.err([...path10, "on", i], `${stepLabel} "retry.on" has unknown failure reason ${JSON.stringify(reason)}. Valid reasons: ${PROGRAM_RETRY_REASONS.join(", ")}.`);
16182
+ ctx.err([...path8, "on", i], `${stepLabel} "retry.on" has unknown failure reason ${JSON.stringify(reason)}. Valid reasons: ${PROGRAM_RETRY_REASONS.join(", ")}.`);
16584
16183
  ok = false;
16585
16184
  }
16586
16185
  });
16587
16186
  } else {
16588
- ctx.err([...path10, "on"], `${stepLabel} "retry.on" is required and must be a non-empty list of failure reasons (${PROGRAM_RETRY_REASONS.join(", ")}).`);
16187
+ ctx.err([...path8, "on"], `${stepLabel} "retry.on" is required and must be a non-empty list of failure reasons (${PROGRAM_RETRY_REASONS.join(", ")}).`);
16589
16188
  ok = false;
16590
16189
  }
16591
16190
  return ok ? { max: raw.max, on } : undefined;
16592
16191
  }
16593
- function parseTimeoutField(ctx, raw, path10, label) {
16192
+ function parseTimeoutField(ctx, raw, path8, label) {
16594
16193
  if (raw === undefined)
16595
16194
  return;
16596
16195
  if (typeof raw === "number") {
16597
16196
  if (Number.isInteger(raw) && raw > 0)
16598
16197
  return raw;
16599
- ctx.err(path10, `${label} has a non-positive timeout ${JSON.stringify(raw)}. ${TIMEOUT_HINT}.`);
16198
+ ctx.err(path8, `${label} has a non-positive timeout ${JSON.stringify(raw)}. ${TIMEOUT_HINT}.`);
16600
16199
  return;
16601
16200
  }
16602
16201
  if (typeof raw !== "string") {
16603
- ctx.err(path10, `${label} must be a duration string. ${TIMEOUT_HINT}.`);
16202
+ ctx.err(path8, `${label} must be a duration string. ${TIMEOUT_HINT}.`);
16604
16203
  return;
16605
16204
  }
16606
16205
  const value = raw.trim().toLowerCase();
@@ -16608,31 +16207,31 @@ function parseTimeoutField(ctx, raw, path10, label) {
16608
16207
  return null;
16609
16208
  const match = value.match(TIMEOUT_VALUE);
16610
16209
  if (!match) {
16611
- ctx.err(path10, `${label} has an invalid timeout "${raw}". ${TIMEOUT_HINT}.`);
16210
+ ctx.err(path8, `${label} has an invalid timeout "${raw}". ${TIMEOUT_HINT}.`);
16612
16211
  return;
16613
16212
  }
16614
16213
  const n = Number.parseInt(match[1], 10);
16615
16214
  const unit = match[2] ?? "ms";
16616
16215
  const timeoutMs = unit === "m" ? n * 60000 : unit === "s" ? n * 1000 : n;
16617
16216
  if (timeoutMs <= 0) {
16618
- ctx.err(path10, `${label} has a non-positive timeout "${raw}". Use a positive duration or "none".`);
16217
+ ctx.err(path8, `${label} has a non-positive timeout "${raw}". Use a positive duration or "none".`);
16619
16218
  return;
16620
16219
  }
16621
16220
  return timeoutMs;
16622
16221
  }
16623
- function parseEnumField(ctx, raw, path10, label, allowed) {
16222
+ function parseEnumField(ctx, raw, path8, label, allowed) {
16624
16223
  if (raw === undefined)
16625
16224
  return;
16626
16225
  if (typeof raw === "string" && allowed.includes(raw))
16627
16226
  return raw;
16628
- ctx.err(path10, `${label} must be one of: ${allowed.join(" | ")} (got ${JSON.stringify(raw)}).`);
16227
+ ctx.err(path8, `${label} must be one of: ${allowed.join(" | ")} (got ${JSON.stringify(raw)}).`);
16629
16228
  return;
16630
16229
  }
16631
- function parseLlmOverrides(ctx, raw, path10, label) {
16230
+ function parseLlmOverrides(ctx, raw, path8, label) {
16632
16231
  if (raw === undefined)
16633
16232
  return;
16634
16233
  if (!isPlainRecord(raw)) {
16635
- ctx.err(path10, `${label} must be a mapping of LLM invocation overrides.`);
16234
+ ctx.err(path8, `${label} must be a mapping of LLM invocation overrides.`);
16636
16235
  return;
16637
16236
  }
16638
16237
  const keys = [
@@ -16643,36 +16242,36 @@ function parseLlmOverrides(ctx, raw, path10, label) {
16643
16242
  "context_length",
16644
16243
  "enable_thinking"
16645
16244
  ];
16646
- checkUnknownKeys(ctx, raw, path10, keys, label);
16245
+ checkUnknownKeys(ctx, raw, path8, keys, label);
16647
16246
  const result = {};
16648
16247
  if (raw.temperature !== undefined) {
16649
16248
  if (typeof raw.temperature === "number" && Number.isFinite(raw.temperature))
16650
16249
  result.temperature = raw.temperature;
16651
16250
  else
16652
- ctx.err([...path10, "temperature"], `${label}.temperature must be a finite number.`);
16251
+ ctx.err([...path8, "temperature"], `${label}.temperature must be a finite number.`);
16653
16252
  }
16654
16253
  if (raw.max_tokens !== undefined) {
16655
16254
  if (typeof raw.max_tokens === "number" && Number.isInteger(raw.max_tokens) && raw.max_tokens > 0) {
16656
16255
  result.maxTokens = raw.max_tokens;
16657
16256
  } else
16658
- ctx.err([...path10, "max_tokens"], `${label}.max_tokens must be a positive integer.`);
16257
+ ctx.err([...path8, "max_tokens"], `${label}.max_tokens must be a positive integer.`);
16659
16258
  }
16660
16259
  if (raw.supports_json_schema !== undefined) {
16661
16260
  if (typeof raw.supports_json_schema === "boolean")
16662
16261
  result.supportsJsonSchema = raw.supports_json_schema;
16663
16262
  else
16664
- ctx.err([...path10, "supports_json_schema"], `${label}.supports_json_schema must be a boolean.`);
16263
+ ctx.err([...path8, "supports_json_schema"], `${label}.supports_json_schema must be a boolean.`);
16665
16264
  }
16666
16265
  if (raw.extra_params !== undefined) {
16667
16266
  if (!isPlainRecord(raw.extra_params)) {
16668
- ctx.err([...path10, "extra_params"], `${label}.extra_params must be a JSON object.`);
16267
+ ctx.err([...path8, "extra_params"], `${label}.extra_params must be a JSON object.`);
16669
16268
  } else {
16670
16269
  const issues = validateExtraParams(raw.extra_params);
16671
16270
  for (const issue of issues) {
16672
- ctx.err([...path10, "extra_params", ...issue.path], `${formatExtraParamsIssue(`${label}.extra_params`, issue)}.`);
16271
+ ctx.err([...path8, "extra_params", ...issue.path], `${formatExtraParamsIssue(`${label}.extra_params`, issue)}.`);
16673
16272
  }
16674
16273
  if (jsonBytes(raw.extra_params) > WORKFLOW_MAX_EXTRA_PARAMS_BYTES) {
16675
- ctx.err([...path10, "extra_params"], `${label}.extra_params exceeds the 64 KiB resource limit.`);
16274
+ ctx.err([...path8, "extra_params"], `${label}.extra_params exceeds the 64 KiB resource limit.`);
16676
16275
  }
16677
16276
  if (issues.length === 0 && jsonBytes(raw.extra_params) <= WORKFLOW_MAX_EXTRA_PARAMS_BYTES) {
16678
16277
  result.extraParams = raw.extra_params;
@@ -16683,32 +16282,32 @@ function parseLlmOverrides(ctx, raw, path10, label) {
16683
16282
  if (typeof raw.context_length === "number" && Number.isInteger(raw.context_length) && raw.context_length > 0) {
16684
16283
  result.contextLength = raw.context_length;
16685
16284
  } else
16686
- ctx.err([...path10, "context_length"], `${label}.context_length must be a positive integer.`);
16285
+ ctx.err([...path8, "context_length"], `${label}.context_length must be a positive integer.`);
16687
16286
  }
16688
16287
  if (raw.enable_thinking !== undefined) {
16689
16288
  if (typeof raw.enable_thinking === "boolean")
16690
16289
  result.enableThinking = raw.enable_thinking;
16691
16290
  else
16692
- ctx.err([...path10, "enable_thinking"], `${label}.enable_thinking must be a boolean.`);
16291
+ ctx.err([...path8, "enable_thinking"], `${label}.enable_thinking must be a boolean.`);
16693
16292
  }
16694
16293
  return Object.keys(result).length > 0 ? result : undefined;
16695
16294
  }
16696
- function parseSchemaObject(ctx, raw, path10, label) {
16295
+ function parseSchemaObject(ctx, raw, path8, label) {
16697
16296
  if (raw === undefined)
16698
16297
  return;
16699
16298
  if (!isPlainRecord(raw)) {
16700
- ctx.err(path10, `${label} must be a JSON Schema object (e.g. { type: object, properties: { … } }).`);
16299
+ ctx.err(path8, `${label} must be a JSON Schema object (e.g. { type: object, properties: { … } }).`);
16701
16300
  return;
16702
16301
  }
16703
16302
  if (jsonBytes(raw) > WORKFLOW_MAX_SCHEMA_BYTES) {
16704
- ctx.err(path10, `${label} exceeds the 256 KiB resource limit.`);
16303
+ ctx.err(path8, `${label} exceeds the 256 KiB resource limit.`);
16705
16304
  }
16706
16305
  return raw;
16707
16306
  }
16708
- function checkUnknownKeys(ctx, obj, path10, allowed, label) {
16307
+ function checkUnknownKeys(ctx, obj, path8, allowed, label) {
16709
16308
  for (const key of Object.keys(obj)) {
16710
16309
  if (!allowed.includes(key)) {
16711
- ctx.err([...path10, key], `Unknown ${label} key "${key}". Allowed keys: ${allowed.join(", ")}.`);
16310
+ ctx.err([...path8, key], `Unknown ${label} key "${key}". Allowed keys: ${allowed.join(", ")}.`);
16712
16311
  }
16713
16312
  }
16714
16313
  }
@@ -16716,7 +16315,7 @@ function rangedLine(ctx, node, fallbackPath) {
16716
16315
  const range = node?.range;
16717
16316
  return range ? ctx.lineAtOffset(range[0]) : ctx.lineAt(fallbackPath);
16718
16317
  }
16719
- function checkTemplates(ctx, text, path10, label) {
16318
+ function checkTemplates(ctx, text, path8, label) {
16720
16319
  let idx = 0;
16721
16320
  while (true) {
16722
16321
  const open = text.indexOf("${{", idx);
@@ -16725,7 +16324,7 @@ function checkTemplates(ctx, text, path10, label) {
16725
16324
  const close = text.indexOf("}}", open + 3);
16726
16325
  const nextOpen = text.indexOf("${{", open + 3);
16727
16326
  if (close === -1 || nextOpen !== -1 && nextOpen < close) {
16728
- ctx.err(path10, `${label} contains an unterminated "\${{" expression. Close it with "}}".`);
16327
+ ctx.err(path8, `${label} contains an unterminated "\${{" expression. Close it with "}}".`);
16729
16328
  return;
16730
16329
  }
16731
16330
  idx = close + 2;
@@ -16734,7 +16333,7 @@ function checkTemplates(ctx, text, path10, label) {
16734
16333
  if (checker) {
16735
16334
  const message = checker(text);
16736
16335
  if (message !== null)
16737
- ctx.err(path10, `${label}: ${message}`);
16336
+ ctx.err(path8, `${label}: ${message}`);
16738
16337
  }
16739
16338
  }
16740
16339
  function loadExpressionChecker() {
@@ -17140,8 +16739,8 @@ var init_asset_registry = __esm(() => {
17140
16739
  });
17141
16740
 
17142
16741
  // src/core/asset/asset-spec.ts
17143
- import fs10 from "node:fs";
17144
- import path10 from "node:path";
16742
+ import fs8 from "node:fs";
16743
+ import path8 from "node:path";
17145
16744
  function toPosix2(input) {
17146
16745
  return input.replace(/\\/g, "/");
17147
16746
  }
@@ -17154,9 +16753,9 @@ var init_asset_spec = __esm(() => {
17154
16753
  init_asset_registry();
17155
16754
  WORKFLOW_EXTENSIONS = [".md", ".yaml", ".yml"];
17156
16755
  workflowSpec = {
17157
- isRelevantFile: (fileName) => WORKFLOW_EXTENSIONS.includes(path10.extname(fileName).toLowerCase()),
16756
+ isRelevantFile: (fileName) => WORKFLOW_EXTENSIONS.includes(path8.extname(fileName).toLowerCase()),
17158
16757
  toCanonicalName: (typeRoot, filePath) => {
17159
- const rel = toPosix2(path10.relative(typeRoot, filePath));
16758
+ const rel = toPosix2(path8.relative(typeRoot, filePath));
17160
16759
  for (const ext of WORKFLOW_EXTENSIONS) {
17161
16760
  if (rel.toLowerCase().endsWith(ext))
17162
16761
  return rel.slice(0, -ext.length);
@@ -17167,25 +16766,25 @@ var init_asset_spec = __esm(() => {
17167
16766
  const lower = name.toLowerCase();
17168
16767
  for (const ext of WORKFLOW_EXTENSIONS) {
17169
16768
  if (lower.endsWith(ext))
17170
- return path10.join(typeRoot, name);
16769
+ return path8.join(typeRoot, name);
17171
16770
  }
17172
16771
  for (const ext of WORKFLOW_EXTENSIONS) {
17173
- const candidate = path10.join(typeRoot, `${name}${ext}`);
17174
- if (fs10.existsSync(candidate))
16772
+ const candidate = path8.join(typeRoot, `${name}${ext}`);
16773
+ if (fs8.existsSync(candidate))
17175
16774
  return candidate;
17176
16775
  }
17177
- return path10.join(typeRoot, `${name}.md`);
16776
+ return path8.join(typeRoot, `${name}.md`);
17178
16777
  }
17179
16778
  };
17180
16779
  markdownSpec = {
17181
- isRelevantFile: (fileName) => path10.extname(fileName).toLowerCase() === ".md",
16780
+ isRelevantFile: (fileName) => path8.extname(fileName).toLowerCase() === ".md",
17182
16781
  toCanonicalName: (typeRoot, filePath) => {
17183
- const rel = toPosix2(path10.relative(typeRoot, filePath));
16782
+ const rel = toPosix2(path8.relative(typeRoot, filePath));
17184
16783
  return rel.endsWith(".md") ? rel.slice(0, -3) : rel;
17185
16784
  },
17186
16785
  toAssetPath: (typeRoot, name) => {
17187
16786
  const withExt = name.endsWith(".md") ? name : `${name}.md`;
17188
- return path10.join(typeRoot, withExt);
16787
+ return path8.join(typeRoot, withExt);
17189
16788
  }
17190
16789
  };
17191
16790
  SCRIPT_EXTENSIONS = new Set([
@@ -17207,21 +16806,21 @@ var init_asset_spec = __esm(() => {
17207
16806
  ".kts"
17208
16807
  ]);
17209
16808
  scriptSpec = {
17210
- isRelevantFile: (fileName) => SCRIPT_EXTENSIONS.has(path10.extname(fileName).toLowerCase()),
17211
- toCanonicalName: (typeRoot, filePath) => toPosix2(path10.relative(typeRoot, filePath)),
17212
- toAssetPath: (typeRoot, name) => path10.join(typeRoot, name)
16809
+ isRelevantFile: (fileName) => SCRIPT_EXTENSIONS.has(path8.extname(fileName).toLowerCase()),
16810
+ toCanonicalName: (typeRoot, filePath) => toPosix2(path8.relative(typeRoot, filePath)),
16811
+ toAssetPath: (typeRoot, name) => path8.join(typeRoot, name)
17213
16812
  };
17214
16813
  ASSET_SPECS_INTERNAL = {
17215
16814
  skill: {
17216
16815
  stashDir: "skills",
17217
16816
  isRelevantFile: (fileName) => fileName === "SKILL.md",
17218
16817
  toCanonicalName: (typeRoot, filePath) => {
17219
- const relDir = toPosix2(path10.dirname(path10.relative(typeRoot, filePath)));
16818
+ const relDir = toPosix2(path8.dirname(path8.relative(typeRoot, filePath)));
17220
16819
  if (!relDir || relDir === ".")
17221
16820
  return;
17222
16821
  return relDir;
17223
16822
  },
17224
- toAssetPath: (typeRoot, name) => path10.join(typeRoot, name, "SKILL.md")
16823
+ toAssetPath: (typeRoot, name) => path8.join(typeRoot, name, "SKILL.md")
17225
16824
  },
17226
16825
  command: { stashDir: "commands", ...markdownSpec },
17227
16826
  agent: { stashDir: "agents", ...markdownSpec },
@@ -17238,10 +16837,10 @@ var init_asset_spec = __esm(() => {
17238
16837
  stashDir: "env",
17239
16838
  isRelevantFile: (fileName) => fileName === ".env" || fileName.endsWith(".env"),
17240
16839
  toCanonicalName: (typeRoot, filePath) => {
17241
- const rel = toPosix2(path10.relative(typeRoot, filePath));
17242
- const fileName = path10.basename(rel);
16840
+ const rel = toPosix2(path8.relative(typeRoot, filePath));
16841
+ const fileName = path8.basename(rel);
17243
16842
  if (fileName === ".env") {
17244
- const dir = path10.dirname(rel);
16843
+ const dir = path8.dirname(rel);
17245
16844
  return dir === "." || dir === "" ? "default" : `${dir}/default`;
17246
16845
  }
17247
16846
  const stripped = rel.endsWith(".env") ? rel.slice(0, -4) : rel;
@@ -17249,8 +16848,8 @@ var init_asset_spec = __esm(() => {
17249
16848
  },
17250
16849
  toAssetPath: (typeRoot, name) => {
17251
16850
  if (name === "default")
17252
- return path10.join(typeRoot, ".env");
17253
- return path10.join(typeRoot, name.endsWith(".env") ? name : `${name}.env`);
16851
+ return path8.join(typeRoot, ".env");
16852
+ return path8.join(typeRoot, name.endsWith(".env") ? name : `${name}.env`);
17254
16853
  },
17255
16854
  rendererName: "env-file",
17256
16855
  actionBuilder: (ref) => `akm show ${ref} -> inspect key names; akm env run ${ref} -- <command> -> run with the whole .env injected (prefer --clean to minimize inherited parent env; child stdout is not redacted); akm env export ${ref} --out <file> -> write a sourceable script to a file`
@@ -17258,8 +16857,8 @@ var init_asset_spec = __esm(() => {
17258
16857
  secret: {
17259
16858
  stashDir: "secrets",
17260
16859
  isRelevantFile: (fileName) => !fileName.endsWith(".lock") && !fileName.endsWith(".sensitive"),
17261
- toCanonicalName: (typeRoot, filePath) => toPosix2(path10.relative(typeRoot, filePath)),
17262
- toAssetPath: (typeRoot, name) => path10.join(typeRoot, name),
16860
+ toCanonicalName: (typeRoot, filePath) => toPosix2(path8.relative(typeRoot, filePath)),
16861
+ toAssetPath: (typeRoot, name) => path8.join(typeRoot, name),
17263
16862
  rendererName: "secret-file",
17264
16863
  actionBuilder: (ref) => `akm show ${ref} -> name only (value never shown); akm secret path ${ref} -> file path; akm secret run ${ref} <VAR> -- <command> -> run with value injected into $VAR`
17265
16864
  },
@@ -17277,14 +16876,14 @@ var init_asset_spec = __esm(() => {
17277
16876
  },
17278
16877
  task: {
17279
16878
  stashDir: "tasks",
17280
- isRelevantFile: (fileName) => path10.extname(fileName).toLowerCase() === ".yml",
16879
+ isRelevantFile: (fileName) => path8.extname(fileName).toLowerCase() === ".yml",
17281
16880
  toCanonicalName: (typeRoot, filePath) => {
17282
- const rel = toPosix2(path10.relative(typeRoot, filePath));
16881
+ const rel = toPosix2(path8.relative(typeRoot, filePath));
17283
16882
  return rel.endsWith(".yml") ? rel.slice(0, -4) : rel;
17284
16883
  },
17285
16884
  toAssetPath: (typeRoot, name) => {
17286
16885
  const withExt = name.endsWith(".yml") ? name : `${name}.yml`;
17287
- return path10.join(typeRoot, withExt);
16886
+ return path8.join(typeRoot, withExt);
17288
16887
  },
17289
16888
  rendererName: "task-yaml",
17290
16889
  actionBuilder: buildTaskAction
@@ -17306,32 +16905,29 @@ var init_asset_spec = __esm(() => {
17306
16905
  });
17307
16906
 
17308
16907
  // src/core/common.ts
17309
- import crypto2 from "node:crypto";
17310
- import fs11 from "node:fs";
17311
- import path11 from "node:path";
16908
+ import fs9 from "node:fs";
17312
16909
  function isAssetType(type) {
17313
16910
  return Object.hasOwn(TYPE_DIRS, type);
17314
16911
  }
17315
- function writeFileAtomic(target, content, mode) {
17316
- const tmp = `${target}.tmp.${process.pid}.${crypto2.randomBytes(8).toString("hex")}`;
17317
- const fd = fs11.openSync(tmp, "w", mode ?? 384);
17318
- try {
17319
- fs11.writeSync(fd, typeof content === "string" ? Buffer.from(content) : content);
17320
- try {
17321
- fs11.fdatasyncSync(fd);
17322
- } catch {}
17323
- } finally {
17324
- fs11.closeSync(fd);
16912
+ function readTextFileDescriptorWithLimit(fd, maxBytes, label = "File", displayPath = "(open file)") {
16913
+ const stat = fs9.fstatSync(fd);
16914
+ if (!stat.isFile())
16915
+ throw new ConfigError(`${label} is not a regular file: ${displayPath}.`, "INVALID_CONFIG_FILE");
16916
+ if (stat.size > maxBytes) {
16917
+ throw new ConfigError(`${label} exceeds the ${maxBytes}-byte limit: ${displayPath}.`, "INVALID_CONFIG_FILE");
16918
+ }
16919
+ const buffer = Buffer.allocUnsafe(maxBytes + 1);
16920
+ let total = 0;
16921
+ while (total <= maxBytes) {
16922
+ const bytesRead = fs9.readSync(fd, buffer, total, maxBytes + 1 - total, null);
16923
+ if (bytesRead === 0)
16924
+ break;
16925
+ total += bytesRead;
17325
16926
  }
17326
- fs11.renameSync(tmp, target);
17327
- try {
17328
- const dirFd = fs11.openSync(path11.dirname(target), "r");
17329
- try {
17330
- fs11.fsyncSync(dirFd);
17331
- } finally {
17332
- fs11.closeSync(dirFd);
17333
- }
17334
- } catch {}
16927
+ if (total > maxBytes) {
16928
+ throw new ConfigError(`${label} exceeds the ${maxBytes}-byte limit: ${displayPath}.`, "INVALID_CONFIG_FILE");
16929
+ }
16930
+ return buffer.subarray(0, total).toString("utf8");
17335
16931
  }
17336
16932
  function asNonEmptyString3(value) {
17337
16933
  if (typeof value !== "string")
@@ -17347,7 +16943,7 @@ function isProcessAlive(pid) {
17347
16943
  return false;
17348
16944
  }
17349
16945
  }
17350
- var ASSET_TYPES, ASSET_TYPE_SET, IS_WINDOWS, DEFAULT_RESPONSE_BYTE_CAP;
16946
+ var ASSET_TYPES, ASSET_TYPE_SET, IS_WINDOWS, MAX_CONFIG_FILE_BYTES, MAX_LOCAL_METADATA_BYTES, MAX_LOCK_METADATA_BYTES, DEFAULT_RESPONSE_BYTE_CAP;
17351
16947
  var init_common = __esm(() => {
17352
16948
  init_asset_spec();
17353
16949
  init_errors();
@@ -17355,9 +16951,147 @@ var init_common = __esm(() => {
17355
16951
  ASSET_TYPES = Object.freeze([...getAssetTypes()]);
17356
16952
  ASSET_TYPE_SET = new Set(ASSET_TYPES);
17357
16953
  IS_WINDOWS = process.platform === "win32";
16954
+ MAX_CONFIG_FILE_BYTES = 1024 * 1024;
16955
+ MAX_LOCAL_METADATA_BYTES = 1024 * 1024;
16956
+ MAX_LOCK_METADATA_BYTES = 64 * 1024;
17358
16957
  DEFAULT_RESPONSE_BYTE_CAP = 10 * 1024 * 1024;
17359
16958
  });
17360
16959
 
16960
+ // src/storage/engines/sqlite-migrations.ts
16961
+ import crypto2 from "node:crypto";
16962
+ function migrationChecksum(migration) {
16963
+ return crypto2.createHash("sha256").update(migration.id).update("\x00").update(migration.up).digest("hex");
16964
+ }
16965
+ function migrationsTableExists(db) {
16966
+ return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get();
16967
+ }
16968
+ function ledgerHasChecksum(db) {
16969
+ return db.prepare("PRAGMA table_info(schema_migrations)").all().some((column) => column.name === "checksum");
16970
+ }
16971
+ function inspectMigrationLedger(db, migrations) {
16972
+ const registryIds = migrations.map((migration) => migration.id);
16973
+ if (new Set(registryIds).size !== registryIds.length) {
16974
+ return {
16975
+ status: "inconsistent",
16976
+ migrationIds: [],
16977
+ checksums: [],
16978
+ detail: "migration registry contains duplicate IDs"
16979
+ };
16980
+ }
16981
+ if (!migrationsTableExists(db))
16982
+ return { status: registryIds.length === 0 ? "current" : "old", migrationIds: [], checksums: [] };
16983
+ const hasChecksum = ledgerHasChecksum(db);
16984
+ const rows = db.prepare(`SELECT id${hasChecksum ? ", checksum" : ""} FROM schema_migrations ORDER BY rowid`).all();
16985
+ const migrationIds = rows.map((row) => row.id);
16986
+ const checksums = rows.map((row) => row.checksum ?? null);
16987
+ for (let index = 0;index < rows.length; index += 1) {
16988
+ const expected = migrations[index];
16989
+ const row = rows[index];
16990
+ if (!expected) {
16991
+ return { status: "newer", migrationIds, checksums, detail: `unknown migration ID ${row.id}` };
16992
+ }
16993
+ if (row.id !== expected.id) {
16994
+ const knownLater = registryIds.includes(row.id);
16995
+ return {
16996
+ status: knownLater ? "inconsistent" : "newer",
16997
+ migrationIds,
16998
+ checksums,
16999
+ detail: knownLater ? `migration ledger is not an exact ordered prefix at position ${index + 1}` : `unknown migration ID ${row.id}`
17000
+ };
17001
+ }
17002
+ const expectedChecksum = migrationChecksum(expected);
17003
+ if (row.checksum && row.checksum !== expectedChecksum) {
17004
+ return {
17005
+ status: "inconsistent",
17006
+ migrationIds,
17007
+ checksums,
17008
+ detail: `migration ${row.id} checksum does not match the released migration body`
17009
+ };
17010
+ }
17011
+ }
17012
+ const missingChecksum = checksums.indexOf(null);
17013
+ if (missingChecksum >= 0) {
17014
+ return {
17015
+ status: "old",
17016
+ migrationIds,
17017
+ checksums,
17018
+ detail: `migration ${migrationIds[missingChecksum]} has not been sealed with a checksum`
17019
+ };
17020
+ }
17021
+ return {
17022
+ status: rows.length === migrations.length ? "current" : "old",
17023
+ migrationIds,
17024
+ checksums
17025
+ };
17026
+ }
17027
+ function assertMigrationLedger(db, migrations) {
17028
+ const state = inspectMigrationLedger(db, migrations);
17029
+ if (state.status === "newer") {
17030
+ throw new Error(`Refusing to open a database with a newer migration ledger: ${state.detail}.`);
17031
+ }
17032
+ if (state.status === "inconsistent") {
17033
+ throw new Error(`Refusing a database whose migrations are not an exact ordered prefix: ${state.detail}.`);
17034
+ }
17035
+ return state;
17036
+ }
17037
+ function assertCurrentMigrationLedger(db, migrations) {
17038
+ const state = assertMigrationLedger(db, migrations);
17039
+ if (state.status !== "current") {
17040
+ throw new Error(`Refusing to open an obsolete writable schema; run \`akm migrate apply\`: ${state.detail ?? "pending migrations"}.`);
17041
+ }
17042
+ return state;
17043
+ }
17044
+ function ensureMigrationsTable(db) {
17045
+ db.exec(`
17046
+ CREATE TABLE IF NOT EXISTS schema_migrations (
17047
+ id TEXT PRIMARY KEY,
17048
+ applied_at TEXT NOT NULL DEFAULT (datetime('now')),
17049
+ checksum TEXT
17050
+ );
17051
+ `);
17052
+ if (!ledgerHasChecksum(db))
17053
+ db.exec("ALTER TABLE schema_migrations ADD COLUMN checksum TEXT");
17054
+ }
17055
+ function runMigrations(db, migrations, opts) {
17056
+ if (opts?.applyPending === false) {
17057
+ assertMigrationLedger(db, migrations);
17058
+ return;
17059
+ }
17060
+ if (migrationsTableExists(db))
17061
+ assertMigrationLedger(db, migrations);
17062
+ ensureMigrationsTable(db);
17063
+ opts?.bootstrap?.(db);
17064
+ const ledger = assertMigrationLedger(db, migrations);
17065
+ db.transaction(() => {
17066
+ const update = db.prepare("UPDATE schema_migrations SET checksum = ? WHERE id = ? AND checksum IS NULL");
17067
+ for (let index = 0;index < ledger.migrationIds.length; index += 1) {
17068
+ update.run(migrationChecksum(migrations[index]), ledger.migrationIds[index]);
17069
+ }
17070
+ if (opts?.generationMarker) {
17071
+ db.exec(`
17072
+ CREATE TABLE IF NOT EXISTS akm_migration_generation (
17073
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
17074
+ operation_id TEXT NOT NULL,
17075
+ phase TEXT NOT NULL
17076
+ )
17077
+ `);
17078
+ db.prepare("INSERT INTO akm_migration_generation(singleton, operation_id, phase) VALUES (1, ?, ?) ON CONFLICT(singleton) DO UPDATE SET operation_id=excluded.operation_id, phase=excluded.phase").run(opts.generationMarker.operationId, opts.generationMarker.phase);
17079
+ }
17080
+ })();
17081
+ const appliedRows = db.prepare("SELECT id FROM schema_migrations ORDER BY rowid").all();
17082
+ const applied = new Set(appliedRows.map((r) => r.id));
17083
+ for (const migration of migrations) {
17084
+ if (applied.has(migration.id))
17085
+ continue;
17086
+ opts?.beforeMigration?.(migration);
17087
+ db.transaction(() => {
17088
+ db.exec(migration.up);
17089
+ db.prepare("INSERT INTO schema_migrations (id, checksum) VALUES (?, ?)").run(migration.id, migrationChecksum(migration));
17090
+ })();
17091
+ }
17092
+ }
17093
+ var init_sqlite_migrations = () => {};
17094
+
17361
17095
  // src/storage/sqlite-pragmas.ts
17362
17096
  function resolveJournalMode(raw) {
17363
17097
  if (raw === undefined)
@@ -17425,12 +17159,12 @@ var init_sqlite_pragmas = __esm(() => {
17425
17159
  });
17426
17160
 
17427
17161
  // src/storage/managed-db.ts
17428
- import fs12 from "node:fs";
17429
- import path13 from "node:path";
17162
+ import fs10 from "node:fs";
17163
+ import path10 from "node:path";
17430
17164
  function openManagedDatabase(spec) {
17431
- const dir = path13.dirname(spec.path);
17432
- if (!fs12.existsSync(dir)) {
17433
- fs12.mkdirSync(dir, { recursive: true });
17165
+ const dir = path10.dirname(spec.path);
17166
+ if (!fs10.existsSync(dir)) {
17167
+ fs10.mkdirSync(dir, { recursive: true });
17434
17168
  }
17435
17169
  const db = openDatabase(spec.path);
17436
17170
  applyStandardPragmas(db, spec.pragmas ?? { dataDir: dir });
@@ -17464,44 +17198,81 @@ var init_managed_db = __esm(() => {
17464
17198
  init_sqlite_pragmas();
17465
17199
  });
17466
17200
 
17467
- // src/storage/engines/sqlite-migrations.ts
17468
- function ensureMigrationsTable(db) {
17469
- db.exec(`
17470
- CREATE TABLE IF NOT EXISTS schema_migrations (
17471
- id TEXT PRIMARY KEY,
17472
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
17473
- );
17474
- `);
17201
+ // src/core/maintenance-barrier.ts
17202
+ import { AsyncLocalStorage } from "node:async_hooks";
17203
+ import { randomUUID as randomUUID2 } from "node:crypto";
17204
+ import fs11 from "node:fs";
17205
+ import path11 from "node:path";
17206
+ function tryAcquireMaintenanceBarrier() {
17207
+ const lockPath = getMaintenanceBarrierPath();
17208
+ fs11.mkdirSync(path11.dirname(lockPath), { recursive: true });
17209
+ for (let attempt = 0;attempt < 2; attempt += 1) {
17210
+ const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: "maintenance-start" }));
17211
+ if (ownership) {
17212
+ return () => releaseLock(ownership);
17213
+ }
17214
+ const probe = probeLock(lockPath);
17215
+ if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
17216
+ return;
17217
+ }
17218
+ return;
17475
17219
  }
17476
- function runMigrations(db, migrations, opts) {
17477
- ensureMigrationsTable(db);
17478
- opts?.bootstrap?.(db);
17479
- const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
17480
- const applied = new Set(appliedRows.map((r) => r.id));
17481
- for (const migration of migrations) {
17482
- if (applied.has(migration.id))
17483
- continue;
17484
- opts?.beforeMigration?.(migration);
17485
- db.transaction(() => {
17486
- db.exec(migration.up);
17487
- db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
17488
- })();
17220
+ function acquireMaintenanceBarrier() {
17221
+ const release = tryAcquireMaintenanceBarrier();
17222
+ if (release)
17223
+ return release;
17224
+ throw new ConfigError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry after it completes.`, "INVALID_CONFIG_FILE");
17225
+ }
17226
+ function withMaintenanceStartBarrierSyncWait(run) {
17227
+ if (heldBarrierContext.getStore()?.active)
17228
+ return run();
17229
+ const deadline = Date.now() + 5000;
17230
+ let release = tryAcquireMaintenanceBarrier();
17231
+ while (!release && Date.now() < deadline) {
17232
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
17233
+ release = tryAcquireMaintenanceBarrier();
17234
+ }
17235
+ if (!release)
17236
+ release = acquireMaintenanceBarrier();
17237
+ const ownership = { active: true };
17238
+ try {
17239
+ return heldBarrierContext.run(ownership, run);
17240
+ } finally {
17241
+ ownership.active = false;
17242
+ release();
17489
17243
  }
17490
17244
  }
17245
+ function acquireMaintenanceActivitySync(name) {
17246
+ return withMaintenanceStartBarrierSyncWait(() => {
17247
+ const directory = path11.join(path11.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
17248
+ fs11.mkdirSync(directory, { recursive: true, mode: 448 });
17249
+ const lockPath = path11.join(directory, `${name}-${process.pid}-${randomUUID2()}.lock`);
17250
+ const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: name }));
17251
+ if (!ownership) {
17252
+ throw new ConfigError(`Could not register AKM maintenance activity at ${lockPath}.`, "INVALID_CONFIG_FILE");
17253
+ }
17254
+ return () => releaseLock(ownership);
17255
+ });
17256
+ }
17257
+ var heldBarrierContext;
17258
+ var init_maintenance_barrier = __esm(() => {
17259
+ init_errors();
17260
+ init_file_lock();
17261
+ init_paths();
17262
+ heldBarrierContext = new AsyncLocalStorage;
17263
+ });
17491
17264
 
17492
17265
  // src/core/state/migrations.ts
17493
17266
  function runMigrations2(db, options) {
17494
- runMigrations(db, MIGRATIONS, {
17495
- beforeMigration(migration) {
17496
- if (options?.ensureCutoverBackup && migration.id === "017-improve-run-strategy")
17497
- ensureMigrationBackup();
17498
- }
17267
+ runMigrations(db, STATE_MIGRATIONS, {
17268
+ applyPending: options?.applyPending,
17269
+ generationMarker: options?.generationMarker
17499
17270
  });
17500
17271
  }
17501
- var MIGRATIONS;
17272
+ var STATE_MIGRATIONS;
17502
17273
  var init_migrations = __esm(() => {
17503
- init_migration_backup();
17504
- MIGRATIONS = [
17274
+ init_sqlite_migrations();
17275
+ STATE_MIGRATIONS = [
17505
17276
  {
17506
17277
  id: "001-initial-schema",
17507
17278
  up: `
@@ -17947,22 +17718,36 @@ __export(exports_state_db, {
17947
17718
  listExistingTableNames: () => listExistingTableNames,
17948
17719
  getStateDbPath: () => getStateDbPath
17949
17720
  });
17950
- import fs13 from "node:fs";
17951
- import path14 from "node:path";
17721
+ import fs12 from "node:fs";
17722
+ import path12 from "node:path";
17952
17723
  function getStateDbPath() {
17953
- return path14.join(getDataDir(), "state.db");
17724
+ return path12.join(getDataDir(), "state.db");
17954
17725
  }
17955
17726
  function openStateDatabase(dbPath) {
17956
17727
  const canonicalPath = getStateDbPath();
17957
17728
  const resolvedPath = dbPath ?? canonicalPath;
17958
- const isCanonical = path14.resolve(resolvedPath) === path14.resolve(canonicalPath);
17729
+ const isCanonical = path12.resolve(resolvedPath) === path12.resolve(canonicalPath);
17730
+ if (isCanonical)
17731
+ assertNoPendingMigrationOperation();
17959
17732
  const releaseActivity = isCanonical ? acquireMaintenanceActivitySync("state-db") : undefined;
17960
17733
  try {
17961
- if (isCanonical && !fs13.existsSync(getMigrationBackupDir()))
17962
- ensureMigrationBackup();
17734
+ if (isCanonical)
17735
+ assertNoPendingMigrationOperation();
17736
+ const existed = fs12.existsSync(resolvedPath);
17737
+ if (existed) {
17738
+ const preflight = openDatabase(resolvedPath, { readonly: true });
17739
+ try {
17740
+ if (isCanonical)
17741
+ assertCurrentMigrationLedger(preflight, STATE_MIGRATIONS);
17742
+ else
17743
+ assertMigrationLedger(preflight, STATE_MIGRATIONS);
17744
+ } finally {
17745
+ preflight.close();
17746
+ }
17747
+ }
17963
17748
  const db = openManagedDatabase({
17964
17749
  path: resolvedPath,
17965
- init: (db2) => runMigrations2(db2, { ensureCutoverBackup: isCanonical })
17750
+ init: (db2) => runMigrations2(db2, { applyPending: !(isCanonical && existed) })
17966
17751
  });
17967
17752
  if (!releaseActivity)
17968
17753
  return db;
@@ -18001,7 +17786,7 @@ function isRetryableBeginError(err) {
18001
17786
  const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
18002
17787
  return msg.includes("database is locked") || msg.includes("database table is locked") || msg.includes("did not open a transaction");
18003
17788
  }
18004
- function sleepSyncMs2(ms) {
17789
+ function sleepSyncMs(ms) {
18005
17790
  if (ms <= 0)
18006
17791
  return;
18007
17792
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
@@ -18025,7 +17810,7 @@ function withImmediateTransaction(db, fn) {
18025
17810
  db.exec("ROLLBACK");
18026
17811
  } catch {}
18027
17812
  }
18028
- sleepSyncMs2(2 ** (attempt - 1));
17813
+ sleepSyncMs(2 ** (attempt - 1));
18029
17814
  continue;
18030
17815
  }
18031
17816
  throw err;
@@ -18056,9 +17841,11 @@ function listExistingTableNames(db, names) {
18056
17841
  }
18057
17842
  var WITH_IMMEDIATE_TX_MAX_ATTEMPTS = 5;
18058
17843
  var init_state_db = __esm(() => {
17844
+ init_database();
17845
+ init_sqlite_migrations();
18059
17846
  init_managed_db();
18060
17847
  init_maintenance_barrier();
18061
- init_migration_backup();
17848
+ init_migration_operation();
18062
17849
  init_paths();
18063
17850
  init_migrations();
18064
17851
  });
@@ -18068,6 +17855,7 @@ var exports_events_repository = {};
18068
17855
  __export(exports_events_repository, {
18069
17856
  readStateEvents: () => readStateEvents,
18070
17857
  purgeOldEvents: () => purgeOldEvents,
17858
+ insertEventOnce: () => insertEventOnce,
18071
17859
  insertEvent: () => insertEvent,
18072
17860
  importEventsJsonl: () => importEventsJsonl,
18073
17861
  eventRowToEnvelope: () => eventRowToEnvelope
@@ -18101,6 +17889,22 @@ function insertEvent(db, input) {
18101
17889
  return;
18102
17890
  }
18103
17891
  }
17892
+ function insertEventOnce(db, input) {
17893
+ const rows = db.prepare("SELECT id, metadata_json FROM events WHERE event_type = ? AND ref IS ? ORDER BY id").all(input.eventType, input.ref ?? null);
17894
+ for (const row of rows) {
17895
+ try {
17896
+ const metadata = JSON.parse(row.metadata_json);
17897
+ if (metadata[input.idempotencyMetadataKey ?? "proposalTransactionId"] === input.idempotencyKey)
17898
+ return row.id;
17899
+ } catch {}
17900
+ }
17901
+ const result = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
17902
+ VALUES (?, ?, ?, ?)
17903
+ RETURNING id`).get(input.eventType, input.ts, input.ref ?? null, JSON.stringify(input.metadata));
17904
+ if (!result)
17905
+ throw new Error(`Failed to persist ${input.eventType} event.`);
17906
+ return result.id;
17907
+ }
18104
17908
  function readStateEvents(db, options = {}) {
18105
17909
  const conditions = [];
18106
17910
  const params = [];
@@ -18966,9 +18770,9 @@ __export(exports_db, {
18966
18770
  UTILITY_REVIEW_THRESHOLD: () => UTILITY_REVIEW_THRESHOLD,
18967
18771
  HIGH_UTILITY_THRESHOLD: () => HIGH_UTILITY_THRESHOLD
18968
18772
  });
18969
- import fs14 from "node:fs";
18773
+ import fs13 from "node:fs";
18970
18774
  import { createRequire as createRequire6 } from "node:module";
18971
- import path15 from "node:path";
18775
+ import path13 from "node:path";
18972
18776
  function openIndexDatabase(dbPath, options) {
18973
18777
  return openManagedDatabase({
18974
18778
  path: dbPath ?? getDbPath(),
@@ -18998,7 +18802,7 @@ function openExistingDatabase2(dbPath) {
18998
18802
  }
18999
18803
  function openReadonlyExistingDatabase(dbPath) {
19000
18804
  const resolvedPath = dbPath ?? getDbPath();
19001
- if (!fs14.existsSync(resolvedPath))
18805
+ if (!fs13.existsSync(resolvedPath))
19002
18806
  return;
19003
18807
  return openDatabase(resolvedPath, { readonly: true, create: false });
19004
18808
  }
@@ -19073,7 +18877,7 @@ function deleteIndexDirState(db, dirPath) {
19073
18877
  db.prepare("DELETE FROM index_dir_state WHERE dir_path = ?").run(dirPath);
19074
18878
  }
19075
18879
  function deleteIndexDirStatesByStashDir(db, stashDir) {
19076
- db.prepare("DELETE FROM index_dir_state WHERE dir_path = ? OR dir_path LIKE ?").run(stashDir, `${stashDir}${path15.sep}%`);
18880
+ db.prepare("DELETE FROM index_dir_state WHERE dir_path = ? OR dir_path LIKE ?").run(stashDir, `${stashDir}${path13.sep}%`);
19077
18881
  }
19078
18882
  function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, searchText) {
19079
18883
  const stmts = getUpsertStmts(db);
@@ -19148,16 +18952,19 @@ function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
19148
18952
  return out;
19149
18953
  }
19150
18954
  function rekeyEntryInPlace(db, opts) {
19151
- const row = db.prepare("SELECT id, entry_json, search_text FROM entries WHERE entry_key = ?").get(opts.oldEntryKey);
18955
+ const row = db.prepare("SELECT id, stash_dir, entry_json, search_text FROM entries WHERE entry_key = ?").get(opts.oldEntryKey);
19152
18956
  if (!row)
19153
18957
  return null;
18958
+ if (opts.sourceRoot && path13.resolve(row.stash_dir) !== path13.resolve(opts.sourceRoot)) {
18959
+ throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: source root does not match.`);
18960
+ }
19154
18961
  let entryJson = row.entry_json;
19155
18962
  let searchText = row.search_text;
19156
18963
  try {
19157
18964
  const entry = JSON.parse(row.entry_json);
19158
18965
  entry.name = opts.newName;
19159
18966
  if (typeof entry.filename === "string")
19160
- entry.filename = path15.basename(opts.newFilePath);
18967
+ entry.filename = path13.basename(opts.newFilePath);
19161
18968
  if (opts.newDerivedFrom !== undefined)
19162
18969
  entry.derivedFrom = opts.newDerivedFrom;
19163
18970
  entryJson = JSON.stringify(entry);
@@ -19169,22 +18976,32 @@ function rekeyEntryInPlace(db, opts) {
19169
18976
  deleteRelatedRows(db, [{ id: stale.id }]);
19170
18977
  db.prepare("DELETE FROM entries WHERE id = ?").run(stale.id);
19171
18978
  }
19172
- db.prepare("UPDATE entries SET entry_key = ?, dir_path = ?, file_path = ?, entry_json = ?, search_text = ? WHERE id = ?").run(opts.newEntryKey, path15.dirname(opts.newFilePath), opts.newFilePath, entryJson, searchText, row.id);
18979
+ db.prepare("UPDATE entries SET entry_key = ?, dir_path = ?, file_path = ?, entry_json = ?, search_text = ? WHERE id = ?").run(opts.newEntryKey, path13.dirname(opts.newFilePath), opts.newFilePath, entryJson, searchText, row.id);
19173
18980
  if (opts.newDerivedFrom !== undefined) {
19174
18981
  db.prepare("UPDATE entries SET derived_from = ? WHERE id = ?").run(opts.newDerivedFrom, row.id);
19175
18982
  }
19176
- bestEffort(() => {
19177
- db.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(opts.newRef);
19178
- db.prepare(`DELETE FROM usage_events
19179
- WHERE entry_id IS NULL
19180
- AND instr(entry_ref, '//') > 0
19181
- AND substr(entry_ref, instr(entry_ref, '//') + 2) = ?`).run(opts.newRef);
19182
- db.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(opts.newRef, opts.oldRef);
19183
- db.prepare(`UPDATE usage_events
19184
- SET entry_ref = substr(entry_ref, 1, instr(entry_ref, '//') + 1) || ?
19185
- WHERE instr(entry_ref, '//') > 0
19186
- AND substr(entry_ref, instr(entry_ref, '//') + 2) = ?`).run(opts.newRef, opts.oldRef);
19187
- }, "usage_events table may be missing on legacy DBs — no usage history to re-key");
18983
+ try {
18984
+ if (opts.sourceName) {
18985
+ const origins = new Set([opts.sourceName]);
18986
+ if (opts.sourceName === "stash" && opts.includeLegacyBare)
18987
+ origins.add("local");
18988
+ for (const origin of origins) {
18989
+ const oldQualifiedRef = `${origin}//${opts.oldRef}`;
18990
+ const newQualifiedRef = `${origin}//${opts.newRef}`;
18991
+ db.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(newQualifiedRef);
18992
+ db.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(newQualifiedRef, oldQualifiedRef);
18993
+ }
18994
+ }
18995
+ if (opts.includeLegacyBare || !opts.sourceName) {
18996
+ db.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(opts.newRef);
18997
+ db.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(opts.newRef, opts.oldRef);
18998
+ }
18999
+ } catch (error2) {
19000
+ const message = error2 instanceof Error ? error2.message : String(error2);
19001
+ const missingLegacyUsageSchema = /no such table:\s*(?:main\.)?usage_events\b/i.test(message) || /no such column:\s*(?:usage_events\.)?(?:entry_id|entry_ref)\b/i.test(message) || /table\s+usage_events\s+has no column named\s+(?:entry_id|entry_ref)\b/i.test(message);
19002
+ if (!missingLegacyUsageSchema)
19003
+ throw error2;
19004
+ }
19188
19005
  db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)").run(row.id);
19189
19006
  })();
19190
19007
  return row.id;
@@ -19506,7 +19323,7 @@ function getEntitiesByEntryIds(db, entryIds) {
19506
19323
  }
19507
19324
  return result;
19508
19325
  }
19509
- function findEntryIdByRef(db, ref) {
19326
+ function findEntryIdByRef(db, ref, stashDir) {
19510
19327
  const parsed = parseAssetRef(ref);
19511
19328
  const nameVariants = [parsed.name];
19512
19329
  if (parsed.name.endsWith(".md")) {
@@ -19514,10 +19331,14 @@ function findEntryIdByRef(db, ref) {
19514
19331
  } else {
19515
19332
  nameVariants.push(`${parsed.name}.md`);
19516
19333
  }
19517
- const stmt = db.prepare("SELECT id FROM entries WHERE entry_type = ? AND substr(entry_key, length(entry_key) - length(?) + 1) = ? LIMIT 1");
19334
+ const stmt = db.prepare(`SELECT id FROM entries
19335
+ WHERE entry_type = ?
19336
+ AND substr(entry_key, length(entry_key) - length(?) + 1) = ?
19337
+ ${stashDir ? "AND stash_dir = ?" : ""}
19338
+ LIMIT 1`);
19518
19339
  for (const name of nameVariants) {
19519
19340
  const suffix = `${parsed.type}:${name}`;
19520
- const row = stmt.get(parsed.type, suffix, suffix);
19341
+ const row = stmt.get(parsed.type, suffix, suffix, ...stashDir ? [stashDir] : []);
19521
19342
  if (row)
19522
19343
  return row.id;
19523
19344
  }
@@ -19535,7 +19356,7 @@ function getEmbeddingCount(db) {
19535
19356
  return row.cnt;
19536
19357
  }
19537
19358
  function getEntryById(db, id) {
19538
- const row = db.prepare("SELECT file_path, entry_json FROM entries WHERE id = ?").get(id);
19359
+ const row = db.prepare("SELECT file_path, stash_dir, entry_json FROM entries WHERE id = ?").get(id);
19539
19360
  if (!row)
19540
19361
  return;
19541
19362
  let entry;
@@ -19545,7 +19366,7 @@ function getEntryById(db, id) {
19545
19366
  warn(`[db] getEntryById: skipping entry id=${id} — corrupt entry_json`);
19546
19367
  return;
19547
19368
  }
19548
- return { filePath: row.file_path, entry };
19369
+ return { filePath: row.file_path, stashDir: row.stash_dir, entry };
19549
19370
  }
19550
19371
  function getEntriesByDir(db, dirPath) {
19551
19372
  const rows = db.prepare(`SELECT ${ENTRY_COLUMNS} FROM entries WHERE dir_path = ?`).all(dirPath);
@@ -19694,9 +19515,12 @@ function bareRef(ref) {
19694
19515
  return ref;
19695
19516
  }
19696
19517
  }
19697
- function getRetrievalCounts(db, refs) {
19518
+ function getRetrievalCounts(db, refs, options = {}) {
19698
19519
  if (refs.length === 0)
19699
19520
  return new Map;
19521
+ if (options.sourceName || options.stashDir) {
19522
+ return getSourceScopedRetrievalCounts(db, refs, options);
19523
+ }
19700
19524
  const bareToInputs = new Map;
19701
19525
  for (const ref of refs) {
19702
19526
  const bare = bareRef(ref);
@@ -19740,6 +19564,51 @@ function getRetrievalCounts(db, refs) {
19740
19564
  }
19741
19565
  return result;
19742
19566
  }
19567
+ function getSourceScopedRetrievalCounts(db, refs, options) {
19568
+ const bareToInputs = new Map;
19569
+ for (const ref of refs) {
19570
+ const bare = bareRef(ref);
19571
+ const inputs = bareToInputs.get(bare);
19572
+ if (inputs)
19573
+ inputs.push(ref);
19574
+ else
19575
+ bareToInputs.set(bare, [ref]);
19576
+ }
19577
+ const countsByBare = new Map;
19578
+ const bareForms = [...bareToInputs.keys()];
19579
+ const selectedRoot = options.stashDir ? path13.resolve(options.stashDir) : undefined;
19580
+ for (let i = 0;i < bareForms.length; i += SQLITE_CHUNK_SIZE) {
19581
+ const chunk = bareForms.slice(i, i + SQLITE_CHUNK_SIZE);
19582
+ const placeholders = chunk.map(() => "?").join(", ");
19583
+ const rows = db.prepare(`SELECT ue.entry_ref, ue.entry_id, e.stash_dir
19584
+ FROM usage_events ue
19585
+ LEFT JOIN entries e ON e.id = ue.entry_id
19586
+ WHERE ue.event_type IN ('search','show','curate')
19587
+ AND ue.entry_ref IS NOT NULL
19588
+ AND (ue.source IS NULL OR ue.source NOT IN ('improve','task'))
19589
+ AND CASE
19590
+ WHEN instr(ue.entry_ref, '//') > 0
19591
+ THEN substr(ue.entry_ref, instr(ue.entry_ref, '//') + 2)
19592
+ ELSE ue.entry_ref
19593
+ END IN (${placeholders})`).all(...chunk);
19594
+ for (const row of rows) {
19595
+ const bare = bareRef(row.entry_ref);
19596
+ const linkedToSelectedRoot = row.entry_id !== null && selectedRoot !== undefined && row.stash_dir !== null ? path13.resolve(row.stash_dir) === selectedRoot : false;
19597
+ const detached = row.entry_id === null || selectedRoot === undefined;
19598
+ const qualifiedForSource = detached && options.sourceName !== undefined && row.entry_ref === `${options.sourceName}//${bare}`;
19599
+ const acceptedLegacyBare = detached && options.includeLegacyBare === true && row.entry_ref === bare;
19600
+ if (!linkedToSelectedRoot && !qualifiedForSource && !acceptedLegacyBare)
19601
+ continue;
19602
+ countsByBare.set(bare, (countsByBare.get(bare) ?? 0) + 1);
19603
+ }
19604
+ }
19605
+ const result = new Map;
19606
+ for (const [bare, count] of countsByBare) {
19607
+ for (const input of bareToInputs.get(bare) ?? [])
19608
+ result.set(input, count);
19609
+ }
19610
+ return result;
19611
+ }
19743
19612
  function bumpUtilityScoresBatch(db, entryIds, reward, lr = 0.1, scopeKey) {
19744
19613
  if (entryIds.length === 0)
19745
19614
  return;
@@ -19833,7 +19702,15 @@ function applyFeedbackToUtilityScore(db, entryId, positiveCount, negativeCount)
19833
19702
  `).run(entryId, result.nextUtility, now, now, result.nextUtility, now);
19834
19703
  return result;
19835
19704
  }
19836
- function relinkUsageEvents(db) {
19705
+ function resolveUsageEventEntryId(db, ref, options) {
19706
+ const parsed = parseAssetRef(ref);
19707
+ if (!parsed.origin) {
19708
+ return options.defaultStashDir ? findEntryIdByRef(db, ref, options.defaultStashDir) : undefined;
19709
+ }
19710
+ const source = parsed.origin === "local" || parsed.origin === "stash" ? options.sources?.[0] : options.sources?.find((candidate) => candidate.registryId === parsed.origin);
19711
+ return source ? findEntryIdByRef(db, ref, source.path) : undefined;
19712
+ }
19713
+ function relinkUsageEvents(db, options = {}) {
19837
19714
  bestEffort(() => {
19838
19715
  db.exec(`
19839
19716
  UPDATE usage_events
@@ -19847,7 +19724,7 @@ function relinkUsageEvents(db) {
19847
19724
  for (const { ref } of refs) {
19848
19725
  let id;
19849
19726
  try {
19850
- id = findEntryIdByRef(db, ref);
19727
+ id = resolveUsageEventEntryId(db, ref, options);
19851
19728
  } catch (err) {
19852
19729
  if (err instanceof Error && err.name === "UsageError")
19853
19730
  continue;
@@ -19912,12 +19789,12 @@ __export(exports_graph_db, {
19912
19789
  drainExtractionQueue: () => drainExtractionQueue,
19913
19790
  deleteStoredGraph: () => deleteStoredGraph
19914
19791
  });
19915
- import fs15 from "node:fs";
19792
+ import fs14 from "node:fs";
19916
19793
  function withReadableGraphDb(db, fn) {
19917
19794
  if (db)
19918
19795
  return fn(db);
19919
19796
  const dbPath = getDbPath();
19920
- if (!fs15.existsSync(dbPath))
19797
+ if (!fs14.existsSync(dbPath))
19921
19798
  throw new Error("GRAPH_DB_MISSING");
19922
19799
  const opened = openExistingDatabase(dbPath);
19923
19800
  try {
@@ -20260,15 +20137,15 @@ var init_graph_db = __esm(() => {
20260
20137
  });
20261
20138
 
20262
20139
  // scripts/migrate-storage.ts
20263
- import fs16 from "fs";
20140
+ import fs15 from "fs";
20264
20141
  import os3 from "os";
20265
- import path16 from "path";
20142
+ import path14 from "path";
20266
20143
  import readline from "readline";
20267
20144
 
20268
20145
  // src/core/paths.ts
20269
20146
  init_common();
20270
20147
  init_errors();
20271
- import path12 from "node:path";
20148
+ import path9 from "node:path";
20272
20149
  function isTransientStashPath2(p) {
20273
20150
  return p.startsWith("/tmp/") || p === "/tmp" || p.startsWith("/var/tmp/") || p === "/var/tmp" || p.startsWith("/private/tmp/") || p.startsWith("/private/var/folders/") || p.startsWith("/var/folders/");
20274
20151
  }
@@ -20279,34 +20156,34 @@ function getConfigDir2(env = process.env, platform = process.platform) {
20279
20156
  if (platform === "win32") {
20280
20157
  const appData = env.APPDATA?.trim();
20281
20158
  if (appData)
20282
- return path12.join(appData, "akm");
20159
+ return path9.join(appData, "akm");
20283
20160
  } else {
20284
20161
  const xdgConfigHome2 = env.XDG_CONFIG_HOME?.trim();
20285
20162
  if (xdgConfigHome2)
20286
- return path12.join(xdgConfigHome2, "akm");
20163
+ return path9.join(xdgConfigHome2, "akm");
20287
20164
  }
20288
20165
  const stashOverride = env.AKM_STASH_DIR?.trim();
20289
20166
  if (stashOverride && isTransientStashPath2(stashOverride)) {
20290
- return path12.join(stashOverride, ".akm");
20167
+ return path9.join(stashOverride, ".akm");
20291
20168
  }
20292
20169
  if (platform === "win32") {
20293
20170
  const appData = env.APPDATA?.trim();
20294
20171
  if (appData)
20295
- return path12.join(appData, "akm");
20172
+ return path9.join(appData, "akm");
20296
20173
  const userProfile = env.USERPROFILE?.trim();
20297
20174
  if (!userProfile) {
20298
20175
  throw new ConfigError("Unable to determine config directory. Set APPDATA or USERPROFILE.", "CONFIG_DIR_UNRESOLVABLE");
20299
20176
  }
20300
- return path12.join(userProfile, "AppData", "Roaming", "akm");
20177
+ return path9.join(userProfile, "AppData", "Roaming", "akm");
20301
20178
  }
20302
20179
  const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
20303
20180
  if (xdgConfigHome)
20304
- return path12.join(xdgConfigHome, "akm");
20181
+ return path9.join(xdgConfigHome, "akm");
20305
20182
  const home = env.HOME?.trim();
20306
20183
  if (!home) {
20307
20184
  throw new ConfigError("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.", "CONFIG_DIR_UNRESOLVABLE");
20308
20185
  }
20309
- return path12.join(home, ".config", "akm");
20186
+ return path9.join(home, ".config", "akm");
20310
20187
  }
20311
20188
  function getCacheDir2(env = process.env) {
20312
20189
  const override = env.AKM_CACHE_DIR?.trim();
@@ -20315,30 +20192,30 @@ function getCacheDir2(env = process.env) {
20315
20192
  if (IS_WINDOWS) {
20316
20193
  const localAppData = env.LOCALAPPDATA?.trim();
20317
20194
  if (localAppData)
20318
- return path12.join(localAppData, "akm");
20195
+ return path9.join(localAppData, "akm");
20319
20196
  const userProfile = env.USERPROFILE?.trim();
20320
20197
  if (userProfile)
20321
- return path12.join(userProfile, "AppData", "Local", "akm");
20198
+ return path9.join(userProfile, "AppData", "Local", "akm");
20322
20199
  const appData = env.APPDATA?.trim();
20323
20200
  if (appData) {
20324
- return path12.join(appData, "..", "Local", "akm");
20201
+ return path9.join(appData, "..", "Local", "akm");
20325
20202
  }
20326
20203
  } else {
20327
20204
  const xdgCacheHome = env.XDG_CACHE_HOME?.trim();
20328
20205
  if (xdgCacheHome)
20329
- return path12.join(xdgCacheHome, "akm");
20206
+ return path9.join(xdgCacheHome, "akm");
20330
20207
  }
20331
20208
  const stashOverride = env.AKM_STASH_DIR?.trim();
20332
20209
  if (stashOverride && isTransientStashPath2(stashOverride)) {
20333
- return path12.join(stashOverride, ".akm", "cache");
20210
+ return path9.join(stashOverride, ".akm", "cache");
20334
20211
  }
20335
20212
  if (IS_WINDOWS) {
20336
20213
  throw new ConfigError("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
20337
20214
  }
20338
20215
  const home = env.HOME?.trim();
20339
20216
  if (!home)
20340
- return path12.join("/tmp", "akm-cache");
20341
- return path12.join(home, ".cache", "akm");
20217
+ return path9.join("/tmp", "akm-cache");
20218
+ return path9.join(home, ".cache", "akm");
20342
20219
  }
20343
20220
  function getDefaultStashDir2(env = process.env) {
20344
20221
  const override = env.AKM_STASH_DIR?.trim();
@@ -20347,14 +20224,14 @@ function getDefaultStashDir2(env = process.env) {
20347
20224
  if (IS_WINDOWS) {
20348
20225
  const userProfile = env.USERPROFILE?.trim();
20349
20226
  if (userProfile)
20350
- return path12.join(userProfile, "Documents", "akm");
20351
- return path12.join("C:\\", "akm");
20227
+ return path9.join(userProfile, "Documents", "akm");
20228
+ return path9.join("C:\\", "akm");
20352
20229
  }
20353
20230
  const home = env.HOME?.trim();
20354
20231
  if (!home) {
20355
20232
  throw new ConfigError("Unable to determine default stash directory. Set HOME.", "STASH_DIR_NOT_FOUND");
20356
20233
  }
20357
- return path12.join(home, "akm");
20234
+ return path9.join(home, "akm");
20358
20235
  }
20359
20236
 
20360
20237
  // src/indexer/usage/unmigrated-vaults-guard.ts
@@ -20379,12 +20256,12 @@ function parseFromArg() {
20379
20256
  return val;
20380
20257
  }
20381
20258
  var FROM_VERSION = parseFromArg();
20382
- var dataDir = process.env.AKM_DATA_DIR?.trim() ?? (process.env.XDG_DATA_HOME?.trim() ? path16.join(process.env.XDG_DATA_HOME.trim(), "akm") : path16.join(os3.homedir(), ".local", "share", "akm"));
20383
- var stateDir = process.env.AKM_STATE_DIR?.trim() ?? (process.env.XDG_STATE_HOME?.trim() ? path16.join(process.env.XDG_STATE_HOME.trim(), "akm") : path16.join(os3.homedir(), ".local", "state", "akm"));
20259
+ var dataDir = process.env.AKM_DATA_DIR?.trim() ?? (process.env.XDG_DATA_HOME?.trim() ? path14.join(process.env.XDG_DATA_HOME.trim(), "akm") : path14.join(os3.homedir(), ".local", "share", "akm"));
20260
+ var stateDir = process.env.AKM_STATE_DIR?.trim() ?? (process.env.XDG_STATE_HOME?.trim() ? path14.join(process.env.XDG_STATE_HOME.trim(), "akm") : path14.join(os3.homedir(), ".local", "state", "akm"));
20384
20261
  var cacheDir = getCacheDir2();
20385
20262
  var configDir = getConfigDir2();
20386
- var stateDbPath = path16.join(dataDir, "state.db");
20387
- var indexDbPath = path16.join(dataDir, "index.db");
20263
+ var stateDbPath = path14.join(dataDir, "state.db");
20264
+ var indexDbPath = path14.join(dataDir, "index.db");
20388
20265
  var PATHS = {
20389
20266
  cacheDir,
20390
20267
  configDir,
@@ -20395,14 +20272,14 @@ var PATHS = {
20395
20272
  };
20396
20273
  var versionReports = [];
20397
20274
  function ensureDir(dir) {
20398
- if (!fs16.existsSync(dir)) {
20399
- fs16.mkdirSync(dir, { recursive: true });
20275
+ if (!fs15.existsSync(dir)) {
20276
+ fs15.mkdirSync(dir, { recursive: true });
20400
20277
  }
20401
20278
  }
20402
20279
  function copyAndVerify(src, dest) {
20403
- fs16.copyFileSync(src, dest);
20404
- const srcStat = fs16.statSync(src);
20405
- const destStat = fs16.statSync(dest);
20280
+ fs15.copyFileSync(src, dest);
20281
+ const srcStat = fs15.statSync(src);
20282
+ const destStat = fs15.statSync(dest);
20406
20283
  return destStat.size === srcStat.size;
20407
20284
  }
20408
20285
  function copyTree(src, dest, opts) {
@@ -20410,9 +20287,9 @@ function copyTree(src, dest, opts) {
20410
20287
  let skipped = 0;
20411
20288
  let failed = 0;
20412
20289
  ensureDir(dest);
20413
- for (const entry of fs16.readdirSync(src, { withFileTypes: true })) {
20414
- const srcEntry = path16.join(src, entry.name);
20415
- const destEntry = path16.join(dest, entry.name);
20290
+ for (const entry of fs15.readdirSync(src, { withFileTypes: true })) {
20291
+ const srcEntry = path14.join(src, entry.name);
20292
+ const destEntry = path14.join(dest, entry.name);
20416
20293
  if (entry.isDirectory()) {
20417
20294
  const sub = copyTree(srcEntry, destEntry, opts);
20418
20295
  copied += sub.copied;
@@ -20420,7 +20297,7 @@ function copyTree(src, dest, opts) {
20420
20297
  failed += sub.failed;
20421
20298
  continue;
20422
20299
  }
20423
- if (!opts.clobber && fs16.existsSync(destEntry)) {
20300
+ if (!opts.clobber && fs15.existsSync(destEntry)) {
20424
20301
  skipped++;
20425
20302
  continue;
20426
20303
  }
@@ -20438,9 +20315,9 @@ function copyTree(src, dest, opts) {
20438
20315
  }
20439
20316
  function countFilesRecursive(dir) {
20440
20317
  let count = 0;
20441
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
20318
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
20442
20319
  if (entry.isDirectory()) {
20443
- count += countFilesRecursive(path16.join(dir, entry.name));
20320
+ count += countFilesRecursive(path14.join(dir, entry.name));
20444
20321
  } else {
20445
20322
  count++;
20446
20323
  }
@@ -20453,12 +20330,12 @@ async function runSteps(steps, ctx) {
20453
20330
  }
20454
20331
  }
20455
20332
  function migrateDb(ctx, filename) {
20456
- const src = path16.join(ctx.paths.cacheDir, filename);
20457
- const dest = path16.join(ctx.paths.dataDir, filename);
20458
- if (!fs16.existsSync(src)) {
20333
+ const src = path14.join(ctx.paths.cacheDir, filename);
20334
+ const dest = path14.join(ctx.paths.dataDir, filename);
20335
+ if (!fs15.existsSync(src)) {
20459
20336
  return { name: filename, status: "skipped", detail: "source not found" };
20460
20337
  }
20461
- if (fs16.existsSync(dest)) {
20338
+ if (fs15.existsSync(dest)) {
20462
20339
  return { name: filename, status: "skipped", detail: "destination already exists" };
20463
20340
  }
20464
20341
  if (ctx.dryRun) {
@@ -20476,8 +20353,8 @@ function migrateDb(ctx, filename) {
20476
20353
  return { name: filename, status: "failed", detail: `size mismatch after copy: ${src} \u2192 ${dest}` };
20477
20354
  }
20478
20355
  async function migrateEventsJsonl(ctx) {
20479
- const src = path16.join(ctx.paths.cacheDir, "events.jsonl");
20480
- if (!fs16.existsSync(src)) {
20356
+ const src = path14.join(ctx.paths.cacheDir, "events.jsonl");
20357
+ if (!fs15.existsSync(src)) {
20481
20358
  return { name: "events.jsonl \u2192 state.db", status: "skipped", detail: "source not found" };
20482
20359
  }
20483
20360
  if (ctx.dryRun) {
@@ -20522,13 +20399,13 @@ async function migrateEventsJsonl(ctx) {
20522
20399
  }
20523
20400
  }
20524
20401
  function migrateTaskHistory(ctx) {
20525
- const src = path16.join(ctx.paths.cacheDir, "tasks", "history");
20526
- const dest = path16.join(ctx.paths.stateDir, "tasks", "history");
20527
- if (!fs16.existsSync(src)) {
20402
+ const src = path14.join(ctx.paths.cacheDir, "tasks", "history");
20403
+ const dest = path14.join(ctx.paths.stateDir, "tasks", "history");
20404
+ if (!fs15.existsSync(src)) {
20528
20405
  return { name: "tasks/history/", status: "skipped", detail: "source directory not found" };
20529
20406
  }
20530
20407
  try {
20531
- const files = fs16.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
20408
+ const files = fs15.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
20532
20409
  if (files.length === 0) {
20533
20410
  return { name: "tasks/history/", status: "skipped", detail: "no *.jsonl files found in source directory" };
20534
20411
  }
@@ -20543,8 +20420,8 @@ function migrateTaskHistory(ctx) {
20543
20420
  let copied = 0;
20544
20421
  let failed = 0;
20545
20422
  for (const file of files) {
20546
- const srcFile = path16.join(src, file);
20547
- const destFile = path16.join(dest, file);
20423
+ const srcFile = path14.join(src, file);
20424
+ const destFile = path14.join(dest, file);
20548
20425
  try {
20549
20426
  if (copyAndVerify(srcFile, destFile)) {
20550
20427
  copied++;
@@ -20573,12 +20450,12 @@ function migrateTaskHistory(ctx) {
20573
20450
  }
20574
20451
  }
20575
20452
  function migrateLockfile(ctx) {
20576
- const src = path16.join(ctx.paths.configDir, "akm.lock");
20577
- const dest = path16.join(ctx.paths.dataDir, "akm.lock");
20578
- if (!fs16.existsSync(src)) {
20453
+ const src = path14.join(ctx.paths.configDir, "akm.lock");
20454
+ const dest = path14.join(ctx.paths.dataDir, "akm.lock");
20455
+ if (!fs15.existsSync(src)) {
20579
20456
  return { name: "akm.lock", status: "skipped", detail: "source not found" };
20580
20457
  }
20581
- if (fs16.existsSync(dest)) {
20458
+ if (fs15.existsSync(dest)) {
20582
20459
  return { name: "akm.lock", status: "skipped", detail: "destination already exists" };
20583
20460
  }
20584
20461
  if (ctx.dryRun) {
@@ -20598,12 +20475,12 @@ function migrateLockfile(ctx) {
20598
20475
  return { name: "akm.lock", status: "failed", detail: `size mismatch after copy: ${src} \u2192 ${dest}` };
20599
20476
  }
20600
20477
  function migrateConfigBackups(ctx) {
20601
- const src = path16.join(ctx.paths.cacheDir, "config-backups");
20602
- const dest = path16.join(ctx.paths.dataDir, "config-backups");
20603
- if (!fs16.existsSync(src)) {
20478
+ const src = path14.join(ctx.paths.cacheDir, "config-backups");
20479
+ const dest = path14.join(ctx.paths.dataDir, "config-backups");
20480
+ if (!fs15.existsSync(src)) {
20604
20481
  return { name: "config-backups/", status: "skipped", detail: "source directory not found" };
20605
20482
  }
20606
- if (fs16.existsSync(dest)) {
20483
+ if (fs15.existsSync(dest)) {
20607
20484
  return { name: "config-backups/", status: "skipped", detail: "destination already exists" };
20608
20485
  }
20609
20486
  if (ctx.dryRun) {
@@ -20620,7 +20497,7 @@ function migrateConfigBackups(ctx) {
20620
20497
  try {
20621
20498
  const srcCount = countFilesRecursive(src);
20622
20499
  const { copied, failed } = copyTree(src, dest, { clobber: true });
20623
- const destCount = fs16.existsSync(dest) ? countFilesRecursive(dest) : 0;
20500
+ const destCount = fs15.existsSync(dest) ? countFilesRecursive(dest) : 0;
20624
20501
  if (failed === 0 && destCount === srcCount) {
20625
20502
  return {
20626
20503
  name: "config-backups/",
@@ -20639,11 +20516,11 @@ function migrateConfigBackups(ctx) {
20639
20516
  }
20640
20517
  }
20641
20518
  async function migrateTaskHistoryToDb(ctx) {
20642
- const src = path16.join(ctx.paths.cacheDir, "tasks", "history");
20643
- if (!fs16.existsSync(src)) {
20519
+ const src = path14.join(ctx.paths.cacheDir, "tasks", "history");
20520
+ if (!fs15.existsSync(src)) {
20644
20521
  return { name: "tasks/history/ \u2192 state.db", status: "skipped", detail: "source directory not found" };
20645
20522
  }
20646
- const files = fs16.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
20523
+ const files = fs15.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
20647
20524
  if (files.length === 0) {
20648
20525
  return {
20649
20526
  name: "tasks/history/ \u2192 state.db",
@@ -20667,8 +20544,8 @@ async function migrateTaskHistoryToDb(ctx) {
20667
20544
  let failed = 0;
20668
20545
  try {
20669
20546
  for (const file of files) {
20670
- const filePath = path16.join(src, file);
20671
- const text = fs16.readFileSync(filePath, "utf8");
20547
+ const filePath = path14.join(src, file);
20548
+ const text = fs15.readFileSync(filePath, "utf8");
20672
20549
  const lines = text.split(`
20673
20550
  `).filter((l) => l.trim().length > 0);
20674
20551
  db.exec("BEGIN");
@@ -20727,11 +20604,11 @@ async function migrateTaskHistoryToDb(ctx) {
20727
20604
  }
20728
20605
  }
20729
20606
  function noteRegistryIndexCache(ctx) {
20730
- const src = path16.join(ctx.paths.cacheDir, "registry-index");
20731
- if (!fs16.existsSync(src)) {
20607
+ const src = path14.join(ctx.paths.cacheDir, "registry-index");
20608
+ if (!fs15.existsSync(src)) {
20732
20609
  return { name: "registry-index/ (note)", status: "skipped", detail: "no old $CACHE/registry-index/ directory found" };
20733
20610
  }
20734
- const legacyFiles = fs16.readdirSync(src).filter((f) => f.endsWith(".json") && !f.startsWith("website-"));
20611
+ const legacyFiles = fs15.readdirSync(src).filter((f) => f.endsWith(".json") && !f.startsWith("website-"));
20735
20612
  if (legacyFiles.length === 0) {
20736
20613
  return { name: "registry-index/ (note)", status: "skipped", detail: "no old *.json cache files found" };
20737
20614
  }
@@ -20741,7 +20618,7 @@ function noteRegistryIndexCache(ctx) {
20741
20618
  These are ignored in v0.9 \u2014 data is now stored in the registry_index_cache` + `
20742
20619
  table in $DATA/index.db and will be rebuilt on next 'akm registry search'.
20743
20620
  You may safely delete these files after migration:
20744
- ` + legacyFiles.map((f) => ` ${path16.join(src, f)}`).join(`
20621
+ ` + legacyFiles.map((f) => ` ${path14.join(src, f)}`).join(`
20745
20622
  `));
20746
20623
  }
20747
20624
  return {
@@ -20755,15 +20632,15 @@ var v07To08Migration = {
20755
20632
  sourceVersion: "0.7",
20756
20633
  isNeeded: (paths) => {
20757
20634
  const candidates = [
20758
- path16.join(paths.cacheDir, "index.db"),
20759
- path16.join(paths.cacheDir, "workflow.db"),
20760
- path16.join(paths.cacheDir, "events.jsonl"),
20761
- path16.join(paths.cacheDir, "tasks", "history"),
20762
- path16.join(paths.configDir, "akm.lock"),
20763
- path16.join(paths.cacheDir, "config-backups"),
20764
- path16.join(paths.cacheDir, "registry-index")
20635
+ path14.join(paths.cacheDir, "index.db"),
20636
+ path14.join(paths.cacheDir, "workflow.db"),
20637
+ path14.join(paths.cacheDir, "events.jsonl"),
20638
+ path14.join(paths.cacheDir, "tasks", "history"),
20639
+ path14.join(paths.configDir, "akm.lock"),
20640
+ path14.join(paths.cacheDir, "config-backups"),
20641
+ path14.join(paths.cacheDir, "registry-index")
20765
20642
  ];
20766
- return candidates.some((p) => fs16.existsSync(p));
20643
+ return candidates.some((p) => fs15.existsSync(p));
20767
20644
  },
20768
20645
  steps: [
20769
20646
  { id: "index-db", title: "index.db", run: (ctx) => migrateDb(ctx, "index.db") },
@@ -20778,20 +20655,20 @@ var v07To08Migration = {
20778
20655
  };
20779
20656
  function legacyGraphCandidatePaths(ctx) {
20780
20657
  return [
20781
- path16.join(ctx.paths.cacheDir, "graph-snapshot.json"),
20782
- path16.join(ctx.paths.dataDir, "graph-snapshot.json"),
20783
- path16.join(ctx.paths.dataDir, "graph-export.json")
20658
+ path14.join(ctx.paths.cacheDir, "graph-snapshot.json"),
20659
+ path14.join(ctx.paths.dataDir, "graph-snapshot.json"),
20660
+ path14.join(ctx.paths.dataDir, "graph-export.json")
20784
20661
  ];
20785
20662
  }
20786
20663
  function findLegacyGraphFile(ctx) {
20787
20664
  for (const candidate of legacyGraphCandidatePaths(ctx)) {
20788
- if (fs16.existsSync(candidate) && fs16.statSync(candidate).isFile())
20665
+ if (fs15.existsSync(candidate) && fs15.statSync(candidate).isFile())
20789
20666
  return candidate;
20790
20667
  }
20791
- const graphDir = path16.join(ctx.paths.cacheDir, "graph");
20792
- if (fs16.existsSync(graphDir) && fs16.statSync(graphDir).isDirectory()) {
20668
+ const graphDir = path14.join(ctx.paths.cacheDir, "graph");
20669
+ if (fs15.existsSync(graphDir) && fs15.statSync(graphDir).isDirectory()) {
20793
20670
  try {
20794
- const json = fs16.readdirSync(graphDir).filter((f) => f.endsWith(".json")).map((f) => path16.join(graphDir, f));
20671
+ const json = fs15.readdirSync(graphDir).filter((f) => f.endsWith(".json")).map((f) => path14.join(graphDir, f));
20795
20672
  if (json.length > 0)
20796
20673
  return json[0];
20797
20674
  } catch {}
@@ -20827,7 +20704,7 @@ async function migrateGraphFileToDb(ctx) {
20827
20704
  if (!legacyFile) {
20828
20705
  return { name, status: "skipped", detail: "no legacy graph file found" };
20829
20706
  }
20830
- if (!fs16.existsSync(ctx.paths.indexDbPath)) {
20707
+ if (!fs15.existsSync(ctx.paths.indexDbPath)) {
20831
20708
  return {
20832
20709
  name,
20833
20710
  status: "skipped",
@@ -20842,7 +20719,7 @@ async function migrateGraphFileToDb(ctx) {
20842
20719
  };
20843
20720
  }
20844
20721
  try {
20845
- const raw = fs16.readFileSync(legacyFile, "utf8");
20722
+ const raw = fs15.readFileSync(legacyFile, "utf8");
20846
20723
  let parsed;
20847
20724
  try {
20848
20725
  parsed = JSON.parse(raw);
@@ -20919,7 +20796,7 @@ async function migrateGraphFileToDb(ctx) {
20919
20796
  }
20920
20797
  const renamed = `${legacyFile}.migrated`;
20921
20798
  try {
20922
- fs16.renameSync(legacyFile, renamed);
20799
+ fs15.renameSync(legacyFile, renamed);
20923
20800
  } catch (err) {
20924
20801
  const msg = err instanceof Error ? err.message : String(err);
20925
20802
  return {
@@ -20943,9 +20820,9 @@ async function migrateGraphFileToDb(ctx) {
20943
20820
  }
20944
20821
  function resolvePrimaryStashDir(configDirPath) {
20945
20822
  try {
20946
- const cfgPath = path16.join(configDirPath, "config.json");
20947
- if (fs16.existsSync(cfgPath)) {
20948
- const cfg = JSON.parse(fs16.readFileSync(cfgPath, "utf8"));
20823
+ const cfgPath = path14.join(configDirPath, "config.json");
20824
+ if (fs15.existsSync(cfgPath)) {
20825
+ const cfg = JSON.parse(fs15.readFileSync(cfgPath, "utf8"));
20949
20826
  if (typeof cfg.stashDir === "string" && cfg.stashDir.length > 0)
20950
20827
  return cfg.stashDir;
20951
20828
  }
@@ -20954,8 +20831,8 @@ function resolvePrimaryStashDir(configDirPath) {
20954
20831
  }
20955
20832
  function countEnvFilesRecursive(dir) {
20956
20833
  let count = 0;
20957
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
20958
- const full = path16.join(dir, entry.name);
20834
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
20835
+ const full = path14.join(dir, entry.name);
20959
20836
  if (entry.isDirectory()) {
20960
20837
  count += countEnvFilesRecursive(full);
20961
20838
  } else if (entry.name === ".env" || entry.name.endsWith(".env")) {
@@ -20967,13 +20844,13 @@ function countEnvFilesRecursive(dir) {
20967
20844
  function chmodTreeSecure(dir) {
20968
20845
  const isWin = process.platform === "win32";
20969
20846
  try {
20970
- fs16.chmodSync(dir, 448);
20847
+ fs15.chmodSync(dir, 448);
20971
20848
  } catch (err) {
20972
20849
  if (!isWin)
20973
20850
  return { ok: false, detail: `chmod 0700 ${dir} failed: ${err instanceof Error ? err.message : err}` };
20974
20851
  }
20975
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
20976
- const full = path16.join(dir, entry.name);
20852
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
20853
+ const full = path14.join(dir, entry.name);
20977
20854
  if (entry.isDirectory()) {
20978
20855
  const sub = chmodTreeSecure(full);
20979
20856
  if (!sub.ok)
@@ -20981,8 +20858,8 @@ function chmodTreeSecure(dir) {
20981
20858
  continue;
20982
20859
  }
20983
20860
  try {
20984
- fs16.chmodSync(full, 384);
20985
- if (!isWin && (fs16.statSync(full).mode & 511) !== 384) {
20861
+ fs15.chmodSync(full, 384);
20862
+ if (!isWin && (fs15.statSync(full).mode & 511) !== 384) {
20986
20863
  return { ok: false, detail: `mode verification failed for ${full}` };
20987
20864
  }
20988
20865
  } catch (err) {
@@ -20995,13 +20872,13 @@ function chmodTreeSecure(dir) {
20995
20872
  async function migrateVaultsToEnv(ctx) {
20996
20873
  const name = "vaults/ \u2192 env/";
20997
20874
  const stashDir = resolvePrimaryStashDir(ctx.paths.configDir);
20998
- const vaultsDir = path16.join(stashDir, "vaults");
20999
- const envDir = path16.join(stashDir, "env");
21000
- const marker = path16.join(vaultsDir, MIGRATED_MARKER);
21001
- if (!fs16.existsSync(vaultsDir)) {
20875
+ const vaultsDir = path14.join(stashDir, "vaults");
20876
+ const envDir = path14.join(stashDir, "env");
20877
+ const marker = path14.join(vaultsDir, MIGRATED_MARKER);
20878
+ if (!fs15.existsSync(vaultsDir)) {
21002
20879
  return { name, status: "skipped", detail: `no vaults/ directory under ${stashDir}` };
21003
20880
  }
21004
- if (fs16.existsSync(marker)) {
20881
+ if (fs15.existsSync(marker)) {
21005
20882
  return { name, status: "skipped", detail: "already migrated (.migrated marker present)" };
21006
20883
  }
21007
20884
  const vaultEnvCount = countEnvFilesRecursive(vaultsDir);
@@ -21032,7 +20909,7 @@ async function migrateVaultsToEnv(ctx) {
21032
20909
  detail: `post-copy count ${envEnvCount} < source ${vaultEnvCount}; no marker written`
21033
20910
  };
21034
20911
  }
21035
- fs16.writeFileSync(marker, `migrated vaults/ -> env/ at ${new Date().toISOString()}
20912
+ fs15.writeFileSync(marker, `migrated vaults/ -> env/ at ${new Date().toISOString()}
21036
20913
  `, { mode: 384 });
21037
20914
  return {
21038
20915
  name,
@@ -21054,7 +20931,7 @@ var v08To09Migration = {
21054
20931
  { id: "vaults-to-env", title: "vaults/ \u2192 env/", run: migrateVaultsToEnv }
21055
20932
  ]
21056
20933
  };
21057
- var MIGRATIONS2 = [v07To08Migration, v08To09Migration];
20934
+ var MIGRATIONS = [v07To08Migration, v08To09Migration];
21058
20935
  async function confirm() {
21059
20936
  if (YES)
21060
20937
  return true;
@@ -21068,7 +20945,7 @@ async function confirm() {
21068
20945
  }
21069
20946
  function printList() {
21070
20947
  console.log("akm storage migrations (in execution order):");
21071
- for (const m of MIGRATIONS2) {
20948
+ for (const m of MIGRATIONS) {
21072
20949
  console.log(` \u2022 ${m.label} [sourceVersion=${m.sourceVersion}]`);
21073
20950
  }
21074
20951
  console.log();
@@ -21092,8 +20969,8 @@ function compareVersion(a, b) {
21092
20969
  }
21093
20970
  function filteredMigrations() {
21094
20971
  if (!FROM_VERSION)
21095
- return MIGRATIONS2;
21096
- return MIGRATIONS2.filter((m) => compareVersion(m.sourceVersion, FROM_VERSION) >= 0);
20972
+ return MIGRATIONS;
20973
+ return MIGRATIONS.filter((m) => compareVersion(m.sourceVersion, FROM_VERSION) >= 0);
21097
20974
  }
21098
20975
  function printGroupedSummary() {
21099
20976
  console.log(`
@@ -21138,12 +21015,12 @@ task_history: task_id PK).
21138
21015
  Migration complete. No errors.`);
21139
21016
  console.log(`
21140
21017
  Old files at the original locations are safe to delete manually after verifying akm works:
21141
- ${path16.join(cacheDir, "index.db")}
21142
- ${path16.join(cacheDir, "workflow.db")}
21143
- ${path16.join(cacheDir, "events.jsonl")}
21144
- ${path16.join(cacheDir, "tasks", "history")}
21145
- ${path16.join(cacheDir, "config-backups")}
21146
- ${path16.join(configDir, "akm.lock")}
21018
+ ${path14.join(cacheDir, "index.db")}
21019
+ ${path14.join(cacheDir, "workflow.db")}
21020
+ ${path14.join(cacheDir, "events.jsonl")}
21021
+ ${path14.join(cacheDir, "tasks", "history")}
21022
+ ${path14.join(cacheDir, "config-backups")}
21023
+ ${path14.join(configDir, "akm.lock")}
21147
21024
 
21148
21025
  Next step \u2014 repopulate graph data (if migrating from 0.7):
21149
21026
  The 0.8.0 graph schema redesign (DB_VERSION 12 \u2192 13) rebuilds the graph
@@ -21236,7 +21113,7 @@ var invokedDirectly = (() => {
21236
21113
  if (!entry)
21237
21114
  return false;
21238
21115
  const here = new URL(import.meta.url).pathname;
21239
- return path16.resolve(entry) === here;
21116
+ return path14.resolve(entry) === here;
21240
21117
  } catch {
21241
21118
  return false;
21242
21119
  }
@@ -21247,5 +21124,5 @@ if (invokedDirectly) {
21247
21124
  export {
21248
21125
  runMigrations3 as runMigrations,
21249
21126
  main,
21250
- MIGRATIONS2 as MIGRATIONS
21127
+ MIGRATIONS
21251
21128
  };