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

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 (115) hide show
  1. package/CHANGELOG.md +40 -12
  2. package/README.md +7 -8
  3. package/SECURITY.md +5 -3
  4. package/dist/akm +44 -33
  5. package/dist/akm-migrate-storage +44 -33
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/improve-strategies/catchup.json +3 -1
  8. package/dist/assets/improve-strategies/consolidate.json +3 -1
  9. package/dist/assets/improve-strategies/frequent.json +3 -1
  10. package/dist/assets/improve-strategies/graph-refresh.json +3 -1
  11. package/dist/assets/improve-strategies/memory-focus.json +4 -1
  12. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
  13. package/dist/assets/improve-strategies/quick.json +3 -1
  14. package/dist/assets/improve-strategies/recombine-only.json +2 -0
  15. package/dist/assets/improve-strategies/reflect-distill.json +1 -0
  16. package/dist/assets/improve-strategies/synthesize.json +2 -0
  17. package/dist/assets/tasks/core/backup.yml +2 -2
  18. package/dist/cli/config-migrate.js +554 -26
  19. package/dist/cli.js +28 -11
  20. package/dist/commands/backup-cli.js +6 -4
  21. package/dist/commands/config-cli.js +10 -3
  22. package/dist/commands/feedback-cli.js +24 -7
  23. package/dist/commands/health/checks.js +94 -15
  24. package/dist/commands/health/html-report.js +23 -2
  25. package/dist/commands/health/improve-metrics.js +45 -6
  26. package/dist/commands/health/md-report.js +10 -1
  27. package/dist/commands/health/surfaces.js +2 -1
  28. package/dist/commands/health/windows.js +1 -1
  29. package/dist/commands/health.js +1 -1
  30. package/dist/commands/improve/anti-collapse.js +3 -3
  31. package/dist/commands/improve/collapse-detector.js +5 -5
  32. package/dist/commands/improve/consolidate.js +123 -66
  33. package/dist/commands/improve/distill/promote-memory.js +8 -6
  34. package/dist/commands/improve/distill/quality-gate.js +1 -1
  35. package/dist/commands/improve/distill-guards.js +1 -1
  36. package/dist/commands/improve/distill-promotion-policy.js +32 -26
  37. package/dist/commands/improve/distill.js +52 -36
  38. package/dist/commands/improve/eligibility.js +9 -8
  39. package/dist/commands/improve/extract-prompt.js +2 -2
  40. package/dist/commands/improve/extract.js +43 -11
  41. package/dist/commands/improve/improve-auto-accept.js +14 -28
  42. package/dist/commands/improve/improve-cli.js +4 -2
  43. package/dist/commands/improve/improve-strategies.js +2 -1
  44. package/dist/commands/improve/improve.js +49 -8
  45. package/dist/commands/improve/loop-stages.js +11 -6
  46. package/dist/commands/improve/preparation.js +51 -17
  47. package/dist/commands/improve/procedural.js +10 -6
  48. package/dist/commands/improve/recombine.js +31 -7
  49. package/dist/commands/improve/reflect.js +24 -14
  50. package/dist/commands/improve/salience.js +5 -1
  51. package/dist/commands/improve/source-identity.js +43 -0
  52. package/dist/commands/migrate-cli.js +36 -0
  53. package/dist/commands/mv-cli.js +647 -258
  54. package/dist/commands/proposal/drain.js +16 -4
  55. package/dist/commands/proposal/proposal-cli.js +3 -2
  56. package/dist/commands/proposal/proposal.js +16 -55
  57. package/dist/commands/proposal/repository.js +664 -58
  58. package/dist/commands/read/curate.js +4 -2
  59. package/dist/commands/read/knowledge.js +4 -1
  60. package/dist/commands/read/search.js +6 -2
  61. package/dist/commands/read/show.js +8 -7
  62. package/dist/commands/sources/self-update.js +156 -112
  63. package/dist/commands/sources/sources-cli.js +7 -2
  64. package/dist/commands/tasks/default-tasks.js +5 -5
  65. package/dist/commands/tasks/tasks-cli.js +7 -3
  66. package/dist/commands/tasks/tasks.js +261 -68
  67. package/dist/core/common.js +36 -3
  68. package/dist/core/config/config-io.js +2 -2
  69. package/dist/core/config/config-schema.js +15 -3
  70. package/dist/core/config/config.js +14 -13
  71. package/dist/core/file-lock.js +2 -2
  72. package/dist/core/improve-result.js +87 -7
  73. package/dist/core/migration-backup.js +816 -171
  74. package/dist/core/migration-operation.js +44 -0
  75. package/dist/core/state/migrations.js +4 -7
  76. package/dist/core/state-db.js +23 -7
  77. package/dist/core/write-source.js +86 -18
  78. package/dist/indexer/db/db.js +109 -53
  79. package/dist/indexer/index-writer-lock.js +38 -37
  80. package/dist/indexer/index-written-assets.js +73 -56
  81. package/dist/indexer/indexer.js +5 -1
  82. package/dist/indexer/search/search-source.js +2 -2
  83. package/dist/indexer/usage/usage-events.js +8 -2
  84. package/dist/integrations/agent/engine-resolution.js +14 -7
  85. package/dist/scripts/migrate-storage.js +887 -992
  86. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +494 -574
  87. package/dist/setup/detected-engines.js +7 -13
  88. package/dist/setup/engine-config.js +14 -4
  89. package/dist/setup/setup.js +1 -1
  90. package/dist/setup/steps/connection.js +1 -1
  91. package/dist/setup/steps/tasks.js +1 -1
  92. package/dist/sources/providers/git-stash.js +58 -21
  93. package/dist/sources/providers/git.js +1 -1
  94. package/dist/storage/engines/sqlite-migrations.js +138 -3
  95. package/dist/storage/repositories/events-repository.js +24 -0
  96. package/dist/storage/repositories/proposals-repository.js +14 -0
  97. package/dist/storage/repositories/task-history-repository.js +30 -3
  98. package/dist/tasks/backends/cron.js +71 -38
  99. package/dist/tasks/backends/exec-utils.js +55 -3
  100. package/dist/tasks/backends/launchd.js +241 -50
  101. package/dist/tasks/backends/schtasks.js +434 -59
  102. package/dist/tasks/command-executable.js +93 -0
  103. package/dist/tasks/embedded.js +2 -0
  104. package/dist/tasks/parser.js +142 -6
  105. package/dist/tasks/resolve-akm-bin.js +19 -4
  106. package/dist/tasks/runner.js +258 -93
  107. package/dist/tasks/schedule.js +108 -19
  108. package/dist/tasks/scheduler-invocation.js +86 -0
  109. package/dist/tasks/task-id.js +37 -0
  110. package/dist/workflows/db.js +24 -13
  111. package/dist/workflows/validator.js +24 -2
  112. package/docs/README.md +103 -0
  113. package/docs/migration/release-notes/0.9.0.md +28 -9
  114. package/docs/migration/v0.8-to-v0.9.md +186 -26
  115. package/package.json +2 -3
@@ -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(),
@@ -14895,328 +14783,7 @@ var init_config_schema = __esm(() => {
14895
14783
  });
14896
14784
  }
14897
14785
  }
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;
15163
- }
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
- }
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,37 @@ __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
+ preflight.exec("PRAGMA busy_timeout = 30000");
17741
+ if (isCanonical)
17742
+ assertCurrentMigrationLedger(preflight, STATE_MIGRATIONS);
17743
+ else
17744
+ assertMigrationLedger(preflight, STATE_MIGRATIONS);
17745
+ } finally {
17746
+ preflight.close();
17747
+ }
17748
+ }
17963
17749
  const db = openManagedDatabase({
17964
17750
  path: resolvedPath,
17965
- init: (db2) => runMigrations2(db2, { ensureCutoverBackup: isCanonical })
17751
+ init: (db2) => runMigrations2(db2, { applyPending: !(isCanonical && existed) })
17966
17752
  });
17967
17753
  if (!releaseActivity)
17968
17754
  return db;
@@ -18001,7 +17787,7 @@ function isRetryableBeginError(err) {
18001
17787
  const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
18002
17788
  return msg.includes("database is locked") || msg.includes("database table is locked") || msg.includes("did not open a transaction");
18003
17789
  }
18004
- function sleepSyncMs2(ms) {
17790
+ function sleepSyncMs(ms) {
18005
17791
  if (ms <= 0)
18006
17792
  return;
18007
17793
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
@@ -18025,7 +17811,7 @@ function withImmediateTransaction(db, fn) {
18025
17811
  db.exec("ROLLBACK");
18026
17812
  } catch {}
18027
17813
  }
18028
- sleepSyncMs2(2 ** (attempt - 1));
17814
+ sleepSyncMs(2 ** (attempt - 1));
18029
17815
  continue;
18030
17816
  }
18031
17817
  throw err;
@@ -18056,9 +17842,11 @@ function listExistingTableNames(db, names) {
18056
17842
  }
18057
17843
  var WITH_IMMEDIATE_TX_MAX_ATTEMPTS = 5;
18058
17844
  var init_state_db = __esm(() => {
17845
+ init_database();
17846
+ init_sqlite_migrations();
18059
17847
  init_managed_db();
18060
17848
  init_maintenance_barrier();
18061
- init_migration_backup();
17849
+ init_migration_operation();
18062
17850
  init_paths();
18063
17851
  init_migrations();
18064
17852
  });
@@ -18068,6 +17856,7 @@ var exports_events_repository = {};
18068
17856
  __export(exports_events_repository, {
18069
17857
  readStateEvents: () => readStateEvents,
18070
17858
  purgeOldEvents: () => purgeOldEvents,
17859
+ insertEventOnce: () => insertEventOnce,
18071
17860
  insertEvent: () => insertEvent,
18072
17861
  importEventsJsonl: () => importEventsJsonl,
18073
17862
  eventRowToEnvelope: () => eventRowToEnvelope
@@ -18101,6 +17890,22 @@ function insertEvent(db, input) {
18101
17890
  return;
18102
17891
  }
18103
17892
  }
17893
+ function insertEventOnce(db, input) {
17894
+ 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);
17895
+ for (const row of rows) {
17896
+ try {
17897
+ const metadata = JSON.parse(row.metadata_json);
17898
+ if (metadata[input.idempotencyMetadataKey ?? "proposalTransactionId"] === input.idempotencyKey)
17899
+ return row.id;
17900
+ } catch {}
17901
+ }
17902
+ const result = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
17903
+ VALUES (?, ?, ?, ?)
17904
+ RETURNING id`).get(input.eventType, input.ts, input.ref ?? null, JSON.stringify(input.metadata));
17905
+ if (!result)
17906
+ throw new Error(`Failed to persist ${input.eventType} event.`);
17907
+ return result.id;
17908
+ }
18104
17909
  function readStateEvents(db, options = {}) {
18105
17910
  const conditions = [];
18106
17911
  const params = [];
@@ -18189,10 +17994,12 @@ var init_events_repository = __esm(() => {
18189
17994
  var exports_task_history_repository = {};
18190
17995
  __export(exports_task_history_repository, {
18191
17996
  upsertTaskHistory: () => upsertTaskHistory,
17997
+ reserveTaskHistoryAttempt: () => reserveTaskHistoryAttempt,
18192
17998
  queryTaskHistory: () => queryTaskHistory,
18193
17999
  queryCompletedTaskIntervals: () => queryCompletedTaskIntervals,
18194
18000
  getTaskHistoryRuns: () => getTaskHistoryRuns,
18195
18001
  getTaskHistory: () => getTaskHistory,
18002
+ finalizeTaskHistoryAttempt: () => finalizeTaskHistoryAttempt,
18196
18003
  decodeTaskHistoryMetadata: () => decodeTaskHistoryMetadata
18197
18004
  });
18198
18005
  function metadataError(message) {
@@ -18271,6 +18078,21 @@ function decodeTaskHistoryMetadata(input) {
18271
18078
  }
18272
18079
  metadataError(`unsupported metadataVersion: ${String(version)}`);
18273
18080
  }
18081
+ function reserveTaskHistoryAttempt(db, row) {
18082
+ const result = db.prepare(`INSERT OR IGNORE INTO task_history
18083
+ (task_id, status, started_at, completed_at, failed_at, log_path,
18084
+ target_kind, target_ref, metadata_json)
18085
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.task_id, row.status, row.started_at, row.completed_at ?? null, row.failed_at ?? null, row.log_path ?? null, row.target_kind ?? null, row.target_ref ?? null, row.metadata_json);
18086
+ return Number(result.changes) === 1;
18087
+ }
18088
+ function finalizeTaskHistoryAttempt(db, row) {
18089
+ const result = db.prepare(`UPDATE task_history
18090
+ SET status = ?, completed_at = ?, failed_at = ?, log_path = ?,
18091
+ target_kind = ?, target_ref = ?, metadata_json = ?
18092
+ WHERE task_id = ? AND started_at = ?
18093
+ AND status = 'active' AND completed_at IS NULL`).run(row.status, row.completed_at ?? null, row.failed_at ?? null, row.log_path ?? null, row.target_kind ?? null, row.target_ref ?? null, row.metadata_json, row.task_id, row.started_at);
18094
+ return Number(result.changes) === 1;
18095
+ }
18274
18096
  function upsertTaskHistory(db, row) {
18275
18097
  db.prepare(`
18276
18098
  INSERT OR IGNORE INTO task_history
@@ -18282,12 +18104,12 @@ function upsertTaskHistory(db, row) {
18282
18104
  function getTaskHistory(db, taskId) {
18283
18105
  return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
18284
18106
  target_kind, target_ref, metadata_json
18285
- FROM task_history WHERE task_id = ? ORDER BY started_at DESC LIMIT 1`).get(taskId);
18107
+ FROM task_history WHERE task_id = ? ORDER BY started_at DESC, id DESC LIMIT 1`).get(taskId);
18286
18108
  }
18287
18109
  function getTaskHistoryRuns(db, taskId, limit = 50) {
18288
18110
  return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
18289
18111
  target_kind, target_ref, metadata_json
18290
- FROM task_history WHERE task_id = ? ORDER BY started_at DESC LIMIT ?`).all(taskId, limit);
18112
+ FROM task_history WHERE task_id = ? ORDER BY started_at DESC, id DESC LIMIT ?`).all(taskId, limit);
18291
18113
  }
18292
18114
  function queryTaskHistory(db, options = {}) {
18293
18115
  const conditions = [];
@@ -18315,7 +18137,7 @@ function queryTaskHistory(db, options = {}) {
18315
18137
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
18316
18138
  return db.prepare(`SELECT task_id, status, started_at, completed_at, failed_at, log_path,
18317
18139
  target_kind, target_ref, metadata_json
18318
- FROM task_history ${where} ORDER BY started_at DESC`).all(...params);
18140
+ FROM task_history ${where} ORDER BY started_at DESC, id DESC`).all(...params);
18319
18141
  }
18320
18142
  function queryCompletedTaskIntervals(db, since, until) {
18321
18143
  const sql = until ? "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND started_at < ? AND completed_at IS NOT NULL ORDER BY started_at" : "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND completed_at IS NOT NULL ORDER BY started_at";
@@ -18966,9 +18788,9 @@ __export(exports_db, {
18966
18788
  UTILITY_REVIEW_THRESHOLD: () => UTILITY_REVIEW_THRESHOLD,
18967
18789
  HIGH_UTILITY_THRESHOLD: () => HIGH_UTILITY_THRESHOLD
18968
18790
  });
18969
- import fs14 from "node:fs";
18791
+ import fs13 from "node:fs";
18970
18792
  import { createRequire as createRequire6 } from "node:module";
18971
- import path15 from "node:path";
18793
+ import path13 from "node:path";
18972
18794
  function openIndexDatabase(dbPath, options) {
18973
18795
  return openManagedDatabase({
18974
18796
  path: dbPath ?? getDbPath(),
@@ -18998,7 +18820,7 @@ function openExistingDatabase2(dbPath) {
18998
18820
  }
18999
18821
  function openReadonlyExistingDatabase(dbPath) {
19000
18822
  const resolvedPath = dbPath ?? getDbPath();
19001
- if (!fs14.existsSync(resolvedPath))
18823
+ if (!fs13.existsSync(resolvedPath))
19002
18824
  return;
19003
18825
  return openDatabase(resolvedPath, { readonly: true, create: false });
19004
18826
  }
@@ -19073,7 +18895,7 @@ function deleteIndexDirState(db, dirPath) {
19073
18895
  db.prepare("DELETE FROM index_dir_state WHERE dir_path = ?").run(dirPath);
19074
18896
  }
19075
18897
  function deleteIndexDirStatesByStashDir(db, stashDir) {
19076
- db.prepare("DELETE FROM index_dir_state WHERE dir_path = ? OR dir_path LIKE ?").run(stashDir, `${stashDir}${path15.sep}%`);
18898
+ db.prepare("DELETE FROM index_dir_state WHERE dir_path = ? OR dir_path LIKE ?").run(stashDir, `${stashDir}${path13.sep}%`);
19077
18899
  }
19078
18900
  function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, searchText) {
19079
18901
  const stmts = getUpsertStmts(db);
@@ -19148,16 +18970,19 @@ function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
19148
18970
  return out;
19149
18971
  }
19150
18972
  function rekeyEntryInPlace(db, opts) {
19151
- const row = db.prepare("SELECT id, entry_json, search_text FROM entries WHERE entry_key = ?").get(opts.oldEntryKey);
18973
+ const row = db.prepare("SELECT id, stash_dir, entry_json, search_text FROM entries WHERE entry_key = ?").get(opts.oldEntryKey);
19152
18974
  if (!row)
19153
18975
  return null;
18976
+ if (opts.sourceRoot && path13.resolve(row.stash_dir) !== path13.resolve(opts.sourceRoot)) {
18977
+ throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: source root does not match.`);
18978
+ }
19154
18979
  let entryJson = row.entry_json;
19155
18980
  let searchText = row.search_text;
19156
18981
  try {
19157
18982
  const entry = JSON.parse(row.entry_json);
19158
18983
  entry.name = opts.newName;
19159
18984
  if (typeof entry.filename === "string")
19160
- entry.filename = path15.basename(opts.newFilePath);
18985
+ entry.filename = path13.basename(opts.newFilePath);
19161
18986
  if (opts.newDerivedFrom !== undefined)
19162
18987
  entry.derivedFrom = opts.newDerivedFrom;
19163
18988
  entryJson = JSON.stringify(entry);
@@ -19169,22 +18994,32 @@ function rekeyEntryInPlace(db, opts) {
19169
18994
  deleteRelatedRows(db, [{ id: stale.id }]);
19170
18995
  db.prepare("DELETE FROM entries WHERE id = ?").run(stale.id);
19171
18996
  }
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);
18997
+ 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
18998
  if (opts.newDerivedFrom !== undefined) {
19174
18999
  db.prepare("UPDATE entries SET derived_from = ? WHERE id = ?").run(opts.newDerivedFrom, row.id);
19175
19000
  }
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");
19001
+ try {
19002
+ if (opts.sourceName) {
19003
+ const origins = new Set([opts.sourceName]);
19004
+ if (opts.sourceName === "stash" && opts.includeLegacyBare)
19005
+ origins.add("local");
19006
+ for (const origin of origins) {
19007
+ const oldQualifiedRef = `${origin}//${opts.oldRef}`;
19008
+ const newQualifiedRef = `${origin}//${opts.newRef}`;
19009
+ db.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(newQualifiedRef);
19010
+ db.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(newQualifiedRef, oldQualifiedRef);
19011
+ }
19012
+ }
19013
+ if (opts.includeLegacyBare || !opts.sourceName) {
19014
+ db.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(opts.newRef);
19015
+ db.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(opts.newRef, opts.oldRef);
19016
+ }
19017
+ } catch (error2) {
19018
+ const message = error2 instanceof Error ? error2.message : String(error2);
19019
+ 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);
19020
+ if (!missingLegacyUsageSchema)
19021
+ throw error2;
19022
+ }
19188
19023
  db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)").run(row.id);
19189
19024
  })();
19190
19025
  return row.id;
@@ -19506,7 +19341,7 @@ function getEntitiesByEntryIds(db, entryIds) {
19506
19341
  }
19507
19342
  return result;
19508
19343
  }
19509
- function findEntryIdByRef(db, ref) {
19344
+ function findEntryIdByRef(db, ref, stashDir) {
19510
19345
  const parsed = parseAssetRef(ref);
19511
19346
  const nameVariants = [parsed.name];
19512
19347
  if (parsed.name.endsWith(".md")) {
@@ -19514,10 +19349,14 @@ function findEntryIdByRef(db, ref) {
19514
19349
  } else {
19515
19350
  nameVariants.push(`${parsed.name}.md`);
19516
19351
  }
19517
- const stmt = db.prepare("SELECT id FROM entries WHERE entry_type = ? AND substr(entry_key, length(entry_key) - length(?) + 1) = ? LIMIT 1");
19352
+ const stmt = db.prepare(`SELECT id FROM entries
19353
+ WHERE entry_type = ?
19354
+ AND substr(entry_key, length(entry_key) - length(?) + 1) = ?
19355
+ ${stashDir ? "AND stash_dir = ?" : ""}
19356
+ LIMIT 1`);
19518
19357
  for (const name of nameVariants) {
19519
19358
  const suffix = `${parsed.type}:${name}`;
19520
- const row = stmt.get(parsed.type, suffix, suffix);
19359
+ const row = stmt.get(parsed.type, suffix, suffix, ...stashDir ? [stashDir] : []);
19521
19360
  if (row)
19522
19361
  return row.id;
19523
19362
  }
@@ -19535,7 +19374,7 @@ function getEmbeddingCount(db) {
19535
19374
  return row.cnt;
19536
19375
  }
19537
19376
  function getEntryById(db, id) {
19538
- const row = db.prepare("SELECT file_path, entry_json FROM entries WHERE id = ?").get(id);
19377
+ const row = db.prepare("SELECT file_path, stash_dir, entry_json FROM entries WHERE id = ?").get(id);
19539
19378
  if (!row)
19540
19379
  return;
19541
19380
  let entry;
@@ -19545,7 +19384,7 @@ function getEntryById(db, id) {
19545
19384
  warn(`[db] getEntryById: skipping entry id=${id} — corrupt entry_json`);
19546
19385
  return;
19547
19386
  }
19548
- return { filePath: row.file_path, entry };
19387
+ return { filePath: row.file_path, stashDir: row.stash_dir, entry };
19549
19388
  }
19550
19389
  function getEntriesByDir(db, dirPath) {
19551
19390
  const rows = db.prepare(`SELECT ${ENTRY_COLUMNS} FROM entries WHERE dir_path = ?`).all(dirPath);
@@ -19694,9 +19533,12 @@ function bareRef(ref) {
19694
19533
  return ref;
19695
19534
  }
19696
19535
  }
19697
- function getRetrievalCounts(db, refs) {
19536
+ function getRetrievalCounts(db, refs, options = {}) {
19698
19537
  if (refs.length === 0)
19699
19538
  return new Map;
19539
+ if (options.sourceName || options.stashDir) {
19540
+ return getSourceScopedRetrievalCounts(db, refs, options);
19541
+ }
19700
19542
  const bareToInputs = new Map;
19701
19543
  for (const ref of refs) {
19702
19544
  const bare = bareRef(ref);
@@ -19740,6 +19582,51 @@ function getRetrievalCounts(db, refs) {
19740
19582
  }
19741
19583
  return result;
19742
19584
  }
19585
+ function getSourceScopedRetrievalCounts(db, refs, options) {
19586
+ const bareToInputs = new Map;
19587
+ for (const ref of refs) {
19588
+ const bare = bareRef(ref);
19589
+ const inputs = bareToInputs.get(bare);
19590
+ if (inputs)
19591
+ inputs.push(ref);
19592
+ else
19593
+ bareToInputs.set(bare, [ref]);
19594
+ }
19595
+ const countsByBare = new Map;
19596
+ const bareForms = [...bareToInputs.keys()];
19597
+ const selectedRoot = options.stashDir ? path13.resolve(options.stashDir) : undefined;
19598
+ for (let i = 0;i < bareForms.length; i += SQLITE_CHUNK_SIZE) {
19599
+ const chunk = bareForms.slice(i, i + SQLITE_CHUNK_SIZE);
19600
+ const placeholders = chunk.map(() => "?").join(", ");
19601
+ const rows = db.prepare(`SELECT ue.entry_ref, ue.entry_id, e.stash_dir
19602
+ FROM usage_events ue
19603
+ LEFT JOIN entries e ON e.id = ue.entry_id
19604
+ WHERE ue.event_type IN ('search','show','curate')
19605
+ AND ue.entry_ref IS NOT NULL
19606
+ AND (ue.source IS NULL OR ue.source NOT IN ('improve','task'))
19607
+ AND CASE
19608
+ WHEN instr(ue.entry_ref, '//') > 0
19609
+ THEN substr(ue.entry_ref, instr(ue.entry_ref, '//') + 2)
19610
+ ELSE ue.entry_ref
19611
+ END IN (${placeholders})`).all(...chunk);
19612
+ for (const row of rows) {
19613
+ const bare = bareRef(row.entry_ref);
19614
+ const linkedToSelectedRoot = row.entry_id !== null && selectedRoot !== undefined && row.stash_dir !== null ? path13.resolve(row.stash_dir) === selectedRoot : false;
19615
+ const detached = row.entry_id === null || selectedRoot === undefined;
19616
+ const qualifiedForSource = detached && options.sourceName !== undefined && row.entry_ref === `${options.sourceName}//${bare}`;
19617
+ const acceptedLegacyBare = detached && options.includeLegacyBare === true && row.entry_ref === bare;
19618
+ if (!linkedToSelectedRoot && !qualifiedForSource && !acceptedLegacyBare)
19619
+ continue;
19620
+ countsByBare.set(bare, (countsByBare.get(bare) ?? 0) + 1);
19621
+ }
19622
+ }
19623
+ const result = new Map;
19624
+ for (const [bare, count] of countsByBare) {
19625
+ for (const input of bareToInputs.get(bare) ?? [])
19626
+ result.set(input, count);
19627
+ }
19628
+ return result;
19629
+ }
19743
19630
  function bumpUtilityScoresBatch(db, entryIds, reward, lr = 0.1, scopeKey) {
19744
19631
  if (entryIds.length === 0)
19745
19632
  return;
@@ -19833,7 +19720,15 @@ function applyFeedbackToUtilityScore(db, entryId, positiveCount, negativeCount)
19833
19720
  `).run(entryId, result.nextUtility, now, now, result.nextUtility, now);
19834
19721
  return result;
19835
19722
  }
19836
- function relinkUsageEvents(db) {
19723
+ function resolveUsageEventEntryId(db, ref, options) {
19724
+ const parsed = parseAssetRef(ref);
19725
+ if (!parsed.origin) {
19726
+ return options.defaultStashDir ? findEntryIdByRef(db, ref, options.defaultStashDir) : undefined;
19727
+ }
19728
+ const source = parsed.origin === "local" || parsed.origin === "stash" ? options.sources?.[0] : options.sources?.find((candidate) => candidate.registryId === parsed.origin);
19729
+ return source ? findEntryIdByRef(db, ref, source.path) : undefined;
19730
+ }
19731
+ function relinkUsageEvents(db, options = {}) {
19837
19732
  bestEffort(() => {
19838
19733
  db.exec(`
19839
19734
  UPDATE usage_events
@@ -19847,7 +19742,7 @@ function relinkUsageEvents(db) {
19847
19742
  for (const { ref } of refs) {
19848
19743
  let id;
19849
19744
  try {
19850
- id = findEntryIdByRef(db, ref);
19745
+ id = resolveUsageEventEntryId(db, ref, options);
19851
19746
  } catch (err) {
19852
19747
  if (err instanceof Error && err.name === "UsageError")
19853
19748
  continue;
@@ -19912,12 +19807,12 @@ __export(exports_graph_db, {
19912
19807
  drainExtractionQueue: () => drainExtractionQueue,
19913
19808
  deleteStoredGraph: () => deleteStoredGraph
19914
19809
  });
19915
- import fs15 from "node:fs";
19810
+ import fs14 from "node:fs";
19916
19811
  function withReadableGraphDb(db, fn) {
19917
19812
  if (db)
19918
19813
  return fn(db);
19919
19814
  const dbPath = getDbPath();
19920
- if (!fs15.existsSync(dbPath))
19815
+ if (!fs14.existsSync(dbPath))
19921
19816
  throw new Error("GRAPH_DB_MISSING");
19922
19817
  const opened = openExistingDatabase(dbPath);
19923
19818
  try {
@@ -20260,15 +20155,15 @@ var init_graph_db = __esm(() => {
20260
20155
  });
20261
20156
 
20262
20157
  // scripts/migrate-storage.ts
20263
- import fs16 from "fs";
20158
+ import fs15 from "fs";
20264
20159
  import os3 from "os";
20265
- import path16 from "path";
20160
+ import path14 from "path";
20266
20161
  import readline from "readline";
20267
20162
 
20268
20163
  // src/core/paths.ts
20269
20164
  init_common();
20270
20165
  init_errors();
20271
- import path12 from "node:path";
20166
+ import path9 from "node:path";
20272
20167
  function isTransientStashPath2(p) {
20273
20168
  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
20169
  }
@@ -20279,34 +20174,34 @@ function getConfigDir2(env = process.env, platform = process.platform) {
20279
20174
  if (platform === "win32") {
20280
20175
  const appData = env.APPDATA?.trim();
20281
20176
  if (appData)
20282
- return path12.join(appData, "akm");
20177
+ return path9.join(appData, "akm");
20283
20178
  } else {
20284
20179
  const xdgConfigHome2 = env.XDG_CONFIG_HOME?.trim();
20285
20180
  if (xdgConfigHome2)
20286
- return path12.join(xdgConfigHome2, "akm");
20181
+ return path9.join(xdgConfigHome2, "akm");
20287
20182
  }
20288
20183
  const stashOverride = env.AKM_STASH_DIR?.trim();
20289
20184
  if (stashOverride && isTransientStashPath2(stashOverride)) {
20290
- return path12.join(stashOverride, ".akm");
20185
+ return path9.join(stashOverride, ".akm");
20291
20186
  }
20292
20187
  if (platform === "win32") {
20293
20188
  const appData = env.APPDATA?.trim();
20294
20189
  if (appData)
20295
- return path12.join(appData, "akm");
20190
+ return path9.join(appData, "akm");
20296
20191
  const userProfile = env.USERPROFILE?.trim();
20297
20192
  if (!userProfile) {
20298
20193
  throw new ConfigError("Unable to determine config directory. Set APPDATA or USERPROFILE.", "CONFIG_DIR_UNRESOLVABLE");
20299
20194
  }
20300
- return path12.join(userProfile, "AppData", "Roaming", "akm");
20195
+ return path9.join(userProfile, "AppData", "Roaming", "akm");
20301
20196
  }
20302
20197
  const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
20303
20198
  if (xdgConfigHome)
20304
- return path12.join(xdgConfigHome, "akm");
20199
+ return path9.join(xdgConfigHome, "akm");
20305
20200
  const home = env.HOME?.trim();
20306
20201
  if (!home) {
20307
20202
  throw new ConfigError("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.", "CONFIG_DIR_UNRESOLVABLE");
20308
20203
  }
20309
- return path12.join(home, ".config", "akm");
20204
+ return path9.join(home, ".config", "akm");
20310
20205
  }
20311
20206
  function getCacheDir2(env = process.env) {
20312
20207
  const override = env.AKM_CACHE_DIR?.trim();
@@ -20315,30 +20210,30 @@ function getCacheDir2(env = process.env) {
20315
20210
  if (IS_WINDOWS) {
20316
20211
  const localAppData = env.LOCALAPPDATA?.trim();
20317
20212
  if (localAppData)
20318
- return path12.join(localAppData, "akm");
20213
+ return path9.join(localAppData, "akm");
20319
20214
  const userProfile = env.USERPROFILE?.trim();
20320
20215
  if (userProfile)
20321
- return path12.join(userProfile, "AppData", "Local", "akm");
20216
+ return path9.join(userProfile, "AppData", "Local", "akm");
20322
20217
  const appData = env.APPDATA?.trim();
20323
20218
  if (appData) {
20324
- return path12.join(appData, "..", "Local", "akm");
20219
+ return path9.join(appData, "..", "Local", "akm");
20325
20220
  }
20326
20221
  } else {
20327
20222
  const xdgCacheHome = env.XDG_CACHE_HOME?.trim();
20328
20223
  if (xdgCacheHome)
20329
- return path12.join(xdgCacheHome, "akm");
20224
+ return path9.join(xdgCacheHome, "akm");
20330
20225
  }
20331
20226
  const stashOverride = env.AKM_STASH_DIR?.trim();
20332
20227
  if (stashOverride && isTransientStashPath2(stashOverride)) {
20333
- return path12.join(stashOverride, ".akm", "cache");
20228
+ return path9.join(stashOverride, ".akm", "cache");
20334
20229
  }
20335
20230
  if (IS_WINDOWS) {
20336
20231
  throw new ConfigError("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
20337
20232
  }
20338
20233
  const home = env.HOME?.trim();
20339
20234
  if (!home)
20340
- return path12.join("/tmp", "akm-cache");
20341
- return path12.join(home, ".cache", "akm");
20235
+ return path9.join("/tmp", "akm-cache");
20236
+ return path9.join(home, ".cache", "akm");
20342
20237
  }
20343
20238
  function getDefaultStashDir2(env = process.env) {
20344
20239
  const override = env.AKM_STASH_DIR?.trim();
@@ -20347,14 +20242,14 @@ function getDefaultStashDir2(env = process.env) {
20347
20242
  if (IS_WINDOWS) {
20348
20243
  const userProfile = env.USERPROFILE?.trim();
20349
20244
  if (userProfile)
20350
- return path12.join(userProfile, "Documents", "akm");
20351
- return path12.join("C:\\", "akm");
20245
+ return path9.join(userProfile, "Documents", "akm");
20246
+ return path9.join("C:\\", "akm");
20352
20247
  }
20353
20248
  const home = env.HOME?.trim();
20354
20249
  if (!home) {
20355
20250
  throw new ConfigError("Unable to determine default stash directory. Set HOME.", "STASH_DIR_NOT_FOUND");
20356
20251
  }
20357
- return path12.join(home, "akm");
20252
+ return path9.join(home, "akm");
20358
20253
  }
20359
20254
 
20360
20255
  // src/indexer/usage/unmigrated-vaults-guard.ts
@@ -20379,12 +20274,12 @@ function parseFromArg() {
20379
20274
  return val;
20380
20275
  }
20381
20276
  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"));
20277
+ 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"));
20278
+ 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
20279
  var cacheDir = getCacheDir2();
20385
20280
  var configDir = getConfigDir2();
20386
- var stateDbPath = path16.join(dataDir, "state.db");
20387
- var indexDbPath = path16.join(dataDir, "index.db");
20281
+ var stateDbPath = path14.join(dataDir, "state.db");
20282
+ var indexDbPath = path14.join(dataDir, "index.db");
20388
20283
  var PATHS = {
20389
20284
  cacheDir,
20390
20285
  configDir,
@@ -20395,14 +20290,14 @@ var PATHS = {
20395
20290
  };
20396
20291
  var versionReports = [];
20397
20292
  function ensureDir(dir) {
20398
- if (!fs16.existsSync(dir)) {
20399
- fs16.mkdirSync(dir, { recursive: true });
20293
+ if (!fs15.existsSync(dir)) {
20294
+ fs15.mkdirSync(dir, { recursive: true });
20400
20295
  }
20401
20296
  }
20402
20297
  function copyAndVerify(src, dest) {
20403
- fs16.copyFileSync(src, dest);
20404
- const srcStat = fs16.statSync(src);
20405
- const destStat = fs16.statSync(dest);
20298
+ fs15.copyFileSync(src, dest);
20299
+ const srcStat = fs15.statSync(src);
20300
+ const destStat = fs15.statSync(dest);
20406
20301
  return destStat.size === srcStat.size;
20407
20302
  }
20408
20303
  function copyTree(src, dest, opts) {
@@ -20410,9 +20305,9 @@ function copyTree(src, dest, opts) {
20410
20305
  let skipped = 0;
20411
20306
  let failed = 0;
20412
20307
  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);
20308
+ for (const entry of fs15.readdirSync(src, { withFileTypes: true })) {
20309
+ const srcEntry = path14.join(src, entry.name);
20310
+ const destEntry = path14.join(dest, entry.name);
20416
20311
  if (entry.isDirectory()) {
20417
20312
  const sub = copyTree(srcEntry, destEntry, opts);
20418
20313
  copied += sub.copied;
@@ -20420,7 +20315,7 @@ function copyTree(src, dest, opts) {
20420
20315
  failed += sub.failed;
20421
20316
  continue;
20422
20317
  }
20423
- if (!opts.clobber && fs16.existsSync(destEntry)) {
20318
+ if (!opts.clobber && fs15.existsSync(destEntry)) {
20424
20319
  skipped++;
20425
20320
  continue;
20426
20321
  }
@@ -20438,9 +20333,9 @@ function copyTree(src, dest, opts) {
20438
20333
  }
20439
20334
  function countFilesRecursive(dir) {
20440
20335
  let count = 0;
20441
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
20336
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
20442
20337
  if (entry.isDirectory()) {
20443
- count += countFilesRecursive(path16.join(dir, entry.name));
20338
+ count += countFilesRecursive(path14.join(dir, entry.name));
20444
20339
  } else {
20445
20340
  count++;
20446
20341
  }
@@ -20453,12 +20348,12 @@ async function runSteps(steps, ctx) {
20453
20348
  }
20454
20349
  }
20455
20350
  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)) {
20351
+ const src = path14.join(ctx.paths.cacheDir, filename);
20352
+ const dest = path14.join(ctx.paths.dataDir, filename);
20353
+ if (!fs15.existsSync(src)) {
20459
20354
  return { name: filename, status: "skipped", detail: "source not found" };
20460
20355
  }
20461
- if (fs16.existsSync(dest)) {
20356
+ if (fs15.existsSync(dest)) {
20462
20357
  return { name: filename, status: "skipped", detail: "destination already exists" };
20463
20358
  }
20464
20359
  if (ctx.dryRun) {
@@ -20476,8 +20371,8 @@ function migrateDb(ctx, filename) {
20476
20371
  return { name: filename, status: "failed", detail: `size mismatch after copy: ${src} \u2192 ${dest}` };
20477
20372
  }
20478
20373
  async function migrateEventsJsonl(ctx) {
20479
- const src = path16.join(ctx.paths.cacheDir, "events.jsonl");
20480
- if (!fs16.existsSync(src)) {
20374
+ const src = path14.join(ctx.paths.cacheDir, "events.jsonl");
20375
+ if (!fs15.existsSync(src)) {
20481
20376
  return { name: "events.jsonl \u2192 state.db", status: "skipped", detail: "source not found" };
20482
20377
  }
20483
20378
  if (ctx.dryRun) {
@@ -20522,13 +20417,13 @@ async function migrateEventsJsonl(ctx) {
20522
20417
  }
20523
20418
  }
20524
20419
  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)) {
20420
+ const src = path14.join(ctx.paths.cacheDir, "tasks", "history");
20421
+ const dest = path14.join(ctx.paths.stateDir, "tasks", "history");
20422
+ if (!fs15.existsSync(src)) {
20528
20423
  return { name: "tasks/history/", status: "skipped", detail: "source directory not found" };
20529
20424
  }
20530
20425
  try {
20531
- const files = fs16.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
20426
+ const files = fs15.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
20532
20427
  if (files.length === 0) {
20533
20428
  return { name: "tasks/history/", status: "skipped", detail: "no *.jsonl files found in source directory" };
20534
20429
  }
@@ -20543,8 +20438,8 @@ function migrateTaskHistory(ctx) {
20543
20438
  let copied = 0;
20544
20439
  let failed = 0;
20545
20440
  for (const file of files) {
20546
- const srcFile = path16.join(src, file);
20547
- const destFile = path16.join(dest, file);
20441
+ const srcFile = path14.join(src, file);
20442
+ const destFile = path14.join(dest, file);
20548
20443
  try {
20549
20444
  if (copyAndVerify(srcFile, destFile)) {
20550
20445
  copied++;
@@ -20573,12 +20468,12 @@ function migrateTaskHistory(ctx) {
20573
20468
  }
20574
20469
  }
20575
20470
  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)) {
20471
+ const src = path14.join(ctx.paths.configDir, "akm.lock");
20472
+ const dest = path14.join(ctx.paths.dataDir, "akm.lock");
20473
+ if (!fs15.existsSync(src)) {
20579
20474
  return { name: "akm.lock", status: "skipped", detail: "source not found" };
20580
20475
  }
20581
- if (fs16.existsSync(dest)) {
20476
+ if (fs15.existsSync(dest)) {
20582
20477
  return { name: "akm.lock", status: "skipped", detail: "destination already exists" };
20583
20478
  }
20584
20479
  if (ctx.dryRun) {
@@ -20598,12 +20493,12 @@ function migrateLockfile(ctx) {
20598
20493
  return { name: "akm.lock", status: "failed", detail: `size mismatch after copy: ${src} \u2192 ${dest}` };
20599
20494
  }
20600
20495
  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)) {
20496
+ const src = path14.join(ctx.paths.cacheDir, "config-backups");
20497
+ const dest = path14.join(ctx.paths.dataDir, "config-backups");
20498
+ if (!fs15.existsSync(src)) {
20604
20499
  return { name: "config-backups/", status: "skipped", detail: "source directory not found" };
20605
20500
  }
20606
- if (fs16.existsSync(dest)) {
20501
+ if (fs15.existsSync(dest)) {
20607
20502
  return { name: "config-backups/", status: "skipped", detail: "destination already exists" };
20608
20503
  }
20609
20504
  if (ctx.dryRun) {
@@ -20620,7 +20515,7 @@ function migrateConfigBackups(ctx) {
20620
20515
  try {
20621
20516
  const srcCount = countFilesRecursive(src);
20622
20517
  const { copied, failed } = copyTree(src, dest, { clobber: true });
20623
- const destCount = fs16.existsSync(dest) ? countFilesRecursive(dest) : 0;
20518
+ const destCount = fs15.existsSync(dest) ? countFilesRecursive(dest) : 0;
20624
20519
  if (failed === 0 && destCount === srcCount) {
20625
20520
  return {
20626
20521
  name: "config-backups/",
@@ -20639,11 +20534,11 @@ function migrateConfigBackups(ctx) {
20639
20534
  }
20640
20535
  }
20641
20536
  async function migrateTaskHistoryToDb(ctx) {
20642
- const src = path16.join(ctx.paths.cacheDir, "tasks", "history");
20643
- if (!fs16.existsSync(src)) {
20537
+ const src = path14.join(ctx.paths.cacheDir, "tasks", "history");
20538
+ if (!fs15.existsSync(src)) {
20644
20539
  return { name: "tasks/history/ \u2192 state.db", status: "skipped", detail: "source directory not found" };
20645
20540
  }
20646
- const files = fs16.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
20541
+ const files = fs15.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
20647
20542
  if (files.length === 0) {
20648
20543
  return {
20649
20544
  name: "tasks/history/ \u2192 state.db",
@@ -20667,8 +20562,8 @@ async function migrateTaskHistoryToDb(ctx) {
20667
20562
  let failed = 0;
20668
20563
  try {
20669
20564
  for (const file of files) {
20670
- const filePath = path16.join(src, file);
20671
- const text = fs16.readFileSync(filePath, "utf8");
20565
+ const filePath = path14.join(src, file);
20566
+ const text = fs15.readFileSync(filePath, "utf8");
20672
20567
  const lines = text.split(`
20673
20568
  `).filter((l) => l.trim().length > 0);
20674
20569
  db.exec("BEGIN");
@@ -20727,11 +20622,11 @@ async function migrateTaskHistoryToDb(ctx) {
20727
20622
  }
20728
20623
  }
20729
20624
  function noteRegistryIndexCache(ctx) {
20730
- const src = path16.join(ctx.paths.cacheDir, "registry-index");
20731
- if (!fs16.existsSync(src)) {
20625
+ const src = path14.join(ctx.paths.cacheDir, "registry-index");
20626
+ if (!fs15.existsSync(src)) {
20732
20627
  return { name: "registry-index/ (note)", status: "skipped", detail: "no old $CACHE/registry-index/ directory found" };
20733
20628
  }
20734
- const legacyFiles = fs16.readdirSync(src).filter((f) => f.endsWith(".json") && !f.startsWith("website-"));
20629
+ const legacyFiles = fs15.readdirSync(src).filter((f) => f.endsWith(".json") && !f.startsWith("website-"));
20735
20630
  if (legacyFiles.length === 0) {
20736
20631
  return { name: "registry-index/ (note)", status: "skipped", detail: "no old *.json cache files found" };
20737
20632
  }
@@ -20741,7 +20636,7 @@ function noteRegistryIndexCache(ctx) {
20741
20636
  These are ignored in v0.9 \u2014 data is now stored in the registry_index_cache` + `
20742
20637
  table in $DATA/index.db and will be rebuilt on next 'akm registry search'.
20743
20638
  You may safely delete these files after migration:
20744
- ` + legacyFiles.map((f) => ` ${path16.join(src, f)}`).join(`
20639
+ ` + legacyFiles.map((f) => ` ${path14.join(src, f)}`).join(`
20745
20640
  `));
20746
20641
  }
20747
20642
  return {
@@ -20755,15 +20650,15 @@ var v07To08Migration = {
20755
20650
  sourceVersion: "0.7",
20756
20651
  isNeeded: (paths) => {
20757
20652
  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")
20653
+ path14.join(paths.cacheDir, "index.db"),
20654
+ path14.join(paths.cacheDir, "workflow.db"),
20655
+ path14.join(paths.cacheDir, "events.jsonl"),
20656
+ path14.join(paths.cacheDir, "tasks", "history"),
20657
+ path14.join(paths.configDir, "akm.lock"),
20658
+ path14.join(paths.cacheDir, "config-backups"),
20659
+ path14.join(paths.cacheDir, "registry-index")
20765
20660
  ];
20766
- return candidates.some((p) => fs16.existsSync(p));
20661
+ return candidates.some((p) => fs15.existsSync(p));
20767
20662
  },
20768
20663
  steps: [
20769
20664
  { id: "index-db", title: "index.db", run: (ctx) => migrateDb(ctx, "index.db") },
@@ -20778,20 +20673,20 @@ var v07To08Migration = {
20778
20673
  };
20779
20674
  function legacyGraphCandidatePaths(ctx) {
20780
20675
  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")
20676
+ path14.join(ctx.paths.cacheDir, "graph-snapshot.json"),
20677
+ path14.join(ctx.paths.dataDir, "graph-snapshot.json"),
20678
+ path14.join(ctx.paths.dataDir, "graph-export.json")
20784
20679
  ];
20785
20680
  }
20786
20681
  function findLegacyGraphFile(ctx) {
20787
20682
  for (const candidate of legacyGraphCandidatePaths(ctx)) {
20788
- if (fs16.existsSync(candidate) && fs16.statSync(candidate).isFile())
20683
+ if (fs15.existsSync(candidate) && fs15.statSync(candidate).isFile())
20789
20684
  return candidate;
20790
20685
  }
20791
- const graphDir = path16.join(ctx.paths.cacheDir, "graph");
20792
- if (fs16.existsSync(graphDir) && fs16.statSync(graphDir).isDirectory()) {
20686
+ const graphDir = path14.join(ctx.paths.cacheDir, "graph");
20687
+ if (fs15.existsSync(graphDir) && fs15.statSync(graphDir).isDirectory()) {
20793
20688
  try {
20794
- const json = fs16.readdirSync(graphDir).filter((f) => f.endsWith(".json")).map((f) => path16.join(graphDir, f));
20689
+ const json = fs15.readdirSync(graphDir).filter((f) => f.endsWith(".json")).map((f) => path14.join(graphDir, f));
20795
20690
  if (json.length > 0)
20796
20691
  return json[0];
20797
20692
  } catch {}
@@ -20827,7 +20722,7 @@ async function migrateGraphFileToDb(ctx) {
20827
20722
  if (!legacyFile) {
20828
20723
  return { name, status: "skipped", detail: "no legacy graph file found" };
20829
20724
  }
20830
- if (!fs16.existsSync(ctx.paths.indexDbPath)) {
20725
+ if (!fs15.existsSync(ctx.paths.indexDbPath)) {
20831
20726
  return {
20832
20727
  name,
20833
20728
  status: "skipped",
@@ -20842,7 +20737,7 @@ async function migrateGraphFileToDb(ctx) {
20842
20737
  };
20843
20738
  }
20844
20739
  try {
20845
- const raw = fs16.readFileSync(legacyFile, "utf8");
20740
+ const raw = fs15.readFileSync(legacyFile, "utf8");
20846
20741
  let parsed;
20847
20742
  try {
20848
20743
  parsed = JSON.parse(raw);
@@ -20919,7 +20814,7 @@ async function migrateGraphFileToDb(ctx) {
20919
20814
  }
20920
20815
  const renamed = `${legacyFile}.migrated`;
20921
20816
  try {
20922
- fs16.renameSync(legacyFile, renamed);
20817
+ fs15.renameSync(legacyFile, renamed);
20923
20818
  } catch (err) {
20924
20819
  const msg = err instanceof Error ? err.message : String(err);
20925
20820
  return {
@@ -20943,9 +20838,9 @@ async function migrateGraphFileToDb(ctx) {
20943
20838
  }
20944
20839
  function resolvePrimaryStashDir(configDirPath) {
20945
20840
  try {
20946
- const cfgPath = path16.join(configDirPath, "config.json");
20947
- if (fs16.existsSync(cfgPath)) {
20948
- const cfg = JSON.parse(fs16.readFileSync(cfgPath, "utf8"));
20841
+ const cfgPath = path14.join(configDirPath, "config.json");
20842
+ if (fs15.existsSync(cfgPath)) {
20843
+ const cfg = JSON.parse(fs15.readFileSync(cfgPath, "utf8"));
20949
20844
  if (typeof cfg.stashDir === "string" && cfg.stashDir.length > 0)
20950
20845
  return cfg.stashDir;
20951
20846
  }
@@ -20954,8 +20849,8 @@ function resolvePrimaryStashDir(configDirPath) {
20954
20849
  }
20955
20850
  function countEnvFilesRecursive(dir) {
20956
20851
  let count = 0;
20957
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
20958
- 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);
20959
20854
  if (entry.isDirectory()) {
20960
20855
  count += countEnvFilesRecursive(full);
20961
20856
  } else if (entry.name === ".env" || entry.name.endsWith(".env")) {
@@ -20967,13 +20862,13 @@ function countEnvFilesRecursive(dir) {
20967
20862
  function chmodTreeSecure(dir) {
20968
20863
  const isWin = process.platform === "win32";
20969
20864
  try {
20970
- fs16.chmodSync(dir, 448);
20865
+ fs15.chmodSync(dir, 448);
20971
20866
  } catch (err) {
20972
20867
  if (!isWin)
20973
20868
  return { ok: false, detail: `chmod 0700 ${dir} failed: ${err instanceof Error ? err.message : err}` };
20974
20869
  }
20975
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
20976
- const full = path16.join(dir, entry.name);
20870
+ for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
20871
+ const full = path14.join(dir, entry.name);
20977
20872
  if (entry.isDirectory()) {
20978
20873
  const sub = chmodTreeSecure(full);
20979
20874
  if (!sub.ok)
@@ -20981,8 +20876,8 @@ function chmodTreeSecure(dir) {
20981
20876
  continue;
20982
20877
  }
20983
20878
  try {
20984
- fs16.chmodSync(full, 384);
20985
- if (!isWin && (fs16.statSync(full).mode & 511) !== 384) {
20879
+ fs15.chmodSync(full, 384);
20880
+ if (!isWin && (fs15.statSync(full).mode & 511) !== 384) {
20986
20881
  return { ok: false, detail: `mode verification failed for ${full}` };
20987
20882
  }
20988
20883
  } catch (err) {
@@ -20995,13 +20890,13 @@ function chmodTreeSecure(dir) {
20995
20890
  async function migrateVaultsToEnv(ctx) {
20996
20891
  const name = "vaults/ \u2192 env/";
20997
20892
  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)) {
20893
+ const vaultsDir = path14.join(stashDir, "vaults");
20894
+ const envDir = path14.join(stashDir, "env");
20895
+ const marker = path14.join(vaultsDir, MIGRATED_MARKER);
20896
+ if (!fs15.existsSync(vaultsDir)) {
21002
20897
  return { name, status: "skipped", detail: `no vaults/ directory under ${stashDir}` };
21003
20898
  }
21004
- if (fs16.existsSync(marker)) {
20899
+ if (fs15.existsSync(marker)) {
21005
20900
  return { name, status: "skipped", detail: "already migrated (.migrated marker present)" };
21006
20901
  }
21007
20902
  const vaultEnvCount = countEnvFilesRecursive(vaultsDir);
@@ -21032,7 +20927,7 @@ async function migrateVaultsToEnv(ctx) {
21032
20927
  detail: `post-copy count ${envEnvCount} < source ${vaultEnvCount}; no marker written`
21033
20928
  };
21034
20929
  }
21035
- fs16.writeFileSync(marker, `migrated vaults/ -> env/ at ${new Date().toISOString()}
20930
+ fs15.writeFileSync(marker, `migrated vaults/ -> env/ at ${new Date().toISOString()}
21036
20931
  `, { mode: 384 });
21037
20932
  return {
21038
20933
  name,
@@ -21054,7 +20949,7 @@ var v08To09Migration = {
21054
20949
  { id: "vaults-to-env", title: "vaults/ \u2192 env/", run: migrateVaultsToEnv }
21055
20950
  ]
21056
20951
  };
21057
- var MIGRATIONS2 = [v07To08Migration, v08To09Migration];
20952
+ var MIGRATIONS = [v07To08Migration, v08To09Migration];
21058
20953
  async function confirm() {
21059
20954
  if (YES)
21060
20955
  return true;
@@ -21068,7 +20963,7 @@ async function confirm() {
21068
20963
  }
21069
20964
  function printList() {
21070
20965
  console.log("akm storage migrations (in execution order):");
21071
- for (const m of MIGRATIONS2) {
20966
+ for (const m of MIGRATIONS) {
21072
20967
  console.log(` \u2022 ${m.label} [sourceVersion=${m.sourceVersion}]`);
21073
20968
  }
21074
20969
  console.log();
@@ -21092,8 +20987,8 @@ function compareVersion(a, b) {
21092
20987
  }
21093
20988
  function filteredMigrations() {
21094
20989
  if (!FROM_VERSION)
21095
- return MIGRATIONS2;
21096
- return MIGRATIONS2.filter((m) => compareVersion(m.sourceVersion, FROM_VERSION) >= 0);
20990
+ return MIGRATIONS;
20991
+ return MIGRATIONS.filter((m) => compareVersion(m.sourceVersion, FROM_VERSION) >= 0);
21097
20992
  }
21098
20993
  function printGroupedSummary() {
21099
20994
  console.log(`
@@ -21138,12 +21033,12 @@ task_history: task_id PK).
21138
21033
  Migration complete. No errors.`);
21139
21034
  console.log(`
21140
21035
  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")}
21036
+ ${path14.join(cacheDir, "index.db")}
21037
+ ${path14.join(cacheDir, "workflow.db")}
21038
+ ${path14.join(cacheDir, "events.jsonl")}
21039
+ ${path14.join(cacheDir, "tasks", "history")}
21040
+ ${path14.join(cacheDir, "config-backups")}
21041
+ ${path14.join(configDir, "akm.lock")}
21147
21042
 
21148
21043
  Next step \u2014 repopulate graph data (if migrating from 0.7):
21149
21044
  The 0.8.0 graph schema redesign (DB_VERSION 12 \u2192 13) rebuilds the graph
@@ -21236,7 +21131,7 @@ var invokedDirectly = (() => {
21236
21131
  if (!entry)
21237
21132
  return false;
21238
21133
  const here = new URL(import.meta.url).pathname;
21239
- return path16.resolve(entry) === here;
21134
+ return path14.resolve(entry) === here;
21240
21135
  } catch {
21241
21136
  return false;
21242
21137
  }
@@ -21247,5 +21142,5 @@ if (invokedDirectly) {
21247
21142
  export {
21248
21143
  runMigrations3 as runMigrations,
21249
21144
  main,
21250
- MIGRATIONS2 as MIGRATIONS
21145
+ MIGRATIONS
21251
21146
  };