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