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
@@ -171,13 +171,6 @@ function statfsType(path) {
171
171
  return;
172
172
  }
173
173
  }
174
- function sleepSync(ms) {
175
- if (isBun2) {
176
- bunGlobal().sleepSync(ms);
177
- return;
178
- }
179
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
180
- }
181
174
  function bunGlobal() {
182
175
  return globalThis.Bun;
183
176
  }
@@ -252,7 +245,7 @@ var require_main = __commonJS((exports, module) => {
252
245
  var fs3 = __require("fs");
253
246
  var path2 = __require("path");
254
247
  var os = __require("os");
255
- var crypto = __require("crypto");
248
+ var crypto2 = __require("crypto");
256
249
  var TIPS = [
257
250
  "◈ encrypted .env [www.dotenvx.com]",
258
251
  "◈ secrets for agents [www.dotenvx.com]",
@@ -498,7 +491,7 @@ var require_main = __commonJS((exports, module) => {
498
491
  const authTag = ciphertext.subarray(-16);
499
492
  ciphertext = ciphertext.subarray(12, -16);
500
493
  try {
501
- const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
494
+ const aesgcm = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
502
495
  aesgcm.setAuthTag(authTag);
503
496
  return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
504
497
  } catch (error) {
@@ -7740,38 +7733,6 @@ function getConfigDir(env = process.env, platform = process.platform) {
7740
7733
  function getConfigPath(env = process.env) {
7741
7734
  return path2.join(getConfigDir(env), "config.json");
7742
7735
  }
7743
- function getCacheDir(env = process.env) {
7744
- const override = env.AKM_CACHE_DIR?.trim();
7745
- if (override)
7746
- return override;
7747
- if (IS_WINDOWS) {
7748
- const localAppData = env.LOCALAPPDATA?.trim();
7749
- if (localAppData)
7750
- return path2.join(localAppData, "akm");
7751
- const userProfile = env.USERPROFILE?.trim();
7752
- if (userProfile)
7753
- return path2.join(userProfile, "AppData", "Local", "akm");
7754
- const appData = env.APPDATA?.trim();
7755
- if (appData) {
7756
- return path2.join(appData, "..", "Local", "akm");
7757
- }
7758
- } else {
7759
- const xdgCacheHome = env.XDG_CACHE_HOME?.trim();
7760
- if (xdgCacheHome)
7761
- return path2.join(xdgCacheHome, "akm");
7762
- }
7763
- const stashOverride = env.AKM_STASH_DIR?.trim();
7764
- if (stashOverride && isTransientStashPath(stashOverride)) {
7765
- return path2.join(stashOverride, ".akm", "cache");
7766
- }
7767
- if (IS_WINDOWS) {
7768
- throw new ConfigError("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
7769
- }
7770
- const home = env.HOME?.trim();
7771
- if (!home)
7772
- return path2.join("/tmp", "akm-cache");
7773
- return path2.join(home, ".cache", "akm");
7774
- }
7775
7736
  function getDataDir(env = process.env, platform = process.platform) {
7776
7737
  const override = env.AKM_DATA_DIR?.trim();
7777
7738
  if (override)
@@ -7803,129 +7764,47 @@ function getDataDir(env = process.env, platform = process.platform) {
7803
7764
  function getMaintenanceBarrierPath() {
7804
7765
  return path2.join(getDataDir(), "maintenance.barrier.lock");
7805
7766
  }
7806
- function getWorkflowDbPath() {
7807
- return path2.join(getDataDir(), "workflow.db");
7808
- }
7809
- function getStateDbPathInDataDir() {
7810
- return path2.join(getDataDir(), "state.db");
7811
- }
7812
7767
  var init_paths = __esm(() => {
7813
7768
  init_common();
7814
7769
  init_errors();
7815
7770
  });
7816
7771
 
7817
- // src/core/config/config-io.ts
7772
+ // src/core/migration-operation.ts
7773
+ import crypto2 from "node:crypto";
7818
7774
  import fs4 from "node:fs";
7819
7775
  import path3 from "node:path";
7820
- function parseConfigText(text, sourcePath) {
7821
- const stripped = stripJsonComments(text);
7822
- const where = sourcePath ? ` at ${sourcePath}` : "";
7823
- let parsed;
7824
- try {
7825
- parsed = JSON.parse(stripped);
7826
- } catch (err) {
7827
- const detail = err instanceof Error ? err.message : String(err);
7828
- 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.");
7829
- }
7830
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
7831
- throw new ConfigError(`Config file${where} must contain a JSON object at the root, got ${describeJsonRoot(parsed)}.`, "INVALID_CONFIG_FILE");
7832
- }
7833
- return parsed;
7776
+ function installationId() {
7777
+ return crypto2.createHash("sha256").update(path3.resolve(path3.dirname(getConfigPath()))).update("\x00").update(path3.resolve(getDataDir())).digest("hex").slice(0, 24);
7834
7778
  }
7835
- function describeJsonRoot(value) {
7836
- if (value === null)
7837
- return "null";
7838
- if (Array.isArray(value))
7839
- return "an array";
7840
- if (typeof value === "string")
7841
- return "a string";
7842
- if (typeof value === "number")
7843
- return "a number";
7844
- if (typeof value === "boolean")
7845
- return "a boolean";
7846
- return typeof value;
7779
+ function getMigrationOperationRoot() {
7780
+ return path3.join(getDataDir(), "backups", "migrations", installationId());
7847
7781
  }
7848
- function getConfigLockPath() {
7849
- return path3.join(getConfigDir(), "config.json.lck");
7782
+ function getMigrationRestoreJournalPath() {
7783
+ return path3.join(getMigrationOperationRoot(), "restore-active.json");
7850
7784
  }
7851
- function sleepSyncMs(ms) {
7852
- sleepSync(ms);
7785
+ function getMigrationApplyJournalPath() {
7786
+ return path3.join(getMigrationOperationRoot(), "apply-active.json");
7853
7787
  }
7854
- function acquireConfigLock() {
7855
- const lockPath = getConfigLockPath();
7856
- try {
7857
- fs4.mkdirSync(path3.dirname(lockPath), { recursive: true });
7858
- } catch {}
7859
- for (let attempt = 0;attempt < CONFIG_LOCK_MAX_RETRIES; attempt++) {
7860
- try {
7861
- const ownership = tryAcquireLockSync(lockPath, createLockPayload());
7862
- if (ownership) {
7863
- return () => releaseLock(ownership);
7864
- }
7865
- } catch (error) {
7866
- throw new ConfigError(`Unable to acquire config lock at ${lockPath}: ${error instanceof Error ? error.message : String(error)}`, "INVALID_CONFIG_FILE");
7867
- }
7868
- const probe = probeLock(lockPath);
7869
- if (probe.state === "stale" && reclaimStaleLock(lockPath, probe)) {
7870
- continue;
7871
- }
7872
- if (attempt < CONFIG_LOCK_MAX_RETRIES - 1) {
7873
- sleepSyncMs(CONFIG_LOCK_RETRY_DELAY_MS);
7874
- }
7875
- }
7876
- throw new ConfigError(`Timed out waiting for config lock at ${lockPath}. Another AKM process may be updating config.`, "INVALID_CONFIG_FILE");
7877
- }
7878
- function withConfigLock(fn) {
7879
- const release = acquireConfigLock();
7880
- try {
7881
- return fn();
7882
- } finally {
7883
- release();
7884
- }
7885
- }
7886
- function stripJsonComments(text) {
7887
- let result = "";
7888
- let i = 0;
7889
- let inString = false;
7890
- while (i < text.length) {
7891
- if (inString) {
7892
- if (text[i] === "\\") {
7893
- result += text[i] + (text[i + 1] ?? "");
7894
- i += 2;
7895
- continue;
7896
- }
7897
- if (text[i] === '"') {
7898
- inString = false;
7899
- }
7900
- result += text[i];
7901
- i++;
7902
- continue;
7903
- }
7904
- if (text[i] === '"') {
7905
- inString = true;
7906
- result += text[i];
7907
- i++;
7908
- continue;
7909
- }
7910
- if (text[i] === "/" && text[i + 1] === "/") {
7911
- while (i < text.length && text[i] !== `
7912
- `)
7913
- i++;
7914
- continue;
7915
- }
7916
- if (text[i] === "/" && text[i + 1] === "*") {
7917
- i += 2;
7918
- while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
7919
- i++;
7920
- i += 2;
7921
- continue;
7788
+ function assertNoPendingMigrationOperation() {
7789
+ for (const [kind, journalPath] of [
7790
+ ["restore", getMigrationRestoreJournalPath()],
7791
+ ["migration apply", getMigrationApplyJournalPath()]
7792
+ ]) {
7793
+ if (fs4.existsSync(journalPath)) {
7794
+ throw new ConfigError(`AKM ${kind} recovery is pending at ${journalPath}; refusing canonical config/database access until recovery completes.`, "INVALID_CONFIG_FILE");
7922
7795
  }
7923
- result += text[i];
7924
- i++;
7925
7796
  }
7926
- return result;
7797
+ const hook = afterPendingCheckHook;
7798
+ afterPendingCheckHook = undefined;
7799
+ hook?.();
7927
7800
  }
7928
- var CONFIG_LOCK_MAX_RETRIES = 10, CONFIG_LOCK_RETRY_DELAY_MS = 50;
7801
+ var afterPendingCheckHook;
7802
+ var init_migration_operation = __esm(() => {
7803
+ init_errors();
7804
+ init_paths();
7805
+ });
7806
+
7807
+ // src/core/config/config-io.ts
7929
7808
  var init_config_io = __esm(() => {
7930
7809
  init_runtime();
7931
7810
  init_common();
@@ -14282,6 +14161,15 @@ var init_config_schema = __esm(() => {
14282
14161
  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."
14283
14162
  });
14284
14163
  }
14164
+ for (const [name, process2] of Object.entries(val)) {
14165
+ if (!(name in IMPROVE_PROCESS_ENGINE_CAPABILITIES) && process2 !== null && typeof process2 === "object" && process2.enabled === true) {
14166
+ ctx.addIssue({
14167
+ code: exports_external.ZodIssueCode.custom,
14168
+ path: [name],
14169
+ message: `Unknown enabled improve process "${name}"`
14170
+ });
14171
+ }
14172
+ }
14285
14173
  });
14286
14174
  ImproveProfileConfigSchema = exports_external.object({
14287
14175
  engine: engineName.optional(),
@@ -14713,263 +14601,6 @@ var init_config_schema = __esm(() => {
14713
14601
  });
14714
14602
  });
14715
14603
 
14716
- // src/core/migration-backup.ts
14717
- import crypto from "node:crypto";
14718
- import fs7 from "node:fs";
14719
- import path6 from "node:path";
14720
- function getMigrationBackupDir() {
14721
- return path6.join(getCacheDir(), "migration-backups", MIGRATION_BACKUP_VERSION);
14722
- }
14723
- function migrationBackupLockPath() {
14724
- return path6.join(getCacheDir(), "migration-backups", `${MIGRATION_BACKUP_VERSION}.lock`);
14725
- }
14726
- function expectedSourcePaths() {
14727
- return {
14728
- "config.json": getConfigPath(),
14729
- "state.db": getStateDbPathInDataDir(),
14730
- "workflow.db": getWorkflowDbPath()
14731
- };
14732
- }
14733
- function sha256File(filePath) {
14734
- return crypto.createHash("sha256").update(fs7.readFileSync(filePath)).digest("hex");
14735
- }
14736
- function ownerOnlyMode(filePath, directory) {
14737
- if (process.platform === "win32")
14738
- return true;
14739
- const mode = fs7.statSync(filePath).mode & 511;
14740
- return mode === (directory ? 448 : 384);
14741
- }
14742
- function fsyncDirectory(directory) {
14743
- try {
14744
- const fd = fs7.openSync(directory, "r");
14745
- try {
14746
- fs7.fsyncSync(fd);
14747
- } finally {
14748
- fs7.closeSync(fd);
14749
- }
14750
- } catch (error) {
14751
- const code = error.code;
14752
- if (code !== "EINVAL" && code !== "ENOTSUP" && code !== "EISDIR" && code !== "EPERM")
14753
- throw error;
14754
- }
14755
- }
14756
- function parseManifest(bundlePath) {
14757
- const manifestPath = path6.join(bundlePath, "manifest.json");
14758
- let value;
14759
- try {
14760
- value = JSON.parse(fs7.readFileSync(manifestPath, "utf8"));
14761
- } catch (error) {
14762
- 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");
14763
- }
14764
- if (!value || typeof value !== "object" || Array.isArray(value)) {
14765
- throw new ConfigError(`Migration backup manifest at ${manifestPath} is invalid.`, "INVALID_CONFIG_FILE");
14766
- }
14767
- const manifest = value;
14768
- if (manifest.version !== MIGRATION_BACKUP_VERSION || typeof manifest.createdAt !== "string") {
14769
- throw new ConfigError(`Migration backup manifest at ${manifestPath} has an unsupported version.`, "INVALID_CONFIG_FILE");
14770
- }
14771
- const expected = expectedSourcePaths();
14772
- for (const name of ARTIFACT_NAMES) {
14773
- const artifact = manifest.artifacts?.[name];
14774
- 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)) {
14775
- throw new ConfigError(`Migration backup manifest has an invalid ${name} entry.`, "INVALID_CONFIG_FILE");
14776
- }
14777
- }
14778
- return manifest;
14779
- }
14780
- function verifyMigrationBackup(bundlePath = getMigrationBackupDir()) {
14781
- if (!fs7.existsSync(bundlePath) || !fs7.statSync(bundlePath).isDirectory()) {
14782
- throw new ConfigError(`Migration backup does not exist at ${bundlePath}.`, "INVALID_CONFIG_FILE");
14783
- }
14784
- if (!ownerOnlyMode(bundlePath, true)) {
14785
- throw new ConfigError(`Migration backup directory ${bundlePath} must have mode 0700.`, "INVALID_CONFIG_FILE");
14786
- }
14787
- const manifest = parseManifest(bundlePath);
14788
- const expectedFiles = new Set(["manifest.json"]);
14789
- for (const name of ARTIFACT_NAMES) {
14790
- const artifact = manifest.artifacts[name];
14791
- const artifactPath = path6.join(bundlePath, name);
14792
- if (!artifact.present) {
14793
- if (fs7.existsSync(artifactPath)) {
14794
- throw new ConfigError(`Migration backup contains ${name}, but its manifest records it absent.`, "INVALID_CONFIG_FILE");
14795
- }
14796
- continue;
14797
- }
14798
- expectedFiles.add(name);
14799
- if (!fs7.existsSync(artifactPath) || !fs7.statSync(artifactPath).isFile()) {
14800
- throw new ConfigError(`Migration backup is missing ${artifactPath}.`, "INVALID_CONFIG_FILE");
14801
- }
14802
- if (!ownerOnlyMode(artifactPath, false)) {
14803
- throw new ConfigError(`Migration backup artifact ${artifactPath} must have mode 0600.`, "INVALID_CONFIG_FILE");
14804
- }
14805
- const stat = fs7.statSync(artifactPath);
14806
- if (stat.size !== artifact.byteSize || sha256File(artifactPath) !== artifact.sha256) {
14807
- throw new ConfigError(`Migration backup artifact ${artifactPath} failed checksum verification.`, "INVALID_CONFIG_FILE");
14808
- }
14809
- }
14810
- if (!ownerOnlyMode(path6.join(bundlePath, "manifest.json"), false)) {
14811
- throw new ConfigError(`Migration backup manifest must have mode 0600.`, "INVALID_CONFIG_FILE");
14812
- }
14813
- const extras = fs7.readdirSync(bundlePath).filter((name) => !expectedFiles.has(name));
14814
- if (extras.length > 0) {
14815
- throw new ConfigError(`Migration backup contains unexpected files: ${extras.join(", ")}.`, "INVALID_CONFIG_FILE");
14816
- }
14817
- return manifest;
14818
- }
14819
- function acquireMigrationBackupLock() {
14820
- const lockPath = migrationBackupLockPath();
14821
- fs7.mkdirSync(path6.dirname(lockPath), { recursive: true, mode: 448 });
14822
- fs7.chmodSync(path6.dirname(lockPath), 448);
14823
- for (let attempt = 0;attempt < 50; attempt += 1) {
14824
- const ownership = tryAcquireLockSync(lockPath, createLockPayload());
14825
- if (ownership)
14826
- return () => releaseLock(ownership);
14827
- const probe = probeLock(lockPath);
14828
- if (probe.state === "stale" && reclaimStaleLock(lockPath, probe)) {
14829
- continue;
14830
- }
14831
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
14832
- }
14833
- throw new ConfigError(`Timed out waiting for migration backup lock at ${lockPath}.`, "INVALID_CONFIG_FILE");
14834
- }
14835
- function assertNotAlreadyCutOver() {
14836
- const configPath = getConfigPath();
14837
- let text;
14838
- try {
14839
- text = fs7.readFileSync(configPath, "utf8");
14840
- } catch (error) {
14841
- if (error.code === "ENOENT")
14842
- return;
14843
- throw error;
14844
- }
14845
- const raw = parseConfigText(text, configPath);
14846
- if (raw.configVersion === CURRENT_CONFIG_VERSION) {
14847
- 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");
14848
- }
14849
- }
14850
- function copyRegularArtifact(source, destination) {
14851
- const stat = fs7.statSync(source);
14852
- if (!stat.isFile())
14853
- throw new ConfigError(`Backup source is not a regular file: ${source}`, "INVALID_CONFIG_FILE");
14854
- writeFileAtomic(destination, fs7.readFileSync(source), 384);
14855
- fs7.chmodSync(destination, 384);
14856
- }
14857
- function sqliteQuote(value) {
14858
- return `'${value.replaceAll("'", "''")}'`;
14859
- }
14860
- function backupSqlite(source, destination) {
14861
- const stat = fs7.statSync(source);
14862
- if (!stat.isFile())
14863
- throw new ConfigError(`SQLite backup source is not a regular file: ${source}`, "INVALID_CONFIG_FILE");
14864
- const resolvedSource = path6.resolve(source);
14865
- const activityName = resolvedSource === path6.resolve(getWorkflowDbPath()) ? "workflow-db" : resolvedSource === path6.resolve(getStateDbPathInDataDir()) ? "state-db" : undefined;
14866
- const releaseActivity = activityName ? acquireMaintenanceActivitySync(activityName) : undefined;
14867
- let db;
14868
- try {
14869
- db = openDatabase(source);
14870
- db.exec("PRAGMA busy_timeout = 10000");
14871
- db.exec("PRAGMA wal_checkpoint(FULL)");
14872
- db.exec(`VACUUM INTO ${sqliteQuote(destination)}`);
14873
- } finally {
14874
- try {
14875
- db?.close();
14876
- } finally {
14877
- releaseActivity?.();
14878
- }
14879
- }
14880
- fs7.chmodSync(destination, 384);
14881
- const fd = fs7.openSync(destination, "r");
14882
- try {
14883
- fs7.fsyncSync(fd);
14884
- } finally {
14885
- fs7.closeSync(fd);
14886
- }
14887
- }
14888
- function createMigrationBackupUnlocked() {
14889
- const bundlePath = getMigrationBackupDir();
14890
- if (fs7.existsSync(bundlePath)) {
14891
- return { path: bundlePath, created: false, manifest: verifyMigrationBackup(bundlePath) };
14892
- }
14893
- assertNotAlreadyCutOver();
14894
- const parent = path6.dirname(bundlePath);
14895
- fs7.mkdirSync(parent, { recursive: true, mode: 448 });
14896
- fs7.chmodSync(parent, 448);
14897
- const temporary = path6.join(parent, `.0.9.0.tmp.${process.pid}.${crypto.randomBytes(8).toString("hex")}`);
14898
- fs7.mkdirSync(temporary, { mode: 448 });
14899
- const createdAt = new Date().toISOString();
14900
- const sources = expectedSourcePaths();
14901
- const artifacts = {};
14902
- try {
14903
- for (const name of ARTIFACT_NAMES) {
14904
- const sourcePath = sources[name];
14905
- const destination = path6.join(temporary, name);
14906
- let present = true;
14907
- try {
14908
- fs7.statSync(sourcePath);
14909
- } catch (error) {
14910
- if (error.code === "ENOENT")
14911
- present = false;
14912
- else
14913
- throw error;
14914
- }
14915
- if (present) {
14916
- if (name === "config.json")
14917
- copyRegularArtifact(sourcePath, destination);
14918
- else
14919
- backupSqlite(sourcePath, destination);
14920
- }
14921
- const byteSize = present ? fs7.statSync(destination).size : 0;
14922
- artifacts[name] = {
14923
- sourcePath,
14924
- present,
14925
- byteSize,
14926
- sha256: present ? sha256File(destination) : null,
14927
- createdAt
14928
- };
14929
- }
14930
- const manifest = { version: MIGRATION_BACKUP_VERSION, createdAt, artifacts };
14931
- writeFileAtomic(path6.join(temporary, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
14932
- `, 384);
14933
- fs7.chmodSync(path6.join(temporary, "manifest.json"), 384);
14934
- fsyncDirectory(temporary);
14935
- fs7.renameSync(temporary, bundlePath);
14936
- fsyncDirectory(parent);
14937
- return { path: bundlePath, created: true, manifest: verifyMigrationBackup(bundlePath) };
14938
- } catch (error) {
14939
- fs7.rmSync(temporary, { recursive: true, force: true });
14940
- if (fs7.existsSync(bundlePath)) {
14941
- throw new ConfigError(`Migration backup creation raced with another writer and left ${bundlePath}. Verify or preserve it before retrying.`, "INVALID_CONFIG_FILE");
14942
- }
14943
- throw error;
14944
- }
14945
- }
14946
- function withMigrationBackupLock(fn) {
14947
- const release = acquireMigrationBackupLock();
14948
- try {
14949
- return fn();
14950
- } finally {
14951
- release();
14952
- }
14953
- }
14954
- function createMigrationBackup() {
14955
- return withConfigLock(() => withMigrationBackupLock(createMigrationBackupUnlocked));
14956
- }
14957
- function ensureMigrationBackup() {
14958
- return createMigrationBackup();
14959
- }
14960
- var MIGRATION_BACKUP_VERSION = "0.9.0", ARTIFACT_NAMES;
14961
- var init_migration_backup = __esm(() => {
14962
- init_database();
14963
- init_common();
14964
- init_config_io();
14965
- init_config_schema();
14966
- init_errors();
14967
- init_file_lock();
14968
- init_maintenance_barrier();
14969
- init_paths();
14970
- ARTIFACT_NAMES = ["config.json", "state.db", "workflow.db"];
14971
- });
14972
-
14973
14604
  // src/core/config/deep-merge.ts
14974
14605
  var UNSAFE_KEYS;
14975
14606
  var init_deep_merge = __esm(() => {
@@ -15150,11 +14781,11 @@ var init_engine_resolution = __esm(() => {
15150
14781
  var init_config_sources = () => {};
15151
14782
 
15152
14783
  // src/core/config/config.ts
15153
- import path7 from "node:path";
14784
+ import path6 from "node:path";
15154
14785
  var PROJECT_CONFIG_RELATIVE_PATH, INDEX_RESERVED_KEYS, PROJECT_CONFIG_DEPRECATION_WARNED;
15155
14786
  var init_config = __esm(() => {
15156
14787
  init_errors();
15157
- init_migration_backup();
14788
+ init_migration_operation();
15158
14789
  init_config_io();
15159
14790
  init_config_schema();
15160
14791
  init_deep_merge();
@@ -15165,7 +14796,7 @@ var init_config = __esm(() => {
15165
14796
  init_config_types();
15166
14797
  init_config_schema();
15167
14798
  init_config_sources();
15168
- PROJECT_CONFIG_RELATIVE_PATH = path7.join(".akm", "config.json");
14799
+ PROJECT_CONFIG_RELATIVE_PATH = path6.join(".akm", "config.json");
15169
14800
  INDEX_RESERVED_KEYS = new Set(["metadataEnhance", "stalenessDetection", "indexBodyOpening"]);
15170
14801
  PROJECT_CONFIG_DEPRECATION_WARNED = new Set;
15171
14802
  });
@@ -15188,11 +14819,11 @@ var init_metadata_contributors = __esm(() => {
15188
14819
  });
15189
14820
 
15190
14821
  // src/indexer/passes/metadata.ts
15191
- import fs8 from "node:fs";
14822
+ import fs7 from "node:fs";
15192
14823
  function extractDescriptionFromComments(filePath) {
15193
14824
  let content;
15194
14825
  try {
15195
- content = fs8.readFileSync(filePath, "utf8");
14826
+ content = fs7.readFileSync(filePath, "utf8");
15196
14827
  } catch {
15197
14828
  return null;
15198
14829
  }
@@ -15242,9 +14873,76 @@ var init_metadata = __esm(() => {
15242
14873
  });
15243
14874
 
15244
14875
  // src/core/asset/asset-ref.ts
14876
+ import path7 from "node:path";
14877
+ function makeAssetRef(type, name, origin) {
14878
+ validateName(name);
14879
+ const normalized = normalizeName(name);
14880
+ const asset = `${type}:${normalized}`;
14881
+ if (!origin)
14882
+ return asset;
14883
+ return `${origin}//${asset}`;
14884
+ }
14885
+ function refToString(ref) {
14886
+ return makeAssetRef(ref.type, ref.name, ref.origin);
14887
+ }
14888
+ function parseAssetRef(ref) {
14889
+ const trimmed = ref.trim();
14890
+ if (!trimmed)
14891
+ throw new UsageError("Empty ref.", "MISSING_REQUIRED_ARGUMENT");
14892
+ let origin;
14893
+ let body = trimmed;
14894
+ const boundary = trimmed.indexOf("//");
14895
+ if (boundary >= 0) {
14896
+ origin = trimmed.slice(0, boundary);
14897
+ body = trimmed.slice(boundary + 2);
14898
+ if (!origin)
14899
+ throw new UsageError("Empty origin in ref.", "MISSING_REQUIRED_ARGUMENT");
14900
+ }
14901
+ const colon = body.indexOf(":");
14902
+ if (colon <= 0) {
14903
+ throw new UsageError(`Invalid ref "${trimmed}". Expected [origin//]type:name, e.g. skill:deploy or knowledge:guide.md`, "MISSING_REQUIRED_ARGUMENT");
14904
+ }
14905
+ const rawType = body.slice(0, colon);
14906
+ const rawName = body.slice(colon + 1);
14907
+ if (rawType === "vault") {
14908
+ throw new UsageError("The `vault` asset type was removed in 0.9.0 — use `env:` (whole .env config) or `secret:` (a single value).", "MISSING_REQUIRED_ARGUMENT");
14909
+ }
14910
+ const resolvedType = TYPE_ALIASES[rawType] ?? rawType;
14911
+ if (!isAssetType(resolvedType)) {
14912
+ throw new UsageError(`Invalid asset type: "${rawType}".`, "MISSING_REQUIRED_ARGUMENT");
14913
+ }
14914
+ validateName(rawName);
14915
+ const name = normalizeName(rawName);
14916
+ return { type: resolvedType, name, origin: origin || undefined };
14917
+ }
14918
+ function validateName(name) {
14919
+ if (!name)
14920
+ throw new UsageError("Empty asset name.", "MISSING_REQUIRED_ARGUMENT");
14921
+ if (name.includes("\x00"))
14922
+ throw new UsageError("Null byte in asset name.", "MISSING_REQUIRED_ARGUMENT");
14923
+ if (/^[A-Za-z]:/.test(name))
14924
+ throw new UsageError("Windows drive path in asset name.", "MISSING_REQUIRED_ARGUMENT");
14925
+ const normalized = path7.posix.normalize(name.replace(/\\/g, "/"));
14926
+ if (path7.posix.isAbsolute(normalized))
14927
+ throw new UsageError("Absolute path in asset name.", "MISSING_REQUIRED_ARGUMENT");
14928
+ if (normalized === ".." || normalized.startsWith("../")) {
14929
+ throw new UsageError("Path traversal in asset name.", "MISSING_REQUIRED_ARGUMENT");
14930
+ }
14931
+ const segments = normalized.split("/");
14932
+ if (segments.some((seg) => seg === "." || seg === "..")) {
14933
+ throw new UsageError("Asset name cannot contain relative path segments.", "MISSING_REQUIRED_ARGUMENT");
14934
+ }
14935
+ }
14936
+ function normalizeName(name) {
14937
+ return path7.posix.normalize(name.replace(/\\/g, "/"));
14938
+ }
14939
+ var TYPE_ALIASES;
15245
14940
  var init_asset_ref = __esm(() => {
15246
14941
  init_common();
15247
14942
  init_errors();
14943
+ TYPE_ALIASES = {
14944
+ environment: "env"
14945
+ };
15248
14946
  });
15249
14947
 
15250
14948
  // src/workflows/resource-limits.ts
@@ -15269,10 +14967,30 @@ var WORKFLOW_SCHEMA_VERSION = 1;
15269
14967
  // src/workflows/validator.ts
15270
14968
  function runSemanticChecks(draft, frontmatterData, frontmatterEndLine, errors3) {
15271
14969
  checkFrontmatterKeys(frontmatterData, frontmatterEndLine, errors3);
14970
+ checkXrefs(frontmatterData.xrefs, frontmatterEndLine, errors3);
15272
14971
  checkStepIdFormat(draft, errors3);
15273
14972
  checkDuplicateStepIds(draft, errors3);
15274
14973
  checkResourceLimits(draft, errors3);
15275
14974
  }
14975
+ function checkXrefs(value, line, errors3) {
14976
+ if (value === undefined)
14977
+ return;
14978
+ if (!Array.isArray(value)) {
14979
+ errors3.push({ line, message: 'Workflow frontmatter "xrefs" must be an array of canonical asset refs.' });
14980
+ return;
14981
+ }
14982
+ for (const ref of value) {
14983
+ try {
14984
+ if (typeof ref !== "string" || refToString(parseAssetRef(ref)) !== ref)
14985
+ throw new Error("non-canonical ref");
14986
+ } catch {
14987
+ errors3.push({
14988
+ line,
14989
+ message: `Workflow frontmatter "xrefs" contains an invalid or non-canonical ref: ${String(ref)}.`
14990
+ });
14991
+ }
14992
+ }
14993
+ }
15276
14994
  function checkResourceLimits(draft, errors3) {
15277
14995
  if (draft.steps.length > WORKFLOW_MAX_STEPS) {
15278
14996
  errors3.push({ line: 1, message: `Workflow must contain at most ${WORKFLOW_MAX_STEPS} steps.` });
@@ -15295,7 +15013,7 @@ function checkFrontmatterKeys(data, fmEndLine, errors3) {
15295
15013
  continue;
15296
15014
  errors3.push({
15297
15015
  line: fmEndLine,
15298
- message: `Workflow frontmatter "${key}" is not supported. Use only: description, tags, params, name, updated, when_to_use.`
15016
+ message: `Workflow frontmatter "${key}" is not supported. Use only: description, tags, params, name, updated, when_to_use, xrefs.`
15299
15017
  });
15300
15018
  }
15301
15019
  }
@@ -15325,9 +15043,10 @@ function checkDuplicateStepIds(draft, errors3) {
15325
15043
  }
15326
15044
  var STEP_ID_REGEX, ALLOWED_FRONTMATTER_KEYS;
15327
15045
  var init_validator = __esm(() => {
15046
+ init_asset_ref();
15328
15047
  init_resource_limits();
15329
15048
  STEP_ID_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
15330
- ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params", "name", "updated", "when_to_use"]);
15049
+ ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params", "name", "updated", "when_to_use", "xrefs"]);
15331
15050
  });
15332
15051
 
15333
15052
  // src/workflows/parser.ts
@@ -16778,7 +16497,7 @@ var init_asset_registry = __esm(() => {
16778
16497
  });
16779
16498
 
16780
16499
  // src/core/asset/asset-spec.ts
16781
- import fs9 from "node:fs";
16500
+ import fs8 from "node:fs";
16782
16501
  import path8 from "node:path";
16783
16502
  function toPosix2(input) {
16784
16503
  return input.replace(/\\/g, "/");
@@ -16809,7 +16528,7 @@ var init_asset_spec = __esm(() => {
16809
16528
  }
16810
16529
  for (const ext of WORKFLOW_EXTENSIONS) {
16811
16530
  const candidate = path8.join(typeRoot, `${name}${ext}`);
16812
- if (fs9.existsSync(candidate))
16531
+ if (fs8.existsSync(candidate))
16813
16532
  return candidate;
16814
16533
  }
16815
16534
  return path8.join(typeRoot, `${name}.md`);
@@ -16944,29 +16663,29 @@ var init_asset_spec = __esm(() => {
16944
16663
  });
16945
16664
 
16946
16665
  // src/core/common.ts
16947
- import crypto2 from "node:crypto";
16948
- import fs10 from "node:fs";
16949
- import path9 from "node:path";
16950
- function writeFileAtomic(target, content, mode) {
16951
- const tmp = `${target}.tmp.${process.pid}.${crypto2.randomBytes(8).toString("hex")}`;
16952
- const fd = fs10.openSync(tmp, "w", mode ?? 384);
16953
- try {
16954
- fs10.writeSync(fd, typeof content === "string" ? Buffer.from(content) : content);
16955
- try {
16956
- fs10.fdatasyncSync(fd);
16957
- } catch {}
16958
- } finally {
16959
- fs10.closeSync(fd);
16666
+ import fs9 from "node:fs";
16667
+ function isAssetType(type) {
16668
+ return Object.hasOwn(TYPE_DIRS, type);
16669
+ }
16670
+ function readTextFileDescriptorWithLimit(fd, maxBytes, label = "File", displayPath = "(open file)") {
16671
+ const stat = fs9.fstatSync(fd);
16672
+ if (!stat.isFile())
16673
+ throw new ConfigError(`${label} is not a regular file: ${displayPath}.`, "INVALID_CONFIG_FILE");
16674
+ if (stat.size > maxBytes) {
16675
+ throw new ConfigError(`${label} exceeds the ${maxBytes}-byte limit: ${displayPath}.`, "INVALID_CONFIG_FILE");
16676
+ }
16677
+ const buffer = Buffer.allocUnsafe(maxBytes + 1);
16678
+ let total = 0;
16679
+ while (total <= maxBytes) {
16680
+ const bytesRead = fs9.readSync(fd, buffer, total, maxBytes + 1 - total, null);
16681
+ if (bytesRead === 0)
16682
+ break;
16683
+ total += bytesRead;
16960
16684
  }
16961
- fs10.renameSync(tmp, target);
16962
- try {
16963
- const dirFd = fs10.openSync(path9.dirname(target), "r");
16964
- try {
16965
- fs10.fsyncSync(dirFd);
16966
- } finally {
16967
- fs10.closeSync(dirFd);
16968
- }
16969
- } catch {}
16685
+ if (total > maxBytes) {
16686
+ throw new ConfigError(`${label} exceeds the ${maxBytes}-byte limit: ${displayPath}.`, "INVALID_CONFIG_FILE");
16687
+ }
16688
+ return buffer.subarray(0, total).toString("utf8");
16970
16689
  }
16971
16690
  function asNonEmptyString3(value) {
16972
16691
  if (typeof value !== "string")
@@ -16982,7 +16701,7 @@ function isProcessAlive(pid) {
16982
16701
  return false;
16983
16702
  }
16984
16703
  }
16985
- var ASSET_TYPES, ASSET_TYPE_SET, IS_WINDOWS, DEFAULT_RESPONSE_BYTE_CAP;
16704
+ var ASSET_TYPES, ASSET_TYPE_SET, IS_WINDOWS, MAX_CONFIG_FILE_BYTES, MAX_LOCAL_METADATA_BYTES, MAX_LOCK_METADATA_BYTES, DEFAULT_RESPONSE_BYTE_CAP;
16986
16705
  var init_common = __esm(() => {
16987
16706
  init_asset_spec();
16988
16707
  init_errors();
@@ -16990,38 +16709,41 @@ var init_common = __esm(() => {
16990
16709
  ASSET_TYPES = Object.freeze([...getAssetTypes()]);
16991
16710
  ASSET_TYPE_SET = new Set(ASSET_TYPES);
16992
16711
  IS_WINDOWS = process.platform === "win32";
16712
+ MAX_CONFIG_FILE_BYTES = 1024 * 1024;
16713
+ MAX_LOCAL_METADATA_BYTES = 1024 * 1024;
16714
+ MAX_LOCK_METADATA_BYTES = 64 * 1024;
16993
16715
  DEFAULT_RESPONSE_BYTE_CAP = 10 * 1024 * 1024;
16994
16716
  });
16995
16717
 
16996
16718
  // src/core/file-lock.ts
16997
16719
  import { randomUUID } from "node:crypto";
16998
- import fs11 from "node:fs";
16999
- import path10 from "node:path";
16720
+ import fs10 from "node:fs";
16721
+ import path9 from "node:path";
17000
16722
  function readLockSnapshot(lockPath) {
17001
16723
  let fd;
17002
16724
  try {
17003
- fd = fs11.openSync(lockPath, "r");
16725
+ fd = fs10.openSync(lockPath, "r");
17004
16726
  } catch (err) {
17005
16727
  if (err.code === "ENOENT")
17006
16728
  return;
17007
16729
  throw err;
17008
16730
  }
17009
16731
  try {
17010
- const rawContent = fs11.readFileSync(fd, "utf8");
17011
- const stat = fs11.fstatSync(fd);
16732
+ const rawContent = readTextFileDescriptorWithLimit(fd, MAX_LOCK_METADATA_BYTES, "Lock metadata", lockPath);
16733
+ const stat = fs10.fstatSync(fd);
17012
16734
  return {
17013
16735
  rawContent,
17014
16736
  identity: { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeMs: stat.mtimeMs }
17015
16737
  };
17016
16738
  } finally {
17017
- fs11.closeSync(fd);
16739
+ fs10.closeSync(fd);
17018
16740
  }
17019
16741
  }
17020
16742
  function sameIdentity(left, right) {
17021
16743
  return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs;
17022
16744
  }
17023
16745
  function operationMutexPath(lockPath) {
17024
- return path10.join(path10.dirname(lockPath), `.${path10.basename(lockPath)}.operations.sensitive`);
16746
+ return path9.join(path9.dirname(lockPath), `.${path9.basename(lockPath)}.operations.sensitive`);
17025
16747
  }
17026
16748
  function withLockOperationMutex(lockPath, run) {
17027
16749
  const db = openDatabase(operationMutexPath(lockPath));
@@ -17053,7 +16775,7 @@ function withLockOperationMutex(lockPath, run) {
17053
16775
  }
17054
16776
  function tryAcquireLockRaw(lockPath, payload) {
17055
16777
  try {
17056
- fs11.writeFileSync(lockPath, payload, { flag: "wx" });
16778
+ fs10.writeFileSync(lockPath, payload, { flag: "wx" });
17057
16779
  } catch (err) {
17058
16780
  if (err.code === "EEXIST")
17059
16781
  return;
@@ -17074,7 +16796,7 @@ function tryAcquireLockRaw(lockPath, payload) {
17074
16796
  }
17075
16797
  function releaseLockRaw(lockPath) {
17076
16798
  try {
17077
- fs11.unlinkSync(lockPath);
16799
+ fs10.unlinkSync(lockPath);
17078
16800
  } catch {}
17079
16801
  }
17080
16802
  function tryAcquireLockSync(lockPath, payload) {
@@ -17123,7 +16845,7 @@ function reclaimStaleLock(lockPath, probe, options) {
17123
16845
  }
17124
16846
  const quarantinePath = `${lockPath}.stale-${process.pid}-${randomUUID()}`;
17125
16847
  try {
17126
- fs11.renameSync(lockPath, quarantinePath);
16848
+ fs10.renameSync(lockPath, quarantinePath);
17127
16849
  } catch (err) {
17128
16850
  if (err.code === "ENOENT")
17129
16851
  return false;
@@ -17137,7 +16859,7 @@ function reclaimStaleLock(lockPath, probe, options) {
17137
16859
  }
17138
16860
  if (!quarantined || quarantined.rawContent !== expectedContent || !sameIdentity(quarantined.identity, expectedIdentity)) {
17139
16861
  try {
17140
- fs11.linkSync(quarantinePath, lockPath);
16862
+ fs10.linkSync(quarantinePath, lockPath);
17141
16863
  } catch (err) {
17142
16864
  if (err.code !== "EEXIST")
17143
16865
  throw err;
@@ -17147,7 +16869,7 @@ function reclaimStaleLock(lockPath, probe, options) {
17147
16869
  }
17148
16870
  options?.afterQuarantineVerified?.();
17149
16871
  try {
17150
- fs11.unlinkSync(quarantinePath);
16872
+ fs10.unlinkSync(quarantinePath);
17151
16873
  return true;
17152
16874
  } catch (err) {
17153
16875
  if (err.code === "ENOENT")
@@ -17158,7 +16880,7 @@ function reclaimStaleLock(lockPath, probe, options) {
17158
16880
  }
17159
16881
  function releaseLock(ownership) {
17160
16882
  const { lockPath } = ownership;
17161
- if (!fs11.existsSync(lockPath) && !fs11.existsSync(operationMutexPath(lockPath)))
16883
+ if (!fs10.existsSync(lockPath) && !fs10.existsSync(operationMutexPath(lockPath)))
17162
16884
  return;
17163
16885
  withLockOperationMutex(lockPath, () => {
17164
16886
  let current;
@@ -17193,73 +16915,9 @@ var init_file_lock = __esm(() => {
17193
16915
  init_common();
17194
16916
  });
17195
16917
 
17196
- // src/core/maintenance-barrier.ts
17197
- import { AsyncLocalStorage } from "node:async_hooks";
17198
- import { randomUUID as randomUUID2 } from "node:crypto";
17199
- import fs12 from "node:fs";
17200
- import path11 from "node:path";
17201
- function tryAcquireMaintenanceBarrier() {
17202
- const lockPath = getMaintenanceBarrierPath();
17203
- fs12.mkdirSync(path11.dirname(lockPath), { recursive: true });
17204
- for (let attempt = 0;attempt < 2; attempt += 1) {
17205
- const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: "maintenance-start" }));
17206
- if (ownership) {
17207
- return () => releaseLock(ownership);
17208
- }
17209
- const probe = probeLock(lockPath);
17210
- if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
17211
- return;
17212
- }
17213
- return;
17214
- }
17215
- function acquireMaintenanceBarrier() {
17216
- const release = tryAcquireMaintenanceBarrier();
17217
- if (release)
17218
- return release;
17219
- throw new ConfigError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry after it completes.`, "INVALID_CONFIG_FILE");
17220
- }
17221
- function withMaintenanceStartBarrierSyncWait(run) {
17222
- if (heldBarrierContext.getStore()?.active)
17223
- return run();
17224
- const deadline = Date.now() + 5000;
17225
- let release = tryAcquireMaintenanceBarrier();
17226
- while (!release && Date.now() < deadline) {
17227
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
17228
- release = tryAcquireMaintenanceBarrier();
17229
- }
17230
- if (!release)
17231
- release = acquireMaintenanceBarrier();
17232
- const ownership = { active: true };
17233
- try {
17234
- return heldBarrierContext.run(ownership, run);
17235
- } finally {
17236
- ownership.active = false;
17237
- release();
17238
- }
17239
- }
17240
- function acquireMaintenanceActivitySync(name) {
17241
- return withMaintenanceStartBarrierSyncWait(() => {
17242
- const directory = path11.join(path11.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
17243
- fs12.mkdirSync(directory, { recursive: true, mode: 448 });
17244
- const lockPath = path11.join(directory, `${name}-${process.pid}-${randomUUID2()}.lock`);
17245
- const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: name }));
17246
- if (!ownership) {
17247
- throw new ConfigError(`Could not register AKM maintenance activity at ${lockPath}.`, "INVALID_CONFIG_FILE");
17248
- }
17249
- return () => releaseLock(ownership);
17250
- });
17251
- }
17252
- var heldBarrierContext;
17253
- var init_maintenance_barrier = __esm(() => {
17254
- init_errors();
17255
- init_file_lock();
17256
- init_paths();
17257
- heldBarrierContext = new AsyncLocalStorage;
17258
- });
17259
-
17260
16918
  // scripts/migrations/import-fs-improve-runs-to-db.ts
17261
- import fs14 from "fs";
17262
- import path13 from "path";
16919
+ import fs13 from "fs";
16920
+ import path12 from "path";
17263
16921
 
17264
16922
  // src/core/improve-result.ts
17265
16923
  var COMMON_FIELDS = [
@@ -17305,8 +16963,26 @@ var COMMON_FIELDS = [
17305
16963
  "sync",
17306
16964
  "terminated"
17307
16965
  ];
17308
- var V1_FIELDS = new Set([...COMMON_FIELDS, "profile", "profileFilteredRefs"]);
16966
+ var V1_FIELDS = new Set([...COMMON_FIELDS, "profile", "profileFilteredRefs", "stalenessDetection"]);
17309
16967
  var V2_FIELDS = new Set([...COMMON_FIELDS, "strategy", "strategyFilteredRefs"]);
16968
+ var STALENESS_DETECTION_FIELDS = new Set([
16969
+ "considered",
16970
+ "deprecated",
16971
+ "confirmed",
16972
+ "skipped",
16973
+ "durationMs",
16974
+ "warnings"
16975
+ ]);
16976
+ var INTERRUPTED_V1_FIELDS = new Set([
16977
+ "schemaVersion",
16978
+ "ok",
16979
+ "profile",
16980
+ "scope",
16981
+ "dryRun",
16982
+ "plannedRefs",
16983
+ "actions",
16984
+ "terminated"
16985
+ ]);
17310
16986
  function fail(message) {
17311
16987
  throw new Error(`invalid improve-result envelope: ${message}`);
17312
16988
  }
@@ -17318,6 +16994,33 @@ function requireExactFields(value, allowed) {
17318
16994
  if (unknown.length > 0)
17319
16995
  fail(`unknown field${unknown.length === 1 ? "" : "s"}: ${unknown.sort().join(", ")}`);
17320
16996
  }
16997
+ function isKnownInterruptedV1Partial(value) {
16998
+ if (Object.keys(value).some((key) => !INTERRUPTED_V1_FIELDS.has(key)))
16999
+ return false;
17000
+ if (value.ok !== false || typeof value.dryRun !== "boolean")
17001
+ return false;
17002
+ if (value.profile !== undefined && typeof value.profile !== "string")
17003
+ return false;
17004
+ if (!Array.isArray(value.plannedRefs) || value.plannedRefs.length !== 0)
17005
+ return false;
17006
+ if (!Array.isArray(value.actions) || value.actions.length !== 0)
17007
+ return false;
17008
+ if (!isRecord(value.scope))
17009
+ return false;
17010
+ if (Object.keys(value.scope).some((key) => key !== "mode" && key !== "value"))
17011
+ return false;
17012
+ if (value.scope.mode !== "all" && value.scope.mode !== "type" && value.scope.mode !== "ref")
17013
+ return false;
17014
+ if (value.scope.value !== undefined && typeof value.scope.value !== "string")
17015
+ return false;
17016
+ if (!isRecord(value.terminated))
17017
+ return false;
17018
+ if (Object.keys(value.terminated).some((key) => !["reason", "at", "errorMessage"].includes(key)))
17019
+ return false;
17020
+ if (typeof value.terminated.reason !== "string" || typeof value.terminated.at !== "string")
17021
+ return false;
17022
+ return value.terminated.errorMessage === undefined || typeof value.terminated.errorMessage === "string";
17023
+ }
17321
17024
  function validateCommon(value) {
17322
17025
  if (typeof value.ok !== "boolean")
17323
17026
  fail("ok must be a boolean");
@@ -17392,6 +17095,32 @@ function validateCommon(value) {
17392
17095
  if (value[field] !== undefined && !isRecord(value[field]))
17393
17096
  fail(`${field} must be an object`);
17394
17097
  }
17098
+ if (isRecord(value.terminated)) {
17099
+ requireExactFields(value.terminated, new Set(["reason", "at", "errorMessage"]));
17100
+ if (typeof value.terminated.reason !== "string" || typeof value.terminated.at !== "string") {
17101
+ fail("terminated.reason and terminated.at must be strings");
17102
+ }
17103
+ if (value.terminated.errorMessage !== undefined && typeof value.terminated.errorMessage !== "string") {
17104
+ fail("terminated.errorMessage must be a string when present");
17105
+ }
17106
+ }
17107
+ }
17108
+ function validateV1StalenessDetection(value) {
17109
+ if (value === undefined)
17110
+ return;
17111
+ if (!isRecord(value))
17112
+ fail("stalenessDetection must be an object");
17113
+ const unknown = Object.keys(value).filter((key) => !STALENESS_DETECTION_FIELDS.has(key));
17114
+ if (unknown.length > 0) {
17115
+ fail(`stalenessDetection has unknown field${unknown.length === 1 ? "" : "s"}: ${unknown.sort().join(", ")}`);
17116
+ }
17117
+ for (const field of ["considered", "deprecated", "confirmed", "skipped", "durationMs"]) {
17118
+ if (typeof value[field] !== "number")
17119
+ fail(`stalenessDetection.${field} must be a number`);
17120
+ }
17121
+ if (!Array.isArray(value.warnings) || value.warnings.some((warning) => typeof warning !== "string")) {
17122
+ fail("stalenessDetection.warnings must be an array of strings");
17123
+ }
17395
17124
  }
17396
17125
  function decodeImproveResult(input) {
17397
17126
  let parsed = input;
@@ -17405,17 +17134,25 @@ function decodeImproveResult(input) {
17405
17134
  if (!isRecord(parsed))
17406
17135
  fail("root must be an object");
17407
17136
  if (parsed.schemaVersion === 1) {
17408
- requireExactFields(parsed, V1_FIELDS);
17409
- validateCommon(parsed);
17410
- if (parsed.profile !== undefined && typeof parsed.profile !== "string")
17137
+ let normalized = parsed;
17138
+ let normalizedLegacyPartial = false;
17139
+ if (normalized.memorySummary === undefined && isKnownInterruptedV1Partial(normalized)) {
17140
+ normalized = { ...normalized, memorySummary: { eligible: 0, derived: 0 } };
17141
+ normalizedLegacyPartial = true;
17142
+ }
17143
+ requireExactFields(normalized, V1_FIELDS);
17144
+ validateCommon(normalized);
17145
+ validateV1StalenessDetection(normalized.stalenessDetection);
17146
+ if (normalized.profile !== undefined && typeof normalized.profile !== "string")
17411
17147
  fail("profile must be a string");
17412
- if (parsed.profileFilteredRefs !== undefined && !Array.isArray(parsed.profileFilteredRefs)) {
17148
+ if (normalized.profileFilteredRefs !== undefined && !Array.isArray(normalized.profileFilteredRefs)) {
17413
17149
  fail("profileFilteredRefs must be an array");
17414
17150
  }
17415
17151
  return {
17416
- envelope: parsed,
17152
+ envelope: normalized,
17417
17153
  strategy: null,
17418
- legacyProfile: typeof parsed.profile === "string" ? parsed.profile : null
17154
+ legacyProfile: typeof normalized.profile === "string" ? normalized.profile : null,
17155
+ normalizedLegacyPartial
17419
17156
  };
17420
17157
  }
17421
17158
  if (parsed.schemaVersion === 2) {
@@ -17430,15 +17167,151 @@ function decodeImproveResult(input) {
17430
17167
  return {
17431
17168
  envelope: parsed,
17432
17169
  strategy: parsed.strategy,
17433
- legacyProfile: null
17170
+ legacyProfile: null,
17171
+ normalizedLegacyPartial: false
17434
17172
  };
17435
17173
  }
17436
17174
  fail(`unsupported schemaVersion: ${String(parsed.schemaVersion)}`);
17437
17175
  }
17438
17176
 
17439
17177
  // src/core/state-db.ts
17440
- import fs13 from "node:fs";
17441
- import path12 from "node:path";
17178
+ init_database();
17179
+ import fs12 from "node:fs";
17180
+ import path11 from "node:path";
17181
+
17182
+ // src/storage/engines/sqlite-migrations.ts
17183
+ import crypto from "node:crypto";
17184
+ function migrationChecksum(migration) {
17185
+ return crypto.createHash("sha256").update(migration.id).update("\x00").update(migration.up).digest("hex");
17186
+ }
17187
+ function migrationsTableExists(db) {
17188
+ return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get();
17189
+ }
17190
+ function ledgerHasChecksum(db) {
17191
+ return db.prepare("PRAGMA table_info(schema_migrations)").all().some((column) => column.name === "checksum");
17192
+ }
17193
+ function inspectMigrationLedger(db, migrations) {
17194
+ const registryIds = migrations.map((migration) => migration.id);
17195
+ if (new Set(registryIds).size !== registryIds.length) {
17196
+ return {
17197
+ status: "inconsistent",
17198
+ migrationIds: [],
17199
+ checksums: [],
17200
+ detail: "migration registry contains duplicate IDs"
17201
+ };
17202
+ }
17203
+ if (!migrationsTableExists(db))
17204
+ return { status: registryIds.length === 0 ? "current" : "old", migrationIds: [], checksums: [] };
17205
+ const hasChecksum = ledgerHasChecksum(db);
17206
+ const rows = db.prepare(`SELECT id${hasChecksum ? ", checksum" : ""} FROM schema_migrations ORDER BY rowid`).all();
17207
+ const migrationIds = rows.map((row) => row.id);
17208
+ const checksums = rows.map((row) => row.checksum ?? null);
17209
+ for (let index = 0;index < rows.length; index += 1) {
17210
+ const expected = migrations[index];
17211
+ const row = rows[index];
17212
+ if (!expected) {
17213
+ return { status: "newer", migrationIds, checksums, detail: `unknown migration ID ${row.id}` };
17214
+ }
17215
+ if (row.id !== expected.id) {
17216
+ const knownLater = registryIds.includes(row.id);
17217
+ return {
17218
+ status: knownLater ? "inconsistent" : "newer",
17219
+ migrationIds,
17220
+ checksums,
17221
+ detail: knownLater ? `migration ledger is not an exact ordered prefix at position ${index + 1}` : `unknown migration ID ${row.id}`
17222
+ };
17223
+ }
17224
+ const expectedChecksum = migrationChecksum(expected);
17225
+ if (row.checksum && row.checksum !== expectedChecksum) {
17226
+ return {
17227
+ status: "inconsistent",
17228
+ migrationIds,
17229
+ checksums,
17230
+ detail: `migration ${row.id} checksum does not match the released migration body`
17231
+ };
17232
+ }
17233
+ }
17234
+ const missingChecksum = checksums.indexOf(null);
17235
+ if (missingChecksum >= 0) {
17236
+ return {
17237
+ status: "old",
17238
+ migrationIds,
17239
+ checksums,
17240
+ detail: `migration ${migrationIds[missingChecksum]} has not been sealed with a checksum`
17241
+ };
17242
+ }
17243
+ return {
17244
+ status: rows.length === migrations.length ? "current" : "old",
17245
+ migrationIds,
17246
+ checksums
17247
+ };
17248
+ }
17249
+ function assertMigrationLedger(db, migrations) {
17250
+ const state = inspectMigrationLedger(db, migrations);
17251
+ if (state.status === "newer") {
17252
+ throw new Error(`Refusing to open a database with a newer migration ledger: ${state.detail}.`);
17253
+ }
17254
+ if (state.status === "inconsistent") {
17255
+ throw new Error(`Refusing a database whose migrations are not an exact ordered prefix: ${state.detail}.`);
17256
+ }
17257
+ return state;
17258
+ }
17259
+ function assertCurrentMigrationLedger(db, migrations) {
17260
+ const state = assertMigrationLedger(db, migrations);
17261
+ if (state.status !== "current") {
17262
+ throw new Error(`Refusing to open an obsolete writable schema; run \`akm migrate apply\`: ${state.detail ?? "pending migrations"}.`);
17263
+ }
17264
+ return state;
17265
+ }
17266
+ function ensureMigrationsTable(db) {
17267
+ db.exec(`
17268
+ CREATE TABLE IF NOT EXISTS schema_migrations (
17269
+ id TEXT PRIMARY KEY,
17270
+ applied_at TEXT NOT NULL DEFAULT (datetime('now')),
17271
+ checksum TEXT
17272
+ );
17273
+ `);
17274
+ if (!ledgerHasChecksum(db))
17275
+ db.exec("ALTER TABLE schema_migrations ADD COLUMN checksum TEXT");
17276
+ }
17277
+ function runMigrations(db, migrations, opts) {
17278
+ if (opts?.applyPending === false) {
17279
+ assertMigrationLedger(db, migrations);
17280
+ return;
17281
+ }
17282
+ if (migrationsTableExists(db))
17283
+ assertMigrationLedger(db, migrations);
17284
+ ensureMigrationsTable(db);
17285
+ opts?.bootstrap?.(db);
17286
+ const ledger = assertMigrationLedger(db, migrations);
17287
+ db.transaction(() => {
17288
+ const update = db.prepare("UPDATE schema_migrations SET checksum = ? WHERE id = ? AND checksum IS NULL");
17289
+ for (let index = 0;index < ledger.migrationIds.length; index += 1) {
17290
+ update.run(migrationChecksum(migrations[index]), ledger.migrationIds[index]);
17291
+ }
17292
+ if (opts?.generationMarker) {
17293
+ db.exec(`
17294
+ CREATE TABLE IF NOT EXISTS akm_migration_generation (
17295
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
17296
+ operation_id TEXT NOT NULL,
17297
+ phase TEXT NOT NULL
17298
+ )
17299
+ `);
17300
+ 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);
17301
+ }
17302
+ })();
17303
+ const appliedRows = db.prepare("SELECT id FROM schema_migrations ORDER BY rowid").all();
17304
+ const applied = new Set(appliedRows.map((r) => r.id));
17305
+ for (const migration of migrations) {
17306
+ if (applied.has(migration.id))
17307
+ continue;
17308
+ opts?.beforeMigration?.(migration);
17309
+ db.transaction(() => {
17310
+ db.exec(migration.up);
17311
+ db.prepare("INSERT INTO schema_migrations (id, checksum) VALUES (?, ?)").run(migration.id, migrationChecksum(migration));
17312
+ })();
17313
+ }
17314
+ }
17442
17315
 
17443
17316
  // src/storage/managed-db.ts
17444
17317
  init_database();
@@ -17527,39 +17400,73 @@ function openManagedDatabase(spec) {
17527
17400
  return db;
17528
17401
  }
17529
17402
 
17530
- // src/core/state-db.ts
17531
- init_maintenance_barrier();
17532
- init_migration_backup();
17403
+ // src/core/maintenance-barrier.ts
17404
+ init_errors();
17405
+ init_file_lock();
17533
17406
  init_paths();
17534
-
17535
- // src/storage/engines/sqlite-migrations.ts
17536
- function ensureMigrationsTable(db) {
17537
- db.exec(`
17538
- CREATE TABLE IF NOT EXISTS schema_migrations (
17539
- id TEXT PRIMARY KEY,
17540
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
17541
- );
17542
- `);
17407
+ import { AsyncLocalStorage } from "node:async_hooks";
17408
+ import { randomUUID as randomUUID2 } from "node:crypto";
17409
+ import fs11 from "node:fs";
17410
+ import path10 from "node:path";
17411
+ var heldBarrierContext = new AsyncLocalStorage;
17412
+ function tryAcquireMaintenanceBarrier() {
17413
+ const lockPath = getMaintenanceBarrierPath();
17414
+ fs11.mkdirSync(path10.dirname(lockPath), { recursive: true });
17415
+ for (let attempt = 0;attempt < 2; attempt += 1) {
17416
+ const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: "maintenance-start" }));
17417
+ if (ownership) {
17418
+ return () => releaseLock(ownership);
17419
+ }
17420
+ const probe = probeLock(lockPath);
17421
+ if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
17422
+ return;
17423
+ }
17424
+ return;
17543
17425
  }
17544
- function runMigrations(db, migrations, opts) {
17545
- ensureMigrationsTable(db);
17546
- opts?.bootstrap?.(db);
17547
- const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
17548
- const applied = new Set(appliedRows.map((r) => r.id));
17549
- for (const migration of migrations) {
17550
- if (applied.has(migration.id))
17551
- continue;
17552
- opts?.beforeMigration?.(migration);
17553
- db.transaction(() => {
17554
- db.exec(migration.up);
17555
- db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
17556
- })();
17426
+ function acquireMaintenanceBarrier() {
17427
+ const release = tryAcquireMaintenanceBarrier();
17428
+ if (release)
17429
+ return release;
17430
+ throw new ConfigError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry after it completes.`, "INVALID_CONFIG_FILE");
17431
+ }
17432
+ function withMaintenanceStartBarrierSyncWait(run) {
17433
+ if (heldBarrierContext.getStore()?.active)
17434
+ return run();
17435
+ const deadline = Date.now() + 5000;
17436
+ let release = tryAcquireMaintenanceBarrier();
17437
+ while (!release && Date.now() < deadline) {
17438
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
17439
+ release = tryAcquireMaintenanceBarrier();
17440
+ }
17441
+ if (!release)
17442
+ release = acquireMaintenanceBarrier();
17443
+ const ownership = { active: true };
17444
+ try {
17445
+ return heldBarrierContext.run(ownership, run);
17446
+ } finally {
17447
+ ownership.active = false;
17448
+ release();
17557
17449
  }
17558
17450
  }
17451
+ function acquireMaintenanceActivitySync(name) {
17452
+ return withMaintenanceStartBarrierSyncWait(() => {
17453
+ const directory = path10.join(path10.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
17454
+ fs11.mkdirSync(directory, { recursive: true, mode: 448 });
17455
+ const lockPath = path10.join(directory, `${name}-${process.pid}-${randomUUID2()}.lock`);
17456
+ const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: name }));
17457
+ if (!ownership) {
17458
+ throw new ConfigError(`Could not register AKM maintenance activity at ${lockPath}.`, "INVALID_CONFIG_FILE");
17459
+ }
17460
+ return () => releaseLock(ownership);
17461
+ });
17462
+ }
17463
+
17464
+ // src/core/state-db.ts
17465
+ init_migration_operation();
17466
+ init_paths();
17559
17467
 
17560
17468
  // src/core/state/migrations.ts
17561
- init_migration_backup();
17562
- var MIGRATIONS = [
17469
+ var STATE_MIGRATIONS = [
17563
17470
  {
17564
17471
  id: "001-initial-schema",
17565
17472
  up: `
@@ -17994,29 +17901,42 @@ var MIGRATIONS = [
17994
17901
  }
17995
17902
  ];
17996
17903
  function runMigrations2(db, options) {
17997
- runMigrations(db, MIGRATIONS, {
17998
- beforeMigration(migration) {
17999
- if (options?.ensureCutoverBackup && migration.id === "017-improve-run-strategy")
18000
- ensureMigrationBackup();
18001
- }
17904
+ runMigrations(db, STATE_MIGRATIONS, {
17905
+ applyPending: options?.applyPending,
17906
+ generationMarker: options?.generationMarker
18002
17907
  });
18003
17908
  }
18004
17909
 
18005
17910
  // src/core/state-db.ts
18006
17911
  function getStateDbPath() {
18007
- return path12.join(getDataDir(), "state.db");
17912
+ return path11.join(getDataDir(), "state.db");
18008
17913
  }
18009
17914
  function openStateDatabase(dbPath) {
18010
17915
  const canonicalPath = getStateDbPath();
18011
17916
  const resolvedPath = dbPath ?? canonicalPath;
18012
- const isCanonical = path12.resolve(resolvedPath) === path12.resolve(canonicalPath);
17917
+ const isCanonical = path11.resolve(resolvedPath) === path11.resolve(canonicalPath);
17918
+ if (isCanonical)
17919
+ assertNoPendingMigrationOperation();
18013
17920
  const releaseActivity = isCanonical ? acquireMaintenanceActivitySync("state-db") : undefined;
18014
17921
  try {
18015
- if (isCanonical && !fs13.existsSync(getMigrationBackupDir()))
18016
- ensureMigrationBackup();
17922
+ if (isCanonical)
17923
+ assertNoPendingMigrationOperation();
17924
+ const existed = fs12.existsSync(resolvedPath);
17925
+ if (existed) {
17926
+ const preflight = openDatabase(resolvedPath, { readonly: true });
17927
+ try {
17928
+ preflight.exec("PRAGMA busy_timeout = 30000");
17929
+ if (isCanonical)
17930
+ assertCurrentMigrationLedger(preflight, STATE_MIGRATIONS);
17931
+ else
17932
+ assertMigrationLedger(preflight, STATE_MIGRATIONS);
17933
+ } finally {
17934
+ preflight.close();
17935
+ }
17936
+ }
18017
17937
  const db = openManagedDatabase({
18018
17938
  path: resolvedPath,
18019
- init: (db2) => runMigrations2(db2, { ensureCutoverBackup: isCanonical })
17939
+ init: (db2) => runMigrations2(db2, { applyPending: !(isCanonical && existed) })
18020
17940
  });
18021
17941
  if (!releaseActivity)
18022
17942
  return db;
@@ -18143,7 +18063,7 @@ function recordImproveRun(db, input) {
18143
18063
  function parseArgs(argv) {
18144
18064
  const stashFromEnv = process.env.AKM_STASH_DIR;
18145
18065
  const out = {
18146
- stashDir: stashFromEnv ?? path13.join(process.env.HOME ?? "", "akm"),
18066
+ stashDir: stashFromEnv ?? path12.join(process.env.HOME ?? "", "akm"),
18147
18067
  dryRun: false,
18148
18068
  archive: true
18149
18069
  };
@@ -18202,12 +18122,12 @@ function inferScope(envelope) {
18202
18122
  }
18203
18123
  async function main() {
18204
18124
  const args = parseArgs(process.argv.slice(2));
18205
- const runsRoot = path13.join(args.stashDir, ".akm", "runs");
18206
- if (!fs14.existsSync(runsRoot)) {
18125
+ const runsRoot = path12.join(args.stashDir, ".akm", "runs");
18126
+ if (!fs13.existsSync(runsRoot)) {
18207
18127
  console.log(`[import] no legacy runs directory at ${runsRoot}; nothing to do`);
18208
18128
  return;
18209
18129
  }
18210
- const entries = fs14.readdirSync(runsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
18130
+ const entries = fs13.readdirSync(runsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
18211
18131
  if (entries.length === 0) {
18212
18132
  console.log(`[import] ${runsRoot} is empty; nothing to do`);
18213
18133
  return;
@@ -18221,8 +18141,8 @@ async function main() {
18221
18141
  let skippedParseFailed = 0;
18222
18142
  let skippedNoTimestamp = 0;
18223
18143
  for (const id of entries) {
18224
- const resultPath = path13.join(runsRoot, id, "improve-result.json");
18225
- if (!fs14.existsSync(resultPath)) {
18144
+ const resultPath = path12.join(runsRoot, id, "improve-result.json");
18145
+ if (!fs13.existsSync(resultPath)) {
18226
18146
  skippedNoResult++;
18227
18147
  continue;
18228
18148
  }
@@ -18232,7 +18152,7 @@ async function main() {
18232
18152
  }
18233
18153
  let decoded;
18234
18154
  try {
18235
- decoded = decodeImproveResult(fs14.readFileSync(resultPath, "utf8"));
18155
+ decoded = decodeImproveResult(fs13.readFileSync(resultPath, "utf8"));
18236
18156
  } catch (err) {
18237
18157
  console.warn(`[import] skipping ${id}: parse failed (${err instanceof Error ? err.message : String(err)})`);
18238
18158
  skippedParseFailed++;
@@ -18298,8 +18218,8 @@ async function main() {
18298
18218
  return;
18299
18219
  }
18300
18220
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
18301
- const archiveTarget = path13.join(args.stashDir, ".akm", `runs.archived-${ts}`);
18302
- fs14.renameSync(runsRoot, archiveTarget);
18221
+ const archiveTarget = path12.join(args.stashDir, ".akm", `runs.archived-${ts}`);
18222
+ fs13.renameSync(runsRoot, archiveTarget);
18303
18223
  console.log(`[import] archived ${runsRoot} \u2192 ${archiveTarget}`);
18304
18224
  }
18305
18225
  main().catch((err) => {