akm-cli 0.9.0-rc.2 → 0.9.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -8
- package/dist/assets/improve-strategies/catchup.json +3 -1
- package/dist/assets/improve-strategies/consolidate.json +3 -1
- package/dist/assets/improve-strategies/frequent.json +3 -1
- package/dist/assets/improve-strategies/graph-refresh.json +3 -1
- package/dist/assets/improve-strategies/memory-focus.json +4 -1
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
- package/dist/assets/improve-strategies/quick.json +3 -1
- package/dist/assets/improve-strategies/recombine-only.json +2 -0
- package/dist/assets/improve-strategies/reflect-distill.json +1 -0
- package/dist/assets/improve-strategies/synthesize.json +2 -0
- package/dist/assets/tasks/core/backup.yml +2 -2
- package/dist/cli/config-migrate.js +554 -26
- package/dist/cli.js +3 -1
- package/dist/commands/backup-cli.js +6 -4
- package/dist/commands/config-cli.js +10 -3
- package/dist/commands/feedback-cli.js +24 -7
- package/dist/commands/health/checks.js +94 -15
- package/dist/commands/health/surfaces.js +2 -1
- package/dist/commands/improve/anti-collapse.js +3 -3
- package/dist/commands/improve/collapse-detector.js +5 -5
- package/dist/commands/improve/consolidate.js +123 -66
- package/dist/commands/improve/distill/promote-memory.js +8 -6
- package/dist/commands/improve/distill/quality-gate.js +1 -1
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +32 -26
- package/dist/commands/improve/distill.js +52 -36
- package/dist/commands/improve/eligibility.js +9 -8
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +43 -11
- package/dist/commands/improve/improve-auto-accept.js +14 -28
- package/dist/commands/improve/improve-cli.js +4 -2
- package/dist/commands/improve/improve-strategies.js +2 -1
- package/dist/commands/improve/improve.js +49 -8
- package/dist/commands/improve/loop-stages.js +11 -6
- package/dist/commands/improve/preparation.js +51 -17
- package/dist/commands/improve/procedural.js +10 -6
- package/dist/commands/improve/recombine.js +31 -7
- package/dist/commands/improve/reflect.js +24 -14
- package/dist/commands/improve/salience.js +5 -1
- package/dist/commands/improve/source-identity.js +43 -0
- package/dist/commands/migrate-cli.js +36 -0
- package/dist/commands/mv-cli.js +647 -258
- package/dist/commands/proposal/drain.js +16 -4
- package/dist/commands/proposal/proposal-cli.js +3 -2
- package/dist/commands/proposal/proposal.js +16 -55
- package/dist/commands/proposal/repository.js +664 -58
- package/dist/commands/read/curate.js +4 -2
- package/dist/commands/read/knowledge.js +4 -1
- package/dist/commands/read/search.js +6 -2
- package/dist/commands/read/show.js +8 -7
- package/dist/commands/sources/self-update.js +156 -112
- package/dist/commands/sources/sources-cli.js +7 -2
- package/dist/core/common.js +36 -3
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-schema.js +15 -3
- package/dist/core/config/config.js +14 -13
- package/dist/core/file-lock.js +2 -2
- package/dist/core/migration-backup.js +816 -171
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/state/migrations.js +4 -7
- package/dist/core/state-db.js +22 -7
- package/dist/core/write-source.js +86 -18
- package/dist/indexer/db/db.js +109 -53
- package/dist/indexer/index-writer-lock.js +38 -37
- package/dist/indexer/index-written-assets.js +73 -56
- package/dist/indexer/indexer.js +5 -1
- package/dist/indexer/search/search-source.js +2 -2
- package/dist/indexer/usage/usage-events.js +8 -2
- package/dist/integrations/agent/engine-resolution.js +14 -7
- package/dist/scripts/migrate-storage.js +867 -990
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +405 -566
- package/dist/setup/detected-engines.js +7 -13
- package/dist/setup/engine-config.js +14 -4
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +1 -1
- package/dist/setup/steps/tasks.js +1 -1
- package/dist/sources/providers/git-stash.js +58 -21
- package/dist/sources/providers/git.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +138 -3
- package/dist/storage/repositories/events-repository.js +24 -0
- package/dist/storage/repositories/proposals-repository.js +14 -0
- package/dist/tasks/embedded.js +2 -0
- package/dist/workflows/db.js +24 -13
- package/dist/workflows/validator.js +24 -2
- package/docs/migration/release-notes/0.9.0.md +24 -7
- package/docs/migration/v0.8-to-v0.9.md +124 -19
- package/package.json +1 -1
|
@@ -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
|
|
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 =
|
|
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/
|
|
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
|
|
7821
|
-
|
|
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
|
|
7836
|
-
|
|
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
|
|
7849
|
-
return path3.join(
|
|
7782
|
+
function getMigrationRestoreJournalPath() {
|
|
7783
|
+
return path3.join(getMigrationOperationRoot(), "restore-active.json");
|
|
7850
7784
|
}
|
|
7851
|
-
function
|
|
7852
|
-
|
|
7785
|
+
function getMigrationApplyJournalPath() {
|
|
7786
|
+
return path3.join(getMigrationOperationRoot(), "apply-active.json");
|
|
7853
7787
|
}
|
|
7854
|
-
function
|
|
7855
|
-
const
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
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
|
-
|
|
7797
|
+
const hook = afterPendingCheckHook;
|
|
7798
|
+
afterPendingCheckHook = undefined;
|
|
7799
|
+
hook?.();
|
|
7927
7800
|
}
|
|
7928
|
-
var
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
14822
|
+
import fs7 from "node:fs";
|
|
15192
14823
|
function extractDescriptionFromComments(filePath) {
|
|
15193
14824
|
let content;
|
|
15194
14825
|
try {
|
|
15195
|
-
content =
|
|
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
|
|
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 (
|
|
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
|
|
16948
|
-
|
|
16949
|
-
|
|
16950
|
-
|
|
16951
|
-
|
|
16952
|
-
const
|
|
16953
|
-
|
|
16954
|
-
|
|
16955
|
-
|
|
16956
|
-
|
|
16957
|
-
|
|
16958
|
-
|
|
16959
|
-
|
|
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
|
-
|
|
16962
|
-
|
|
16963
|
-
|
|
16964
|
-
|
|
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
|
|
16999
|
-
import
|
|
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 =
|
|
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 =
|
|
17011
|
-
const stat =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
17262
|
-
import
|
|
16919
|
+
import fs13 from "fs";
|
|
16920
|
+
import path12 from "path";
|
|
17263
16921
|
|
|
17264
16922
|
// src/core/improve-result.ts
|
|
17265
16923
|
var COMMON_FIELDS = [
|
|
@@ -17437,8 +17095,143 @@ function decodeImproveResult(input) {
|
|
|
17437
17095
|
}
|
|
17438
17096
|
|
|
17439
17097
|
// src/core/state-db.ts
|
|
17440
|
-
|
|
17441
|
-
import
|
|
17098
|
+
init_database();
|
|
17099
|
+
import fs12 from "node:fs";
|
|
17100
|
+
import path11 from "node:path";
|
|
17101
|
+
|
|
17102
|
+
// src/storage/engines/sqlite-migrations.ts
|
|
17103
|
+
import crypto from "node:crypto";
|
|
17104
|
+
function migrationChecksum(migration) {
|
|
17105
|
+
return crypto.createHash("sha256").update(migration.id).update("\x00").update(migration.up).digest("hex");
|
|
17106
|
+
}
|
|
17107
|
+
function migrationsTableExists(db) {
|
|
17108
|
+
return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get();
|
|
17109
|
+
}
|
|
17110
|
+
function ledgerHasChecksum(db) {
|
|
17111
|
+
return db.prepare("PRAGMA table_info(schema_migrations)").all().some((column) => column.name === "checksum");
|
|
17112
|
+
}
|
|
17113
|
+
function inspectMigrationLedger(db, migrations) {
|
|
17114
|
+
const registryIds = migrations.map((migration) => migration.id);
|
|
17115
|
+
if (new Set(registryIds).size !== registryIds.length) {
|
|
17116
|
+
return {
|
|
17117
|
+
status: "inconsistent",
|
|
17118
|
+
migrationIds: [],
|
|
17119
|
+
checksums: [],
|
|
17120
|
+
detail: "migration registry contains duplicate IDs"
|
|
17121
|
+
};
|
|
17122
|
+
}
|
|
17123
|
+
if (!migrationsTableExists(db))
|
|
17124
|
+
return { status: registryIds.length === 0 ? "current" : "old", migrationIds: [], checksums: [] };
|
|
17125
|
+
const hasChecksum = ledgerHasChecksum(db);
|
|
17126
|
+
const rows = db.prepare(`SELECT id${hasChecksum ? ", checksum" : ""} FROM schema_migrations ORDER BY rowid`).all();
|
|
17127
|
+
const migrationIds = rows.map((row) => row.id);
|
|
17128
|
+
const checksums = rows.map((row) => row.checksum ?? null);
|
|
17129
|
+
for (let index = 0;index < rows.length; index += 1) {
|
|
17130
|
+
const expected = migrations[index];
|
|
17131
|
+
const row = rows[index];
|
|
17132
|
+
if (!expected) {
|
|
17133
|
+
return { status: "newer", migrationIds, checksums, detail: `unknown migration ID ${row.id}` };
|
|
17134
|
+
}
|
|
17135
|
+
if (row.id !== expected.id) {
|
|
17136
|
+
const knownLater = registryIds.includes(row.id);
|
|
17137
|
+
return {
|
|
17138
|
+
status: knownLater ? "inconsistent" : "newer",
|
|
17139
|
+
migrationIds,
|
|
17140
|
+
checksums,
|
|
17141
|
+
detail: knownLater ? `migration ledger is not an exact ordered prefix at position ${index + 1}` : `unknown migration ID ${row.id}`
|
|
17142
|
+
};
|
|
17143
|
+
}
|
|
17144
|
+
const expectedChecksum = migrationChecksum(expected);
|
|
17145
|
+
if (row.checksum && row.checksum !== expectedChecksum) {
|
|
17146
|
+
return {
|
|
17147
|
+
status: "inconsistent",
|
|
17148
|
+
migrationIds,
|
|
17149
|
+
checksums,
|
|
17150
|
+
detail: `migration ${row.id} checksum does not match the released migration body`
|
|
17151
|
+
};
|
|
17152
|
+
}
|
|
17153
|
+
}
|
|
17154
|
+
const missingChecksum = checksums.indexOf(null);
|
|
17155
|
+
if (missingChecksum >= 0) {
|
|
17156
|
+
return {
|
|
17157
|
+
status: "old",
|
|
17158
|
+
migrationIds,
|
|
17159
|
+
checksums,
|
|
17160
|
+
detail: `migration ${migrationIds[missingChecksum]} has not been sealed with a checksum`
|
|
17161
|
+
};
|
|
17162
|
+
}
|
|
17163
|
+
return {
|
|
17164
|
+
status: rows.length === migrations.length ? "current" : "old",
|
|
17165
|
+
migrationIds,
|
|
17166
|
+
checksums
|
|
17167
|
+
};
|
|
17168
|
+
}
|
|
17169
|
+
function assertMigrationLedger(db, migrations) {
|
|
17170
|
+
const state = inspectMigrationLedger(db, migrations);
|
|
17171
|
+
if (state.status === "newer") {
|
|
17172
|
+
throw new Error(`Refusing to open a database with a newer migration ledger: ${state.detail}.`);
|
|
17173
|
+
}
|
|
17174
|
+
if (state.status === "inconsistent") {
|
|
17175
|
+
throw new Error(`Refusing a database whose migrations are not an exact ordered prefix: ${state.detail}.`);
|
|
17176
|
+
}
|
|
17177
|
+
return state;
|
|
17178
|
+
}
|
|
17179
|
+
function assertCurrentMigrationLedger(db, migrations) {
|
|
17180
|
+
const state = assertMigrationLedger(db, migrations);
|
|
17181
|
+
if (state.status !== "current") {
|
|
17182
|
+
throw new Error(`Refusing to open an obsolete writable schema; run \`akm migrate apply\`: ${state.detail ?? "pending migrations"}.`);
|
|
17183
|
+
}
|
|
17184
|
+
return state;
|
|
17185
|
+
}
|
|
17186
|
+
function ensureMigrationsTable(db) {
|
|
17187
|
+
db.exec(`
|
|
17188
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
17189
|
+
id TEXT PRIMARY KEY,
|
|
17190
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
17191
|
+
checksum TEXT
|
|
17192
|
+
);
|
|
17193
|
+
`);
|
|
17194
|
+
if (!ledgerHasChecksum(db))
|
|
17195
|
+
db.exec("ALTER TABLE schema_migrations ADD COLUMN checksum TEXT");
|
|
17196
|
+
}
|
|
17197
|
+
function runMigrations(db, migrations, opts) {
|
|
17198
|
+
if (opts?.applyPending === false) {
|
|
17199
|
+
assertMigrationLedger(db, migrations);
|
|
17200
|
+
return;
|
|
17201
|
+
}
|
|
17202
|
+
if (migrationsTableExists(db))
|
|
17203
|
+
assertMigrationLedger(db, migrations);
|
|
17204
|
+
ensureMigrationsTable(db);
|
|
17205
|
+
opts?.bootstrap?.(db);
|
|
17206
|
+
const ledger = assertMigrationLedger(db, migrations);
|
|
17207
|
+
db.transaction(() => {
|
|
17208
|
+
const update = db.prepare("UPDATE schema_migrations SET checksum = ? WHERE id = ? AND checksum IS NULL");
|
|
17209
|
+
for (let index = 0;index < ledger.migrationIds.length; index += 1) {
|
|
17210
|
+
update.run(migrationChecksum(migrations[index]), ledger.migrationIds[index]);
|
|
17211
|
+
}
|
|
17212
|
+
if (opts?.generationMarker) {
|
|
17213
|
+
db.exec(`
|
|
17214
|
+
CREATE TABLE IF NOT EXISTS akm_migration_generation (
|
|
17215
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
17216
|
+
operation_id TEXT NOT NULL,
|
|
17217
|
+
phase TEXT NOT NULL
|
|
17218
|
+
)
|
|
17219
|
+
`);
|
|
17220
|
+
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);
|
|
17221
|
+
}
|
|
17222
|
+
})();
|
|
17223
|
+
const appliedRows = db.prepare("SELECT id FROM schema_migrations ORDER BY rowid").all();
|
|
17224
|
+
const applied = new Set(appliedRows.map((r) => r.id));
|
|
17225
|
+
for (const migration of migrations) {
|
|
17226
|
+
if (applied.has(migration.id))
|
|
17227
|
+
continue;
|
|
17228
|
+
opts?.beforeMigration?.(migration);
|
|
17229
|
+
db.transaction(() => {
|
|
17230
|
+
db.exec(migration.up);
|
|
17231
|
+
db.prepare("INSERT INTO schema_migrations (id, checksum) VALUES (?, ?)").run(migration.id, migrationChecksum(migration));
|
|
17232
|
+
})();
|
|
17233
|
+
}
|
|
17234
|
+
}
|
|
17442
17235
|
|
|
17443
17236
|
// src/storage/managed-db.ts
|
|
17444
17237
|
init_database();
|
|
@@ -17527,39 +17320,73 @@ function openManagedDatabase(spec) {
|
|
|
17527
17320
|
return db;
|
|
17528
17321
|
}
|
|
17529
17322
|
|
|
17530
|
-
// src/core/
|
|
17531
|
-
|
|
17532
|
-
|
|
17323
|
+
// src/core/maintenance-barrier.ts
|
|
17324
|
+
init_errors();
|
|
17325
|
+
init_file_lock();
|
|
17533
17326
|
init_paths();
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17327
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
17328
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
17329
|
+
import fs11 from "node:fs";
|
|
17330
|
+
import path10 from "node:path";
|
|
17331
|
+
var heldBarrierContext = new AsyncLocalStorage;
|
|
17332
|
+
function tryAcquireMaintenanceBarrier() {
|
|
17333
|
+
const lockPath = getMaintenanceBarrierPath();
|
|
17334
|
+
fs11.mkdirSync(path10.dirname(lockPath), { recursive: true });
|
|
17335
|
+
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
17336
|
+
const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: "maintenance-start" }));
|
|
17337
|
+
if (ownership) {
|
|
17338
|
+
return () => releaseLock(ownership);
|
|
17339
|
+
}
|
|
17340
|
+
const probe = probeLock(lockPath);
|
|
17341
|
+
if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
|
|
17342
|
+
return;
|
|
17343
|
+
}
|
|
17344
|
+
return;
|
|
17543
17345
|
}
|
|
17544
|
-
function
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
|
|
17346
|
+
function acquireMaintenanceBarrier() {
|
|
17347
|
+
const release = tryAcquireMaintenanceBarrier();
|
|
17348
|
+
if (release)
|
|
17349
|
+
return release;
|
|
17350
|
+
throw new ConfigError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry after it completes.`, "INVALID_CONFIG_FILE");
|
|
17351
|
+
}
|
|
17352
|
+
function withMaintenanceStartBarrierSyncWait(run) {
|
|
17353
|
+
if (heldBarrierContext.getStore()?.active)
|
|
17354
|
+
return run();
|
|
17355
|
+
const deadline = Date.now() + 5000;
|
|
17356
|
+
let release = tryAcquireMaintenanceBarrier();
|
|
17357
|
+
while (!release && Date.now() < deadline) {
|
|
17358
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
|
|
17359
|
+
release = tryAcquireMaintenanceBarrier();
|
|
17360
|
+
}
|
|
17361
|
+
if (!release)
|
|
17362
|
+
release = acquireMaintenanceBarrier();
|
|
17363
|
+
const ownership = { active: true };
|
|
17364
|
+
try {
|
|
17365
|
+
return heldBarrierContext.run(ownership, run);
|
|
17366
|
+
} finally {
|
|
17367
|
+
ownership.active = false;
|
|
17368
|
+
release();
|
|
17557
17369
|
}
|
|
17558
17370
|
}
|
|
17371
|
+
function acquireMaintenanceActivitySync(name) {
|
|
17372
|
+
return withMaintenanceStartBarrierSyncWait(() => {
|
|
17373
|
+
const directory = path10.join(path10.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
|
|
17374
|
+
fs11.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
17375
|
+
const lockPath = path10.join(directory, `${name}-${process.pid}-${randomUUID2()}.lock`);
|
|
17376
|
+
const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: name }));
|
|
17377
|
+
if (!ownership) {
|
|
17378
|
+
throw new ConfigError(`Could not register AKM maintenance activity at ${lockPath}.`, "INVALID_CONFIG_FILE");
|
|
17379
|
+
}
|
|
17380
|
+
return () => releaseLock(ownership);
|
|
17381
|
+
});
|
|
17382
|
+
}
|
|
17383
|
+
|
|
17384
|
+
// src/core/state-db.ts
|
|
17385
|
+
init_migration_operation();
|
|
17386
|
+
init_paths();
|
|
17559
17387
|
|
|
17560
17388
|
// src/core/state/migrations.ts
|
|
17561
|
-
|
|
17562
|
-
var MIGRATIONS = [
|
|
17389
|
+
var STATE_MIGRATIONS = [
|
|
17563
17390
|
{
|
|
17564
17391
|
id: "001-initial-schema",
|
|
17565
17392
|
up: `
|
|
@@ -17994,29 +17821,41 @@ var MIGRATIONS = [
|
|
|
17994
17821
|
}
|
|
17995
17822
|
];
|
|
17996
17823
|
function runMigrations2(db, options) {
|
|
17997
|
-
runMigrations(db,
|
|
17998
|
-
|
|
17999
|
-
|
|
18000
|
-
ensureMigrationBackup();
|
|
18001
|
-
}
|
|
17824
|
+
runMigrations(db, STATE_MIGRATIONS, {
|
|
17825
|
+
applyPending: options?.applyPending,
|
|
17826
|
+
generationMarker: options?.generationMarker
|
|
18002
17827
|
});
|
|
18003
17828
|
}
|
|
18004
17829
|
|
|
18005
17830
|
// src/core/state-db.ts
|
|
18006
17831
|
function getStateDbPath() {
|
|
18007
|
-
return
|
|
17832
|
+
return path11.join(getDataDir(), "state.db");
|
|
18008
17833
|
}
|
|
18009
17834
|
function openStateDatabase(dbPath) {
|
|
18010
17835
|
const canonicalPath = getStateDbPath();
|
|
18011
17836
|
const resolvedPath = dbPath ?? canonicalPath;
|
|
18012
|
-
const isCanonical =
|
|
17837
|
+
const isCanonical = path11.resolve(resolvedPath) === path11.resolve(canonicalPath);
|
|
17838
|
+
if (isCanonical)
|
|
17839
|
+
assertNoPendingMigrationOperation();
|
|
18013
17840
|
const releaseActivity = isCanonical ? acquireMaintenanceActivitySync("state-db") : undefined;
|
|
18014
17841
|
try {
|
|
18015
|
-
if (isCanonical
|
|
18016
|
-
|
|
17842
|
+
if (isCanonical)
|
|
17843
|
+
assertNoPendingMigrationOperation();
|
|
17844
|
+
const existed = fs12.existsSync(resolvedPath);
|
|
17845
|
+
if (existed) {
|
|
17846
|
+
const preflight = openDatabase(resolvedPath, { readonly: true });
|
|
17847
|
+
try {
|
|
17848
|
+
if (isCanonical)
|
|
17849
|
+
assertCurrentMigrationLedger(preflight, STATE_MIGRATIONS);
|
|
17850
|
+
else
|
|
17851
|
+
assertMigrationLedger(preflight, STATE_MIGRATIONS);
|
|
17852
|
+
} finally {
|
|
17853
|
+
preflight.close();
|
|
17854
|
+
}
|
|
17855
|
+
}
|
|
18017
17856
|
const db = openManagedDatabase({
|
|
18018
17857
|
path: resolvedPath,
|
|
18019
|
-
init: (db2) => runMigrations2(db2, {
|
|
17858
|
+
init: (db2) => runMigrations2(db2, { applyPending: !(isCanonical && existed) })
|
|
18020
17859
|
});
|
|
18021
17860
|
if (!releaseActivity)
|
|
18022
17861
|
return db;
|
|
@@ -18143,7 +17982,7 @@ function recordImproveRun(db, input) {
|
|
|
18143
17982
|
function parseArgs(argv) {
|
|
18144
17983
|
const stashFromEnv = process.env.AKM_STASH_DIR;
|
|
18145
17984
|
const out = {
|
|
18146
|
-
stashDir: stashFromEnv ??
|
|
17985
|
+
stashDir: stashFromEnv ?? path12.join(process.env.HOME ?? "", "akm"),
|
|
18147
17986
|
dryRun: false,
|
|
18148
17987
|
archive: true
|
|
18149
17988
|
};
|
|
@@ -18202,12 +18041,12 @@ function inferScope(envelope) {
|
|
|
18202
18041
|
}
|
|
18203
18042
|
async function main() {
|
|
18204
18043
|
const args = parseArgs(process.argv.slice(2));
|
|
18205
|
-
const runsRoot =
|
|
18206
|
-
if (!
|
|
18044
|
+
const runsRoot = path12.join(args.stashDir, ".akm", "runs");
|
|
18045
|
+
if (!fs13.existsSync(runsRoot)) {
|
|
18207
18046
|
console.log(`[import] no legacy runs directory at ${runsRoot}; nothing to do`);
|
|
18208
18047
|
return;
|
|
18209
18048
|
}
|
|
18210
|
-
const entries =
|
|
18049
|
+
const entries = fs13.readdirSync(runsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
18211
18050
|
if (entries.length === 0) {
|
|
18212
18051
|
console.log(`[import] ${runsRoot} is empty; nothing to do`);
|
|
18213
18052
|
return;
|
|
@@ -18221,8 +18060,8 @@ async function main() {
|
|
|
18221
18060
|
let skippedParseFailed = 0;
|
|
18222
18061
|
let skippedNoTimestamp = 0;
|
|
18223
18062
|
for (const id of entries) {
|
|
18224
|
-
const resultPath =
|
|
18225
|
-
if (!
|
|
18063
|
+
const resultPath = path12.join(runsRoot, id, "improve-result.json");
|
|
18064
|
+
if (!fs13.existsSync(resultPath)) {
|
|
18226
18065
|
skippedNoResult++;
|
|
18227
18066
|
continue;
|
|
18228
18067
|
}
|
|
@@ -18232,7 +18071,7 @@ async function main() {
|
|
|
18232
18071
|
}
|
|
18233
18072
|
let decoded;
|
|
18234
18073
|
try {
|
|
18235
|
-
decoded = decodeImproveResult(
|
|
18074
|
+
decoded = decodeImproveResult(fs13.readFileSync(resultPath, "utf8"));
|
|
18236
18075
|
} catch (err) {
|
|
18237
18076
|
console.warn(`[import] skipping ${id}: parse failed (${err instanceof Error ? err.message : String(err)})`);
|
|
18238
18077
|
skippedParseFailed++;
|
|
@@ -18298,8 +18137,8 @@ async function main() {
|
|
|
18298
18137
|
return;
|
|
18299
18138
|
}
|
|
18300
18139
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
18301
|
-
const archiveTarget =
|
|
18302
|
-
|
|
18140
|
+
const archiveTarget = path12.join(args.stashDir, ".akm", `runs.archived-${ts}`);
|
|
18141
|
+
fs13.renameSync(runsRoot, archiveTarget);
|
|
18303
18142
|
console.log(`[import] archived ${runsRoot} \u2192 ${archiveTarget}`);
|
|
18304
18143
|
}
|
|
18305
18144
|
main().catch((err) => {
|