akm-cli 0.9.0-rc.2 → 0.9.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -12
- package/README.md +7 -8
- package/SECURITY.md +5 -3
- package/dist/akm +44 -33
- package/dist/akm-migrate-storage +44 -33
- package/dist/assets/backends/schtasks-template.xml +2 -1
- package/dist/assets/improve-strategies/catchup.json +3 -1
- package/dist/assets/improve-strategies/consolidate.json +3 -1
- package/dist/assets/improve-strategies/frequent.json +3 -1
- package/dist/assets/improve-strategies/graph-refresh.json +3 -1
- package/dist/assets/improve-strategies/memory-focus.json +4 -1
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
- package/dist/assets/improve-strategies/quick.json +3 -1
- package/dist/assets/improve-strategies/recombine-only.json +2 -0
- package/dist/assets/improve-strategies/reflect-distill.json +1 -0
- package/dist/assets/improve-strategies/synthesize.json +2 -0
- package/dist/assets/tasks/core/backup.yml +2 -2
- package/dist/cli/config-migrate.js +554 -26
- package/dist/cli.js +28 -11
- package/dist/commands/backup-cli.js +6 -4
- package/dist/commands/config-cli.js +10 -3
- package/dist/commands/feedback-cli.js +24 -7
- package/dist/commands/health/checks.js +94 -15
- package/dist/commands/health/html-report.js +23 -2
- package/dist/commands/health/improve-metrics.js +45 -6
- package/dist/commands/health/md-report.js +10 -1
- package/dist/commands/health/surfaces.js +2 -1
- package/dist/commands/health/windows.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/anti-collapse.js +3 -3
- package/dist/commands/improve/collapse-detector.js +5 -5
- package/dist/commands/improve/consolidate.js +123 -66
- package/dist/commands/improve/distill/promote-memory.js +8 -6
- package/dist/commands/improve/distill/quality-gate.js +1 -1
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +32 -26
- package/dist/commands/improve/distill.js +52 -36
- package/dist/commands/improve/eligibility.js +9 -8
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +43 -11
- package/dist/commands/improve/improve-auto-accept.js +14 -28
- package/dist/commands/improve/improve-cli.js +4 -2
- package/dist/commands/improve/improve-strategies.js +2 -1
- package/dist/commands/improve/improve.js +49 -8
- package/dist/commands/improve/loop-stages.js +11 -6
- package/dist/commands/improve/preparation.js +51 -17
- package/dist/commands/improve/procedural.js +10 -6
- package/dist/commands/improve/recombine.js +31 -7
- package/dist/commands/improve/reflect.js +24 -14
- package/dist/commands/improve/salience.js +5 -1
- package/dist/commands/improve/source-identity.js +43 -0
- package/dist/commands/migrate-cli.js +36 -0
- package/dist/commands/mv-cli.js +647 -258
- package/dist/commands/proposal/drain.js +16 -4
- package/dist/commands/proposal/proposal-cli.js +3 -2
- package/dist/commands/proposal/proposal.js +16 -55
- package/dist/commands/proposal/repository.js +664 -58
- package/dist/commands/read/curate.js +4 -2
- package/dist/commands/read/knowledge.js +4 -1
- package/dist/commands/read/search.js +6 -2
- package/dist/commands/read/show.js +8 -7
- package/dist/commands/sources/self-update.js +156 -112
- package/dist/commands/sources/sources-cli.js +7 -2
- package/dist/commands/tasks/default-tasks.js +5 -5
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +261 -68
- package/dist/core/common.js +36 -3
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-schema.js +15 -3
- package/dist/core/config/config.js +14 -13
- package/dist/core/file-lock.js +2 -2
- package/dist/core/improve-result.js +87 -7
- package/dist/core/migration-backup.js +816 -171
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/state/migrations.js +4 -7
- package/dist/core/state-db.js +23 -7
- package/dist/core/write-source.js +86 -18
- package/dist/indexer/db/db.js +109 -53
- package/dist/indexer/index-writer-lock.js +38 -37
- package/dist/indexer/index-written-assets.js +73 -56
- package/dist/indexer/indexer.js +5 -1
- package/dist/indexer/search/search-source.js +2 -2
- package/dist/indexer/usage/usage-events.js +8 -2
- package/dist/integrations/agent/engine-resolution.js +14 -7
- package/dist/scripts/migrate-storage.js +887 -992
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +494 -574
- package/dist/setup/detected-engines.js +7 -13
- package/dist/setup/engine-config.js +14 -4
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +1 -1
- package/dist/setup/steps/tasks.js +1 -1
- package/dist/sources/providers/git-stash.js +58 -21
- package/dist/sources/providers/git.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +138 -3
- package/dist/storage/repositories/events-repository.js +24 -0
- package/dist/storage/repositories/proposals-repository.js +14 -0
- package/dist/storage/repositories/task-history-repository.js +30 -3
- package/dist/tasks/backends/cron.js +71 -38
- package/dist/tasks/backends/exec-utils.js +55 -3
- package/dist/tasks/backends/launchd.js +241 -50
- package/dist/tasks/backends/schtasks.js +434 -59
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +2 -0
- package/dist/tasks/parser.js +142 -6
- package/dist/tasks/resolve-akm-bin.js +19 -4
- package/dist/tasks/runner.js +258 -93
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +86 -0
- package/dist/tasks/task-id.js +37 -0
- package/dist/workflows/db.js +24 -13
- package/dist/workflows/validator.js +24 -2
- package/docs/README.md +103 -0
- package/docs/migration/release-notes/0.9.0.md +28 -9
- package/docs/migration/v0.8-to-v0.9.md +186 -26
- package/package.json +2 -3
|
@@ -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(),
|
|
@@ -14895,328 +14783,7 @@ var init_config_schema = __esm(() => {
|
|
|
14895
14783
|
});
|
|
14896
14784
|
}
|
|
14897
14785
|
}
|
|
14898
|
-
});
|
|
14899
|
-
});
|
|
14900
|
-
|
|
14901
|
-
// src/core/maintenance-barrier.ts
|
|
14902
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14903
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
14904
|
-
import fs6 from "node:fs";
|
|
14905
|
-
import path6 from "node:path";
|
|
14906
|
-
function tryAcquireMaintenanceBarrier() {
|
|
14907
|
-
const lockPath = getMaintenanceBarrierPath();
|
|
14908
|
-
fs6.mkdirSync(path6.dirname(lockPath), { recursive: true });
|
|
14909
|
-
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
14910
|
-
const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: "maintenance-start" }));
|
|
14911
|
-
if (ownership) {
|
|
14912
|
-
return () => releaseLock(ownership);
|
|
14913
|
-
}
|
|
14914
|
-
const probe = probeLock(lockPath);
|
|
14915
|
-
if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
|
|
14916
|
-
return;
|
|
14917
|
-
}
|
|
14918
|
-
return;
|
|
14919
|
-
}
|
|
14920
|
-
function acquireMaintenanceBarrier() {
|
|
14921
|
-
const release = tryAcquireMaintenanceBarrier();
|
|
14922
|
-
if (release)
|
|
14923
|
-
return release;
|
|
14924
|
-
throw new ConfigError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry after it completes.`, "INVALID_CONFIG_FILE");
|
|
14925
|
-
}
|
|
14926
|
-
function withMaintenanceStartBarrierSyncWait(run) {
|
|
14927
|
-
if (heldBarrierContext.getStore()?.active)
|
|
14928
|
-
return run();
|
|
14929
|
-
const deadline = Date.now() + 5000;
|
|
14930
|
-
let release = tryAcquireMaintenanceBarrier();
|
|
14931
|
-
while (!release && Date.now() < deadline) {
|
|
14932
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
|
|
14933
|
-
release = tryAcquireMaintenanceBarrier();
|
|
14934
|
-
}
|
|
14935
|
-
if (!release)
|
|
14936
|
-
release = acquireMaintenanceBarrier();
|
|
14937
|
-
const ownership = { active: true };
|
|
14938
|
-
try {
|
|
14939
|
-
return heldBarrierContext.run(ownership, run);
|
|
14940
|
-
} finally {
|
|
14941
|
-
ownership.active = false;
|
|
14942
|
-
release();
|
|
14943
|
-
}
|
|
14944
|
-
}
|
|
14945
|
-
function acquireMaintenanceActivitySync(name) {
|
|
14946
|
-
return withMaintenanceStartBarrierSyncWait(() => {
|
|
14947
|
-
const directory = path6.join(path6.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
|
|
14948
|
-
fs6.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
14949
|
-
const lockPath = path6.join(directory, `${name}-${process.pid}-${randomUUID2()}.lock`);
|
|
14950
|
-
const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: name }));
|
|
14951
|
-
if (!ownership) {
|
|
14952
|
-
throw new ConfigError(`Could not register AKM maintenance activity at ${lockPath}.`, "INVALID_CONFIG_FILE");
|
|
14953
|
-
}
|
|
14954
|
-
return () => releaseLock(ownership);
|
|
14955
|
-
});
|
|
14956
|
-
}
|
|
14957
|
-
var heldBarrierContext;
|
|
14958
|
-
var init_maintenance_barrier = __esm(() => {
|
|
14959
|
-
init_errors();
|
|
14960
|
-
init_file_lock();
|
|
14961
|
-
init_paths();
|
|
14962
|
-
heldBarrierContext = new AsyncLocalStorage;
|
|
14963
|
-
});
|
|
14964
|
-
|
|
14965
|
-
// src/core/migration-backup.ts
|
|
14966
|
-
import crypto from "node:crypto";
|
|
14967
|
-
import fs7 from "node:fs";
|
|
14968
|
-
import path7 from "node:path";
|
|
14969
|
-
function getMigrationBackupDir() {
|
|
14970
|
-
return path7.join(getCacheDir(), "migration-backups", MIGRATION_BACKUP_VERSION);
|
|
14971
|
-
}
|
|
14972
|
-
function migrationBackupLockPath() {
|
|
14973
|
-
return path7.join(getCacheDir(), "migration-backups", `${MIGRATION_BACKUP_VERSION}.lock`);
|
|
14974
|
-
}
|
|
14975
|
-
function expectedSourcePaths() {
|
|
14976
|
-
return {
|
|
14977
|
-
"config.json": getConfigPath(),
|
|
14978
|
-
"state.db": getStateDbPathInDataDir(),
|
|
14979
|
-
"workflow.db": getWorkflowDbPath()
|
|
14980
|
-
};
|
|
14981
|
-
}
|
|
14982
|
-
function sha256File(filePath) {
|
|
14983
|
-
return crypto.createHash("sha256").update(fs7.readFileSync(filePath)).digest("hex");
|
|
14984
|
-
}
|
|
14985
|
-
function ownerOnlyMode(filePath, directory) {
|
|
14986
|
-
if (process.platform === "win32")
|
|
14987
|
-
return true;
|
|
14988
|
-
const mode = fs7.statSync(filePath).mode & 511;
|
|
14989
|
-
return mode === (directory ? 448 : 384);
|
|
14990
|
-
}
|
|
14991
|
-
function fsyncDirectory(directory) {
|
|
14992
|
-
try {
|
|
14993
|
-
const fd = fs7.openSync(directory, "r");
|
|
14994
|
-
try {
|
|
14995
|
-
fs7.fsyncSync(fd);
|
|
14996
|
-
} finally {
|
|
14997
|
-
fs7.closeSync(fd);
|
|
14998
|
-
}
|
|
14999
|
-
} catch (error) {
|
|
15000
|
-
const code = error.code;
|
|
15001
|
-
if (code !== "EINVAL" && code !== "ENOTSUP" && code !== "EISDIR" && code !== "EPERM")
|
|
15002
|
-
throw error;
|
|
15003
|
-
}
|
|
15004
|
-
}
|
|
15005
|
-
function parseManifest(bundlePath) {
|
|
15006
|
-
const manifestPath = path7.join(bundlePath, "manifest.json");
|
|
15007
|
-
let value;
|
|
15008
|
-
try {
|
|
15009
|
-
value = JSON.parse(fs7.readFileSync(manifestPath, "utf8"));
|
|
15010
|
-
} catch (error) {
|
|
15011
|
-
throw new ConfigError(`Migration backup at ${bundlePath} is incomplete or unreadable: ${error instanceof Error ? error.message : String(error)}. Remove it only after preserving any recoverable files, then retry backup creation.`, "INVALID_CONFIG_FILE");
|
|
15012
|
-
}
|
|
15013
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
15014
|
-
throw new ConfigError(`Migration backup manifest at ${manifestPath} is invalid.`, "INVALID_CONFIG_FILE");
|
|
15015
|
-
}
|
|
15016
|
-
const manifest = value;
|
|
15017
|
-
if (manifest.version !== MIGRATION_BACKUP_VERSION || typeof manifest.createdAt !== "string") {
|
|
15018
|
-
throw new ConfigError(`Migration backup manifest at ${manifestPath} has an unsupported version.`, "INVALID_CONFIG_FILE");
|
|
15019
|
-
}
|
|
15020
|
-
const expected = expectedSourcePaths();
|
|
15021
|
-
for (const name of ARTIFACT_NAMES) {
|
|
15022
|
-
const artifact = manifest.artifacts?.[name];
|
|
15023
|
-
if (!artifact || artifact.sourcePath !== expected[name] || typeof artifact.present !== "boolean" || !Number.isSafeInteger(artifact.byteSize) || artifact.byteSize < 0 || typeof artifact.createdAt !== "string" || (artifact.present ? typeof artifact.sha256 !== "string" || artifact.sha256.length !== 64 : artifact.sha256 !== null)) {
|
|
15024
|
-
throw new ConfigError(`Migration backup manifest has an invalid ${name} entry.`, "INVALID_CONFIG_FILE");
|
|
15025
|
-
}
|
|
15026
|
-
}
|
|
15027
|
-
return manifest;
|
|
15028
|
-
}
|
|
15029
|
-
function verifyMigrationBackup(bundlePath = getMigrationBackupDir()) {
|
|
15030
|
-
if (!fs7.existsSync(bundlePath) || !fs7.statSync(bundlePath).isDirectory()) {
|
|
15031
|
-
throw new ConfigError(`Migration backup does not exist at ${bundlePath}.`, "INVALID_CONFIG_FILE");
|
|
15032
|
-
}
|
|
15033
|
-
if (!ownerOnlyMode(bundlePath, true)) {
|
|
15034
|
-
throw new ConfigError(`Migration backup directory ${bundlePath} must have mode 0700.`, "INVALID_CONFIG_FILE");
|
|
15035
|
-
}
|
|
15036
|
-
const manifest = parseManifest(bundlePath);
|
|
15037
|
-
const expectedFiles = new Set(["manifest.json"]);
|
|
15038
|
-
for (const name of ARTIFACT_NAMES) {
|
|
15039
|
-
const artifact = manifest.artifacts[name];
|
|
15040
|
-
const artifactPath = path7.join(bundlePath, name);
|
|
15041
|
-
if (!artifact.present) {
|
|
15042
|
-
if (fs7.existsSync(artifactPath)) {
|
|
15043
|
-
throw new ConfigError(`Migration backup contains ${name}, but its manifest records it absent.`, "INVALID_CONFIG_FILE");
|
|
15044
|
-
}
|
|
15045
|
-
continue;
|
|
15046
|
-
}
|
|
15047
|
-
expectedFiles.add(name);
|
|
15048
|
-
if (!fs7.existsSync(artifactPath) || !fs7.statSync(artifactPath).isFile()) {
|
|
15049
|
-
throw new ConfigError(`Migration backup is missing ${artifactPath}.`, "INVALID_CONFIG_FILE");
|
|
15050
|
-
}
|
|
15051
|
-
if (!ownerOnlyMode(artifactPath, false)) {
|
|
15052
|
-
throw new ConfigError(`Migration backup artifact ${artifactPath} must have mode 0600.`, "INVALID_CONFIG_FILE");
|
|
15053
|
-
}
|
|
15054
|
-
const stat = fs7.statSync(artifactPath);
|
|
15055
|
-
if (stat.size !== artifact.byteSize || sha256File(artifactPath) !== artifact.sha256) {
|
|
15056
|
-
throw new ConfigError(`Migration backup artifact ${artifactPath} failed checksum verification.`, "INVALID_CONFIG_FILE");
|
|
15057
|
-
}
|
|
15058
|
-
}
|
|
15059
|
-
if (!ownerOnlyMode(path7.join(bundlePath, "manifest.json"), false)) {
|
|
15060
|
-
throw new ConfigError(`Migration backup manifest must have mode 0600.`, "INVALID_CONFIG_FILE");
|
|
15061
|
-
}
|
|
15062
|
-
const extras = fs7.readdirSync(bundlePath).filter((name) => !expectedFiles.has(name));
|
|
15063
|
-
if (extras.length > 0) {
|
|
15064
|
-
throw new ConfigError(`Migration backup contains unexpected files: ${extras.join(", ")}.`, "INVALID_CONFIG_FILE");
|
|
15065
|
-
}
|
|
15066
|
-
return manifest;
|
|
15067
|
-
}
|
|
15068
|
-
function acquireMigrationBackupLock() {
|
|
15069
|
-
const lockPath = migrationBackupLockPath();
|
|
15070
|
-
fs7.mkdirSync(path7.dirname(lockPath), { recursive: true, mode: 448 });
|
|
15071
|
-
fs7.chmodSync(path7.dirname(lockPath), 448);
|
|
15072
|
-
for (let attempt = 0;attempt < 50; attempt += 1) {
|
|
15073
|
-
const ownership = tryAcquireLockSync(lockPath, createLockPayload());
|
|
15074
|
-
if (ownership)
|
|
15075
|
-
return () => releaseLock(ownership);
|
|
15076
|
-
const probe = probeLock(lockPath);
|
|
15077
|
-
if (probe.state === "stale" && reclaimStaleLock(lockPath, probe)) {
|
|
15078
|
-
continue;
|
|
15079
|
-
}
|
|
15080
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
|
|
15081
|
-
}
|
|
15082
|
-
throw new ConfigError(`Timed out waiting for migration backup lock at ${lockPath}.`, "INVALID_CONFIG_FILE");
|
|
15083
|
-
}
|
|
15084
|
-
function assertNotAlreadyCutOver() {
|
|
15085
|
-
const configPath = getConfigPath();
|
|
15086
|
-
let text;
|
|
15087
|
-
try {
|
|
15088
|
-
text = fs7.readFileSync(configPath, "utf8");
|
|
15089
|
-
} catch (error) {
|
|
15090
|
-
if (error.code === "ENOENT")
|
|
15091
|
-
return;
|
|
15092
|
-
throw error;
|
|
15093
|
-
}
|
|
15094
|
-
const raw = parseConfigText(text, configPath);
|
|
15095
|
-
if (raw.configVersion === CURRENT_CONFIG_VERSION) {
|
|
15096
|
-
throw new ConfigError(`Refusing to create a pre-0.9 migration backup from an existing ${CURRENT_CONFIG_VERSION} config at ${configPath}. Restore the original migration bundle or pre-cutover config before retrying.`, "INVALID_CONFIG_FILE");
|
|
15097
|
-
}
|
|
15098
|
-
}
|
|
15099
|
-
function copyRegularArtifact(source, destination) {
|
|
15100
|
-
const stat = fs7.statSync(source);
|
|
15101
|
-
if (!stat.isFile())
|
|
15102
|
-
throw new ConfigError(`Backup source is not a regular file: ${source}`, "INVALID_CONFIG_FILE");
|
|
15103
|
-
writeFileAtomic(destination, fs7.readFileSync(source), 384);
|
|
15104
|
-
fs7.chmodSync(destination, 384);
|
|
15105
|
-
}
|
|
15106
|
-
function sqliteQuote(value) {
|
|
15107
|
-
return `'${value.replaceAll("'", "''")}'`;
|
|
15108
|
-
}
|
|
15109
|
-
function backupSqlite(source, destination) {
|
|
15110
|
-
const stat = fs7.statSync(source);
|
|
15111
|
-
if (!stat.isFile())
|
|
15112
|
-
throw new ConfigError(`SQLite backup source is not a regular file: ${source}`, "INVALID_CONFIG_FILE");
|
|
15113
|
-
const resolvedSource = path7.resolve(source);
|
|
15114
|
-
const activityName = resolvedSource === path7.resolve(getWorkflowDbPath()) ? "workflow-db" : resolvedSource === path7.resolve(getStateDbPathInDataDir()) ? "state-db" : undefined;
|
|
15115
|
-
const releaseActivity = activityName ? acquireMaintenanceActivitySync(activityName) : undefined;
|
|
15116
|
-
let db;
|
|
15117
|
-
try {
|
|
15118
|
-
db = openDatabase(source);
|
|
15119
|
-
db.exec("PRAGMA busy_timeout = 10000");
|
|
15120
|
-
db.exec("PRAGMA wal_checkpoint(FULL)");
|
|
15121
|
-
db.exec(`VACUUM INTO ${sqliteQuote(destination)}`);
|
|
15122
|
-
} finally {
|
|
15123
|
-
try {
|
|
15124
|
-
db?.close();
|
|
15125
|
-
} finally {
|
|
15126
|
-
releaseActivity?.();
|
|
15127
|
-
}
|
|
15128
|
-
}
|
|
15129
|
-
fs7.chmodSync(destination, 384);
|
|
15130
|
-
const fd = fs7.openSync(destination, "r");
|
|
15131
|
-
try {
|
|
15132
|
-
fs7.fsyncSync(fd);
|
|
15133
|
-
} finally {
|
|
15134
|
-
fs7.closeSync(fd);
|
|
15135
|
-
}
|
|
15136
|
-
}
|
|
15137
|
-
function createMigrationBackupUnlocked() {
|
|
15138
|
-
const bundlePath = getMigrationBackupDir();
|
|
15139
|
-
if (fs7.existsSync(bundlePath)) {
|
|
15140
|
-
return { path: bundlePath, created: false, manifest: verifyMigrationBackup(bundlePath) };
|
|
15141
|
-
}
|
|
15142
|
-
assertNotAlreadyCutOver();
|
|
15143
|
-
const parent = path7.dirname(bundlePath);
|
|
15144
|
-
fs7.mkdirSync(parent, { recursive: true, mode: 448 });
|
|
15145
|
-
fs7.chmodSync(parent, 448);
|
|
15146
|
-
const temporary = path7.join(parent, `.0.9.0.tmp.${process.pid}.${crypto.randomBytes(8).toString("hex")}`);
|
|
15147
|
-
fs7.mkdirSync(temporary, { mode: 448 });
|
|
15148
|
-
const createdAt = new Date().toISOString();
|
|
15149
|
-
const sources = expectedSourcePaths();
|
|
15150
|
-
const artifacts = {};
|
|
15151
|
-
try {
|
|
15152
|
-
for (const name of ARTIFACT_NAMES) {
|
|
15153
|
-
const sourcePath = sources[name];
|
|
15154
|
-
const destination = path7.join(temporary, name);
|
|
15155
|
-
let present = true;
|
|
15156
|
-
try {
|
|
15157
|
-
fs7.statSync(sourcePath);
|
|
15158
|
-
} catch (error) {
|
|
15159
|
-
if (error.code === "ENOENT")
|
|
15160
|
-
present = false;
|
|
15161
|
-
else
|
|
15162
|
-
throw error;
|
|
15163
|
-
}
|
|
15164
|
-
if (present) {
|
|
15165
|
-
if (name === "config.json")
|
|
15166
|
-
copyRegularArtifact(sourcePath, destination);
|
|
15167
|
-
else
|
|
15168
|
-
backupSqlite(sourcePath, destination);
|
|
15169
|
-
}
|
|
15170
|
-
const byteSize = present ? fs7.statSync(destination).size : 0;
|
|
15171
|
-
artifacts[name] = {
|
|
15172
|
-
sourcePath,
|
|
15173
|
-
present,
|
|
15174
|
-
byteSize,
|
|
15175
|
-
sha256: present ? sha256File(destination) : null,
|
|
15176
|
-
createdAt
|
|
15177
|
-
};
|
|
15178
|
-
}
|
|
15179
|
-
const manifest = { version: MIGRATION_BACKUP_VERSION, createdAt, artifacts };
|
|
15180
|
-
writeFileAtomic(path7.join(temporary, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
15181
|
-
`, 384);
|
|
15182
|
-
fs7.chmodSync(path7.join(temporary, "manifest.json"), 384);
|
|
15183
|
-
fsyncDirectory(temporary);
|
|
15184
|
-
fs7.renameSync(temporary, bundlePath);
|
|
15185
|
-
fsyncDirectory(parent);
|
|
15186
|
-
return { path: bundlePath, created: true, manifest: verifyMigrationBackup(bundlePath) };
|
|
15187
|
-
} catch (error) {
|
|
15188
|
-
fs7.rmSync(temporary, { recursive: true, force: true });
|
|
15189
|
-
if (fs7.existsSync(bundlePath)) {
|
|
15190
|
-
throw new ConfigError(`Migration backup creation raced with another writer and left ${bundlePath}. Verify or preserve it before retrying.`, "INVALID_CONFIG_FILE");
|
|
15191
|
-
}
|
|
15192
|
-
throw error;
|
|
15193
|
-
}
|
|
15194
|
-
}
|
|
15195
|
-
function withMigrationBackupLock(fn) {
|
|
15196
|
-
const release = acquireMigrationBackupLock();
|
|
15197
|
-
try {
|
|
15198
|
-
return fn();
|
|
15199
|
-
} finally {
|
|
15200
|
-
release();
|
|
15201
|
-
}
|
|
15202
|
-
}
|
|
15203
|
-
function createMigrationBackup() {
|
|
15204
|
-
return withConfigLock(() => withMigrationBackupLock(createMigrationBackupUnlocked));
|
|
15205
|
-
}
|
|
15206
|
-
function ensureMigrationBackup() {
|
|
15207
|
-
return createMigrationBackup();
|
|
15208
|
-
}
|
|
15209
|
-
var MIGRATION_BACKUP_VERSION = "0.9.0", ARTIFACT_NAMES;
|
|
15210
|
-
var init_migration_backup = __esm(() => {
|
|
15211
|
-
init_database();
|
|
15212
|
-
init_common();
|
|
15213
|
-
init_config_io();
|
|
15214
|
-
init_config_schema();
|
|
15215
|
-
init_errors();
|
|
15216
|
-
init_file_lock();
|
|
15217
|
-
init_maintenance_barrier();
|
|
15218
|
-
init_paths();
|
|
15219
|
-
ARTIFACT_NAMES = ["config.json", "state.db", "workflow.db"];
|
|
14786
|
+
});
|
|
15220
14787
|
});
|
|
15221
14788
|
|
|
15222
14789
|
// src/core/config/deep-merge.ts
|
|
@@ -15396,7 +14963,7 @@ var init_engine_resolution = __esm(() => {
|
|
|
15396
14963
|
});
|
|
15397
14964
|
|
|
15398
14965
|
// src/core/warn.ts
|
|
15399
|
-
import
|
|
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,37 @@ __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
|
+
preflight.exec("PRAGMA busy_timeout = 30000");
|
|
17741
|
+
if (isCanonical)
|
|
17742
|
+
assertCurrentMigrationLedger(preflight, STATE_MIGRATIONS);
|
|
17743
|
+
else
|
|
17744
|
+
assertMigrationLedger(preflight, STATE_MIGRATIONS);
|
|
17745
|
+
} finally {
|
|
17746
|
+
preflight.close();
|
|
17747
|
+
}
|
|
17748
|
+
}
|
|
17963
17749
|
const db = openManagedDatabase({
|
|
17964
17750
|
path: resolvedPath,
|
|
17965
|
-
init: (db2) => runMigrations2(db2, {
|
|
17751
|
+
init: (db2) => runMigrations2(db2, { applyPending: !(isCanonical && existed) })
|
|
17966
17752
|
});
|
|
17967
17753
|
if (!releaseActivity)
|
|
17968
17754
|
return db;
|
|
@@ -18001,7 +17787,7 @@ function isRetryableBeginError(err) {
|
|
|
18001
17787
|
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
18002
17788
|
return msg.includes("database is locked") || msg.includes("database table is locked") || msg.includes("did not open a transaction");
|
|
18003
17789
|
}
|
|
18004
|
-
function
|
|
17790
|
+
function sleepSyncMs(ms) {
|
|
18005
17791
|
if (ms <= 0)
|
|
18006
17792
|
return;
|
|
18007
17793
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
@@ -18025,7 +17811,7 @@ function withImmediateTransaction(db, fn) {
|
|
|
18025
17811
|
db.exec("ROLLBACK");
|
|
18026
17812
|
} catch {}
|
|
18027
17813
|
}
|
|
18028
|
-
|
|
17814
|
+
sleepSyncMs(2 ** (attempt - 1));
|
|
18029
17815
|
continue;
|
|
18030
17816
|
}
|
|
18031
17817
|
throw err;
|
|
@@ -18056,9 +17842,11 @@ function listExistingTableNames(db, names) {
|
|
|
18056
17842
|
}
|
|
18057
17843
|
var WITH_IMMEDIATE_TX_MAX_ATTEMPTS = 5;
|
|
18058
17844
|
var init_state_db = __esm(() => {
|
|
17845
|
+
init_database();
|
|
17846
|
+
init_sqlite_migrations();
|
|
18059
17847
|
init_managed_db();
|
|
18060
17848
|
init_maintenance_barrier();
|
|
18061
|
-
|
|
17849
|
+
init_migration_operation();
|
|
18062
17850
|
init_paths();
|
|
18063
17851
|
init_migrations();
|
|
18064
17852
|
});
|
|
@@ -18068,6 +17856,7 @@ var exports_events_repository = {};
|
|
|
18068
17856
|
__export(exports_events_repository, {
|
|
18069
17857
|
readStateEvents: () => readStateEvents,
|
|
18070
17858
|
purgeOldEvents: () => purgeOldEvents,
|
|
17859
|
+
insertEventOnce: () => insertEventOnce,
|
|
18071
17860
|
insertEvent: () => insertEvent,
|
|
18072
17861
|
importEventsJsonl: () => importEventsJsonl,
|
|
18073
17862
|
eventRowToEnvelope: () => eventRowToEnvelope
|
|
@@ -18101,6 +17890,22 @@ function insertEvent(db, input) {
|
|
|
18101
17890
|
return;
|
|
18102
17891
|
}
|
|
18103
17892
|
}
|
|
17893
|
+
function insertEventOnce(db, input) {
|
|
17894
|
+
const rows = db.prepare("SELECT id, metadata_json FROM events WHERE event_type = ? AND ref IS ? ORDER BY id").all(input.eventType, input.ref ?? null);
|
|
17895
|
+
for (const row of rows) {
|
|
17896
|
+
try {
|
|
17897
|
+
const metadata = JSON.parse(row.metadata_json);
|
|
17898
|
+
if (metadata[input.idempotencyMetadataKey ?? "proposalTransactionId"] === input.idempotencyKey)
|
|
17899
|
+
return row.id;
|
|
17900
|
+
} catch {}
|
|
17901
|
+
}
|
|
17902
|
+
const result = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
|
|
17903
|
+
VALUES (?, ?, ?, ?)
|
|
17904
|
+
RETURNING id`).get(input.eventType, input.ts, input.ref ?? null, JSON.stringify(input.metadata));
|
|
17905
|
+
if (!result)
|
|
17906
|
+
throw new Error(`Failed to persist ${input.eventType} event.`);
|
|
17907
|
+
return result.id;
|
|
17908
|
+
}
|
|
18104
17909
|
function readStateEvents(db, options = {}) {
|
|
18105
17910
|
const conditions = [];
|
|
18106
17911
|
const params = [];
|
|
@@ -18189,10 +17994,12 @@ var init_events_repository = __esm(() => {
|
|
|
18189
17994
|
var exports_task_history_repository = {};
|
|
18190
17995
|
__export(exports_task_history_repository, {
|
|
18191
17996
|
upsertTaskHistory: () => upsertTaskHistory,
|
|
17997
|
+
reserveTaskHistoryAttempt: () => reserveTaskHistoryAttempt,
|
|
18192
17998
|
queryTaskHistory: () => queryTaskHistory,
|
|
18193
17999
|
queryCompletedTaskIntervals: () => queryCompletedTaskIntervals,
|
|
18194
18000
|
getTaskHistoryRuns: () => getTaskHistoryRuns,
|
|
18195
18001
|
getTaskHistory: () => getTaskHistory,
|
|
18002
|
+
finalizeTaskHistoryAttempt: () => finalizeTaskHistoryAttempt,
|
|
18196
18003
|
decodeTaskHistoryMetadata: () => decodeTaskHistoryMetadata
|
|
18197
18004
|
});
|
|
18198
18005
|
function metadataError(message) {
|
|
@@ -18271,6 +18078,21 @@ function decodeTaskHistoryMetadata(input) {
|
|
|
18271
18078
|
}
|
|
18272
18079
|
metadataError(`unsupported metadataVersion: ${String(version)}`);
|
|
18273
18080
|
}
|
|
18081
|
+
function reserveTaskHistoryAttempt(db, row) {
|
|
18082
|
+
const result = db.prepare(`INSERT OR IGNORE INTO task_history
|
|
18083
|
+
(task_id, status, started_at, completed_at, failed_at, log_path,
|
|
18084
|
+
target_kind, target_ref, metadata_json)
|
|
18085
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.task_id, row.status, row.started_at, row.completed_at ?? null, row.failed_at ?? null, row.log_path ?? null, row.target_kind ?? null, row.target_ref ?? null, row.metadata_json);
|
|
18086
|
+
return Number(result.changes) === 1;
|
|
18087
|
+
}
|
|
18088
|
+
function finalizeTaskHistoryAttempt(db, row) {
|
|
18089
|
+
const result = db.prepare(`UPDATE task_history
|
|
18090
|
+
SET status = ?, completed_at = ?, failed_at = ?, log_path = ?,
|
|
18091
|
+
target_kind = ?, target_ref = ?, metadata_json = ?
|
|
18092
|
+
WHERE task_id = ? AND started_at = ?
|
|
18093
|
+
AND status = 'active' AND completed_at IS NULL`).run(row.status, row.completed_at ?? null, row.failed_at ?? null, row.log_path ?? null, row.target_kind ?? null, row.target_ref ?? null, row.metadata_json, row.task_id, row.started_at);
|
|
18094
|
+
return Number(result.changes) === 1;
|
|
18095
|
+
}
|
|
18274
18096
|
function upsertTaskHistory(db, row) {
|
|
18275
18097
|
db.prepare(`
|
|
18276
18098
|
INSERT OR IGNORE INTO task_history
|
|
@@ -18282,12 +18104,12 @@ function upsertTaskHistory(db, row) {
|
|
|
18282
18104
|
function getTaskHistory(db, taskId) {
|
|
18283
18105
|
return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
|
|
18284
18106
|
target_kind, target_ref, metadata_json
|
|
18285
|
-
FROM task_history WHERE task_id = ? ORDER BY started_at DESC LIMIT 1`).get(taskId);
|
|
18107
|
+
FROM task_history WHERE task_id = ? ORDER BY started_at DESC, id DESC LIMIT 1`).get(taskId);
|
|
18286
18108
|
}
|
|
18287
18109
|
function getTaskHistoryRuns(db, taskId, limit = 50) {
|
|
18288
18110
|
return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
|
|
18289
18111
|
target_kind, target_ref, metadata_json
|
|
18290
|
-
FROM task_history WHERE task_id = ? ORDER BY started_at DESC LIMIT ?`).all(taskId, limit);
|
|
18112
|
+
FROM task_history WHERE task_id = ? ORDER BY started_at DESC, id DESC LIMIT ?`).all(taskId, limit);
|
|
18291
18113
|
}
|
|
18292
18114
|
function queryTaskHistory(db, options = {}) {
|
|
18293
18115
|
const conditions = [];
|
|
@@ -18315,7 +18137,7 @@ function queryTaskHistory(db, options = {}) {
|
|
|
18315
18137
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
18316
18138
|
return db.prepare(`SELECT task_id, status, started_at, completed_at, failed_at, log_path,
|
|
18317
18139
|
target_kind, target_ref, metadata_json
|
|
18318
|
-
FROM task_history ${where} ORDER BY started_at DESC`).all(...params);
|
|
18140
|
+
FROM task_history ${where} ORDER BY started_at DESC, id DESC`).all(...params);
|
|
18319
18141
|
}
|
|
18320
18142
|
function queryCompletedTaskIntervals(db, since, until) {
|
|
18321
18143
|
const sql = until ? "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND started_at < ? AND completed_at IS NOT NULL ORDER BY started_at" : "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND completed_at IS NOT NULL ORDER BY started_at";
|
|
@@ -18966,9 +18788,9 @@ __export(exports_db, {
|
|
|
18966
18788
|
UTILITY_REVIEW_THRESHOLD: () => UTILITY_REVIEW_THRESHOLD,
|
|
18967
18789
|
HIGH_UTILITY_THRESHOLD: () => HIGH_UTILITY_THRESHOLD
|
|
18968
18790
|
});
|
|
18969
|
-
import
|
|
18791
|
+
import fs13 from "node:fs";
|
|
18970
18792
|
import { createRequire as createRequire6 } from "node:module";
|
|
18971
|
-
import
|
|
18793
|
+
import path13 from "node:path";
|
|
18972
18794
|
function openIndexDatabase(dbPath, options) {
|
|
18973
18795
|
return openManagedDatabase({
|
|
18974
18796
|
path: dbPath ?? getDbPath(),
|
|
@@ -18998,7 +18820,7 @@ function openExistingDatabase2(dbPath) {
|
|
|
18998
18820
|
}
|
|
18999
18821
|
function openReadonlyExistingDatabase(dbPath) {
|
|
19000
18822
|
const resolvedPath = dbPath ?? getDbPath();
|
|
19001
|
-
if (!
|
|
18823
|
+
if (!fs13.existsSync(resolvedPath))
|
|
19002
18824
|
return;
|
|
19003
18825
|
return openDatabase(resolvedPath, { readonly: true, create: false });
|
|
19004
18826
|
}
|
|
@@ -19073,7 +18895,7 @@ function deleteIndexDirState(db, dirPath) {
|
|
|
19073
18895
|
db.prepare("DELETE FROM index_dir_state WHERE dir_path = ?").run(dirPath);
|
|
19074
18896
|
}
|
|
19075
18897
|
function deleteIndexDirStatesByStashDir(db, stashDir) {
|
|
19076
|
-
db.prepare("DELETE FROM index_dir_state WHERE dir_path = ? OR dir_path LIKE ?").run(stashDir, `${stashDir}${
|
|
18898
|
+
db.prepare("DELETE FROM index_dir_state WHERE dir_path = ? OR dir_path LIKE ?").run(stashDir, `${stashDir}${path13.sep}%`);
|
|
19077
18899
|
}
|
|
19078
18900
|
function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, searchText) {
|
|
19079
18901
|
const stmts = getUpsertStmts(db);
|
|
@@ -19148,16 +18970,19 @@ function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
|
19148
18970
|
return out;
|
|
19149
18971
|
}
|
|
19150
18972
|
function rekeyEntryInPlace(db, opts) {
|
|
19151
|
-
const row = db.prepare("SELECT id, entry_json, search_text FROM entries WHERE entry_key = ?").get(opts.oldEntryKey);
|
|
18973
|
+
const row = db.prepare("SELECT id, stash_dir, entry_json, search_text FROM entries WHERE entry_key = ?").get(opts.oldEntryKey);
|
|
19152
18974
|
if (!row)
|
|
19153
18975
|
return null;
|
|
18976
|
+
if (opts.sourceRoot && path13.resolve(row.stash_dir) !== path13.resolve(opts.sourceRoot)) {
|
|
18977
|
+
throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: source root does not match.`);
|
|
18978
|
+
}
|
|
19154
18979
|
let entryJson = row.entry_json;
|
|
19155
18980
|
let searchText = row.search_text;
|
|
19156
18981
|
try {
|
|
19157
18982
|
const entry = JSON.parse(row.entry_json);
|
|
19158
18983
|
entry.name = opts.newName;
|
|
19159
18984
|
if (typeof entry.filename === "string")
|
|
19160
|
-
entry.filename =
|
|
18985
|
+
entry.filename = path13.basename(opts.newFilePath);
|
|
19161
18986
|
if (opts.newDerivedFrom !== undefined)
|
|
19162
18987
|
entry.derivedFrom = opts.newDerivedFrom;
|
|
19163
18988
|
entryJson = JSON.stringify(entry);
|
|
@@ -19169,22 +18994,32 @@ function rekeyEntryInPlace(db, opts) {
|
|
|
19169
18994
|
deleteRelatedRows(db, [{ id: stale.id }]);
|
|
19170
18995
|
db.prepare("DELETE FROM entries WHERE id = ?").run(stale.id);
|
|
19171
18996
|
}
|
|
19172
|
-
db.prepare("UPDATE entries SET entry_key = ?, dir_path = ?, file_path = ?, entry_json = ?, search_text = ? WHERE id = ?").run(opts.newEntryKey,
|
|
18997
|
+
db.prepare("UPDATE entries SET entry_key = ?, dir_path = ?, file_path = ?, entry_json = ?, search_text = ? WHERE id = ?").run(opts.newEntryKey, path13.dirname(opts.newFilePath), opts.newFilePath, entryJson, searchText, row.id);
|
|
19173
18998
|
if (opts.newDerivedFrom !== undefined) {
|
|
19174
18999
|
db.prepare("UPDATE entries SET derived_from = ? WHERE id = ?").run(opts.newDerivedFrom, row.id);
|
|
19175
19000
|
}
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
WHERE
|
|
19186
|
-
|
|
19187
|
-
|
|
19001
|
+
try {
|
|
19002
|
+
if (opts.sourceName) {
|
|
19003
|
+
const origins = new Set([opts.sourceName]);
|
|
19004
|
+
if (opts.sourceName === "stash" && opts.includeLegacyBare)
|
|
19005
|
+
origins.add("local");
|
|
19006
|
+
for (const origin of origins) {
|
|
19007
|
+
const oldQualifiedRef = `${origin}//${opts.oldRef}`;
|
|
19008
|
+
const newQualifiedRef = `${origin}//${opts.newRef}`;
|
|
19009
|
+
db.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(newQualifiedRef);
|
|
19010
|
+
db.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(newQualifiedRef, oldQualifiedRef);
|
|
19011
|
+
}
|
|
19012
|
+
}
|
|
19013
|
+
if (opts.includeLegacyBare || !opts.sourceName) {
|
|
19014
|
+
db.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(opts.newRef);
|
|
19015
|
+
db.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(opts.newRef, opts.oldRef);
|
|
19016
|
+
}
|
|
19017
|
+
} catch (error2) {
|
|
19018
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19019
|
+
const missingLegacyUsageSchema = /no such table:\s*(?:main\.)?usage_events\b/i.test(message) || /no such column:\s*(?:usage_events\.)?(?:entry_id|entry_ref)\b/i.test(message) || /table\s+usage_events\s+has no column named\s+(?:entry_id|entry_ref)\b/i.test(message);
|
|
19020
|
+
if (!missingLegacyUsageSchema)
|
|
19021
|
+
throw error2;
|
|
19022
|
+
}
|
|
19188
19023
|
db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)").run(row.id);
|
|
19189
19024
|
})();
|
|
19190
19025
|
return row.id;
|
|
@@ -19506,7 +19341,7 @@ function getEntitiesByEntryIds(db, entryIds) {
|
|
|
19506
19341
|
}
|
|
19507
19342
|
return result;
|
|
19508
19343
|
}
|
|
19509
|
-
function findEntryIdByRef(db, ref) {
|
|
19344
|
+
function findEntryIdByRef(db, ref, stashDir) {
|
|
19510
19345
|
const parsed = parseAssetRef(ref);
|
|
19511
19346
|
const nameVariants = [parsed.name];
|
|
19512
19347
|
if (parsed.name.endsWith(".md")) {
|
|
@@ -19514,10 +19349,14 @@ function findEntryIdByRef(db, ref) {
|
|
|
19514
19349
|
} else {
|
|
19515
19350
|
nameVariants.push(`${parsed.name}.md`);
|
|
19516
19351
|
}
|
|
19517
|
-
const stmt = db.prepare(
|
|
19352
|
+
const stmt = db.prepare(`SELECT id FROM entries
|
|
19353
|
+
WHERE entry_type = ?
|
|
19354
|
+
AND substr(entry_key, length(entry_key) - length(?) + 1) = ?
|
|
19355
|
+
${stashDir ? "AND stash_dir = ?" : ""}
|
|
19356
|
+
LIMIT 1`);
|
|
19518
19357
|
for (const name of nameVariants) {
|
|
19519
19358
|
const suffix = `${parsed.type}:${name}`;
|
|
19520
|
-
const row = stmt.get(parsed.type, suffix, suffix);
|
|
19359
|
+
const row = stmt.get(parsed.type, suffix, suffix, ...stashDir ? [stashDir] : []);
|
|
19521
19360
|
if (row)
|
|
19522
19361
|
return row.id;
|
|
19523
19362
|
}
|
|
@@ -19535,7 +19374,7 @@ function getEmbeddingCount(db) {
|
|
|
19535
19374
|
return row.cnt;
|
|
19536
19375
|
}
|
|
19537
19376
|
function getEntryById(db, id) {
|
|
19538
|
-
const row = db.prepare("SELECT file_path, entry_json FROM entries WHERE id = ?").get(id);
|
|
19377
|
+
const row = db.prepare("SELECT file_path, stash_dir, entry_json FROM entries WHERE id = ?").get(id);
|
|
19539
19378
|
if (!row)
|
|
19540
19379
|
return;
|
|
19541
19380
|
let entry;
|
|
@@ -19545,7 +19384,7 @@ function getEntryById(db, id) {
|
|
|
19545
19384
|
warn(`[db] getEntryById: skipping entry id=${id} — corrupt entry_json`);
|
|
19546
19385
|
return;
|
|
19547
19386
|
}
|
|
19548
|
-
return { filePath: row.file_path, entry };
|
|
19387
|
+
return { filePath: row.file_path, stashDir: row.stash_dir, entry };
|
|
19549
19388
|
}
|
|
19550
19389
|
function getEntriesByDir(db, dirPath) {
|
|
19551
19390
|
const rows = db.prepare(`SELECT ${ENTRY_COLUMNS} FROM entries WHERE dir_path = ?`).all(dirPath);
|
|
@@ -19694,9 +19533,12 @@ function bareRef(ref) {
|
|
|
19694
19533
|
return ref;
|
|
19695
19534
|
}
|
|
19696
19535
|
}
|
|
19697
|
-
function getRetrievalCounts(db, refs) {
|
|
19536
|
+
function getRetrievalCounts(db, refs, options = {}) {
|
|
19698
19537
|
if (refs.length === 0)
|
|
19699
19538
|
return new Map;
|
|
19539
|
+
if (options.sourceName || options.stashDir) {
|
|
19540
|
+
return getSourceScopedRetrievalCounts(db, refs, options);
|
|
19541
|
+
}
|
|
19700
19542
|
const bareToInputs = new Map;
|
|
19701
19543
|
for (const ref of refs) {
|
|
19702
19544
|
const bare = bareRef(ref);
|
|
@@ -19740,6 +19582,51 @@ function getRetrievalCounts(db, refs) {
|
|
|
19740
19582
|
}
|
|
19741
19583
|
return result;
|
|
19742
19584
|
}
|
|
19585
|
+
function getSourceScopedRetrievalCounts(db, refs, options) {
|
|
19586
|
+
const bareToInputs = new Map;
|
|
19587
|
+
for (const ref of refs) {
|
|
19588
|
+
const bare = bareRef(ref);
|
|
19589
|
+
const inputs = bareToInputs.get(bare);
|
|
19590
|
+
if (inputs)
|
|
19591
|
+
inputs.push(ref);
|
|
19592
|
+
else
|
|
19593
|
+
bareToInputs.set(bare, [ref]);
|
|
19594
|
+
}
|
|
19595
|
+
const countsByBare = new Map;
|
|
19596
|
+
const bareForms = [...bareToInputs.keys()];
|
|
19597
|
+
const selectedRoot = options.stashDir ? path13.resolve(options.stashDir) : undefined;
|
|
19598
|
+
for (let i = 0;i < bareForms.length; i += SQLITE_CHUNK_SIZE) {
|
|
19599
|
+
const chunk = bareForms.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
19600
|
+
const placeholders = chunk.map(() => "?").join(", ");
|
|
19601
|
+
const rows = db.prepare(`SELECT ue.entry_ref, ue.entry_id, e.stash_dir
|
|
19602
|
+
FROM usage_events ue
|
|
19603
|
+
LEFT JOIN entries e ON e.id = ue.entry_id
|
|
19604
|
+
WHERE ue.event_type IN ('search','show','curate')
|
|
19605
|
+
AND ue.entry_ref IS NOT NULL
|
|
19606
|
+
AND (ue.source IS NULL OR ue.source NOT IN ('improve','task'))
|
|
19607
|
+
AND CASE
|
|
19608
|
+
WHEN instr(ue.entry_ref, '//') > 0
|
|
19609
|
+
THEN substr(ue.entry_ref, instr(ue.entry_ref, '//') + 2)
|
|
19610
|
+
ELSE ue.entry_ref
|
|
19611
|
+
END IN (${placeholders})`).all(...chunk);
|
|
19612
|
+
for (const row of rows) {
|
|
19613
|
+
const bare = bareRef(row.entry_ref);
|
|
19614
|
+
const linkedToSelectedRoot = row.entry_id !== null && selectedRoot !== undefined && row.stash_dir !== null ? path13.resolve(row.stash_dir) === selectedRoot : false;
|
|
19615
|
+
const detached = row.entry_id === null || selectedRoot === undefined;
|
|
19616
|
+
const qualifiedForSource = detached && options.sourceName !== undefined && row.entry_ref === `${options.sourceName}//${bare}`;
|
|
19617
|
+
const acceptedLegacyBare = detached && options.includeLegacyBare === true && row.entry_ref === bare;
|
|
19618
|
+
if (!linkedToSelectedRoot && !qualifiedForSource && !acceptedLegacyBare)
|
|
19619
|
+
continue;
|
|
19620
|
+
countsByBare.set(bare, (countsByBare.get(bare) ?? 0) + 1);
|
|
19621
|
+
}
|
|
19622
|
+
}
|
|
19623
|
+
const result = new Map;
|
|
19624
|
+
for (const [bare, count] of countsByBare) {
|
|
19625
|
+
for (const input of bareToInputs.get(bare) ?? [])
|
|
19626
|
+
result.set(input, count);
|
|
19627
|
+
}
|
|
19628
|
+
return result;
|
|
19629
|
+
}
|
|
19743
19630
|
function bumpUtilityScoresBatch(db, entryIds, reward, lr = 0.1, scopeKey) {
|
|
19744
19631
|
if (entryIds.length === 0)
|
|
19745
19632
|
return;
|
|
@@ -19833,7 +19720,15 @@ function applyFeedbackToUtilityScore(db, entryId, positiveCount, negativeCount)
|
|
|
19833
19720
|
`).run(entryId, result.nextUtility, now, now, result.nextUtility, now);
|
|
19834
19721
|
return result;
|
|
19835
19722
|
}
|
|
19836
|
-
function
|
|
19723
|
+
function resolveUsageEventEntryId(db, ref, options) {
|
|
19724
|
+
const parsed = parseAssetRef(ref);
|
|
19725
|
+
if (!parsed.origin) {
|
|
19726
|
+
return options.defaultStashDir ? findEntryIdByRef(db, ref, options.defaultStashDir) : undefined;
|
|
19727
|
+
}
|
|
19728
|
+
const source = parsed.origin === "local" || parsed.origin === "stash" ? options.sources?.[0] : options.sources?.find((candidate) => candidate.registryId === parsed.origin);
|
|
19729
|
+
return source ? findEntryIdByRef(db, ref, source.path) : undefined;
|
|
19730
|
+
}
|
|
19731
|
+
function relinkUsageEvents(db, options = {}) {
|
|
19837
19732
|
bestEffort(() => {
|
|
19838
19733
|
db.exec(`
|
|
19839
19734
|
UPDATE usage_events
|
|
@@ -19847,7 +19742,7 @@ function relinkUsageEvents(db) {
|
|
|
19847
19742
|
for (const { ref } of refs) {
|
|
19848
19743
|
let id;
|
|
19849
19744
|
try {
|
|
19850
|
-
id =
|
|
19745
|
+
id = resolveUsageEventEntryId(db, ref, options);
|
|
19851
19746
|
} catch (err) {
|
|
19852
19747
|
if (err instanceof Error && err.name === "UsageError")
|
|
19853
19748
|
continue;
|
|
@@ -19912,12 +19807,12 @@ __export(exports_graph_db, {
|
|
|
19912
19807
|
drainExtractionQueue: () => drainExtractionQueue,
|
|
19913
19808
|
deleteStoredGraph: () => deleteStoredGraph
|
|
19914
19809
|
});
|
|
19915
|
-
import
|
|
19810
|
+
import fs14 from "node:fs";
|
|
19916
19811
|
function withReadableGraphDb(db, fn) {
|
|
19917
19812
|
if (db)
|
|
19918
19813
|
return fn(db);
|
|
19919
19814
|
const dbPath = getDbPath();
|
|
19920
|
-
if (!
|
|
19815
|
+
if (!fs14.existsSync(dbPath))
|
|
19921
19816
|
throw new Error("GRAPH_DB_MISSING");
|
|
19922
19817
|
const opened = openExistingDatabase(dbPath);
|
|
19923
19818
|
try {
|
|
@@ -20260,15 +20155,15 @@ var init_graph_db = __esm(() => {
|
|
|
20260
20155
|
});
|
|
20261
20156
|
|
|
20262
20157
|
// scripts/migrate-storage.ts
|
|
20263
|
-
import
|
|
20158
|
+
import fs15 from "fs";
|
|
20264
20159
|
import os3 from "os";
|
|
20265
|
-
import
|
|
20160
|
+
import path14 from "path";
|
|
20266
20161
|
import readline from "readline";
|
|
20267
20162
|
|
|
20268
20163
|
// src/core/paths.ts
|
|
20269
20164
|
init_common();
|
|
20270
20165
|
init_errors();
|
|
20271
|
-
import
|
|
20166
|
+
import path9 from "node:path";
|
|
20272
20167
|
function isTransientStashPath2(p) {
|
|
20273
20168
|
return p.startsWith("/tmp/") || p === "/tmp" || p.startsWith("/var/tmp/") || p === "/var/tmp" || p.startsWith("/private/tmp/") || p.startsWith("/private/var/folders/") || p.startsWith("/var/folders/");
|
|
20274
20169
|
}
|
|
@@ -20279,34 +20174,34 @@ function getConfigDir2(env = process.env, platform = process.platform) {
|
|
|
20279
20174
|
if (platform === "win32") {
|
|
20280
20175
|
const appData = env.APPDATA?.trim();
|
|
20281
20176
|
if (appData)
|
|
20282
|
-
return
|
|
20177
|
+
return path9.join(appData, "akm");
|
|
20283
20178
|
} else {
|
|
20284
20179
|
const xdgConfigHome2 = env.XDG_CONFIG_HOME?.trim();
|
|
20285
20180
|
if (xdgConfigHome2)
|
|
20286
|
-
return
|
|
20181
|
+
return path9.join(xdgConfigHome2, "akm");
|
|
20287
20182
|
}
|
|
20288
20183
|
const stashOverride = env.AKM_STASH_DIR?.trim();
|
|
20289
20184
|
if (stashOverride && isTransientStashPath2(stashOverride)) {
|
|
20290
|
-
return
|
|
20185
|
+
return path9.join(stashOverride, ".akm");
|
|
20291
20186
|
}
|
|
20292
20187
|
if (platform === "win32") {
|
|
20293
20188
|
const appData = env.APPDATA?.trim();
|
|
20294
20189
|
if (appData)
|
|
20295
|
-
return
|
|
20190
|
+
return path9.join(appData, "akm");
|
|
20296
20191
|
const userProfile = env.USERPROFILE?.trim();
|
|
20297
20192
|
if (!userProfile) {
|
|
20298
20193
|
throw new ConfigError("Unable to determine config directory. Set APPDATA or USERPROFILE.", "CONFIG_DIR_UNRESOLVABLE");
|
|
20299
20194
|
}
|
|
20300
|
-
return
|
|
20195
|
+
return path9.join(userProfile, "AppData", "Roaming", "akm");
|
|
20301
20196
|
}
|
|
20302
20197
|
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
|
|
20303
20198
|
if (xdgConfigHome)
|
|
20304
|
-
return
|
|
20199
|
+
return path9.join(xdgConfigHome, "akm");
|
|
20305
20200
|
const home = env.HOME?.trim();
|
|
20306
20201
|
if (!home) {
|
|
20307
20202
|
throw new ConfigError("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.", "CONFIG_DIR_UNRESOLVABLE");
|
|
20308
20203
|
}
|
|
20309
|
-
return
|
|
20204
|
+
return path9.join(home, ".config", "akm");
|
|
20310
20205
|
}
|
|
20311
20206
|
function getCacheDir2(env = process.env) {
|
|
20312
20207
|
const override = env.AKM_CACHE_DIR?.trim();
|
|
@@ -20315,30 +20210,30 @@ function getCacheDir2(env = process.env) {
|
|
|
20315
20210
|
if (IS_WINDOWS) {
|
|
20316
20211
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
20317
20212
|
if (localAppData)
|
|
20318
|
-
return
|
|
20213
|
+
return path9.join(localAppData, "akm");
|
|
20319
20214
|
const userProfile = env.USERPROFILE?.trim();
|
|
20320
20215
|
if (userProfile)
|
|
20321
|
-
return
|
|
20216
|
+
return path9.join(userProfile, "AppData", "Local", "akm");
|
|
20322
20217
|
const appData = env.APPDATA?.trim();
|
|
20323
20218
|
if (appData) {
|
|
20324
|
-
return
|
|
20219
|
+
return path9.join(appData, "..", "Local", "akm");
|
|
20325
20220
|
}
|
|
20326
20221
|
} else {
|
|
20327
20222
|
const xdgCacheHome = env.XDG_CACHE_HOME?.trim();
|
|
20328
20223
|
if (xdgCacheHome)
|
|
20329
|
-
return
|
|
20224
|
+
return path9.join(xdgCacheHome, "akm");
|
|
20330
20225
|
}
|
|
20331
20226
|
const stashOverride = env.AKM_STASH_DIR?.trim();
|
|
20332
20227
|
if (stashOverride && isTransientStashPath2(stashOverride)) {
|
|
20333
|
-
return
|
|
20228
|
+
return path9.join(stashOverride, ".akm", "cache");
|
|
20334
20229
|
}
|
|
20335
20230
|
if (IS_WINDOWS) {
|
|
20336
20231
|
throw new ConfigError("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
|
|
20337
20232
|
}
|
|
20338
20233
|
const home = env.HOME?.trim();
|
|
20339
20234
|
if (!home)
|
|
20340
|
-
return
|
|
20341
|
-
return
|
|
20235
|
+
return path9.join("/tmp", "akm-cache");
|
|
20236
|
+
return path9.join(home, ".cache", "akm");
|
|
20342
20237
|
}
|
|
20343
20238
|
function getDefaultStashDir2(env = process.env) {
|
|
20344
20239
|
const override = env.AKM_STASH_DIR?.trim();
|
|
@@ -20347,14 +20242,14 @@ function getDefaultStashDir2(env = process.env) {
|
|
|
20347
20242
|
if (IS_WINDOWS) {
|
|
20348
20243
|
const userProfile = env.USERPROFILE?.trim();
|
|
20349
20244
|
if (userProfile)
|
|
20350
|
-
return
|
|
20351
|
-
return
|
|
20245
|
+
return path9.join(userProfile, "Documents", "akm");
|
|
20246
|
+
return path9.join("C:\\", "akm");
|
|
20352
20247
|
}
|
|
20353
20248
|
const home = env.HOME?.trim();
|
|
20354
20249
|
if (!home) {
|
|
20355
20250
|
throw new ConfigError("Unable to determine default stash directory. Set HOME.", "STASH_DIR_NOT_FOUND");
|
|
20356
20251
|
}
|
|
20357
|
-
return
|
|
20252
|
+
return path9.join(home, "akm");
|
|
20358
20253
|
}
|
|
20359
20254
|
|
|
20360
20255
|
// src/indexer/usage/unmigrated-vaults-guard.ts
|
|
@@ -20379,12 +20274,12 @@ function parseFromArg() {
|
|
|
20379
20274
|
return val;
|
|
20380
20275
|
}
|
|
20381
20276
|
var FROM_VERSION = parseFromArg();
|
|
20382
|
-
var dataDir = process.env.AKM_DATA_DIR?.trim() ?? (process.env.XDG_DATA_HOME?.trim() ?
|
|
20383
|
-
var stateDir = process.env.AKM_STATE_DIR?.trim() ?? (process.env.XDG_STATE_HOME?.trim() ?
|
|
20277
|
+
var dataDir = process.env.AKM_DATA_DIR?.trim() ?? (process.env.XDG_DATA_HOME?.trim() ? path14.join(process.env.XDG_DATA_HOME.trim(), "akm") : path14.join(os3.homedir(), ".local", "share", "akm"));
|
|
20278
|
+
var stateDir = process.env.AKM_STATE_DIR?.trim() ?? (process.env.XDG_STATE_HOME?.trim() ? path14.join(process.env.XDG_STATE_HOME.trim(), "akm") : path14.join(os3.homedir(), ".local", "state", "akm"));
|
|
20384
20279
|
var cacheDir = getCacheDir2();
|
|
20385
20280
|
var configDir = getConfigDir2();
|
|
20386
|
-
var stateDbPath =
|
|
20387
|
-
var indexDbPath =
|
|
20281
|
+
var stateDbPath = path14.join(dataDir, "state.db");
|
|
20282
|
+
var indexDbPath = path14.join(dataDir, "index.db");
|
|
20388
20283
|
var PATHS = {
|
|
20389
20284
|
cacheDir,
|
|
20390
20285
|
configDir,
|
|
@@ -20395,14 +20290,14 @@ var PATHS = {
|
|
|
20395
20290
|
};
|
|
20396
20291
|
var versionReports = [];
|
|
20397
20292
|
function ensureDir(dir) {
|
|
20398
|
-
if (!
|
|
20399
|
-
|
|
20293
|
+
if (!fs15.existsSync(dir)) {
|
|
20294
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
20400
20295
|
}
|
|
20401
20296
|
}
|
|
20402
20297
|
function copyAndVerify(src, dest) {
|
|
20403
|
-
|
|
20404
|
-
const srcStat =
|
|
20405
|
-
const destStat =
|
|
20298
|
+
fs15.copyFileSync(src, dest);
|
|
20299
|
+
const srcStat = fs15.statSync(src);
|
|
20300
|
+
const destStat = fs15.statSync(dest);
|
|
20406
20301
|
return destStat.size === srcStat.size;
|
|
20407
20302
|
}
|
|
20408
20303
|
function copyTree(src, dest, opts) {
|
|
@@ -20410,9 +20305,9 @@ function copyTree(src, dest, opts) {
|
|
|
20410
20305
|
let skipped = 0;
|
|
20411
20306
|
let failed = 0;
|
|
20412
20307
|
ensureDir(dest);
|
|
20413
|
-
for (const entry of
|
|
20414
|
-
const srcEntry =
|
|
20415
|
-
const destEntry =
|
|
20308
|
+
for (const entry of fs15.readdirSync(src, { withFileTypes: true })) {
|
|
20309
|
+
const srcEntry = path14.join(src, entry.name);
|
|
20310
|
+
const destEntry = path14.join(dest, entry.name);
|
|
20416
20311
|
if (entry.isDirectory()) {
|
|
20417
20312
|
const sub = copyTree(srcEntry, destEntry, opts);
|
|
20418
20313
|
copied += sub.copied;
|
|
@@ -20420,7 +20315,7 @@ function copyTree(src, dest, opts) {
|
|
|
20420
20315
|
failed += sub.failed;
|
|
20421
20316
|
continue;
|
|
20422
20317
|
}
|
|
20423
|
-
if (!opts.clobber &&
|
|
20318
|
+
if (!opts.clobber && fs15.existsSync(destEntry)) {
|
|
20424
20319
|
skipped++;
|
|
20425
20320
|
continue;
|
|
20426
20321
|
}
|
|
@@ -20438,9 +20333,9 @@ function copyTree(src, dest, opts) {
|
|
|
20438
20333
|
}
|
|
20439
20334
|
function countFilesRecursive(dir) {
|
|
20440
20335
|
let count = 0;
|
|
20441
|
-
for (const entry of
|
|
20336
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
20442
20337
|
if (entry.isDirectory()) {
|
|
20443
|
-
count += countFilesRecursive(
|
|
20338
|
+
count += countFilesRecursive(path14.join(dir, entry.name));
|
|
20444
20339
|
} else {
|
|
20445
20340
|
count++;
|
|
20446
20341
|
}
|
|
@@ -20453,12 +20348,12 @@ async function runSteps(steps, ctx) {
|
|
|
20453
20348
|
}
|
|
20454
20349
|
}
|
|
20455
20350
|
function migrateDb(ctx, filename) {
|
|
20456
|
-
const src =
|
|
20457
|
-
const dest =
|
|
20458
|
-
if (!
|
|
20351
|
+
const src = path14.join(ctx.paths.cacheDir, filename);
|
|
20352
|
+
const dest = path14.join(ctx.paths.dataDir, filename);
|
|
20353
|
+
if (!fs15.existsSync(src)) {
|
|
20459
20354
|
return { name: filename, status: "skipped", detail: "source not found" };
|
|
20460
20355
|
}
|
|
20461
|
-
if (
|
|
20356
|
+
if (fs15.existsSync(dest)) {
|
|
20462
20357
|
return { name: filename, status: "skipped", detail: "destination already exists" };
|
|
20463
20358
|
}
|
|
20464
20359
|
if (ctx.dryRun) {
|
|
@@ -20476,8 +20371,8 @@ function migrateDb(ctx, filename) {
|
|
|
20476
20371
|
return { name: filename, status: "failed", detail: `size mismatch after copy: ${src} \u2192 ${dest}` };
|
|
20477
20372
|
}
|
|
20478
20373
|
async function migrateEventsJsonl(ctx) {
|
|
20479
|
-
const src =
|
|
20480
|
-
if (!
|
|
20374
|
+
const src = path14.join(ctx.paths.cacheDir, "events.jsonl");
|
|
20375
|
+
if (!fs15.existsSync(src)) {
|
|
20481
20376
|
return { name: "events.jsonl \u2192 state.db", status: "skipped", detail: "source not found" };
|
|
20482
20377
|
}
|
|
20483
20378
|
if (ctx.dryRun) {
|
|
@@ -20522,13 +20417,13 @@ async function migrateEventsJsonl(ctx) {
|
|
|
20522
20417
|
}
|
|
20523
20418
|
}
|
|
20524
20419
|
function migrateTaskHistory(ctx) {
|
|
20525
|
-
const src =
|
|
20526
|
-
const dest =
|
|
20527
|
-
if (!
|
|
20420
|
+
const src = path14.join(ctx.paths.cacheDir, "tasks", "history");
|
|
20421
|
+
const dest = path14.join(ctx.paths.stateDir, "tasks", "history");
|
|
20422
|
+
if (!fs15.existsSync(src)) {
|
|
20528
20423
|
return { name: "tasks/history/", status: "skipped", detail: "source directory not found" };
|
|
20529
20424
|
}
|
|
20530
20425
|
try {
|
|
20531
|
-
const files =
|
|
20426
|
+
const files = fs15.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
|
|
20532
20427
|
if (files.length === 0) {
|
|
20533
20428
|
return { name: "tasks/history/", status: "skipped", detail: "no *.jsonl files found in source directory" };
|
|
20534
20429
|
}
|
|
@@ -20543,8 +20438,8 @@ function migrateTaskHistory(ctx) {
|
|
|
20543
20438
|
let copied = 0;
|
|
20544
20439
|
let failed = 0;
|
|
20545
20440
|
for (const file of files) {
|
|
20546
|
-
const srcFile =
|
|
20547
|
-
const destFile =
|
|
20441
|
+
const srcFile = path14.join(src, file);
|
|
20442
|
+
const destFile = path14.join(dest, file);
|
|
20548
20443
|
try {
|
|
20549
20444
|
if (copyAndVerify(srcFile, destFile)) {
|
|
20550
20445
|
copied++;
|
|
@@ -20573,12 +20468,12 @@ function migrateTaskHistory(ctx) {
|
|
|
20573
20468
|
}
|
|
20574
20469
|
}
|
|
20575
20470
|
function migrateLockfile(ctx) {
|
|
20576
|
-
const src =
|
|
20577
|
-
const dest =
|
|
20578
|
-
if (!
|
|
20471
|
+
const src = path14.join(ctx.paths.configDir, "akm.lock");
|
|
20472
|
+
const dest = path14.join(ctx.paths.dataDir, "akm.lock");
|
|
20473
|
+
if (!fs15.existsSync(src)) {
|
|
20579
20474
|
return { name: "akm.lock", status: "skipped", detail: "source not found" };
|
|
20580
20475
|
}
|
|
20581
|
-
if (
|
|
20476
|
+
if (fs15.existsSync(dest)) {
|
|
20582
20477
|
return { name: "akm.lock", status: "skipped", detail: "destination already exists" };
|
|
20583
20478
|
}
|
|
20584
20479
|
if (ctx.dryRun) {
|
|
@@ -20598,12 +20493,12 @@ function migrateLockfile(ctx) {
|
|
|
20598
20493
|
return { name: "akm.lock", status: "failed", detail: `size mismatch after copy: ${src} \u2192 ${dest}` };
|
|
20599
20494
|
}
|
|
20600
20495
|
function migrateConfigBackups(ctx) {
|
|
20601
|
-
const src =
|
|
20602
|
-
const dest =
|
|
20603
|
-
if (!
|
|
20496
|
+
const src = path14.join(ctx.paths.cacheDir, "config-backups");
|
|
20497
|
+
const dest = path14.join(ctx.paths.dataDir, "config-backups");
|
|
20498
|
+
if (!fs15.existsSync(src)) {
|
|
20604
20499
|
return { name: "config-backups/", status: "skipped", detail: "source directory not found" };
|
|
20605
20500
|
}
|
|
20606
|
-
if (
|
|
20501
|
+
if (fs15.existsSync(dest)) {
|
|
20607
20502
|
return { name: "config-backups/", status: "skipped", detail: "destination already exists" };
|
|
20608
20503
|
}
|
|
20609
20504
|
if (ctx.dryRun) {
|
|
@@ -20620,7 +20515,7 @@ function migrateConfigBackups(ctx) {
|
|
|
20620
20515
|
try {
|
|
20621
20516
|
const srcCount = countFilesRecursive(src);
|
|
20622
20517
|
const { copied, failed } = copyTree(src, dest, { clobber: true });
|
|
20623
|
-
const destCount =
|
|
20518
|
+
const destCount = fs15.existsSync(dest) ? countFilesRecursive(dest) : 0;
|
|
20624
20519
|
if (failed === 0 && destCount === srcCount) {
|
|
20625
20520
|
return {
|
|
20626
20521
|
name: "config-backups/",
|
|
@@ -20639,11 +20534,11 @@ function migrateConfigBackups(ctx) {
|
|
|
20639
20534
|
}
|
|
20640
20535
|
}
|
|
20641
20536
|
async function migrateTaskHistoryToDb(ctx) {
|
|
20642
|
-
const src =
|
|
20643
|
-
if (!
|
|
20537
|
+
const src = path14.join(ctx.paths.cacheDir, "tasks", "history");
|
|
20538
|
+
if (!fs15.existsSync(src)) {
|
|
20644
20539
|
return { name: "tasks/history/ \u2192 state.db", status: "skipped", detail: "source directory not found" };
|
|
20645
20540
|
}
|
|
20646
|
-
const files =
|
|
20541
|
+
const files = fs15.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
|
|
20647
20542
|
if (files.length === 0) {
|
|
20648
20543
|
return {
|
|
20649
20544
|
name: "tasks/history/ \u2192 state.db",
|
|
@@ -20667,8 +20562,8 @@ async function migrateTaskHistoryToDb(ctx) {
|
|
|
20667
20562
|
let failed = 0;
|
|
20668
20563
|
try {
|
|
20669
20564
|
for (const file of files) {
|
|
20670
|
-
const filePath =
|
|
20671
|
-
const text =
|
|
20565
|
+
const filePath = path14.join(src, file);
|
|
20566
|
+
const text = fs15.readFileSync(filePath, "utf8");
|
|
20672
20567
|
const lines = text.split(`
|
|
20673
20568
|
`).filter((l) => l.trim().length > 0);
|
|
20674
20569
|
db.exec("BEGIN");
|
|
@@ -20727,11 +20622,11 @@ async function migrateTaskHistoryToDb(ctx) {
|
|
|
20727
20622
|
}
|
|
20728
20623
|
}
|
|
20729
20624
|
function noteRegistryIndexCache(ctx) {
|
|
20730
|
-
const src =
|
|
20731
|
-
if (!
|
|
20625
|
+
const src = path14.join(ctx.paths.cacheDir, "registry-index");
|
|
20626
|
+
if (!fs15.existsSync(src)) {
|
|
20732
20627
|
return { name: "registry-index/ (note)", status: "skipped", detail: "no old $CACHE/registry-index/ directory found" };
|
|
20733
20628
|
}
|
|
20734
|
-
const legacyFiles =
|
|
20629
|
+
const legacyFiles = fs15.readdirSync(src).filter((f) => f.endsWith(".json") && !f.startsWith("website-"));
|
|
20735
20630
|
if (legacyFiles.length === 0) {
|
|
20736
20631
|
return { name: "registry-index/ (note)", status: "skipped", detail: "no old *.json cache files found" };
|
|
20737
20632
|
}
|
|
@@ -20741,7 +20636,7 @@ function noteRegistryIndexCache(ctx) {
|
|
|
20741
20636
|
These are ignored in v0.9 \u2014 data is now stored in the registry_index_cache` + `
|
|
20742
20637
|
table in $DATA/index.db and will be rebuilt on next 'akm registry search'.
|
|
20743
20638
|
You may safely delete these files after migration:
|
|
20744
|
-
` + legacyFiles.map((f) => ` ${
|
|
20639
|
+
` + legacyFiles.map((f) => ` ${path14.join(src, f)}`).join(`
|
|
20745
20640
|
`));
|
|
20746
20641
|
}
|
|
20747
20642
|
return {
|
|
@@ -20755,15 +20650,15 @@ var v07To08Migration = {
|
|
|
20755
20650
|
sourceVersion: "0.7",
|
|
20756
20651
|
isNeeded: (paths) => {
|
|
20757
20652
|
const candidates = [
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20763
|
-
|
|
20764
|
-
|
|
20653
|
+
path14.join(paths.cacheDir, "index.db"),
|
|
20654
|
+
path14.join(paths.cacheDir, "workflow.db"),
|
|
20655
|
+
path14.join(paths.cacheDir, "events.jsonl"),
|
|
20656
|
+
path14.join(paths.cacheDir, "tasks", "history"),
|
|
20657
|
+
path14.join(paths.configDir, "akm.lock"),
|
|
20658
|
+
path14.join(paths.cacheDir, "config-backups"),
|
|
20659
|
+
path14.join(paths.cacheDir, "registry-index")
|
|
20765
20660
|
];
|
|
20766
|
-
return candidates.some((p) =>
|
|
20661
|
+
return candidates.some((p) => fs15.existsSync(p));
|
|
20767
20662
|
},
|
|
20768
20663
|
steps: [
|
|
20769
20664
|
{ id: "index-db", title: "index.db", run: (ctx) => migrateDb(ctx, "index.db") },
|
|
@@ -20778,20 +20673,20 @@ var v07To08Migration = {
|
|
|
20778
20673
|
};
|
|
20779
20674
|
function legacyGraphCandidatePaths(ctx) {
|
|
20780
20675
|
return [
|
|
20781
|
-
|
|
20782
|
-
|
|
20783
|
-
|
|
20676
|
+
path14.join(ctx.paths.cacheDir, "graph-snapshot.json"),
|
|
20677
|
+
path14.join(ctx.paths.dataDir, "graph-snapshot.json"),
|
|
20678
|
+
path14.join(ctx.paths.dataDir, "graph-export.json")
|
|
20784
20679
|
];
|
|
20785
20680
|
}
|
|
20786
20681
|
function findLegacyGraphFile(ctx) {
|
|
20787
20682
|
for (const candidate of legacyGraphCandidatePaths(ctx)) {
|
|
20788
|
-
if (
|
|
20683
|
+
if (fs15.existsSync(candidate) && fs15.statSync(candidate).isFile())
|
|
20789
20684
|
return candidate;
|
|
20790
20685
|
}
|
|
20791
|
-
const graphDir =
|
|
20792
|
-
if (
|
|
20686
|
+
const graphDir = path14.join(ctx.paths.cacheDir, "graph");
|
|
20687
|
+
if (fs15.existsSync(graphDir) && fs15.statSync(graphDir).isDirectory()) {
|
|
20793
20688
|
try {
|
|
20794
|
-
const json =
|
|
20689
|
+
const json = fs15.readdirSync(graphDir).filter((f) => f.endsWith(".json")).map((f) => path14.join(graphDir, f));
|
|
20795
20690
|
if (json.length > 0)
|
|
20796
20691
|
return json[0];
|
|
20797
20692
|
} catch {}
|
|
@@ -20827,7 +20722,7 @@ async function migrateGraphFileToDb(ctx) {
|
|
|
20827
20722
|
if (!legacyFile) {
|
|
20828
20723
|
return { name, status: "skipped", detail: "no legacy graph file found" };
|
|
20829
20724
|
}
|
|
20830
|
-
if (!
|
|
20725
|
+
if (!fs15.existsSync(ctx.paths.indexDbPath)) {
|
|
20831
20726
|
return {
|
|
20832
20727
|
name,
|
|
20833
20728
|
status: "skipped",
|
|
@@ -20842,7 +20737,7 @@ async function migrateGraphFileToDb(ctx) {
|
|
|
20842
20737
|
};
|
|
20843
20738
|
}
|
|
20844
20739
|
try {
|
|
20845
|
-
const raw =
|
|
20740
|
+
const raw = fs15.readFileSync(legacyFile, "utf8");
|
|
20846
20741
|
let parsed;
|
|
20847
20742
|
try {
|
|
20848
20743
|
parsed = JSON.parse(raw);
|
|
@@ -20919,7 +20814,7 @@ async function migrateGraphFileToDb(ctx) {
|
|
|
20919
20814
|
}
|
|
20920
20815
|
const renamed = `${legacyFile}.migrated`;
|
|
20921
20816
|
try {
|
|
20922
|
-
|
|
20817
|
+
fs15.renameSync(legacyFile, renamed);
|
|
20923
20818
|
} catch (err) {
|
|
20924
20819
|
const msg = err instanceof Error ? err.message : String(err);
|
|
20925
20820
|
return {
|
|
@@ -20943,9 +20838,9 @@ async function migrateGraphFileToDb(ctx) {
|
|
|
20943
20838
|
}
|
|
20944
20839
|
function resolvePrimaryStashDir(configDirPath) {
|
|
20945
20840
|
try {
|
|
20946
|
-
const cfgPath =
|
|
20947
|
-
if (
|
|
20948
|
-
const cfg = JSON.parse(
|
|
20841
|
+
const cfgPath = path14.join(configDirPath, "config.json");
|
|
20842
|
+
if (fs15.existsSync(cfgPath)) {
|
|
20843
|
+
const cfg = JSON.parse(fs15.readFileSync(cfgPath, "utf8"));
|
|
20949
20844
|
if (typeof cfg.stashDir === "string" && cfg.stashDir.length > 0)
|
|
20950
20845
|
return cfg.stashDir;
|
|
20951
20846
|
}
|
|
@@ -20954,8 +20849,8 @@ function resolvePrimaryStashDir(configDirPath) {
|
|
|
20954
20849
|
}
|
|
20955
20850
|
function countEnvFilesRecursive(dir) {
|
|
20956
20851
|
let count = 0;
|
|
20957
|
-
for (const entry of
|
|
20958
|
-
const full =
|
|
20852
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
20853
|
+
const full = path14.join(dir, entry.name);
|
|
20959
20854
|
if (entry.isDirectory()) {
|
|
20960
20855
|
count += countEnvFilesRecursive(full);
|
|
20961
20856
|
} else if (entry.name === ".env" || entry.name.endsWith(".env")) {
|
|
@@ -20967,13 +20862,13 @@ function countEnvFilesRecursive(dir) {
|
|
|
20967
20862
|
function chmodTreeSecure(dir) {
|
|
20968
20863
|
const isWin = process.platform === "win32";
|
|
20969
20864
|
try {
|
|
20970
|
-
|
|
20865
|
+
fs15.chmodSync(dir, 448);
|
|
20971
20866
|
} catch (err) {
|
|
20972
20867
|
if (!isWin)
|
|
20973
20868
|
return { ok: false, detail: `chmod 0700 ${dir} failed: ${err instanceof Error ? err.message : err}` };
|
|
20974
20869
|
}
|
|
20975
|
-
for (const entry of
|
|
20976
|
-
const full =
|
|
20870
|
+
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
20871
|
+
const full = path14.join(dir, entry.name);
|
|
20977
20872
|
if (entry.isDirectory()) {
|
|
20978
20873
|
const sub = chmodTreeSecure(full);
|
|
20979
20874
|
if (!sub.ok)
|
|
@@ -20981,8 +20876,8 @@ function chmodTreeSecure(dir) {
|
|
|
20981
20876
|
continue;
|
|
20982
20877
|
}
|
|
20983
20878
|
try {
|
|
20984
|
-
|
|
20985
|
-
if (!isWin && (
|
|
20879
|
+
fs15.chmodSync(full, 384);
|
|
20880
|
+
if (!isWin && (fs15.statSync(full).mode & 511) !== 384) {
|
|
20986
20881
|
return { ok: false, detail: `mode verification failed for ${full}` };
|
|
20987
20882
|
}
|
|
20988
20883
|
} catch (err) {
|
|
@@ -20995,13 +20890,13 @@ function chmodTreeSecure(dir) {
|
|
|
20995
20890
|
async function migrateVaultsToEnv(ctx) {
|
|
20996
20891
|
const name = "vaults/ \u2192 env/";
|
|
20997
20892
|
const stashDir = resolvePrimaryStashDir(ctx.paths.configDir);
|
|
20998
|
-
const vaultsDir =
|
|
20999
|
-
const envDir =
|
|
21000
|
-
const marker =
|
|
21001
|
-
if (!
|
|
20893
|
+
const vaultsDir = path14.join(stashDir, "vaults");
|
|
20894
|
+
const envDir = path14.join(stashDir, "env");
|
|
20895
|
+
const marker = path14.join(vaultsDir, MIGRATED_MARKER);
|
|
20896
|
+
if (!fs15.existsSync(vaultsDir)) {
|
|
21002
20897
|
return { name, status: "skipped", detail: `no vaults/ directory under ${stashDir}` };
|
|
21003
20898
|
}
|
|
21004
|
-
if (
|
|
20899
|
+
if (fs15.existsSync(marker)) {
|
|
21005
20900
|
return { name, status: "skipped", detail: "already migrated (.migrated marker present)" };
|
|
21006
20901
|
}
|
|
21007
20902
|
const vaultEnvCount = countEnvFilesRecursive(vaultsDir);
|
|
@@ -21032,7 +20927,7 @@ async function migrateVaultsToEnv(ctx) {
|
|
|
21032
20927
|
detail: `post-copy count ${envEnvCount} < source ${vaultEnvCount}; no marker written`
|
|
21033
20928
|
};
|
|
21034
20929
|
}
|
|
21035
|
-
|
|
20930
|
+
fs15.writeFileSync(marker, `migrated vaults/ -> env/ at ${new Date().toISOString()}
|
|
21036
20931
|
`, { mode: 384 });
|
|
21037
20932
|
return {
|
|
21038
20933
|
name,
|
|
@@ -21054,7 +20949,7 @@ var v08To09Migration = {
|
|
|
21054
20949
|
{ id: "vaults-to-env", title: "vaults/ \u2192 env/", run: migrateVaultsToEnv }
|
|
21055
20950
|
]
|
|
21056
20951
|
};
|
|
21057
|
-
var
|
|
20952
|
+
var MIGRATIONS = [v07To08Migration, v08To09Migration];
|
|
21058
20953
|
async function confirm() {
|
|
21059
20954
|
if (YES)
|
|
21060
20955
|
return true;
|
|
@@ -21068,7 +20963,7 @@ async function confirm() {
|
|
|
21068
20963
|
}
|
|
21069
20964
|
function printList() {
|
|
21070
20965
|
console.log("akm storage migrations (in execution order):");
|
|
21071
|
-
for (const m of
|
|
20966
|
+
for (const m of MIGRATIONS) {
|
|
21072
20967
|
console.log(` \u2022 ${m.label} [sourceVersion=${m.sourceVersion}]`);
|
|
21073
20968
|
}
|
|
21074
20969
|
console.log();
|
|
@@ -21092,8 +20987,8 @@ function compareVersion(a, b) {
|
|
|
21092
20987
|
}
|
|
21093
20988
|
function filteredMigrations() {
|
|
21094
20989
|
if (!FROM_VERSION)
|
|
21095
|
-
return
|
|
21096
|
-
return
|
|
20990
|
+
return MIGRATIONS;
|
|
20991
|
+
return MIGRATIONS.filter((m) => compareVersion(m.sourceVersion, FROM_VERSION) >= 0);
|
|
21097
20992
|
}
|
|
21098
20993
|
function printGroupedSummary() {
|
|
21099
20994
|
console.log(`
|
|
@@ -21138,12 +21033,12 @@ task_history: task_id PK).
|
|
|
21138
21033
|
Migration complete. No errors.`);
|
|
21139
21034
|
console.log(`
|
|
21140
21035
|
Old files at the original locations are safe to delete manually after verifying akm works:
|
|
21141
|
-
${
|
|
21142
|
-
${
|
|
21143
|
-
${
|
|
21144
|
-
${
|
|
21145
|
-
${
|
|
21146
|
-
${
|
|
21036
|
+
${path14.join(cacheDir, "index.db")}
|
|
21037
|
+
${path14.join(cacheDir, "workflow.db")}
|
|
21038
|
+
${path14.join(cacheDir, "events.jsonl")}
|
|
21039
|
+
${path14.join(cacheDir, "tasks", "history")}
|
|
21040
|
+
${path14.join(cacheDir, "config-backups")}
|
|
21041
|
+
${path14.join(configDir, "akm.lock")}
|
|
21147
21042
|
|
|
21148
21043
|
Next step \u2014 repopulate graph data (if migrating from 0.7):
|
|
21149
21044
|
The 0.8.0 graph schema redesign (DB_VERSION 12 \u2192 13) rebuilds the graph
|
|
@@ -21236,7 +21131,7 @@ var invokedDirectly = (() => {
|
|
|
21236
21131
|
if (!entry)
|
|
21237
21132
|
return false;
|
|
21238
21133
|
const here = new URL(import.meta.url).pathname;
|
|
21239
|
-
return
|
|
21134
|
+
return path14.resolve(entry) === here;
|
|
21240
21135
|
} catch {
|
|
21241
21136
|
return false;
|
|
21242
21137
|
}
|
|
@@ -21247,5 +21142,5 @@ if (invokedDirectly) {
|
|
|
21247
21142
|
export {
|
|
21248
21143
|
runMigrations3 as runMigrations,
|
|
21249
21144
|
main,
|
|
21250
|
-
|
|
21145
|
+
MIGRATIONS
|
|
21251
21146
|
};
|