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.
- package/CHANGELOG.md +40 -12
- package/README.md +7 -8
- package/SECURITY.md +5 -3
- package/dist/akm +44 -33
- package/dist/akm-migrate-storage +44 -33
- package/dist/assets/backends/schtasks-template.xml +2 -1
- 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 +28 -11
- 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/html-report.js +23 -2
- package/dist/commands/health/improve-metrics.js +45 -6
- package/dist/commands/health/md-report.js +10 -1
- package/dist/commands/health/surfaces.js +2 -1
- package/dist/commands/health/windows.js +1 -1
- package/dist/commands/health.js +1 -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/commands/tasks/default-tasks.js +5 -5
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +261 -68
- 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/improve-result.js +87 -7
- 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 +23 -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 +887 -992
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +494 -574
- 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/storage/repositories/task-history-repository.js +30 -3
- package/dist/tasks/backends/cron.js +71 -38
- package/dist/tasks/backends/exec-utils.js +55 -3
- package/dist/tasks/backends/launchd.js +241 -50
- package/dist/tasks/backends/schtasks.js +434 -59
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +2 -0
- package/dist/tasks/parser.js +142 -6
- package/dist/tasks/resolve-akm-bin.js +19 -4
- package/dist/tasks/runner.js +258 -93
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +86 -0
- package/dist/tasks/task-id.js +37 -0
- package/dist/workflows/db.js +24 -13
- package/dist/workflows/validator.js +24 -2
- package/docs/README.md +103 -0
- package/docs/migration/release-notes/0.9.0.md +28 -9
- package/docs/migration/v0.8-to-v0.9.md +186 -26
- 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
|
|
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 = [
|
|
@@ -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
|
-
|
|
17409
|
-
|
|
17410
|
-
if (
|
|
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 (
|
|
17148
|
+
if (normalized.profileFilteredRefs !== undefined && !Array.isArray(normalized.profileFilteredRefs)) {
|
|
17413
17149
|
fail("profileFilteredRefs must be an array");
|
|
17414
17150
|
}
|
|
17415
17151
|
return {
|
|
17416
|
-
envelope:
|
|
17152
|
+
envelope: normalized,
|
|
17417
17153
|
strategy: null,
|
|
17418
|
-
legacyProfile: typeof
|
|
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
|
-
|
|
17441
|
-
import
|
|
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/
|
|
17531
|
-
|
|
17532
|
-
|
|
17403
|
+
// src/core/maintenance-barrier.ts
|
|
17404
|
+
init_errors();
|
|
17405
|
+
init_file_lock();
|
|
17533
17406
|
init_paths();
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
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
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
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
|
-
|
|
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,
|
|
17998
|
-
|
|
17999
|
-
|
|
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
|
|
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 =
|
|
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
|
|
18016
|
-
|
|
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, {
|
|
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 ??
|
|
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 =
|
|
18206
|
-
if (!
|
|
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 =
|
|
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 =
|
|
18225
|
-
if (!
|
|
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(
|
|
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 =
|
|
18302
|
-
|
|
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) => {
|