akm-cli 0.9.0-beta.45 → 0.9.0-beta.47
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/dist/assets/wiki/ingest-workflow-template.md +17 -10
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +1 -2
- package/dist/commands/env/env-cli.js +16 -24
- package/dist/commands/env/secret-cli.js +12 -20
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +3 -3
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/improve/consolidate/types.js +4 -0
- package/dist/commands/improve/consolidate.js +20 -571
- package/dist/commands/improve/distill.js +5 -9
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/extract-cli.js +9 -1
- package/dist/commands/improve/extract.js +5 -19
- package/dist/commands/improve/improve-auto-accept.js +4 -8
- package/dist/commands/improve/improve-cli.js +35 -60
- package/dist/commands/improve/improve-result-file.js +5 -23
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +107 -3606
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1079 -0
- package/dist/commands/improve/preparation.js +1963 -0
- package/dist/commands/improve/recombine.js +6 -12
- package/dist/commands/improve/reflect.js +29 -34
- package/dist/commands/proposal/drain.js +25 -48
- package/dist/commands/proposal/proposal-cli.js +21 -31
- package/dist/commands/proposal/validators/proposals.js +3 -7
- package/dist/commands/read/curate.js +70 -14
- package/dist/commands/read/knowledge.js +2 -2
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/stash-cli.js +9 -37
- package/dist/commands/tasks/tasks-cli.js +19 -27
- package/dist/commands/wiki-cli.js +21 -35
- package/dist/core/config/config.js +18 -2
- package/dist/core/events.js +3 -7
- package/dist/core/logs-db.js +6 -63
- package/dist/core/state/migrations.js +714 -0
- package/dist/core/state-db.js +28 -779
- package/dist/indexer/db/db.js +82 -216
- package/dist/indexer/indexer.js +11 -112
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/search/search-source.js +10 -24
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/llm/client.js +22 -11
- package/dist/llm/embedder.js +15 -0
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/graph-extract.js +28 -39
- package/dist/llm/memory-infer.js +34 -22
- package/dist/llm/metadata-enhance.js +35 -30
- package/dist/llm/structured-call.js +49 -0
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +22 -9
- package/dist/scripts/migrate-storage.js +892 -1186
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +214 -179
- package/dist/setup/setup.js +26 -5
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +206 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -671
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/website.js +2 -3
- package/dist/sources/website-ingest.js +51 -9
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +185 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/registry-cache.js +92 -0
- package/dist/tasks/runner.js +5 -13
- package/dist/workflows/runtime/runs.js +1 -117
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/package.json +5 -5
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
|
@@ -8751,11 +8751,15 @@ var init_paths = __esm(() => {
|
|
|
8751
8751
|
|
|
8752
8752
|
// src/storage/database.ts
|
|
8753
8753
|
import { createRequire } from "module";
|
|
8754
|
-
function
|
|
8755
|
-
|
|
8756
|
-
|
|
8754
|
+
function selectProvider() {
|
|
8755
|
+
const provider = PROVIDERS.find((p) => p.supported());
|
|
8756
|
+
if (!provider) {
|
|
8757
|
+
throw new Error(`No storage provider supports the current runtime (${isBun ? "Bun" : "Node"}).`);
|
|
8757
8758
|
}
|
|
8758
|
-
return
|
|
8759
|
+
return provider;
|
|
8760
|
+
}
|
|
8761
|
+
function openDatabase(path5, opts) {
|
|
8762
|
+
return selectProvider().open(path5, opts);
|
|
8759
8763
|
}
|
|
8760
8764
|
function openBunDatabase(path5, opts) {
|
|
8761
8765
|
const { Database: BunDatabase } = loadBunSqlite();
|
|
@@ -8778,7 +8782,16 @@ function loadBunSqlite() {
|
|
|
8778
8782
|
}
|
|
8779
8783
|
function loadBetterSqlite3() {
|
|
8780
8784
|
if (!betterSqlite3Ctor) {
|
|
8781
|
-
|
|
8785
|
+
let mod;
|
|
8786
|
+
try {
|
|
8787
|
+
mod = nodeRequire("better-sqlite3");
|
|
8788
|
+
} catch (err) {
|
|
8789
|
+
throw new Error(`akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.
|
|
8790
|
+
` + ` \u2022 Reinstall akm with a working C/C++ build toolchain so its optional
|
|
8791
|
+
` + " 'better-sqlite3' native binding rebuilds (a global `npm i -g better-sqlite3`\n" + ` will NOT be resolved \u2014 Node loads it from akm's own node_modules).
|
|
8792
|
+
` + ` \u2022 Or run akm under Bun, which has a built-in SQLite driver and needs no native build.
|
|
8793
|
+
` + ` Underlying load error: ${err instanceof Error ? err.message : String(err)}`);
|
|
8794
|
+
}
|
|
8782
8795
|
betterSqlite3Ctor = mod.default ?? mod;
|
|
8783
8796
|
}
|
|
8784
8797
|
return betterSqlite3Ctor;
|
|
@@ -8793,36 +8806,23 @@ function openNodeDatabase(path5, opts) {
|
|
|
8793
8806
|
const db = opts ? new BetterSqlite3(path5, options) : new BetterSqlite3(path5);
|
|
8794
8807
|
return db;
|
|
8795
8808
|
}
|
|
8796
|
-
var isBun, nodeRequire, bunSqliteModule, betterSqlite3Ctor;
|
|
8809
|
+
var isBun, nodeRequire, bunSqliteProvider, nodeSqliteProvider, PROVIDERS, bunSqliteModule, betterSqlite3Ctor;
|
|
8797
8810
|
var init_database = __esm(() => {
|
|
8798
8811
|
isBun = !!process.versions?.bun;
|
|
8799
8812
|
nodeRequire = createRequire(import.meta.url);
|
|
8813
|
+
bunSqliteProvider = {
|
|
8814
|
+
name: "bun:sqlite",
|
|
8815
|
+
supported: () => isBun,
|
|
8816
|
+
open: openBunDatabase
|
|
8817
|
+
};
|
|
8818
|
+
nodeSqliteProvider = {
|
|
8819
|
+
name: "better-sqlite3",
|
|
8820
|
+
supported: () => !isBun,
|
|
8821
|
+
open: openNodeDatabase
|
|
8822
|
+
};
|
|
8823
|
+
PROVIDERS = [bunSqliteProvider, nodeSqliteProvider];
|
|
8800
8824
|
});
|
|
8801
8825
|
|
|
8802
|
-
// src/storage/engines/sqlite-migrations.ts
|
|
8803
|
-
function ensureMigrationsTable(db) {
|
|
8804
|
-
db.exec(`
|
|
8805
|
-
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
8806
|
-
id TEXT PRIMARY KEY,
|
|
8807
|
-
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
8808
|
-
);
|
|
8809
|
-
`);
|
|
8810
|
-
}
|
|
8811
|
-
function runMigrations(db, migrations, opts) {
|
|
8812
|
-
ensureMigrationsTable(db);
|
|
8813
|
-
opts?.bootstrap?.(db);
|
|
8814
|
-
const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
|
|
8815
|
-
const applied = new Set(appliedRows.map((r) => r.id));
|
|
8816
|
-
for (const migration of migrations) {
|
|
8817
|
-
if (applied.has(migration.id))
|
|
8818
|
-
continue;
|
|
8819
|
-
db.transaction(() => {
|
|
8820
|
-
db.exec(migration.up);
|
|
8821
|
-
db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
|
|
8822
|
-
})();
|
|
8823
|
-
}
|
|
8824
|
-
}
|
|
8825
|
-
|
|
8826
8826
|
// src/runtime.ts
|
|
8827
8827
|
import { createHash } from "crypto";
|
|
8828
8828
|
import { createWriteStream, statfsSync } from "fs";
|
|
@@ -8920,6 +8920,46 @@ var init_sqlite_pragmas = __esm(() => {
|
|
|
8920
8920
|
]);
|
|
8921
8921
|
});
|
|
8922
8922
|
|
|
8923
|
+
// src/storage/managed-db.ts
|
|
8924
|
+
import fs5 from "fs";
|
|
8925
|
+
import path5 from "path";
|
|
8926
|
+
function openManagedDatabase(spec) {
|
|
8927
|
+
const dir = path5.dirname(spec.path);
|
|
8928
|
+
if (!fs5.existsSync(dir)) {
|
|
8929
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
8930
|
+
}
|
|
8931
|
+
const db = openDatabase(spec.path);
|
|
8932
|
+
applyStandardPragmas(db, spec.pragmas ?? { dataDir: dir });
|
|
8933
|
+
spec.init?.(db);
|
|
8934
|
+
return db;
|
|
8935
|
+
}
|
|
8936
|
+
function withManagedDb(open, fn, opts) {
|
|
8937
|
+
if (opts?.borrowed) {
|
|
8938
|
+
return fn(opts.borrowed);
|
|
8939
|
+
}
|
|
8940
|
+
const db = open();
|
|
8941
|
+
try {
|
|
8942
|
+
return fn(db);
|
|
8943
|
+
} finally {
|
|
8944
|
+
db.close();
|
|
8945
|
+
}
|
|
8946
|
+
}
|
|
8947
|
+
async function withManagedDbAsync(open, fn, opts) {
|
|
8948
|
+
if (opts?.borrowed) {
|
|
8949
|
+
return fn(opts.borrowed);
|
|
8950
|
+
}
|
|
8951
|
+
const db = open();
|
|
8952
|
+
try {
|
|
8953
|
+
return await fn(db);
|
|
8954
|
+
} finally {
|
|
8955
|
+
db.close();
|
|
8956
|
+
}
|
|
8957
|
+
}
|
|
8958
|
+
var init_managed_db = __esm(() => {
|
|
8959
|
+
init_database();
|
|
8960
|
+
init_sqlite_pragmas();
|
|
8961
|
+
});
|
|
8962
|
+
|
|
8923
8963
|
// src/core/assert.ts
|
|
8924
8964
|
function assertNever(x, context) {
|
|
8925
8965
|
let serialized;
|
|
@@ -8959,683 +8999,36 @@ function classifyImproveAction(mode) {
|
|
|
8959
8999
|
}
|
|
8960
9000
|
var init_improve_types = () => {};
|
|
8961
9001
|
|
|
8962
|
-
// src/
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
upsertBodyEmbeddings: () => upsertBodyEmbeddings,
|
|
8971
|
-
shouldSkipAlreadyExtractedSession: () => shouldSkipAlreadyExtractedSession,
|
|
8972
|
-
runMigrations: () => runMigrations2,
|
|
8973
|
-
recordRecombineInduction: () => recordRecombineInduction,
|
|
8974
|
-
recordImproveRun: () => recordImproveRun,
|
|
8975
|
-
recordFsProposalsImport: () => recordFsProposalsImport,
|
|
8976
|
-
readStateEvents: () => readStateEvents,
|
|
8977
|
-
queryTaskHistory: () => queryTaskHistory,
|
|
8978
|
-
queryImproveRuns: () => queryImproveRuns,
|
|
8979
|
-
queryCompletedTaskIntervals: () => queryCompletedTaskIntervals,
|
|
8980
|
-
purgeOldImproveRuns: () => purgeOldImproveRuns,
|
|
8981
|
-
purgeOldEvents: () => purgeOldEvents,
|
|
8982
|
-
proposalToRowValues: () => proposalToRowValues,
|
|
8983
|
-
proposalRowToProposal: () => proposalRowToProposal,
|
|
8984
|
-
persistPhaseThreshold: () => persistPhaseThreshold,
|
|
8985
|
-
openStateDatabase: () => openStateDatabase,
|
|
8986
|
-
markRecombineHypothesisPromoted: () => markRecombineHypothesisPromoted,
|
|
8987
|
-
listStateProposals: () => listStateProposals,
|
|
8988
|
-
listStateProposalIdsByPrefix: () => listStateProposalIdsByPrefix,
|
|
8989
|
-
listProposalGateDecisions: () => listProposalGateDecisions,
|
|
8990
|
-
listExistingTableNames: () => listExistingTableNames,
|
|
8991
|
-
insertProposalIfAbsent: () => insertProposalIfAbsent,
|
|
8992
|
-
insertEvent: () => insertEvent,
|
|
8993
|
-
importEventsJsonl: () => importEventsJsonl,
|
|
8994
|
-
hasImportedFsProposals: () => hasImportedFsProposals,
|
|
8995
|
-
getTaskHistoryRuns: () => getTaskHistoryRuns,
|
|
8996
|
-
getTaskHistory: () => getTaskHistory,
|
|
8997
|
-
getStateProposal: () => getStateProposal,
|
|
8998
|
-
getStateDbPath: () => getStateDbPath,
|
|
8999
|
-
getRecombineHypothesis: () => getRecombineHypothesis,
|
|
9000
|
-
getPhaseThreshold: () => getPhaseThreshold,
|
|
9001
|
-
getLastExtractRunAt: () => getLastExtractRunAt,
|
|
9002
|
-
getExtractedSessionsMap: () => getExtractedSessionsMap,
|
|
9003
|
-
getExtractedSession: () => getExtractedSession,
|
|
9004
|
-
getConsolidationJudgedMap: () => getConsolidationJudgedMap,
|
|
9005
|
-
getBodyEmbeddings: () => getBodyEmbeddings,
|
|
9006
|
-
findMatchingRecombineHypothesis: () => findMatchingRecombineHypothesis,
|
|
9007
|
-
eventRowToEnvelope: () => eventRowToEnvelope,
|
|
9008
|
-
embeddingToBlob: () => embeddingToBlob,
|
|
9009
|
-
decayUnseenRecombineHypotheses: () => decayUnseenRecombineHypotheses,
|
|
9010
|
-
computeImproveRunMetrics: () => computeImproveRunMetrics,
|
|
9011
|
-
blobToEmbedding: () => blobToEmbedding,
|
|
9012
|
-
REGISTRY_INDEX_CACHE_DDL: () => REGISTRY_INDEX_CACHE_DDL
|
|
9013
|
-
});
|
|
9014
|
-
import fs5 from "fs";
|
|
9015
|
-
import path5 from "path";
|
|
9016
|
-
function getStateDbPath() {
|
|
9017
|
-
return path5.join(getDataDir(), "state.db");
|
|
9002
|
+
// src/storage/engines/sqlite-migrations.ts
|
|
9003
|
+
function ensureMigrationsTable(db) {
|
|
9004
|
+
db.exec(`
|
|
9005
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
9006
|
+
id TEXT PRIMARY KEY,
|
|
9007
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
9008
|
+
);
|
|
9009
|
+
`);
|
|
9018
9010
|
}
|
|
9019
|
-
function
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9011
|
+
function runMigrations(db, migrations, opts) {
|
|
9012
|
+
ensureMigrationsTable(db);
|
|
9013
|
+
opts?.bootstrap?.(db);
|
|
9014
|
+
const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
|
|
9015
|
+
const applied = new Set(appliedRows.map((r) => r.id));
|
|
9016
|
+
for (const migration of migrations) {
|
|
9017
|
+
if (applied.has(migration.id))
|
|
9018
|
+
continue;
|
|
9019
|
+
db.transaction(() => {
|
|
9020
|
+
db.exec(migration.up);
|
|
9021
|
+
db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
|
|
9022
|
+
})();
|
|
9024
9023
|
}
|
|
9025
|
-
const db = openDatabase(resolvedPath);
|
|
9026
|
-
applyStandardPragmas(db, { dataDir: dir });
|
|
9027
|
-
runMigrations2(db);
|
|
9028
|
-
return db;
|
|
9029
9024
|
}
|
|
9025
|
+
|
|
9026
|
+
// src/core/state/migrations.ts
|
|
9030
9027
|
function runMigrations2(db) {
|
|
9031
9028
|
runMigrations(db, MIGRATIONS);
|
|
9032
9029
|
}
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
try {
|
|
9036
|
-
const parsed = JSON.parse(row.metadata_json);
|
|
9037
|
-
if (Object.keys(parsed).length > 0) {
|
|
9038
|
-
metadata = parsed;
|
|
9039
|
-
}
|
|
9040
|
-
} catch {}
|
|
9041
|
-
return {
|
|
9042
|
-
schemaVersion: 1,
|
|
9043
|
-
id: row.id,
|
|
9044
|
-
ts: row.ts,
|
|
9045
|
-
eventType: row.event_type,
|
|
9046
|
-
...row.ref !== null ? { ref: row.ref } : {},
|
|
9047
|
-
...metadata !== undefined ? { metadata } : {}
|
|
9048
|
-
};
|
|
9049
|
-
}
|
|
9050
|
-
function proposalRowToProposal(row) {
|
|
9051
|
-
let frontmatter;
|
|
9052
|
-
if (row.frontmatter_json) {
|
|
9053
|
-
try {
|
|
9054
|
-
frontmatter = JSON.parse(row.frontmatter_json);
|
|
9055
|
-
} catch {}
|
|
9056
|
-
}
|
|
9057
|
-
let meta = {};
|
|
9058
|
-
try {
|
|
9059
|
-
meta = JSON.parse(row.metadata_json);
|
|
9060
|
-
} catch {}
|
|
9061
|
-
return {
|
|
9062
|
-
id: row.id,
|
|
9063
|
-
ref: row.ref,
|
|
9064
|
-
status: row.status,
|
|
9065
|
-
source: row.source,
|
|
9066
|
-
...typeof meta.sourceRun === "string" ? { sourceRun: meta.sourceRun } : {},
|
|
9067
|
-
createdAt: row.created_at,
|
|
9068
|
-
updatedAt: row.updated_at,
|
|
9069
|
-
payload: {
|
|
9070
|
-
content: row.content,
|
|
9071
|
-
...frontmatter !== undefined ? { frontmatter } : {}
|
|
9072
|
-
},
|
|
9073
|
-
...meta.review !== undefined ? { review: meta.review } : {},
|
|
9074
|
-
...typeof meta.confidence === "number" ? { confidence: meta.confidence } : {},
|
|
9075
|
-
...meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {},
|
|
9076
|
-
...typeof meta.backupContent === "string" ? { backupContent: meta.backupContent } : {},
|
|
9077
|
-
...typeof meta.eligibilitySource === "string" ? { eligibilitySource: meta.eligibilitySource } : {}
|
|
9078
|
-
};
|
|
9079
|
-
}
|
|
9080
|
-
function proposalToRowValues(proposal, stashDir) {
|
|
9081
|
-
const metaObj = {};
|
|
9082
|
-
if (proposal.sourceRun !== undefined)
|
|
9083
|
-
metaObj.sourceRun = proposal.sourceRun;
|
|
9084
|
-
if (proposal.review !== undefined)
|
|
9085
|
-
metaObj.review = proposal.review;
|
|
9086
|
-
if (proposal.confidence !== undefined)
|
|
9087
|
-
metaObj.confidence = proposal.confidence;
|
|
9088
|
-
if (proposal.gateDecision !== undefined)
|
|
9089
|
-
metaObj.gateDecision = proposal.gateDecision;
|
|
9090
|
-
if (proposal.backupContent !== undefined)
|
|
9091
|
-
metaObj.backupContent = proposal.backupContent;
|
|
9092
|
-
if (proposal.eligibilitySource !== undefined)
|
|
9093
|
-
metaObj.eligibilitySource = proposal.eligibilitySource;
|
|
9094
|
-
return {
|
|
9095
|
-
id: proposal.id,
|
|
9096
|
-
stash_dir: stashDir,
|
|
9097
|
-
ref: proposal.ref,
|
|
9098
|
-
status: proposal.status,
|
|
9099
|
-
source: proposal.source,
|
|
9100
|
-
created_at: proposal.createdAt,
|
|
9101
|
-
updated_at: proposal.updatedAt,
|
|
9102
|
-
content: proposal.payload.content,
|
|
9103
|
-
frontmatter_json: proposal.payload.frontmatter ? JSON.stringify(proposal.payload.frontmatter) : null,
|
|
9104
|
-
metadata_json: JSON.stringify(metaObj)
|
|
9105
|
-
};
|
|
9106
|
-
}
|
|
9107
|
-
function insertEvent(db, input) {
|
|
9108
|
-
try {
|
|
9109
|
-
const result = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
|
|
9110
|
-
VALUES (?, ?, ?, ?)
|
|
9111
|
-
RETURNING id`).get(input.eventType, input.ts, input.ref ?? null, JSON.stringify(input.metadata ?? {}));
|
|
9112
|
-
return result?.id;
|
|
9113
|
-
} catch (err) {
|
|
9114
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
9115
|
-
error(`akm: state.db event insert failed (${message})`);
|
|
9116
|
-
return;
|
|
9117
|
-
}
|
|
9118
|
-
}
|
|
9119
|
-
function readStateEvents(db, options = {}) {
|
|
9120
|
-
const conditions = [];
|
|
9121
|
-
const params = [];
|
|
9122
|
-
if (options.sinceId !== undefined && options.sinceId > 0) {
|
|
9123
|
-
conditions.push("id > ?");
|
|
9124
|
-
params.push(options.sinceId);
|
|
9125
|
-
}
|
|
9126
|
-
if (options.since) {
|
|
9127
|
-
conditions.push("ts >= ?");
|
|
9128
|
-
params.push(options.since);
|
|
9129
|
-
}
|
|
9130
|
-
if (options.type) {
|
|
9131
|
-
conditions.push("event_type = ?");
|
|
9132
|
-
params.push(options.type);
|
|
9133
|
-
}
|
|
9134
|
-
if (options.ref) {
|
|
9135
|
-
conditions.push("ref = ?");
|
|
9136
|
-
params.push(options.ref);
|
|
9137
|
-
}
|
|
9138
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9139
|
-
const rows = db.prepare(`SELECT id, event_type, ts, ref, metadata_json FROM events ${where} ORDER BY id ASC`).all(...params);
|
|
9140
|
-
const events = rows.map(eventRowToEnvelope);
|
|
9141
|
-
const nextId = events.length > 0 ? events[events.length - 1].id : options.sinceId ?? 0;
|
|
9142
|
-
return { events, nextId };
|
|
9143
|
-
}
|
|
9144
|
-
function purgeOldEvents(db, retentionDays = 90) {
|
|
9145
|
-
if (!Number.isFinite(retentionDays) || retentionDays <= 0)
|
|
9146
|
-
return 0;
|
|
9147
|
-
const cutoff = new Date(Date.now() - retentionDays * 86400000).toISOString();
|
|
9148
|
-
const result = db.prepare("DELETE FROM events WHERE ts < ?").run(cutoff);
|
|
9149
|
-
const changes = result.changes ?? 0;
|
|
9150
|
-
return typeof changes === "bigint" ? Number(changes) : changes;
|
|
9151
|
-
}
|
|
9152
|
-
function upsertProposal(db, proposal, stashDir) {
|
|
9153
|
-
const v = proposalToRowValues(proposal, stashDir);
|
|
9154
|
-
db.prepare(`
|
|
9155
|
-
INSERT INTO proposals
|
|
9156
|
-
(id, stash_dir, ref, status, source, created_at, updated_at, content, frontmatter_json, metadata_json)
|
|
9157
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9158
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
9159
|
-
stash_dir = excluded.stash_dir,
|
|
9160
|
-
ref = excluded.ref,
|
|
9161
|
-
status = excluded.status,
|
|
9162
|
-
source = excluded.source,
|
|
9163
|
-
updated_at = excluded.updated_at,
|
|
9164
|
-
content = excluded.content,
|
|
9165
|
-
frontmatter_json = excluded.frontmatter_json,
|
|
9166
|
-
metadata_json = excluded.metadata_json
|
|
9167
|
-
`).run(v.id, v.stash_dir, v.ref, v.status, v.source, v.created_at, v.updated_at, v.content, v.frontmatter_json, v.metadata_json);
|
|
9168
|
-
}
|
|
9169
|
-
function listStateProposals(db, options = {}) {
|
|
9170
|
-
const conditions = [];
|
|
9171
|
-
const params = [];
|
|
9172
|
-
if (options.stashDir) {
|
|
9173
|
-
conditions.push("stash_dir = ?");
|
|
9174
|
-
params.push(options.stashDir);
|
|
9175
|
-
}
|
|
9176
|
-
if (options.status) {
|
|
9177
|
-
conditions.push("status = ?");
|
|
9178
|
-
params.push(options.status);
|
|
9179
|
-
}
|
|
9180
|
-
if (options.ref) {
|
|
9181
|
-
conditions.push("ref = ?");
|
|
9182
|
-
params.push(options.ref);
|
|
9183
|
-
}
|
|
9184
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9185
|
-
const rows = db.prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
9186
|
-
content, frontmatter_json, metadata_json
|
|
9187
|
-
FROM proposals ${where} ORDER BY created_at ASC, rowid ASC`).all(...params);
|
|
9188
|
-
return rows.map(proposalRowToProposal);
|
|
9189
|
-
}
|
|
9190
|
-
function listProposalGateDecisions(db) {
|
|
9191
|
-
const rows = db.prepare("SELECT metadata_json FROM proposals ORDER BY created_at ASC, rowid ASC").all();
|
|
9192
|
-
const decisions = [];
|
|
9193
|
-
for (const row of rows) {
|
|
9194
|
-
let meta;
|
|
9195
|
-
try {
|
|
9196
|
-
meta = JSON.parse(row.metadata_json);
|
|
9197
|
-
} catch {
|
|
9198
|
-
continue;
|
|
9199
|
-
}
|
|
9200
|
-
const decision = meta.gateDecision;
|
|
9201
|
-
if (decision && typeof decision === "object" && typeof decision.outcome === "string") {
|
|
9202
|
-
decisions.push(decision);
|
|
9203
|
-
}
|
|
9204
|
-
}
|
|
9205
|
-
decisions.sort((a, b) => new Date(a.decidedAt).getTime() - new Date(b.decidedAt).getTime());
|
|
9206
|
-
return decisions;
|
|
9207
|
-
}
|
|
9208
|
-
function getPhaseThreshold(db, phase) {
|
|
9209
|
-
const row = db.prepare("SELECT threshold FROM improve_gate_thresholds WHERE phase = ?").get(phase);
|
|
9210
|
-
return row?.threshold;
|
|
9211
|
-
}
|
|
9212
|
-
function persistPhaseThreshold(db, phase, threshold) {
|
|
9213
|
-
db.prepare(`INSERT OR REPLACE INTO improve_gate_thresholds (phase, threshold, updated_at)
|
|
9214
|
-
VALUES (?, ?, ?)`).run(phase, Math.round(threshold), Date.now());
|
|
9215
|
-
}
|
|
9216
|
-
function getStateProposal(db, id, stashDir) {
|
|
9217
|
-
const sql = `SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
9218
|
-
content, frontmatter_json, metadata_json
|
|
9219
|
-
FROM proposals WHERE id = ?${stashDir ? " AND stash_dir = ?" : ""}`;
|
|
9220
|
-
const row = stashDir ? db.prepare(sql).get(id, stashDir) : db.prepare(sql).get(id);
|
|
9221
|
-
return row ? proposalRowToProposal(row) : undefined;
|
|
9222
|
-
}
|
|
9223
|
-
function listStateProposalIdsByPrefix(db, stashDir, idPrefix) {
|
|
9224
|
-
const escaped = idPrefix.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
9225
|
-
const rows = db.prepare(`SELECT id FROM proposals
|
|
9226
|
-
WHERE stash_dir = ? AND status = 'pending' AND id LIKE ? ESCAPE '\\'
|
|
9227
|
-
ORDER BY id ASC`).all(stashDir, `${escaped}%`);
|
|
9228
|
-
return rows.map((r) => r.id);
|
|
9229
|
-
}
|
|
9230
|
-
function hasImportedFsProposals(db, stashDir) {
|
|
9231
|
-
return Boolean(db.prepare("SELECT 1 FROM proposal_fs_imports WHERE stash_dir = ?").get(stashDir));
|
|
9232
|
-
}
|
|
9233
|
-
function recordFsProposalsImport(db, stashDir, importedCount) {
|
|
9234
|
-
db.prepare("INSERT OR REPLACE INTO proposal_fs_imports (stash_dir, imported_at, imported_count) VALUES (?, ?, ?)").run(stashDir, new Date().toISOString(), importedCount);
|
|
9235
|
-
}
|
|
9236
|
-
function insertProposalIfAbsent(db, proposal, stashDir) {
|
|
9237
|
-
const v = proposalToRowValues(proposal, stashDir);
|
|
9238
|
-
const result = db.prepare(`
|
|
9239
|
-
INSERT OR IGNORE INTO proposals
|
|
9240
|
-
(id, stash_dir, ref, status, source, created_at, updated_at, content, frontmatter_json, metadata_json)
|
|
9241
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9242
|
-
`).run(v.id, v.stash_dir, v.ref, v.status, v.source, v.created_at, v.updated_at, v.content, v.frontmatter_json, v.metadata_json);
|
|
9243
|
-
const changes = result.changes ?? 0;
|
|
9244
|
-
return Number(changes) > 0;
|
|
9245
|
-
}
|
|
9246
|
-
function withImmediateTransaction(db, fn) {
|
|
9247
|
-
db.exec("BEGIN IMMEDIATE");
|
|
9248
|
-
try {
|
|
9249
|
-
const result = fn();
|
|
9250
|
-
db.exec("COMMIT");
|
|
9251
|
-
return result;
|
|
9252
|
-
} catch (err) {
|
|
9253
|
-
try {
|
|
9254
|
-
db.exec("ROLLBACK");
|
|
9255
|
-
} catch {}
|
|
9256
|
-
throw err;
|
|
9257
|
-
}
|
|
9258
|
-
}
|
|
9259
|
-
function upsertTaskHistory(db, row) {
|
|
9260
|
-
db.prepare(`
|
|
9261
|
-
INSERT OR IGNORE INTO task_history
|
|
9262
|
-
(task_id, status, started_at, completed_at, failed_at, log_path,
|
|
9263
|
-
target_kind, target_ref, metadata_json)
|
|
9264
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9265
|
-
`).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);
|
|
9266
|
-
}
|
|
9267
|
-
function getTaskHistory(db, taskId) {
|
|
9268
|
-
return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
|
|
9269
|
-
target_kind, target_ref, metadata_json
|
|
9270
|
-
FROM task_history WHERE task_id = ? ORDER BY started_at DESC LIMIT 1`).get(taskId);
|
|
9271
|
-
}
|
|
9272
|
-
function getTaskHistoryRuns(db, taskId, limit = 50) {
|
|
9273
|
-
return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
|
|
9274
|
-
target_kind, target_ref, metadata_json
|
|
9275
|
-
FROM task_history WHERE task_id = ? ORDER BY started_at DESC LIMIT ?`).all(taskId, limit);
|
|
9276
|
-
}
|
|
9277
|
-
function queryTaskHistory(db, options = {}) {
|
|
9278
|
-
const conditions = [];
|
|
9279
|
-
const params = [];
|
|
9280
|
-
if (options.since) {
|
|
9281
|
-
conditions.push("started_at >= ?");
|
|
9282
|
-
params.push(options.since);
|
|
9283
|
-
}
|
|
9284
|
-
if (options.until) {
|
|
9285
|
-
conditions.push("started_at <= ?");
|
|
9286
|
-
params.push(options.until);
|
|
9287
|
-
}
|
|
9288
|
-
if (options.status) {
|
|
9289
|
-
conditions.push("status = ?");
|
|
9290
|
-
params.push(options.status);
|
|
9291
|
-
}
|
|
9292
|
-
if (options.targetKind) {
|
|
9293
|
-
conditions.push("target_kind = ?");
|
|
9294
|
-
params.push(options.targetKind);
|
|
9295
|
-
}
|
|
9296
|
-
if (options.targetRef) {
|
|
9297
|
-
conditions.push("target_ref = ?");
|
|
9298
|
-
params.push(options.targetRef);
|
|
9299
|
-
}
|
|
9300
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9301
|
-
return db.prepare(`SELECT task_id, status, started_at, completed_at, failed_at, log_path,
|
|
9302
|
-
target_kind, target_ref, metadata_json
|
|
9303
|
-
FROM task_history ${where} ORDER BY started_at DESC`).all(...params);
|
|
9304
|
-
}
|
|
9305
|
-
function queryCompletedTaskIntervals(db, since, until) {
|
|
9306
|
-
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";
|
|
9307
|
-
return until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since);
|
|
9308
|
-
}
|
|
9309
|
-
function listExistingTableNames(db, names) {
|
|
9310
|
-
if (names.length === 0)
|
|
9311
|
-
return [];
|
|
9312
|
-
const placeholders = names.map(() => "?").join(", ");
|
|
9313
|
-
return db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${placeholders}) ORDER BY name`).all(...names);
|
|
9314
|
-
}
|
|
9315
|
-
async function importEventsJsonl(db, jsonlPath) {
|
|
9316
|
-
const { readFileSync, existsSync } = await import("fs");
|
|
9317
|
-
if (!existsSync(jsonlPath)) {
|
|
9318
|
-
return { imported: 0, maxId: 0, skipped: 0 };
|
|
9319
|
-
}
|
|
9320
|
-
const text = readFileSync(jsonlPath, "utf8");
|
|
9321
|
-
const lines = text.split(`
|
|
9322
|
-
`).filter((l) => l.trim().length > 0);
|
|
9323
|
-
let imported = 0;
|
|
9324
|
-
let maxId = 0;
|
|
9325
|
-
let skipped = 0;
|
|
9326
|
-
const insertStmt = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
|
|
9327
|
-
VALUES (?, ?, ?, ?)
|
|
9328
|
-
RETURNING id`);
|
|
9329
|
-
const existsStmt = db.prepare(`SELECT 1 FROM events
|
|
9330
|
-
WHERE event_type = ?
|
|
9331
|
-
AND ts = ?
|
|
9332
|
-
AND ref IS ?
|
|
9333
|
-
AND metadata_json = ?
|
|
9334
|
-
LIMIT 1`);
|
|
9335
|
-
db.transaction(() => {
|
|
9336
|
-
for (const line of lines) {
|
|
9337
|
-
let parsed;
|
|
9338
|
-
try {
|
|
9339
|
-
parsed = JSON.parse(line);
|
|
9340
|
-
} catch {
|
|
9341
|
-
continue;
|
|
9342
|
-
}
|
|
9343
|
-
const eventType = typeof parsed.eventType === "string" ? parsed.eventType : "unknown";
|
|
9344
|
-
const ts = typeof parsed.ts === "string" ? parsed.ts : new Date().toISOString();
|
|
9345
|
-
const ref = typeof parsed.ref === "string" ? parsed.ref : null;
|
|
9346
|
-
const metadata = parsed.metadata !== undefined && typeof parsed.metadata === "object" ? JSON.stringify(parsed.metadata) : "{}";
|
|
9347
|
-
const duplicate = existsStmt.get(eventType, ts, ref, metadata);
|
|
9348
|
-
if (duplicate) {
|
|
9349
|
-
skipped++;
|
|
9350
|
-
continue;
|
|
9351
|
-
}
|
|
9352
|
-
const result = insertStmt.get(eventType, ts, ref, metadata);
|
|
9353
|
-
if (result) {
|
|
9354
|
-
imported++;
|
|
9355
|
-
if (result.id > maxId)
|
|
9356
|
-
maxId = result.id;
|
|
9357
|
-
}
|
|
9358
|
-
}
|
|
9359
|
-
})();
|
|
9360
|
-
return { imported, maxId, skipped };
|
|
9361
|
-
}
|
|
9362
|
-
function computeImproveRunMetrics(result) {
|
|
9363
|
-
const plannedCount = Array.isArray(result.plannedRefs) ? result.plannedRefs.length : 0;
|
|
9364
|
-
const actions = Array.isArray(result.actions) ? result.actions : [];
|
|
9365
|
-
const actionsCount = actions.length;
|
|
9366
|
-
let acceptedCount = 0;
|
|
9367
|
-
let rejectedCount = 0;
|
|
9368
|
-
let autoAcceptedCount = 0;
|
|
9369
|
-
let errorCount = 0;
|
|
9370
|
-
for (const action of actions) {
|
|
9371
|
-
switch (classifyImproveAction(action.mode)) {
|
|
9372
|
-
case "accepted":
|
|
9373
|
-
acceptedCount++;
|
|
9374
|
-
break;
|
|
9375
|
-
case "rejected":
|
|
9376
|
-
rejectedCount++;
|
|
9377
|
-
break;
|
|
9378
|
-
case "error":
|
|
9379
|
-
errorCount++;
|
|
9380
|
-
break;
|
|
9381
|
-
case "noop":
|
|
9382
|
-
break;
|
|
9383
|
-
}
|
|
9384
|
-
const r = action.result;
|
|
9385
|
-
if (r && r.autoAccepted === true)
|
|
9386
|
-
autoAcceptedCount++;
|
|
9387
|
-
}
|
|
9388
|
-
autoAcceptedCount += result.gateAutoAcceptedCount ?? 0;
|
|
9389
|
-
return { plannedCount, actionsCount, acceptedCount, rejectedCount, autoAcceptedCount, errorCount };
|
|
9390
|
-
}
|
|
9391
|
-
function recordImproveRun(db, input) {
|
|
9392
|
-
const metricsObj = input.metrics ?? computeImproveRunMetrics(input.result);
|
|
9393
|
-
db.prepare(`
|
|
9394
|
-
INSERT INTO improve_runs
|
|
9395
|
-
(id, started_at, completed_at, stash_dir, dry_run, profile,
|
|
9396
|
-
scope_mode, scope_value, guidance, ok, result_json, metrics_json, metadata_json)
|
|
9397
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9398
|
-
`).run(input.id, input.startedAt, input.completedAt, input.stashDir, input.dryRun ? 1 : 0, input.profile, input.scopeMode, input.scopeValue, input.guidance, input.ok ? 1 : 0, JSON.stringify(input.result), JSON.stringify(metricsObj), JSON.stringify(input.metadata ?? {}));
|
|
9399
|
-
}
|
|
9400
|
-
function queryImproveRuns(db, since, until) {
|
|
9401
|
-
const sql = until ? "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND started_at < ? AND dry_run = 0 ORDER BY started_at DESC" : "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND dry_run = 0 ORDER BY started_at DESC";
|
|
9402
|
-
return until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since);
|
|
9403
|
-
}
|
|
9404
|
-
function purgeOldImproveRuns(db, retentionDays = 90) {
|
|
9405
|
-
if (!Number.isFinite(retentionDays) || retentionDays <= 0)
|
|
9406
|
-
return 0;
|
|
9407
|
-
const cutoff = new Date(Date.now() - retentionDays * 86400000).toISOString();
|
|
9408
|
-
const result = db.prepare("DELETE FROM improve_runs WHERE started_at < ?").run(cutoff);
|
|
9409
|
-
const changes = result.changes ?? 0;
|
|
9410
|
-
return typeof changes === "bigint" ? Number(changes) : changes;
|
|
9411
|
-
}
|
|
9412
|
-
function upsertExtractedSession(db, input) {
|
|
9413
|
-
const endedAtIso = typeof input.sessionEndedAt === "number" && Number.isFinite(input.sessionEndedAt) ? new Date(input.sessionEndedAt).toISOString() : null;
|
|
9414
|
-
db.prepare(`
|
|
9415
|
-
INSERT OR REPLACE INTO extract_sessions_seen
|
|
9416
|
-
(harness, session_id, processed_at, session_ended_at, outcome,
|
|
9417
|
-
candidate_count, proposal_count, rationale, source_run, metadata_json,
|
|
9418
|
-
content_hash)
|
|
9419
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9420
|
-
`).run(input.harness, input.sessionId, input.processedAt, endedAtIso, input.outcome, input.candidateCount, input.proposalCount, input.rationale ?? null, input.sourceRun ?? null, JSON.stringify(input.metadata ?? {}), input.contentHash);
|
|
9421
|
-
}
|
|
9422
|
-
function getExtractedSession(db, harness, sessionId) {
|
|
9423
|
-
const row = db.prepare("SELECT * FROM extract_sessions_seen WHERE harness = ? AND session_id = ?").get(harness, sessionId);
|
|
9424
|
-
return row ?? undefined;
|
|
9425
|
-
}
|
|
9426
|
-
function getExtractedSessionsMap(db, harness, sessionIds) {
|
|
9427
|
-
const out = new Map;
|
|
9428
|
-
if (sessionIds.length === 0)
|
|
9429
|
-
return out;
|
|
9430
|
-
const CHUNK = 500;
|
|
9431
|
-
for (let i = 0;i < sessionIds.length; i += CHUNK) {
|
|
9432
|
-
const chunk = sessionIds.slice(i, i + CHUNK);
|
|
9433
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
9434
|
-
const rows = db.prepare(`SELECT * FROM extract_sessions_seen
|
|
9435
|
-
WHERE harness = ? AND session_id IN (${placeholders})`).all(harness, ...chunk);
|
|
9436
|
-
for (const row of rows)
|
|
9437
|
-
out.set(row.session_id, row);
|
|
9438
|
-
}
|
|
9439
|
-
return out;
|
|
9440
|
-
}
|
|
9441
|
-
function getLastExtractRunAt(db, harness) {
|
|
9442
|
-
const row = db.prepare("SELECT MAX(processed_at) AS last FROM extract_sessions_seen WHERE harness = ?").get(harness);
|
|
9443
|
-
if (!row?.last)
|
|
9444
|
-
return null;
|
|
9445
|
-
const ms = Date.parse(row.last);
|
|
9446
|
-
return Number.isFinite(ms) ? ms : null;
|
|
9447
|
-
}
|
|
9448
|
-
function shouldSkipAlreadyExtractedSession(prior, currentContentHash) {
|
|
9449
|
-
if (!prior)
|
|
9450
|
-
return false;
|
|
9451
|
-
if (prior.content_hash == null)
|
|
9452
|
-
return false;
|
|
9453
|
-
return prior.content_hash === currentContentHash;
|
|
9454
|
-
}
|
|
9455
|
-
function getConsolidationJudgedMap(db, entryKeys) {
|
|
9456
|
-
const out = new Map;
|
|
9457
|
-
if (entryKeys.length === 0)
|
|
9458
|
-
return out;
|
|
9459
|
-
const CHUNK = 500;
|
|
9460
|
-
for (let i = 0;i < entryKeys.length; i += CHUNK) {
|
|
9461
|
-
const chunk = entryKeys.slice(i, i + CHUNK);
|
|
9462
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
9463
|
-
const rows = db.prepare(`SELECT * FROM consolidation_judged WHERE entry_key IN (${placeholders})`).all(...chunk);
|
|
9464
|
-
for (const row of rows)
|
|
9465
|
-
out.set(row.entry_key, row);
|
|
9466
|
-
}
|
|
9467
|
-
return out;
|
|
9468
|
-
}
|
|
9469
|
-
function upsertConsolidationJudged(db, input) {
|
|
9470
|
-
db.prepare(`
|
|
9471
|
-
INSERT OR REPLACE INTO consolidation_judged
|
|
9472
|
-
(entry_key, content_hash, judged_at, outcome)
|
|
9473
|
-
VALUES (?, ?, ?, ?)
|
|
9474
|
-
`).run(input.entryKey, input.contentHash, input.judgedAt, input.outcome);
|
|
9475
|
-
}
|
|
9476
|
-
function recordRecombineInduction(db, input) {
|
|
9477
|
-
const row = db.prepare(`
|
|
9478
|
-
INSERT INTO recombine_hypotheses
|
|
9479
|
-
(hypothesis_ref, signature, member_key, consecutive_count, first_seen_at, last_seen_at, last_run)
|
|
9480
|
-
VALUES (?, ?, ?, 1, ?, ?, ?)
|
|
9481
|
-
ON CONFLICT(hypothesis_ref) DO UPDATE SET
|
|
9482
|
-
consecutive_count = consecutive_count + (CASE WHEN last_run IS excluded.last_run THEN 0 ELSE 1 END),
|
|
9483
|
-
last_seen_at = excluded.last_seen_at,
|
|
9484
|
-
last_run = excluded.last_run,
|
|
9485
|
-
signature = excluded.signature,
|
|
9486
|
-
member_key = excluded.member_key
|
|
9487
|
-
RETURNING consecutive_count
|
|
9488
|
-
`).get(input.hypothesisRef, input.signature, input.memberKey, input.seenAt, input.seenAt, input.run);
|
|
9489
|
-
return row?.consecutive_count ?? 0;
|
|
9490
|
-
}
|
|
9491
|
-
function findMatchingRecombineHypothesis(db, input) {
|
|
9492
|
-
const candidateMembers = new Set(input.memberKey.split("|").filter((m) => m.length > 0));
|
|
9493
|
-
if (candidateMembers.size === 0)
|
|
9494
|
-
return;
|
|
9495
|
-
const rows = db.prepare("SELECT * FROM recombine_hypotheses WHERE signature = ? AND promoted_at IS NULL ORDER BY last_seen_at DESC").all(input.signature);
|
|
9496
|
-
let best;
|
|
9497
|
-
let bestOverlap = -1;
|
|
9498
|
-
for (const row of rows) {
|
|
9499
|
-
const rowMembers = row.member_key.split("|").filter((m) => m.length > 0);
|
|
9500
|
-
if (rowMembers.length === 0)
|
|
9501
|
-
continue;
|
|
9502
|
-
let intersection = 0;
|
|
9503
|
-
for (const m of rowMembers) {
|
|
9504
|
-
if (candidateMembers.has(m))
|
|
9505
|
-
intersection += 1;
|
|
9506
|
-
}
|
|
9507
|
-
const union = candidateMembers.size + rowMembers.length - intersection;
|
|
9508
|
-
const overlap = union === 0 ? 0 : intersection / union;
|
|
9509
|
-
if (overlap >= input.minOverlap && overlap > bestOverlap) {
|
|
9510
|
-
best = row;
|
|
9511
|
-
bestOverlap = overlap;
|
|
9512
|
-
}
|
|
9513
|
-
}
|
|
9514
|
-
return best;
|
|
9515
|
-
}
|
|
9516
|
-
function getRecombineHypothesis(db, hypothesisRef) {
|
|
9517
|
-
const row = db.prepare("SELECT * FROM recombine_hypotheses WHERE hypothesis_ref = ?").get(hypothesisRef);
|
|
9518
|
-
return row ?? undefined;
|
|
9519
|
-
}
|
|
9520
|
-
function markRecombineHypothesisPromoted(db, hypothesisRef, promotedAt) {
|
|
9521
|
-
db.prepare("UPDATE recombine_hypotheses SET promoted_at = ?, consecutive_count = 0 WHERE hypothesis_ref = ?").run(promotedAt, hypothesisRef);
|
|
9522
|
-
}
|
|
9523
|
-
function hypothesisMatchesAnyPresentCluster(row, presentClusters, minOverlap) {
|
|
9524
|
-
const rowMembers = row.member_key.split("|").filter((m) => m.length > 0);
|
|
9525
|
-
if (rowMembers.length === 0)
|
|
9526
|
-
return false;
|
|
9527
|
-
const rowSet = new Set(rowMembers);
|
|
9528
|
-
for (const cluster of presentClusters) {
|
|
9529
|
-
if (cluster.signature !== row.signature)
|
|
9530
|
-
continue;
|
|
9531
|
-
const clusterMembers = cluster.memberKey.split("|").filter((m) => m.length > 0);
|
|
9532
|
-
if (clusterMembers.length === 0)
|
|
9533
|
-
continue;
|
|
9534
|
-
let intersection = 0;
|
|
9535
|
-
for (const m of clusterMembers) {
|
|
9536
|
-
if (rowSet.has(m))
|
|
9537
|
-
intersection += 1;
|
|
9538
|
-
}
|
|
9539
|
-
const union = rowSet.size + clusterMembers.length - intersection;
|
|
9540
|
-
const overlap = union === 0 ? 0 : intersection / union;
|
|
9541
|
-
if (overlap >= minOverlap)
|
|
9542
|
-
return true;
|
|
9543
|
-
}
|
|
9544
|
-
return false;
|
|
9545
|
-
}
|
|
9546
|
-
function decayUnseenRecombineHypotheses(db, currentRun, seenRefs, opts) {
|
|
9547
|
-
let effectiveSeen = seenRefs;
|
|
9548
|
-
if (opts && opts.presentClusters.length > 0) {
|
|
9549
|
-
const candidates = db.prepare("SELECT hypothesis_ref, signature, member_key FROM recombine_hypotheses WHERE promoted_at IS NULL AND (last_run IS NULL OR last_run != ?) AND consecutive_count != 0").all(currentRun);
|
|
9550
|
-
const seenSet = new Set(seenRefs);
|
|
9551
|
-
for (const row of candidates) {
|
|
9552
|
-
if (seenSet.has(row.hypothesis_ref))
|
|
9553
|
-
continue;
|
|
9554
|
-
if (hypothesisMatchesAnyPresentCluster(row, opts.presentClusters, opts.minOverlap)) {
|
|
9555
|
-
seenSet.add(row.hypothesis_ref);
|
|
9556
|
-
}
|
|
9557
|
-
}
|
|
9558
|
-
effectiveSeen = [...seenSet];
|
|
9559
|
-
}
|
|
9560
|
-
return decayUnseenRecombineHypothesesInner(db, currentRun, effectiveSeen);
|
|
9561
|
-
}
|
|
9562
|
-
function decayUnseenRecombineHypothesesInner(db, currentRun, seenRefs) {
|
|
9563
|
-
if (seenRefs.length === 0) {
|
|
9564
|
-
const res2 = db.prepare("UPDATE recombine_hypotheses SET consecutive_count = 0 WHERE promoted_at IS NULL AND (last_run IS NULL OR last_run != ?) AND consecutive_count != 0").run(currentRun);
|
|
9565
|
-
return Number(res2.changes);
|
|
9566
|
-
}
|
|
9567
|
-
if (seenRefs.length > 900) {
|
|
9568
|
-
const res2 = db.prepare("UPDATE recombine_hypotheses SET consecutive_count = 0 WHERE promoted_at IS NULL AND (last_run IS NULL OR last_run != ?) AND consecutive_count != 0").run(currentRun);
|
|
9569
|
-
return Number(res2.changes);
|
|
9570
|
-
}
|
|
9571
|
-
const placeholders = seenRefs.map(() => "?").join(",");
|
|
9572
|
-
const res = db.prepare(`UPDATE recombine_hypotheses SET consecutive_count = 0
|
|
9573
|
-
WHERE promoted_at IS NULL
|
|
9574
|
-
AND (last_run IS NULL OR last_run != ?)
|
|
9575
|
-
AND consecutive_count != 0
|
|
9576
|
-
AND hypothesis_ref NOT IN (${placeholders})`).run(currentRun, ...seenRefs);
|
|
9577
|
-
return Number(res.changes);
|
|
9578
|
-
}
|
|
9579
|
-
function embeddingToBlob(vec) {
|
|
9580
|
-
const f32 = new Float32Array(vec);
|
|
9581
|
-
return new Uint8Array(f32.buffer);
|
|
9582
|
-
}
|
|
9583
|
-
function blobToEmbedding(blob) {
|
|
9584
|
-
const f32 = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
|
|
9585
|
-
return Array.from(f32);
|
|
9586
|
-
}
|
|
9587
|
-
function getBodyEmbeddings(db, contentHashes, expectedModelId) {
|
|
9588
|
-
const out = new Map;
|
|
9589
|
-
if (contentHashes.length === 0)
|
|
9590
|
-
return out;
|
|
9591
|
-
const firstRow = db.prepare("SELECT model_id FROM body_embeddings LIMIT 1").get();
|
|
9592
|
-
if (firstRow && firstRow.model_id !== expectedModelId) {
|
|
9593
|
-
db.exec("DELETE FROM body_embeddings");
|
|
9594
|
-
return out;
|
|
9595
|
-
}
|
|
9596
|
-
const CHUNK = 500;
|
|
9597
|
-
for (let i = 0;i < contentHashes.length; i += CHUNK) {
|
|
9598
|
-
const chunk = contentHashes.slice(i, i + CHUNK);
|
|
9599
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
9600
|
-
const rows = db.prepare(`SELECT content_hash, embedding FROM body_embeddings WHERE content_hash IN (${placeholders})`).all(...chunk);
|
|
9601
|
-
for (const row of rows) {
|
|
9602
|
-
out.set(row.content_hash, blobToEmbedding(row.embedding));
|
|
9603
|
-
}
|
|
9604
|
-
}
|
|
9605
|
-
return out;
|
|
9606
|
-
}
|
|
9607
|
-
function upsertBodyEmbeddings(db, entries) {
|
|
9608
|
-
if (entries.length === 0)
|
|
9609
|
-
return;
|
|
9610
|
-
const now = Date.now();
|
|
9611
|
-
const stmt = db.prepare(`
|
|
9612
|
-
INSERT OR REPLACE INTO body_embeddings (content_hash, embedding, model_id, created_at)
|
|
9613
|
-
VALUES (?, ?, ?, ?)
|
|
9614
|
-
`);
|
|
9615
|
-
db.transaction(() => {
|
|
9616
|
-
for (const { contentHash, embedding, modelId } of entries) {
|
|
9617
|
-
stmt.run(contentHash, embeddingToBlob(embedding), modelId, now);
|
|
9618
|
-
}
|
|
9619
|
-
})();
|
|
9620
|
-
}
|
|
9621
|
-
var MIGRATIONS, REGISTRY_INDEX_CACHE_DDL = `
|
|
9622
|
-
CREATE TABLE IF NOT EXISTS registry_index_cache (
|
|
9623
|
-
registry_url TEXT PRIMARY KEY,
|
|
9624
|
-
fetched_at TEXT NOT NULL,
|
|
9625
|
-
etag TEXT,
|
|
9626
|
-
last_modified TEXT,
|
|
9627
|
-
index_json TEXT NOT NULL DEFAULT '{}'
|
|
9628
|
-
);
|
|
9629
|
-
|
|
9630
|
-
CREATE INDEX IF NOT EXISTS idx_registry_cache_fetched
|
|
9631
|
-
ON registry_index_cache(fetched_at);
|
|
9632
|
-
`;
|
|
9633
|
-
var init_state_db = __esm(() => {
|
|
9634
|
-
init_database();
|
|
9635
|
-
init_sqlite_pragmas();
|
|
9636
|
-
init_improve_types();
|
|
9637
|
-
init_paths();
|
|
9638
|
-
init_warn();
|
|
9030
|
+
var MIGRATIONS;
|
|
9031
|
+
var init_migrations = __esm(() => {
|
|
9639
9032
|
MIGRATIONS = [
|
|
9640
9033
|
{
|
|
9641
9034
|
id: "001-initial-schema",
|
|
@@ -10018,13 +9411,676 @@ var init_state_db = __esm(() => {
|
|
|
10018
9411
|
ON recombine_hypotheses(last_seen_at);
|
|
10019
9412
|
`
|
|
10020
9413
|
},
|
|
10021
|
-
{
|
|
10022
|
-
id: "015-asset-salience-encoding-source",
|
|
10023
|
-
up: `
|
|
10024
|
-
ALTER TABLE asset_salience ADD COLUMN encoding_source TEXT DEFAULT NULL;
|
|
10025
|
-
`
|
|
9414
|
+
{
|
|
9415
|
+
id: "015-asset-salience-encoding-source",
|
|
9416
|
+
up: `
|
|
9417
|
+
ALTER TABLE asset_salience ADD COLUMN encoding_source TEXT DEFAULT NULL;
|
|
9418
|
+
`
|
|
9419
|
+
}
|
|
9420
|
+
];
|
|
9421
|
+
});
|
|
9422
|
+
|
|
9423
|
+
// src/core/state-db.ts
|
|
9424
|
+
var exports_state_db = {};
|
|
9425
|
+
__export(exports_state_db, {
|
|
9426
|
+
withStateDbAsync: () => withStateDbAsync,
|
|
9427
|
+
withStateDb: () => withStateDb,
|
|
9428
|
+
withImmediateTransaction: () => withImmediateTransaction,
|
|
9429
|
+
upsertTaskHistory: () => upsertTaskHistory,
|
|
9430
|
+
upsertProposal: () => upsertProposal,
|
|
9431
|
+
upsertExtractedSession: () => upsertExtractedSession,
|
|
9432
|
+
upsertConsolidationJudged: () => upsertConsolidationJudged,
|
|
9433
|
+
upsertBodyEmbeddings: () => upsertBodyEmbeddings,
|
|
9434
|
+
shouldSkipAlreadyExtractedSession: () => shouldSkipAlreadyExtractedSession,
|
|
9435
|
+
runMigrations: () => runMigrations2,
|
|
9436
|
+
recordRecombineInduction: () => recordRecombineInduction,
|
|
9437
|
+
recordImproveRun: () => recordImproveRun,
|
|
9438
|
+
recordFsProposalsImport: () => recordFsProposalsImport,
|
|
9439
|
+
readStateEvents: () => readStateEvents,
|
|
9440
|
+
queryTaskHistory: () => queryTaskHistory,
|
|
9441
|
+
queryImproveRuns: () => queryImproveRuns,
|
|
9442
|
+
queryCompletedTaskIntervals: () => queryCompletedTaskIntervals,
|
|
9443
|
+
purgeOldImproveRuns: () => purgeOldImproveRuns,
|
|
9444
|
+
purgeOldEvents: () => purgeOldEvents,
|
|
9445
|
+
proposalToRowValues: () => proposalToRowValues,
|
|
9446
|
+
proposalRowToProposal: () => proposalRowToProposal,
|
|
9447
|
+
persistPhaseThreshold: () => persistPhaseThreshold,
|
|
9448
|
+
openStateDatabase: () => openStateDatabase,
|
|
9449
|
+
markRecombineHypothesisPromoted: () => markRecombineHypothesisPromoted,
|
|
9450
|
+
listStateProposals: () => listStateProposals,
|
|
9451
|
+
listStateProposalIdsByPrefix: () => listStateProposalIdsByPrefix,
|
|
9452
|
+
listProposalGateDecisions: () => listProposalGateDecisions,
|
|
9453
|
+
listExistingTableNames: () => listExistingTableNames,
|
|
9454
|
+
insertProposalIfAbsent: () => insertProposalIfAbsent,
|
|
9455
|
+
insertEvent: () => insertEvent,
|
|
9456
|
+
importEventsJsonl: () => importEventsJsonl,
|
|
9457
|
+
hasImportedFsProposals: () => hasImportedFsProposals,
|
|
9458
|
+
getTaskHistoryRuns: () => getTaskHistoryRuns,
|
|
9459
|
+
getTaskHistory: () => getTaskHistory,
|
|
9460
|
+
getStateProposal: () => getStateProposal,
|
|
9461
|
+
getStateDbPath: () => getStateDbPath,
|
|
9462
|
+
getRecombineHypothesis: () => getRecombineHypothesis,
|
|
9463
|
+
getPhaseThreshold: () => getPhaseThreshold,
|
|
9464
|
+
getLastExtractRunAt: () => getLastExtractRunAt,
|
|
9465
|
+
getExtractedSessionsMap: () => getExtractedSessionsMap,
|
|
9466
|
+
getExtractedSession: () => getExtractedSession,
|
|
9467
|
+
getConsolidationJudgedMap: () => getConsolidationJudgedMap,
|
|
9468
|
+
getBodyEmbeddings: () => getBodyEmbeddings,
|
|
9469
|
+
findMatchingRecombineHypothesis: () => findMatchingRecombineHypothesis,
|
|
9470
|
+
eventRowToEnvelope: () => eventRowToEnvelope,
|
|
9471
|
+
embeddingToBlob: () => embeddingToBlob,
|
|
9472
|
+
decayUnseenRecombineHypotheses: () => decayUnseenRecombineHypotheses,
|
|
9473
|
+
computeImproveRunMetrics: () => computeImproveRunMetrics,
|
|
9474
|
+
blobToEmbedding: () => blobToEmbedding
|
|
9475
|
+
});
|
|
9476
|
+
import path6 from "path";
|
|
9477
|
+
function getStateDbPath() {
|
|
9478
|
+
return path6.join(getDataDir(), "state.db");
|
|
9479
|
+
}
|
|
9480
|
+
function openStateDatabase(dbPath) {
|
|
9481
|
+
return openManagedDatabase({ path: dbPath ?? getStateDbPath(), init: runMigrations2 });
|
|
9482
|
+
}
|
|
9483
|
+
function withStateDb(fn, opts) {
|
|
9484
|
+
return withManagedDb(() => openStateDatabase(opts?.path), fn, opts);
|
|
9485
|
+
}
|
|
9486
|
+
function withStateDbAsync(fn, opts) {
|
|
9487
|
+
return withManagedDbAsync(() => openStateDatabase(opts?.path), fn, opts);
|
|
9488
|
+
}
|
|
9489
|
+
function eventRowToEnvelope(row) {
|
|
9490
|
+
let metadata;
|
|
9491
|
+
try {
|
|
9492
|
+
const parsed = JSON.parse(row.metadata_json);
|
|
9493
|
+
if (Object.keys(parsed).length > 0) {
|
|
9494
|
+
metadata = parsed;
|
|
9495
|
+
}
|
|
9496
|
+
} catch {}
|
|
9497
|
+
return {
|
|
9498
|
+
schemaVersion: 1,
|
|
9499
|
+
id: row.id,
|
|
9500
|
+
ts: row.ts,
|
|
9501
|
+
eventType: row.event_type,
|
|
9502
|
+
...row.ref !== null ? { ref: row.ref } : {},
|
|
9503
|
+
...metadata !== undefined ? { metadata } : {}
|
|
9504
|
+
};
|
|
9505
|
+
}
|
|
9506
|
+
function proposalRowToProposal(row) {
|
|
9507
|
+
let frontmatter;
|
|
9508
|
+
if (row.frontmatter_json) {
|
|
9509
|
+
try {
|
|
9510
|
+
frontmatter = JSON.parse(row.frontmatter_json);
|
|
9511
|
+
} catch {}
|
|
9512
|
+
}
|
|
9513
|
+
let meta = {};
|
|
9514
|
+
try {
|
|
9515
|
+
meta = JSON.parse(row.metadata_json);
|
|
9516
|
+
} catch {}
|
|
9517
|
+
return {
|
|
9518
|
+
id: row.id,
|
|
9519
|
+
ref: row.ref,
|
|
9520
|
+
status: row.status,
|
|
9521
|
+
source: row.source,
|
|
9522
|
+
...typeof meta.sourceRun === "string" ? { sourceRun: meta.sourceRun } : {},
|
|
9523
|
+
createdAt: row.created_at,
|
|
9524
|
+
updatedAt: row.updated_at,
|
|
9525
|
+
payload: {
|
|
9526
|
+
content: row.content,
|
|
9527
|
+
...frontmatter !== undefined ? { frontmatter } : {}
|
|
9528
|
+
},
|
|
9529
|
+
...meta.review !== undefined ? { review: meta.review } : {},
|
|
9530
|
+
...typeof meta.confidence === "number" ? { confidence: meta.confidence } : {},
|
|
9531
|
+
...meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {},
|
|
9532
|
+
...typeof meta.backupContent === "string" ? { backupContent: meta.backupContent } : {},
|
|
9533
|
+
...typeof meta.eligibilitySource === "string" ? { eligibilitySource: meta.eligibilitySource } : {}
|
|
9534
|
+
};
|
|
9535
|
+
}
|
|
9536
|
+
function proposalToRowValues(proposal, stashDir) {
|
|
9537
|
+
const metaObj = {};
|
|
9538
|
+
if (proposal.sourceRun !== undefined)
|
|
9539
|
+
metaObj.sourceRun = proposal.sourceRun;
|
|
9540
|
+
if (proposal.review !== undefined)
|
|
9541
|
+
metaObj.review = proposal.review;
|
|
9542
|
+
if (proposal.confidence !== undefined)
|
|
9543
|
+
metaObj.confidence = proposal.confidence;
|
|
9544
|
+
if (proposal.gateDecision !== undefined)
|
|
9545
|
+
metaObj.gateDecision = proposal.gateDecision;
|
|
9546
|
+
if (proposal.backupContent !== undefined)
|
|
9547
|
+
metaObj.backupContent = proposal.backupContent;
|
|
9548
|
+
if (proposal.eligibilitySource !== undefined)
|
|
9549
|
+
metaObj.eligibilitySource = proposal.eligibilitySource;
|
|
9550
|
+
return {
|
|
9551
|
+
id: proposal.id,
|
|
9552
|
+
stash_dir: stashDir,
|
|
9553
|
+
ref: proposal.ref,
|
|
9554
|
+
status: proposal.status,
|
|
9555
|
+
source: proposal.source,
|
|
9556
|
+
created_at: proposal.createdAt,
|
|
9557
|
+
updated_at: proposal.updatedAt,
|
|
9558
|
+
content: proposal.payload.content,
|
|
9559
|
+
frontmatter_json: proposal.payload.frontmatter ? JSON.stringify(proposal.payload.frontmatter) : null,
|
|
9560
|
+
metadata_json: JSON.stringify(metaObj)
|
|
9561
|
+
};
|
|
9562
|
+
}
|
|
9563
|
+
function insertEvent(db, input) {
|
|
9564
|
+
try {
|
|
9565
|
+
const result = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
|
|
9566
|
+
VALUES (?, ?, ?, ?)
|
|
9567
|
+
RETURNING id`).get(input.eventType, input.ts, input.ref ?? null, JSON.stringify(input.metadata ?? {}));
|
|
9568
|
+
return result?.id;
|
|
9569
|
+
} catch (err) {
|
|
9570
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9571
|
+
error(`akm: state.db event insert failed (${message})`);
|
|
9572
|
+
return;
|
|
9573
|
+
}
|
|
9574
|
+
}
|
|
9575
|
+
function readStateEvents(db, options = {}) {
|
|
9576
|
+
const conditions = [];
|
|
9577
|
+
const params = [];
|
|
9578
|
+
if (options.sinceId !== undefined && options.sinceId > 0) {
|
|
9579
|
+
conditions.push("id > ?");
|
|
9580
|
+
params.push(options.sinceId);
|
|
9581
|
+
}
|
|
9582
|
+
if (options.since) {
|
|
9583
|
+
conditions.push("ts >= ?");
|
|
9584
|
+
params.push(options.since);
|
|
9585
|
+
}
|
|
9586
|
+
if (options.type) {
|
|
9587
|
+
conditions.push("event_type = ?");
|
|
9588
|
+
params.push(options.type);
|
|
9589
|
+
}
|
|
9590
|
+
if (options.ref) {
|
|
9591
|
+
conditions.push("ref = ?");
|
|
9592
|
+
params.push(options.ref);
|
|
9593
|
+
}
|
|
9594
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9595
|
+
const rows = db.prepare(`SELECT id, event_type, ts, ref, metadata_json FROM events ${where} ORDER BY id ASC`).all(...params);
|
|
9596
|
+
const events = rows.map(eventRowToEnvelope);
|
|
9597
|
+
const nextId = events.length > 0 ? events[events.length - 1].id : options.sinceId ?? 0;
|
|
9598
|
+
return { events, nextId };
|
|
9599
|
+
}
|
|
9600
|
+
function purgeOldEvents(db, retentionDays = 90) {
|
|
9601
|
+
if (!Number.isFinite(retentionDays) || retentionDays <= 0)
|
|
9602
|
+
return 0;
|
|
9603
|
+
const cutoff = new Date(Date.now() - retentionDays * 86400000).toISOString();
|
|
9604
|
+
const result = db.prepare("DELETE FROM events WHERE ts < ?").run(cutoff);
|
|
9605
|
+
const changes = result.changes ?? 0;
|
|
9606
|
+
return typeof changes === "bigint" ? Number(changes) : changes;
|
|
9607
|
+
}
|
|
9608
|
+
function upsertProposal(db, proposal, stashDir) {
|
|
9609
|
+
const v = proposalToRowValues(proposal, stashDir);
|
|
9610
|
+
db.prepare(`
|
|
9611
|
+
INSERT INTO proposals
|
|
9612
|
+
(id, stash_dir, ref, status, source, created_at, updated_at, content, frontmatter_json, metadata_json)
|
|
9613
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9614
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
9615
|
+
stash_dir = excluded.stash_dir,
|
|
9616
|
+
ref = excluded.ref,
|
|
9617
|
+
status = excluded.status,
|
|
9618
|
+
source = excluded.source,
|
|
9619
|
+
updated_at = excluded.updated_at,
|
|
9620
|
+
content = excluded.content,
|
|
9621
|
+
frontmatter_json = excluded.frontmatter_json,
|
|
9622
|
+
metadata_json = excluded.metadata_json
|
|
9623
|
+
`).run(v.id, v.stash_dir, v.ref, v.status, v.source, v.created_at, v.updated_at, v.content, v.frontmatter_json, v.metadata_json);
|
|
9624
|
+
}
|
|
9625
|
+
function listStateProposals(db, options = {}) {
|
|
9626
|
+
const conditions = [];
|
|
9627
|
+
const params = [];
|
|
9628
|
+
if (options.stashDir) {
|
|
9629
|
+
conditions.push("stash_dir = ?");
|
|
9630
|
+
params.push(options.stashDir);
|
|
9631
|
+
}
|
|
9632
|
+
if (options.status) {
|
|
9633
|
+
conditions.push("status = ?");
|
|
9634
|
+
params.push(options.status);
|
|
9635
|
+
}
|
|
9636
|
+
if (options.ref) {
|
|
9637
|
+
conditions.push("ref = ?");
|
|
9638
|
+
params.push(options.ref);
|
|
9639
|
+
}
|
|
9640
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9641
|
+
const rows = db.prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
9642
|
+
content, frontmatter_json, metadata_json
|
|
9643
|
+
FROM proposals ${where} ORDER BY created_at ASC, rowid ASC`).all(...params);
|
|
9644
|
+
return rows.map(proposalRowToProposal);
|
|
9645
|
+
}
|
|
9646
|
+
function listProposalGateDecisions(db) {
|
|
9647
|
+
const rows = db.prepare("SELECT metadata_json FROM proposals ORDER BY created_at ASC, rowid ASC").all();
|
|
9648
|
+
const decisions = [];
|
|
9649
|
+
for (const row of rows) {
|
|
9650
|
+
let meta;
|
|
9651
|
+
try {
|
|
9652
|
+
meta = JSON.parse(row.metadata_json);
|
|
9653
|
+
} catch {
|
|
9654
|
+
continue;
|
|
9655
|
+
}
|
|
9656
|
+
const decision = meta.gateDecision;
|
|
9657
|
+
if (decision && typeof decision === "object" && typeof decision.outcome === "string") {
|
|
9658
|
+
decisions.push(decision);
|
|
9659
|
+
}
|
|
9660
|
+
}
|
|
9661
|
+
decisions.sort((a, b) => new Date(a.decidedAt).getTime() - new Date(b.decidedAt).getTime());
|
|
9662
|
+
return decisions;
|
|
9663
|
+
}
|
|
9664
|
+
function getPhaseThreshold(db, phase) {
|
|
9665
|
+
const row = db.prepare("SELECT threshold FROM improve_gate_thresholds WHERE phase = ?").get(phase);
|
|
9666
|
+
return row?.threshold;
|
|
9667
|
+
}
|
|
9668
|
+
function persistPhaseThreshold(db, phase, threshold) {
|
|
9669
|
+
db.prepare(`INSERT OR REPLACE INTO improve_gate_thresholds (phase, threshold, updated_at)
|
|
9670
|
+
VALUES (?, ?, ?)`).run(phase, Math.round(threshold), Date.now());
|
|
9671
|
+
}
|
|
9672
|
+
function getStateProposal(db, id, stashDir) {
|
|
9673
|
+
const sql = `SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
9674
|
+
content, frontmatter_json, metadata_json
|
|
9675
|
+
FROM proposals WHERE id = ?${stashDir ? " AND stash_dir = ?" : ""}`;
|
|
9676
|
+
const row = stashDir ? db.prepare(sql).get(id, stashDir) : db.prepare(sql).get(id);
|
|
9677
|
+
return row ? proposalRowToProposal(row) : undefined;
|
|
9678
|
+
}
|
|
9679
|
+
function listStateProposalIdsByPrefix(db, stashDir, idPrefix) {
|
|
9680
|
+
const escaped = idPrefix.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
9681
|
+
const rows = db.prepare(`SELECT id FROM proposals
|
|
9682
|
+
WHERE stash_dir = ? AND status = 'pending' AND id LIKE ? ESCAPE '\\'
|
|
9683
|
+
ORDER BY id ASC`).all(stashDir, `${escaped}%`);
|
|
9684
|
+
return rows.map((r) => r.id);
|
|
9685
|
+
}
|
|
9686
|
+
function hasImportedFsProposals(db, stashDir) {
|
|
9687
|
+
return Boolean(db.prepare("SELECT 1 FROM proposal_fs_imports WHERE stash_dir = ?").get(stashDir));
|
|
9688
|
+
}
|
|
9689
|
+
function recordFsProposalsImport(db, stashDir, importedCount) {
|
|
9690
|
+
db.prepare("INSERT OR REPLACE INTO proposal_fs_imports (stash_dir, imported_at, imported_count) VALUES (?, ?, ?)").run(stashDir, new Date().toISOString(), importedCount);
|
|
9691
|
+
}
|
|
9692
|
+
function insertProposalIfAbsent(db, proposal, stashDir) {
|
|
9693
|
+
const v = proposalToRowValues(proposal, stashDir);
|
|
9694
|
+
const result = db.prepare(`
|
|
9695
|
+
INSERT OR IGNORE INTO proposals
|
|
9696
|
+
(id, stash_dir, ref, status, source, created_at, updated_at, content, frontmatter_json, metadata_json)
|
|
9697
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9698
|
+
`).run(v.id, v.stash_dir, v.ref, v.status, v.source, v.created_at, v.updated_at, v.content, v.frontmatter_json, v.metadata_json);
|
|
9699
|
+
const changes = result.changes ?? 0;
|
|
9700
|
+
return Number(changes) > 0;
|
|
9701
|
+
}
|
|
9702
|
+
function withImmediateTransaction(db, fn) {
|
|
9703
|
+
db.exec("BEGIN IMMEDIATE");
|
|
9704
|
+
try {
|
|
9705
|
+
const result = fn();
|
|
9706
|
+
db.exec("COMMIT");
|
|
9707
|
+
return result;
|
|
9708
|
+
} catch (err) {
|
|
9709
|
+
try {
|
|
9710
|
+
db.exec("ROLLBACK");
|
|
9711
|
+
} catch {}
|
|
9712
|
+
throw err;
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
function upsertTaskHistory(db, row) {
|
|
9716
|
+
db.prepare(`
|
|
9717
|
+
INSERT OR IGNORE INTO task_history
|
|
9718
|
+
(task_id, status, started_at, completed_at, failed_at, log_path,
|
|
9719
|
+
target_kind, target_ref, metadata_json)
|
|
9720
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9721
|
+
`).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);
|
|
9722
|
+
}
|
|
9723
|
+
function getTaskHistory(db, taskId) {
|
|
9724
|
+
return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
|
|
9725
|
+
target_kind, target_ref, metadata_json
|
|
9726
|
+
FROM task_history WHERE task_id = ? ORDER BY started_at DESC LIMIT 1`).get(taskId);
|
|
9727
|
+
}
|
|
9728
|
+
function getTaskHistoryRuns(db, taskId, limit = 50) {
|
|
9729
|
+
return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
|
|
9730
|
+
target_kind, target_ref, metadata_json
|
|
9731
|
+
FROM task_history WHERE task_id = ? ORDER BY started_at DESC LIMIT ?`).all(taskId, limit);
|
|
9732
|
+
}
|
|
9733
|
+
function queryTaskHistory(db, options = {}) {
|
|
9734
|
+
const conditions = [];
|
|
9735
|
+
const params = [];
|
|
9736
|
+
if (options.since) {
|
|
9737
|
+
conditions.push("started_at >= ?");
|
|
9738
|
+
params.push(options.since);
|
|
9739
|
+
}
|
|
9740
|
+
if (options.until) {
|
|
9741
|
+
conditions.push("started_at <= ?");
|
|
9742
|
+
params.push(options.until);
|
|
9743
|
+
}
|
|
9744
|
+
if (options.status) {
|
|
9745
|
+
conditions.push("status = ?");
|
|
9746
|
+
params.push(options.status);
|
|
9747
|
+
}
|
|
9748
|
+
if (options.targetKind) {
|
|
9749
|
+
conditions.push("target_kind = ?");
|
|
9750
|
+
params.push(options.targetKind);
|
|
9751
|
+
}
|
|
9752
|
+
if (options.targetRef) {
|
|
9753
|
+
conditions.push("target_ref = ?");
|
|
9754
|
+
params.push(options.targetRef);
|
|
9755
|
+
}
|
|
9756
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9757
|
+
return db.prepare(`SELECT task_id, status, started_at, completed_at, failed_at, log_path,
|
|
9758
|
+
target_kind, target_ref, metadata_json
|
|
9759
|
+
FROM task_history ${where} ORDER BY started_at DESC`).all(...params);
|
|
9760
|
+
}
|
|
9761
|
+
function queryCompletedTaskIntervals(db, since, until) {
|
|
9762
|
+
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";
|
|
9763
|
+
return until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since);
|
|
9764
|
+
}
|
|
9765
|
+
function listExistingTableNames(db, names) {
|
|
9766
|
+
if (names.length === 0)
|
|
9767
|
+
return [];
|
|
9768
|
+
const placeholders = names.map(() => "?").join(", ");
|
|
9769
|
+
return db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${placeholders}) ORDER BY name`).all(...names);
|
|
9770
|
+
}
|
|
9771
|
+
async function importEventsJsonl(db, jsonlPath) {
|
|
9772
|
+
const { readFileSync, existsSync } = await import("fs");
|
|
9773
|
+
if (!existsSync(jsonlPath)) {
|
|
9774
|
+
return { imported: 0, maxId: 0, skipped: 0 };
|
|
9775
|
+
}
|
|
9776
|
+
const text = readFileSync(jsonlPath, "utf8");
|
|
9777
|
+
const lines = text.split(`
|
|
9778
|
+
`).filter((l) => l.trim().length > 0);
|
|
9779
|
+
let imported = 0;
|
|
9780
|
+
let maxId = 0;
|
|
9781
|
+
let skipped = 0;
|
|
9782
|
+
const insertStmt = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
|
|
9783
|
+
VALUES (?, ?, ?, ?)
|
|
9784
|
+
RETURNING id`);
|
|
9785
|
+
const existsStmt = db.prepare(`SELECT 1 FROM events
|
|
9786
|
+
WHERE event_type = ?
|
|
9787
|
+
AND ts = ?
|
|
9788
|
+
AND ref IS ?
|
|
9789
|
+
AND metadata_json = ?
|
|
9790
|
+
LIMIT 1`);
|
|
9791
|
+
db.transaction(() => {
|
|
9792
|
+
for (const line of lines) {
|
|
9793
|
+
let parsed;
|
|
9794
|
+
try {
|
|
9795
|
+
parsed = JSON.parse(line);
|
|
9796
|
+
} catch {
|
|
9797
|
+
continue;
|
|
9798
|
+
}
|
|
9799
|
+
const eventType = typeof parsed.eventType === "string" ? parsed.eventType : "unknown";
|
|
9800
|
+
const ts = typeof parsed.ts === "string" ? parsed.ts : new Date().toISOString();
|
|
9801
|
+
const ref = typeof parsed.ref === "string" ? parsed.ref : null;
|
|
9802
|
+
const metadata = parsed.metadata !== undefined && typeof parsed.metadata === "object" ? JSON.stringify(parsed.metadata) : "{}";
|
|
9803
|
+
const duplicate = existsStmt.get(eventType, ts, ref, metadata);
|
|
9804
|
+
if (duplicate) {
|
|
9805
|
+
skipped++;
|
|
9806
|
+
continue;
|
|
9807
|
+
}
|
|
9808
|
+
const result = insertStmt.get(eventType, ts, ref, metadata);
|
|
9809
|
+
if (result) {
|
|
9810
|
+
imported++;
|
|
9811
|
+
if (result.id > maxId)
|
|
9812
|
+
maxId = result.id;
|
|
9813
|
+
}
|
|
9814
|
+
}
|
|
9815
|
+
})();
|
|
9816
|
+
return { imported, maxId, skipped };
|
|
9817
|
+
}
|
|
9818
|
+
function computeImproveRunMetrics(result) {
|
|
9819
|
+
const plannedCount = Array.isArray(result.plannedRefs) ? result.plannedRefs.length : 0;
|
|
9820
|
+
const actions = Array.isArray(result.actions) ? result.actions : [];
|
|
9821
|
+
const actionsCount = actions.length;
|
|
9822
|
+
let acceptedCount = 0;
|
|
9823
|
+
let rejectedCount = 0;
|
|
9824
|
+
let autoAcceptedCount = 0;
|
|
9825
|
+
let errorCount = 0;
|
|
9826
|
+
for (const action of actions) {
|
|
9827
|
+
switch (classifyImproveAction(action.mode)) {
|
|
9828
|
+
case "accepted":
|
|
9829
|
+
acceptedCount++;
|
|
9830
|
+
break;
|
|
9831
|
+
case "rejected":
|
|
9832
|
+
rejectedCount++;
|
|
9833
|
+
break;
|
|
9834
|
+
case "error":
|
|
9835
|
+
errorCount++;
|
|
9836
|
+
break;
|
|
9837
|
+
case "noop":
|
|
9838
|
+
break;
|
|
9839
|
+
}
|
|
9840
|
+
const r = action.result;
|
|
9841
|
+
if (r && r.autoAccepted === true)
|
|
9842
|
+
autoAcceptedCount++;
|
|
9843
|
+
}
|
|
9844
|
+
autoAcceptedCount += result.gateAutoAcceptedCount ?? 0;
|
|
9845
|
+
return { plannedCount, actionsCount, acceptedCount, rejectedCount, autoAcceptedCount, errorCount };
|
|
9846
|
+
}
|
|
9847
|
+
function recordImproveRun(db, input) {
|
|
9848
|
+
const metricsObj = input.metrics ?? computeImproveRunMetrics(input.result);
|
|
9849
|
+
db.prepare(`
|
|
9850
|
+
INSERT INTO improve_runs
|
|
9851
|
+
(id, started_at, completed_at, stash_dir, dry_run, profile,
|
|
9852
|
+
scope_mode, scope_value, guidance, ok, result_json, metrics_json, metadata_json)
|
|
9853
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9854
|
+
`).run(input.id, input.startedAt, input.completedAt, input.stashDir, input.dryRun ? 1 : 0, input.profile, input.scopeMode, input.scopeValue, input.guidance, input.ok ? 1 : 0, JSON.stringify(input.result), JSON.stringify(metricsObj), JSON.stringify(input.metadata ?? {}));
|
|
9855
|
+
}
|
|
9856
|
+
function queryImproveRuns(db, since, until) {
|
|
9857
|
+
const sql = until ? "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND started_at < ? AND dry_run = 0 ORDER BY started_at DESC" : "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND dry_run = 0 ORDER BY started_at DESC";
|
|
9858
|
+
return until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since);
|
|
9859
|
+
}
|
|
9860
|
+
function purgeOldImproveRuns(db, retentionDays = 90) {
|
|
9861
|
+
if (!Number.isFinite(retentionDays) || retentionDays <= 0)
|
|
9862
|
+
return 0;
|
|
9863
|
+
const cutoff = new Date(Date.now() - retentionDays * 86400000).toISOString();
|
|
9864
|
+
const result = db.prepare("DELETE FROM improve_runs WHERE started_at < ?").run(cutoff);
|
|
9865
|
+
const changes = result.changes ?? 0;
|
|
9866
|
+
return typeof changes === "bigint" ? Number(changes) : changes;
|
|
9867
|
+
}
|
|
9868
|
+
function upsertExtractedSession(db, input) {
|
|
9869
|
+
const endedAtIso = typeof input.sessionEndedAt === "number" && Number.isFinite(input.sessionEndedAt) ? new Date(input.sessionEndedAt).toISOString() : null;
|
|
9870
|
+
db.prepare(`
|
|
9871
|
+
INSERT OR REPLACE INTO extract_sessions_seen
|
|
9872
|
+
(harness, session_id, processed_at, session_ended_at, outcome,
|
|
9873
|
+
candidate_count, proposal_count, rationale, source_run, metadata_json,
|
|
9874
|
+
content_hash)
|
|
9875
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9876
|
+
`).run(input.harness, input.sessionId, input.processedAt, endedAtIso, input.outcome, input.candidateCount, input.proposalCount, input.rationale ?? null, input.sourceRun ?? null, JSON.stringify(input.metadata ?? {}), input.contentHash);
|
|
9877
|
+
}
|
|
9878
|
+
function getExtractedSession(db, harness, sessionId) {
|
|
9879
|
+
const row = db.prepare("SELECT * FROM extract_sessions_seen WHERE harness = ? AND session_id = ?").get(harness, sessionId);
|
|
9880
|
+
return row ?? undefined;
|
|
9881
|
+
}
|
|
9882
|
+
function getExtractedSessionsMap(db, harness, sessionIds) {
|
|
9883
|
+
const out = new Map;
|
|
9884
|
+
if (sessionIds.length === 0)
|
|
9885
|
+
return out;
|
|
9886
|
+
const CHUNK = 500;
|
|
9887
|
+
for (let i = 0;i < sessionIds.length; i += CHUNK) {
|
|
9888
|
+
const chunk = sessionIds.slice(i, i + CHUNK);
|
|
9889
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
9890
|
+
const rows = db.prepare(`SELECT * FROM extract_sessions_seen
|
|
9891
|
+
WHERE harness = ? AND session_id IN (${placeholders})`).all(harness, ...chunk);
|
|
9892
|
+
for (const row of rows)
|
|
9893
|
+
out.set(row.session_id, row);
|
|
9894
|
+
}
|
|
9895
|
+
return out;
|
|
9896
|
+
}
|
|
9897
|
+
function getLastExtractRunAt(db, harness) {
|
|
9898
|
+
const row = db.prepare("SELECT MAX(processed_at) AS last FROM extract_sessions_seen WHERE harness = ?").get(harness);
|
|
9899
|
+
if (!row?.last)
|
|
9900
|
+
return null;
|
|
9901
|
+
const ms = Date.parse(row.last);
|
|
9902
|
+
return Number.isFinite(ms) ? ms : null;
|
|
9903
|
+
}
|
|
9904
|
+
function shouldSkipAlreadyExtractedSession(prior, currentContentHash) {
|
|
9905
|
+
if (!prior)
|
|
9906
|
+
return false;
|
|
9907
|
+
if (prior.content_hash == null)
|
|
9908
|
+
return false;
|
|
9909
|
+
return prior.content_hash === currentContentHash;
|
|
9910
|
+
}
|
|
9911
|
+
function getConsolidationJudgedMap(db, entryKeys) {
|
|
9912
|
+
const out = new Map;
|
|
9913
|
+
if (entryKeys.length === 0)
|
|
9914
|
+
return out;
|
|
9915
|
+
const CHUNK = 500;
|
|
9916
|
+
for (let i = 0;i < entryKeys.length; i += CHUNK) {
|
|
9917
|
+
const chunk = entryKeys.slice(i, i + CHUNK);
|
|
9918
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
9919
|
+
const rows = db.prepare(`SELECT * FROM consolidation_judged WHERE entry_key IN (${placeholders})`).all(...chunk);
|
|
9920
|
+
for (const row of rows)
|
|
9921
|
+
out.set(row.entry_key, row);
|
|
9922
|
+
}
|
|
9923
|
+
return out;
|
|
9924
|
+
}
|
|
9925
|
+
function upsertConsolidationJudged(db, input) {
|
|
9926
|
+
db.prepare(`
|
|
9927
|
+
INSERT OR REPLACE INTO consolidation_judged
|
|
9928
|
+
(entry_key, content_hash, judged_at, outcome)
|
|
9929
|
+
VALUES (?, ?, ?, ?)
|
|
9930
|
+
`).run(input.entryKey, input.contentHash, input.judgedAt, input.outcome);
|
|
9931
|
+
}
|
|
9932
|
+
function recordRecombineInduction(db, input) {
|
|
9933
|
+
const row = db.prepare(`
|
|
9934
|
+
INSERT INTO recombine_hypotheses
|
|
9935
|
+
(hypothesis_ref, signature, member_key, consecutive_count, first_seen_at, last_seen_at, last_run)
|
|
9936
|
+
VALUES (?, ?, ?, 1, ?, ?, ?)
|
|
9937
|
+
ON CONFLICT(hypothesis_ref) DO UPDATE SET
|
|
9938
|
+
consecutive_count = consecutive_count + (CASE WHEN last_run IS excluded.last_run THEN 0 ELSE 1 END),
|
|
9939
|
+
last_seen_at = excluded.last_seen_at,
|
|
9940
|
+
last_run = excluded.last_run,
|
|
9941
|
+
signature = excluded.signature,
|
|
9942
|
+
member_key = excluded.member_key
|
|
9943
|
+
RETURNING consecutive_count
|
|
9944
|
+
`).get(input.hypothesisRef, input.signature, input.memberKey, input.seenAt, input.seenAt, input.run);
|
|
9945
|
+
return row?.consecutive_count ?? 0;
|
|
9946
|
+
}
|
|
9947
|
+
function findMatchingRecombineHypothesis(db, input) {
|
|
9948
|
+
const candidateMembers = new Set(input.memberKey.split("|").filter((m) => m.length > 0));
|
|
9949
|
+
if (candidateMembers.size === 0)
|
|
9950
|
+
return;
|
|
9951
|
+
const rows = db.prepare("SELECT * FROM recombine_hypotheses WHERE signature = ? AND promoted_at IS NULL ORDER BY last_seen_at DESC").all(input.signature);
|
|
9952
|
+
let best;
|
|
9953
|
+
let bestOverlap = -1;
|
|
9954
|
+
for (const row of rows) {
|
|
9955
|
+
const rowMembers = row.member_key.split("|").filter((m) => m.length > 0);
|
|
9956
|
+
if (rowMembers.length === 0)
|
|
9957
|
+
continue;
|
|
9958
|
+
let intersection = 0;
|
|
9959
|
+
for (const m of rowMembers) {
|
|
9960
|
+
if (candidateMembers.has(m))
|
|
9961
|
+
intersection += 1;
|
|
9962
|
+
}
|
|
9963
|
+
const union = candidateMembers.size + rowMembers.length - intersection;
|
|
9964
|
+
const overlap = union === 0 ? 0 : intersection / union;
|
|
9965
|
+
if (overlap >= input.minOverlap && overlap > bestOverlap) {
|
|
9966
|
+
best = row;
|
|
9967
|
+
bestOverlap = overlap;
|
|
10026
9968
|
}
|
|
10027
|
-
|
|
9969
|
+
}
|
|
9970
|
+
return best;
|
|
9971
|
+
}
|
|
9972
|
+
function getRecombineHypothesis(db, hypothesisRef) {
|
|
9973
|
+
const row = db.prepare("SELECT * FROM recombine_hypotheses WHERE hypothesis_ref = ?").get(hypothesisRef);
|
|
9974
|
+
return row ?? undefined;
|
|
9975
|
+
}
|
|
9976
|
+
function markRecombineHypothesisPromoted(db, hypothesisRef, promotedAt) {
|
|
9977
|
+
db.prepare("UPDATE recombine_hypotheses SET promoted_at = ?, consecutive_count = 0 WHERE hypothesis_ref = ?").run(promotedAt, hypothesisRef);
|
|
9978
|
+
}
|
|
9979
|
+
function hypothesisMatchesAnyPresentCluster(row, presentClusters, minOverlap) {
|
|
9980
|
+
const rowMembers = row.member_key.split("|").filter((m) => m.length > 0);
|
|
9981
|
+
if (rowMembers.length === 0)
|
|
9982
|
+
return false;
|
|
9983
|
+
const rowSet = new Set(rowMembers);
|
|
9984
|
+
for (const cluster of presentClusters) {
|
|
9985
|
+
if (cluster.signature !== row.signature)
|
|
9986
|
+
continue;
|
|
9987
|
+
const clusterMembers = cluster.memberKey.split("|").filter((m) => m.length > 0);
|
|
9988
|
+
if (clusterMembers.length === 0)
|
|
9989
|
+
continue;
|
|
9990
|
+
let intersection = 0;
|
|
9991
|
+
for (const m of clusterMembers) {
|
|
9992
|
+
if (rowSet.has(m))
|
|
9993
|
+
intersection += 1;
|
|
9994
|
+
}
|
|
9995
|
+
const union = rowSet.size + clusterMembers.length - intersection;
|
|
9996
|
+
const overlap = union === 0 ? 0 : intersection / union;
|
|
9997
|
+
if (overlap >= minOverlap)
|
|
9998
|
+
return true;
|
|
9999
|
+
}
|
|
10000
|
+
return false;
|
|
10001
|
+
}
|
|
10002
|
+
function decayUnseenRecombineHypotheses(db, currentRun, seenRefs, opts) {
|
|
10003
|
+
let effectiveSeen = seenRefs;
|
|
10004
|
+
if (opts && opts.presentClusters.length > 0) {
|
|
10005
|
+
const candidates = db.prepare("SELECT hypothesis_ref, signature, member_key FROM recombine_hypotheses WHERE promoted_at IS NULL AND (last_run IS NULL OR last_run != ?) AND consecutive_count != 0").all(currentRun);
|
|
10006
|
+
const seenSet = new Set(seenRefs);
|
|
10007
|
+
for (const row of candidates) {
|
|
10008
|
+
if (seenSet.has(row.hypothesis_ref))
|
|
10009
|
+
continue;
|
|
10010
|
+
if (hypothesisMatchesAnyPresentCluster(row, opts.presentClusters, opts.minOverlap)) {
|
|
10011
|
+
seenSet.add(row.hypothesis_ref);
|
|
10012
|
+
}
|
|
10013
|
+
}
|
|
10014
|
+
effectiveSeen = [...seenSet];
|
|
10015
|
+
}
|
|
10016
|
+
return decayUnseenRecombineHypothesesInner(db, currentRun, effectiveSeen);
|
|
10017
|
+
}
|
|
10018
|
+
function decayUnseenRecombineHypothesesInner(db, currentRun, seenRefs) {
|
|
10019
|
+
if (seenRefs.length === 0) {
|
|
10020
|
+
const res2 = db.prepare("UPDATE recombine_hypotheses SET consecutive_count = 0 WHERE promoted_at IS NULL AND (last_run IS NULL OR last_run != ?) AND consecutive_count != 0").run(currentRun);
|
|
10021
|
+
return Number(res2.changes);
|
|
10022
|
+
}
|
|
10023
|
+
if (seenRefs.length > 900) {
|
|
10024
|
+
const res2 = db.prepare("UPDATE recombine_hypotheses SET consecutive_count = 0 WHERE promoted_at IS NULL AND (last_run IS NULL OR last_run != ?) AND consecutive_count != 0").run(currentRun);
|
|
10025
|
+
return Number(res2.changes);
|
|
10026
|
+
}
|
|
10027
|
+
const placeholders = seenRefs.map(() => "?").join(",");
|
|
10028
|
+
const res = db.prepare(`UPDATE recombine_hypotheses SET consecutive_count = 0
|
|
10029
|
+
WHERE promoted_at IS NULL
|
|
10030
|
+
AND (last_run IS NULL OR last_run != ?)
|
|
10031
|
+
AND consecutive_count != 0
|
|
10032
|
+
AND hypothesis_ref NOT IN (${placeholders})`).run(currentRun, ...seenRefs);
|
|
10033
|
+
return Number(res.changes);
|
|
10034
|
+
}
|
|
10035
|
+
function embeddingToBlob(vec) {
|
|
10036
|
+
const f32 = new Float32Array(vec);
|
|
10037
|
+
return new Uint8Array(f32.buffer);
|
|
10038
|
+
}
|
|
10039
|
+
function blobToEmbedding(blob) {
|
|
10040
|
+
const f32 = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
|
|
10041
|
+
return Array.from(f32);
|
|
10042
|
+
}
|
|
10043
|
+
function getBodyEmbeddings(db, contentHashes, expectedModelId) {
|
|
10044
|
+
const out = new Map;
|
|
10045
|
+
if (contentHashes.length === 0)
|
|
10046
|
+
return out;
|
|
10047
|
+
const firstRow = db.prepare("SELECT model_id FROM body_embeddings LIMIT 1").get();
|
|
10048
|
+
if (firstRow && firstRow.model_id !== expectedModelId) {
|
|
10049
|
+
db.exec("DELETE FROM body_embeddings");
|
|
10050
|
+
return out;
|
|
10051
|
+
}
|
|
10052
|
+
const CHUNK = 500;
|
|
10053
|
+
for (let i = 0;i < contentHashes.length; i += CHUNK) {
|
|
10054
|
+
const chunk = contentHashes.slice(i, i + CHUNK);
|
|
10055
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
10056
|
+
const rows = db.prepare(`SELECT content_hash, embedding FROM body_embeddings WHERE content_hash IN (${placeholders})`).all(...chunk);
|
|
10057
|
+
for (const row of rows) {
|
|
10058
|
+
out.set(row.content_hash, blobToEmbedding(row.embedding));
|
|
10059
|
+
}
|
|
10060
|
+
}
|
|
10061
|
+
return out;
|
|
10062
|
+
}
|
|
10063
|
+
function upsertBodyEmbeddings(db, entries) {
|
|
10064
|
+
if (entries.length === 0)
|
|
10065
|
+
return;
|
|
10066
|
+
const now = Date.now();
|
|
10067
|
+
const stmt = db.prepare(`
|
|
10068
|
+
INSERT OR REPLACE INTO body_embeddings (content_hash, embedding, model_id, created_at)
|
|
10069
|
+
VALUES (?, ?, ?, ?)
|
|
10070
|
+
`);
|
|
10071
|
+
db.transaction(() => {
|
|
10072
|
+
for (const { contentHash, embedding, modelId } of entries) {
|
|
10073
|
+
stmt.run(contentHash, embeddingToBlob(embedding), modelId, now);
|
|
10074
|
+
}
|
|
10075
|
+
})();
|
|
10076
|
+
}
|
|
10077
|
+
var init_state_db = __esm(() => {
|
|
10078
|
+
init_managed_db();
|
|
10079
|
+
init_improve_types();
|
|
10080
|
+
init_paths();
|
|
10081
|
+
init_warn();
|
|
10082
|
+
init_migrations();
|
|
10083
|
+
init_migrations();
|
|
10028
10084
|
});
|
|
10029
10085
|
|
|
10030
10086
|
// src/core/best-effort.ts
|
|
@@ -10134,260 +10190,11 @@ function ensureUsageEventsSchema(db) {
|
|
|
10134
10190
|
`);
|
|
10135
10191
|
}
|
|
10136
10192
|
|
|
10137
|
-
// src/indexer/db/db-backup.ts
|
|
10138
|
-
import fs6 from "fs";
|
|
10139
|
-
import path6 from "path";
|
|
10140
|
-
function resolveRetention(env = process.env) {
|
|
10141
|
-
const raw = env.AKM_DB_BACKUP_RETAIN?.trim();
|
|
10142
|
-
if (!raw)
|
|
10143
|
-
return DEFAULT_RETAIN;
|
|
10144
|
-
const parsed = Number.parseInt(raw, 10);
|
|
10145
|
-
if (Number.isNaN(parsed) || parsed < 1) {
|
|
10146
|
-
warn("[akm] AKM_DB_BACKUP_RETAIN=%s is not a positive integer; falling back to %d", raw, DEFAULT_RETAIN);
|
|
10147
|
-
return DEFAULT_RETAIN;
|
|
10148
|
-
}
|
|
10149
|
-
return parsed;
|
|
10150
|
-
}
|
|
10151
|
-
function isBackupDisabled(env = process.env) {
|
|
10152
|
-
const raw = env.AKM_DB_BACKUP?.trim().toLowerCase();
|
|
10153
|
-
if (!raw)
|
|
10154
|
-
return false;
|
|
10155
|
-
return raw === "0" || raw === "false" || raw === "no" || raw === "off";
|
|
10156
|
-
}
|
|
10157
|
-
function measureDataDirSize(dirPath) {
|
|
10158
|
-
if (!fs6.existsSync(dirPath))
|
|
10159
|
-
return 0;
|
|
10160
|
-
let total = 0;
|
|
10161
|
-
const stack = [dirPath];
|
|
10162
|
-
while (stack.length > 0) {
|
|
10163
|
-
const current = stack.pop();
|
|
10164
|
-
if (current === undefined)
|
|
10165
|
-
break;
|
|
10166
|
-
let entries;
|
|
10167
|
-
try {
|
|
10168
|
-
entries = fs6.readdirSync(current, { withFileTypes: true });
|
|
10169
|
-
} catch {
|
|
10170
|
-
continue;
|
|
10171
|
-
}
|
|
10172
|
-
for (const entry of entries) {
|
|
10173
|
-
const full = path6.join(current, entry.name);
|
|
10174
|
-
if (current === dirPath && entry.name === BACKUPS_DIR_NAME && entry.isDirectory())
|
|
10175
|
-
continue;
|
|
10176
|
-
if (entry.isDirectory()) {
|
|
10177
|
-
stack.push(full);
|
|
10178
|
-
} else if (entry.isFile()) {
|
|
10179
|
-
bestEffort(() => {
|
|
10180
|
-
total += fs6.statSync(full).size;
|
|
10181
|
-
}, "file vanished between readdir and stat");
|
|
10182
|
-
}
|
|
10183
|
-
}
|
|
10184
|
-
}
|
|
10185
|
-
return total;
|
|
10186
|
-
}
|
|
10187
|
-
function getFreeSpace(dirPath) {
|
|
10188
|
-
try {
|
|
10189
|
-
const stats = fs6.statfsSync;
|
|
10190
|
-
if (!stats)
|
|
10191
|
-
return null;
|
|
10192
|
-
const res = stats(dirPath);
|
|
10193
|
-
return Number(res.bavail * res.bsize);
|
|
10194
|
-
} catch {
|
|
10195
|
-
return null;
|
|
10196
|
-
}
|
|
10197
|
-
}
|
|
10198
|
-
function formatTimestamp(d) {
|
|
10199
|
-
return d.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "").replace(/-\d{3}$/, "");
|
|
10200
|
-
}
|
|
10201
|
-
function listBackups(dataDir) {
|
|
10202
|
-
const backupsRoot = path6.join(dataDir, BACKUPS_DIR_NAME);
|
|
10203
|
-
if (!fs6.existsSync(backupsRoot))
|
|
10204
|
-
return [];
|
|
10205
|
-
const entries = fs6.readdirSync(backupsRoot, { withFileTypes: true });
|
|
10206
|
-
const results = [];
|
|
10207
|
-
for (const entry of entries) {
|
|
10208
|
-
if (!entry.isDirectory())
|
|
10209
|
-
continue;
|
|
10210
|
-
const full = path6.join(backupsRoot, entry.name);
|
|
10211
|
-
const metaPath = path6.join(full, BACKUP_METADATA_FILE);
|
|
10212
|
-
let createdAt;
|
|
10213
|
-
let sourceVersion = null;
|
|
10214
|
-
let sizeBytes;
|
|
10215
|
-
let reason = DEFAULT_BACKUP_REASON;
|
|
10216
|
-
if (fs6.existsSync(metaPath)) {
|
|
10217
|
-
bestEffort(() => {
|
|
10218
|
-
const raw = fs6.readFileSync(metaPath, "utf8");
|
|
10219
|
-
const parsed = JSON.parse(raw);
|
|
10220
|
-
if (typeof parsed.createdAt === "string")
|
|
10221
|
-
createdAt = parsed.createdAt;
|
|
10222
|
-
if (typeof parsed.sourceVersion === "number")
|
|
10223
|
-
sourceVersion = parsed.sourceVersion;
|
|
10224
|
-
else if (parsed.sourceVersion === null)
|
|
10225
|
-
sourceVersion = null;
|
|
10226
|
-
if (typeof parsed.sizeBytes === "number")
|
|
10227
|
-
sizeBytes = parsed.sizeBytes;
|
|
10228
|
-
if (typeof parsed.reason === "string" && parsed.reason.length > 0)
|
|
10229
|
-
reason = parsed.reason;
|
|
10230
|
-
}, "malformed backup metadata \u2014 fall back to filesystem-derived values");
|
|
10231
|
-
}
|
|
10232
|
-
if (!createdAt) {
|
|
10233
|
-
try {
|
|
10234
|
-
createdAt = fs6.statSync(full).mtime.toISOString();
|
|
10235
|
-
} catch {
|
|
10236
|
-
createdAt = new Date(0).toISOString();
|
|
10237
|
-
}
|
|
10238
|
-
}
|
|
10239
|
-
if (sizeBytes === undefined) {
|
|
10240
|
-
sizeBytes = measureDataDirSize(full);
|
|
10241
|
-
}
|
|
10242
|
-
results.push({
|
|
10243
|
-
path: full,
|
|
10244
|
-
name: entry.name,
|
|
10245
|
-
createdAt,
|
|
10246
|
-
sizeBytes,
|
|
10247
|
-
sourceVersion,
|
|
10248
|
-
reason
|
|
10249
|
-
});
|
|
10250
|
-
}
|
|
10251
|
-
results.sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0);
|
|
10252
|
-
return results;
|
|
10253
|
-
}
|
|
10254
|
-
function pruneOldBackups(dataDir, retain) {
|
|
10255
|
-
const existing = listBackups(dataDir);
|
|
10256
|
-
if (existing.length <= retain)
|
|
10257
|
-
return;
|
|
10258
|
-
const toRemove = existing.slice(retain);
|
|
10259
|
-
for (const entry of toRemove) {
|
|
10260
|
-
try {
|
|
10261
|
-
fs6.rmSync(entry.path, { recursive: true, force: true });
|
|
10262
|
-
} catch (err) {
|
|
10263
|
-
warn("[akm] failed to prune old backup %s \u2014 %s", entry.path, err instanceof Error ? err.message : String(err));
|
|
10264
|
-
}
|
|
10265
|
-
}
|
|
10266
|
-
}
|
|
10267
|
-
function backupDataDir(opts) {
|
|
10268
|
-
const env = opts.env ?? process.env;
|
|
10269
|
-
if (isBackupDisabled(env))
|
|
10270
|
-
return null;
|
|
10271
|
-
const { dataDir, sourceVersion, targetVersion } = opts;
|
|
10272
|
-
const reason = opts.reason && opts.reason.length > 0 ? opts.reason : DEFAULT_BACKUP_REASON;
|
|
10273
|
-
if (!fs6.existsSync(dataDir)) {
|
|
10274
|
-
return null;
|
|
10275
|
-
}
|
|
10276
|
-
const dataDirStat = fs6.statSync(dataDir);
|
|
10277
|
-
if (!dataDirStat.isDirectory()) {
|
|
10278
|
-
warn("[akm] data dir backup skipped \u2014 %s is not a directory", dataDir);
|
|
10279
|
-
return null;
|
|
10280
|
-
}
|
|
10281
|
-
const sourceSize = measureDataDirSize(dataDir);
|
|
10282
|
-
if (sourceSize === 0)
|
|
10283
|
-
return null;
|
|
10284
|
-
const backupsRoot = path6.join(dataDir, BACKUPS_DIR_NAME);
|
|
10285
|
-
try {
|
|
10286
|
-
fs6.mkdirSync(backupsRoot, { recursive: true });
|
|
10287
|
-
} catch (err) {
|
|
10288
|
-
warn("[akm] data dir backup skipped \u2014 could not create %s: %s", backupsRoot, err instanceof Error ? err.message : String(err));
|
|
10289
|
-
return null;
|
|
10290
|
-
}
|
|
10291
|
-
const free = getFreeSpace(backupsRoot);
|
|
10292
|
-
if (free !== null && free < sourceSize * FREE_SPACE_MULTIPLIER) {
|
|
10293
|
-
warn("[akm] data dir backup skipped \u2014 free space %d bytes is less than 1.1\xD7 source size %d bytes (need %d)", free, sourceSize, Math.ceil(sourceSize * FREE_SPACE_MULTIPLIER));
|
|
10294
|
-
return null;
|
|
10295
|
-
}
|
|
10296
|
-
const now = (opts.now ?? (() => new Date))();
|
|
10297
|
-
const stamp = formatTimestamp(now);
|
|
10298
|
-
const dirSuffix = reason === DEFAULT_BACKUP_REASON ? `pre-v${targetVersion}` : reason;
|
|
10299
|
-
const dirName = `${stamp}-${dirSuffix}`;
|
|
10300
|
-
const destPath = path6.join(backupsRoot, dirName);
|
|
10301
|
-
let finalDest = destPath;
|
|
10302
|
-
let suffix = 1;
|
|
10303
|
-
while (fs6.existsSync(finalDest)) {
|
|
10304
|
-
finalDest = `${destPath}-${suffix}`;
|
|
10305
|
-
suffix += 1;
|
|
10306
|
-
}
|
|
10307
|
-
try {
|
|
10308
|
-
copyDataDirExcludingBackups(dataDir, finalDest);
|
|
10309
|
-
} catch (err) {
|
|
10310
|
-
warn("[akm] data dir backup failed \u2014 %s; upgrade will proceed without a snapshot", err instanceof Error ? err.message : String(err));
|
|
10311
|
-
bestEffort(() => fs6.rmSync(finalDest, { recursive: true, force: true }), "cleanup partial backup copy");
|
|
10312
|
-
return null;
|
|
10313
|
-
}
|
|
10314
|
-
const createdAt = now.toISOString();
|
|
10315
|
-
const metadata = {
|
|
10316
|
-
schemaVersion: 1,
|
|
10317
|
-
createdAt,
|
|
10318
|
-
sourceVersion,
|
|
10319
|
-
targetVersion,
|
|
10320
|
-
sizeBytes: sourceSize,
|
|
10321
|
-
reason,
|
|
10322
|
-
hostname: tryHostname(),
|
|
10323
|
-
notes: reason === DEFAULT_BACKUP_REASON ? "Created by AKM before a destructive DB version upgrade. Restore manually by stopping akm and copying the contents back over the live data dir." : `Created by AKM before a destructive ${reason} operation. Restore manually by stopping akm and copying the contents back over the live data dir.`
|
|
10324
|
-
};
|
|
10325
|
-
try {
|
|
10326
|
-
fs6.writeFileSync(path6.join(finalDest, BACKUP_METADATA_FILE), JSON.stringify(metadata, null, 2));
|
|
10327
|
-
} catch (err) {
|
|
10328
|
-
warn("[akm] data dir backup created at %s but metadata write failed \u2014 %s", finalDest, err instanceof Error ? err.message : String(err));
|
|
10329
|
-
}
|
|
10330
|
-
const retain = resolveRetention(env);
|
|
10331
|
-
pruneOldBackups(dataDir, retain);
|
|
10332
|
-
return {
|
|
10333
|
-
path: finalDest,
|
|
10334
|
-
name: path6.basename(finalDest),
|
|
10335
|
-
createdAt,
|
|
10336
|
-
sizeBytes: sourceSize,
|
|
10337
|
-
sourceVersion,
|
|
10338
|
-
targetVersion,
|
|
10339
|
-
reason
|
|
10340
|
-
};
|
|
10341
|
-
}
|
|
10342
|
-
function copyDataDirExcludingBackups(srcRoot, destRoot) {
|
|
10343
|
-
fs6.mkdirSync(destRoot, { recursive: true });
|
|
10344
|
-
const stack = [{ src: srcRoot, dest: destRoot }];
|
|
10345
|
-
while (stack.length > 0) {
|
|
10346
|
-
const frame = stack.pop();
|
|
10347
|
-
if (frame === undefined)
|
|
10348
|
-
break;
|
|
10349
|
-
const { src, dest } = frame;
|
|
10350
|
-
const entries = fs6.readdirSync(src, { withFileTypes: true });
|
|
10351
|
-
for (const entry of entries) {
|
|
10352
|
-
if (src === srcRoot) {
|
|
10353
|
-
if (entry.name === BACKUPS_DIR_NAME && entry.isDirectory())
|
|
10354
|
-
continue;
|
|
10355
|
-
if (entry.name === "akm.lock" || entry.name === "akm.lock.lck")
|
|
10356
|
-
continue;
|
|
10357
|
-
}
|
|
10358
|
-
const srcPath = path6.join(src, entry.name);
|
|
10359
|
-
const destPath = path6.join(dest, entry.name);
|
|
10360
|
-
if (entry.isDirectory()) {
|
|
10361
|
-
fs6.mkdirSync(destPath, { recursive: true });
|
|
10362
|
-
stack.push({ src: srcPath, dest: destPath });
|
|
10363
|
-
} else if (entry.isFile()) {
|
|
10364
|
-
fs6.copyFileSync(srcPath, destPath);
|
|
10365
|
-
} else if (entry.isSymbolicLink()) {
|
|
10366
|
-
const target = fs6.readlinkSync(srcPath);
|
|
10367
|
-
bestEffort(() => fs6.symlinkSync(target, destPath), "symlink creation can fail on Windows without admin");
|
|
10368
|
-
}
|
|
10369
|
-
}
|
|
10370
|
-
}
|
|
10371
|
-
}
|
|
10372
|
-
function tryHostname() {
|
|
10373
|
-
try {
|
|
10374
|
-
const os = __require("os");
|
|
10375
|
-
return os.hostname();
|
|
10376
|
-
} catch {
|
|
10377
|
-
return;
|
|
10378
|
-
}
|
|
10379
|
-
}
|
|
10380
|
-
var DEFAULT_BACKUP_REASON = "version-upgrade", EMBEDDING_DIM_CHANGE_REASON = "embedding-dim-change", BACKUPS_DIR_NAME = "backups", BACKUP_METADATA_FILE = "backup.meta.json", DEFAULT_RETAIN = 5, FREE_SPACE_MULTIPLIER = 1.1;
|
|
10381
|
-
var init_db_backup = __esm(() => {
|
|
10382
|
-
init_best_effort();
|
|
10383
|
-
init_warn();
|
|
10384
|
-
});
|
|
10385
|
-
|
|
10386
10193
|
// src/core/file-lock.ts
|
|
10387
|
-
import
|
|
10194
|
+
import fs6 from "fs";
|
|
10388
10195
|
function tryAcquireLockSync(lockPath, payload) {
|
|
10389
10196
|
try {
|
|
10390
|
-
|
|
10197
|
+
fs6.writeFileSync(lockPath, payload, { flag: "wx" });
|
|
10391
10198
|
return true;
|
|
10392
10199
|
} catch (err) {
|
|
10393
10200
|
if (err.code === "EEXIST")
|
|
@@ -10399,14 +10206,14 @@ function probeLock(lockPath, opts) {
|
|
|
10399
10206
|
let rawContent;
|
|
10400
10207
|
let ageMs;
|
|
10401
10208
|
try {
|
|
10402
|
-
rawContent =
|
|
10209
|
+
rawContent = fs6.readFileSync(lockPath, "utf8");
|
|
10403
10210
|
} catch (err) {
|
|
10404
10211
|
if (err.code === "ENOENT")
|
|
10405
10212
|
return { state: "absent" };
|
|
10406
10213
|
return { state: "stale", reason: "unreadable" };
|
|
10407
10214
|
}
|
|
10408
10215
|
try {
|
|
10409
|
-
const stat =
|
|
10216
|
+
const stat = fs6.statSync(lockPath);
|
|
10410
10217
|
ageMs = Date.now() - stat.mtimeMs;
|
|
10411
10218
|
} catch {
|
|
10412
10219
|
return { state: "stale", reason: "unreadable", rawContent };
|
|
@@ -10425,7 +10232,7 @@ function probeLock(lockPath, opts) {
|
|
|
10425
10232
|
}
|
|
10426
10233
|
function releaseLock(lockPath) {
|
|
10427
10234
|
try {
|
|
10428
|
-
|
|
10235
|
+
fs6.unlinkSync(lockPath);
|
|
10429
10236
|
} catch {}
|
|
10430
10237
|
}
|
|
10431
10238
|
function extractHolderPid(content) {
|
|
@@ -10449,7 +10256,7 @@ var init_file_lock = __esm(() => {
|
|
|
10449
10256
|
});
|
|
10450
10257
|
|
|
10451
10258
|
// src/core/config/config-io.ts
|
|
10452
|
-
import
|
|
10259
|
+
import fs7 from "fs";
|
|
10453
10260
|
import path7 from "path";
|
|
10454
10261
|
function parseConfigText(text, sourcePath) {
|
|
10455
10262
|
const stripped = stripJsonComments(text);
|
|
@@ -10484,22 +10291,22 @@ function writeConfigAtomic(configPath, config) {
|
|
|
10484
10291
|
`);
|
|
10485
10292
|
}
|
|
10486
10293
|
function backupExistingConfig(configPath) {
|
|
10487
|
-
if (!
|
|
10294
|
+
if (!fs7.existsSync(configPath))
|
|
10488
10295
|
return;
|
|
10489
10296
|
const backupDir = path7.join(getCacheDir(), "config-backups");
|
|
10490
|
-
|
|
10297
|
+
fs7.mkdirSync(backupDir, { recursive: true });
|
|
10491
10298
|
const timestamp = new Date().toISOString().replace(/[.:]/g, "-");
|
|
10492
10299
|
const timestamped = path7.join(backupDir, `config-${timestamp}.json`);
|
|
10493
10300
|
const latest = path7.join(backupDir, "config.latest.json");
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10301
|
+
fs7.copyFileSync(configPath, timestamped);
|
|
10302
|
+
fs7.copyFileSync(configPath, latest);
|
|
10303
|
+
pruneOldBackups(backupDir);
|
|
10497
10304
|
return { timestamped, latest };
|
|
10498
10305
|
}
|
|
10499
|
-
function
|
|
10306
|
+
function pruneOldBackups(backupDir) {
|
|
10500
10307
|
let entries;
|
|
10501
10308
|
try {
|
|
10502
|
-
entries =
|
|
10309
|
+
entries = fs7.readdirSync(backupDir);
|
|
10503
10310
|
} catch {
|
|
10504
10311
|
return;
|
|
10505
10312
|
}
|
|
@@ -10507,13 +10314,13 @@ function pruneOldBackups2(backupDir) {
|
|
|
10507
10314
|
const full = path7.join(backupDir, name);
|
|
10508
10315
|
let mtime = 0;
|
|
10509
10316
|
try {
|
|
10510
|
-
mtime =
|
|
10317
|
+
mtime = fs7.statSync(full).mtimeMs;
|
|
10511
10318
|
} catch {}
|
|
10512
10319
|
return { path: full, mtime };
|
|
10513
10320
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
10514
10321
|
for (const stale of timestamped.slice(MAX_CONFIG_BACKUPS)) {
|
|
10515
10322
|
try {
|
|
10516
|
-
|
|
10323
|
+
fs7.unlinkSync(stale.path);
|
|
10517
10324
|
} catch {}
|
|
10518
10325
|
}
|
|
10519
10326
|
}
|
|
@@ -10526,7 +10333,7 @@ function sleepSyncMs(ms) {
|
|
|
10526
10333
|
function acquireConfigLock() {
|
|
10527
10334
|
const lockPath = getConfigLockPath();
|
|
10528
10335
|
try {
|
|
10529
|
-
|
|
10336
|
+
fs7.mkdirSync(path7.dirname(lockPath), { recursive: true });
|
|
10530
10337
|
} catch {}
|
|
10531
10338
|
for (let attempt = 0;attempt < CONFIG_LOCK_MAX_RETRIES; attempt++) {
|
|
10532
10339
|
try {
|
|
@@ -10685,7 +10492,7 @@ var init_inline_refs = __esm(() => {
|
|
|
10685
10492
|
});
|
|
10686
10493
|
|
|
10687
10494
|
// src/integrations/harnesses/claude/session-log.ts
|
|
10688
|
-
import
|
|
10495
|
+
import fs8 from "fs";
|
|
10689
10496
|
import os from "os";
|
|
10690
10497
|
import path8 from "path";
|
|
10691
10498
|
function claudeProjectsDir() {
|
|
@@ -10747,19 +10554,19 @@ function parseClaudeEvent(entry, sessionId, filePath, fallbackTsMs) {
|
|
|
10747
10554
|
class ClaudeCodeProvider {
|
|
10748
10555
|
name = "claude-code";
|
|
10749
10556
|
isAvailable() {
|
|
10750
|
-
return
|
|
10557
|
+
return fs8.existsSync(claudeProjectsDir());
|
|
10751
10558
|
}
|
|
10752
10559
|
watchRoots() {
|
|
10753
10560
|
const dir = claudeProjectsDir();
|
|
10754
|
-
return
|
|
10561
|
+
return fs8.existsSync(dir) ? [dir] : [];
|
|
10755
10562
|
}
|
|
10756
10563
|
*readEvents(input) {
|
|
10757
10564
|
try {
|
|
10758
10565
|
for (const jsonlPath of this.#walkJsonl(claudeProjectsDir())) {
|
|
10759
|
-
const stat =
|
|
10566
|
+
const stat = fs8.statSync(jsonlPath);
|
|
10760
10567
|
if (stat.mtimeMs < input.sinceMs)
|
|
10761
10568
|
continue;
|
|
10762
|
-
const lines =
|
|
10569
|
+
const lines = fs8.readFileSync(jsonlPath, "utf8").split(`
|
|
10763
10570
|
`).filter(Boolean);
|
|
10764
10571
|
for (const line of lines) {
|
|
10765
10572
|
try {
|
|
@@ -10790,7 +10597,7 @@ class ClaudeCodeProvider {
|
|
|
10790
10597
|
for (const jsonlPath of this.#walkJsonl(root)) {
|
|
10791
10598
|
let stat;
|
|
10792
10599
|
try {
|
|
10793
|
-
stat =
|
|
10600
|
+
stat = fs8.statSync(jsonlPath);
|
|
10794
10601
|
} catch {
|
|
10795
10602
|
continue;
|
|
10796
10603
|
}
|
|
@@ -10813,8 +10620,8 @@ class ClaudeCodeProvider {
|
|
|
10813
10620
|
return summaries.sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0));
|
|
10814
10621
|
}
|
|
10815
10622
|
readSession(ref) {
|
|
10816
|
-
const stat =
|
|
10817
|
-
const lines =
|
|
10623
|
+
const stat = fs8.statSync(ref.filePath);
|
|
10624
|
+
const lines = fs8.readFileSync(ref.filePath, "utf8").split(`
|
|
10818
10625
|
`).filter(Boolean);
|
|
10819
10626
|
const events = [];
|
|
10820
10627
|
const inlineRefs = [];
|
|
@@ -10862,12 +10669,12 @@ class ClaudeCodeProvider {
|
|
|
10862
10669
|
#peekJsonl(filePath) {
|
|
10863
10670
|
const result = {};
|
|
10864
10671
|
try {
|
|
10865
|
-
const fd =
|
|
10672
|
+
const fd = fs8.openSync(filePath, "r");
|
|
10866
10673
|
try {
|
|
10867
|
-
const stat =
|
|
10674
|
+
const stat = fs8.fstatSync(fd);
|
|
10868
10675
|
const headSize = Math.min(stat.size, 4096);
|
|
10869
10676
|
const head = Buffer.alloc(headSize);
|
|
10870
|
-
|
|
10677
|
+
fs8.readSync(fd, head, 0, headSize, 0);
|
|
10871
10678
|
const headLines = head.toString("utf8").split(`
|
|
10872
10679
|
`).filter(Boolean);
|
|
10873
10680
|
for (const line of headLines) {
|
|
@@ -10889,7 +10696,7 @@ class ClaudeCodeProvider {
|
|
|
10889
10696
|
if (stat.size > 4096) {
|
|
10890
10697
|
const tailSize = Math.min(stat.size, 4096);
|
|
10891
10698
|
const tail = Buffer.alloc(tailSize);
|
|
10892
|
-
|
|
10699
|
+
fs8.readSync(fd, tail, 0, tailSize, stat.size - tailSize);
|
|
10893
10700
|
const tailLines = tail.toString("utf8").split(`
|
|
10894
10701
|
`).filter(Boolean);
|
|
10895
10702
|
for (let i = tailLines.length - 1;i >= 0; i--) {
|
|
@@ -10906,14 +10713,14 @@ class ClaudeCodeProvider {
|
|
|
10906
10713
|
}
|
|
10907
10714
|
}
|
|
10908
10715
|
} finally {
|
|
10909
|
-
|
|
10716
|
+
fs8.closeSync(fd);
|
|
10910
10717
|
}
|
|
10911
10718
|
} catch {}
|
|
10912
10719
|
return result;
|
|
10913
10720
|
}
|
|
10914
10721
|
*#walkJsonl(dir) {
|
|
10915
10722
|
try {
|
|
10916
|
-
for (const entry of
|
|
10723
|
+
for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
10917
10724
|
const full = path8.join(dir, entry.name);
|
|
10918
10725
|
if (entry.isDirectory())
|
|
10919
10726
|
yield* this.#walkJsonl(full);
|
|
@@ -10971,7 +10778,7 @@ var init_agent_builder2 = __esm(() => {
|
|
|
10971
10778
|
var init_config_import2 = () => {};
|
|
10972
10779
|
|
|
10973
10780
|
// src/integrations/harnesses/opencode/session-log.ts
|
|
10974
|
-
import
|
|
10781
|
+
import fs9 from "fs";
|
|
10975
10782
|
import os2 from "os";
|
|
10976
10783
|
import path9 from "path";
|
|
10977
10784
|
function getOpenCodeBaseDir() {
|
|
@@ -10985,31 +10792,31 @@ class OpenCodeProvider {
|
|
|
10985
10792
|
name = "opencode";
|
|
10986
10793
|
#baseDir = getOpenCodeBaseDir();
|
|
10987
10794
|
isAvailable() {
|
|
10988
|
-
return
|
|
10795
|
+
return fs9.existsSync(this.#baseDir);
|
|
10989
10796
|
}
|
|
10990
10797
|
#dbPath(base) {
|
|
10991
10798
|
return path9.join(base, OPENCODE_DB_FILENAME);
|
|
10992
10799
|
}
|
|
10993
10800
|
watchRoots() {
|
|
10994
10801
|
const roots = [];
|
|
10995
|
-
if (
|
|
10802
|
+
if (fs9.existsSync(this.#dbPath(this.#baseDir)))
|
|
10996
10803
|
roots.push(this.#baseDir);
|
|
10997
10804
|
const sessionRoot = path9.join(this.#baseDir, "storage", "session");
|
|
10998
|
-
if (
|
|
10805
|
+
if (fs9.existsSync(sessionRoot))
|
|
10999
10806
|
roots.push(sessionRoot);
|
|
11000
10807
|
return roots;
|
|
11001
10808
|
}
|
|
11002
10809
|
*readEvents(input) {
|
|
11003
10810
|
const candidates = [this.#baseDir, path9.join(this.#baseDir, "log")];
|
|
11004
10811
|
for (const dir of candidates) {
|
|
11005
|
-
if (!
|
|
10812
|
+
if (!fs9.existsSync(dir))
|
|
11006
10813
|
continue;
|
|
11007
10814
|
try {
|
|
11008
|
-
for (const file of
|
|
10815
|
+
for (const file of fs9.readdirSync(dir)) {
|
|
11009
10816
|
const full = path9.join(dir, file);
|
|
11010
10817
|
let stat;
|
|
11011
10818
|
try {
|
|
11012
|
-
stat =
|
|
10819
|
+
stat = fs9.statSync(full);
|
|
11013
10820
|
} catch {
|
|
11014
10821
|
continue;
|
|
11015
10822
|
}
|
|
@@ -11019,7 +10826,7 @@ class OpenCodeProvider {
|
|
|
11019
10826
|
continue;
|
|
11020
10827
|
if (!file.endsWith(".json") && !file.endsWith(".jsonl") && !file.endsWith(".log"))
|
|
11021
10828
|
continue;
|
|
11022
|
-
const content =
|
|
10829
|
+
const content = fs9.readFileSync(full, "utf8");
|
|
11023
10830
|
const lines = content.includes(`
|
|
11024
10831
|
`) ? content.split(`
|
|
11025
10832
|
`) : [content];
|
|
@@ -11047,30 +10854,30 @@ class OpenCodeProvider {
|
|
|
11047
10854
|
const base = input.location ?? this.#baseDir;
|
|
11048
10855
|
const sinceMs = input.sinceMs ?? 0;
|
|
11049
10856
|
const dbPath = this.#dbPath(base);
|
|
11050
|
-
if (
|
|
10857
|
+
if (fs9.existsSync(dbPath))
|
|
11051
10858
|
return this.#listSessionsFromDb(dbPath, sinceMs);
|
|
11052
10859
|
const sessionRoot = path9.join(base, "storage", "session");
|
|
11053
|
-
if (!
|
|
10860
|
+
if (!fs9.existsSync(sessionRoot))
|
|
11054
10861
|
return [];
|
|
11055
10862
|
const summaries = [];
|
|
11056
10863
|
try {
|
|
11057
|
-
for (const projectId of
|
|
10864
|
+
for (const projectId of fs9.readdirSync(sessionRoot)) {
|
|
11058
10865
|
const projectDir = path9.join(sessionRoot, projectId);
|
|
11059
10866
|
let pstat;
|
|
11060
10867
|
try {
|
|
11061
|
-
pstat =
|
|
10868
|
+
pstat = fs9.statSync(projectDir);
|
|
11062
10869
|
} catch {
|
|
11063
10870
|
continue;
|
|
11064
10871
|
}
|
|
11065
10872
|
if (!pstat.isDirectory())
|
|
11066
10873
|
continue;
|
|
11067
|
-
for (const file of
|
|
10874
|
+
for (const file of fs9.readdirSync(projectDir)) {
|
|
11068
10875
|
if (!file.endsWith(".json"))
|
|
11069
10876
|
continue;
|
|
11070
10877
|
const filePath = path9.join(projectDir, file);
|
|
11071
10878
|
let stat;
|
|
11072
10879
|
try {
|
|
11073
|
-
stat =
|
|
10880
|
+
stat = fs9.statSync(filePath);
|
|
11074
10881
|
} catch {
|
|
11075
10882
|
continue;
|
|
11076
10883
|
}
|
|
@@ -11078,7 +10885,7 @@ class OpenCodeProvider {
|
|
|
11078
10885
|
continue;
|
|
11079
10886
|
let meta;
|
|
11080
10887
|
try {
|
|
11081
|
-
meta = JSON.parse(
|
|
10888
|
+
meta = JSON.parse(fs9.readFileSync(filePath, "utf8"));
|
|
11082
10889
|
} catch {
|
|
11083
10890
|
continue;
|
|
11084
10891
|
}
|
|
@@ -11107,7 +10914,7 @@ class OpenCodeProvider {
|
|
|
11107
10914
|
return this.#readSessionFromDb(ref);
|
|
11108
10915
|
let meta = {};
|
|
11109
10916
|
try {
|
|
11110
|
-
meta = JSON.parse(
|
|
10917
|
+
meta = JSON.parse(fs9.readFileSync(ref.filePath, "utf8"));
|
|
11111
10918
|
} catch {}
|
|
11112
10919
|
const time = meta.time ?? undefined;
|
|
11113
10920
|
const startedAt = typeof time?.created === "number" ? time.created : undefined;
|
|
@@ -11118,14 +10925,14 @@ class OpenCodeProvider {
|
|
|
11118
10925
|
const inlineRefs = [];
|
|
11119
10926
|
const inferredBase = this.#inferBaseFromSessionPath(ref.filePath) ?? this.#baseDir;
|
|
11120
10927
|
const msgDir = path9.join(inferredBase, "storage", "message", ref.sessionId);
|
|
11121
|
-
if (
|
|
10928
|
+
if (fs9.existsSync(msgDir)) {
|
|
11122
10929
|
try {
|
|
11123
|
-
const files =
|
|
10930
|
+
const files = fs9.readdirSync(msgDir).filter((f) => f.endsWith(".json"));
|
|
11124
10931
|
for (const file of files) {
|
|
11125
10932
|
const full = path9.join(msgDir, file);
|
|
11126
10933
|
let msg;
|
|
11127
10934
|
try {
|
|
11128
|
-
msg = JSON.parse(
|
|
10935
|
+
msg = JSON.parse(fs9.readFileSync(full, "utf8"));
|
|
11129
10936
|
} catch {
|
|
11130
10937
|
continue;
|
|
11131
10938
|
}
|
|
@@ -16530,7 +16337,7 @@ __export(exports_config, {
|
|
|
16530
16337
|
DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE: () => DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE,
|
|
16531
16338
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG
|
|
16532
16339
|
});
|
|
16533
|
-
import
|
|
16340
|
+
import fs10 from "fs";
|
|
16534
16341
|
import path10 from "path";
|
|
16535
16342
|
function resolveBatchSize(configured, contextLength) {
|
|
16536
16343
|
const base = configured && configured > 0 ? configured : DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE;
|
|
@@ -16546,7 +16353,7 @@ function loadUserConfig() {
|
|
|
16546
16353
|
const configPath = getConfigPath();
|
|
16547
16354
|
let stat;
|
|
16548
16355
|
try {
|
|
16549
|
-
stat =
|
|
16356
|
+
stat = fs10.statSync(configPath);
|
|
16550
16357
|
} catch {
|
|
16551
16358
|
cachedConfig = undefined;
|
|
16552
16359
|
return applyRuntimeEnvApiKeys({ ...DEFAULT_CONFIG });
|
|
@@ -16556,7 +16363,7 @@ function loadUserConfig() {
|
|
|
16556
16363
|
}
|
|
16557
16364
|
let text;
|
|
16558
16365
|
try {
|
|
16559
|
-
text =
|
|
16366
|
+
text = fs10.readFileSync(configPath, "utf8");
|
|
16560
16367
|
} catch {
|
|
16561
16368
|
cachedConfig = undefined;
|
|
16562
16369
|
return applyRuntimeEnvApiKeys({ ...DEFAULT_CONFIG });
|
|
@@ -16565,7 +16372,7 @@ function loadUserConfig() {
|
|
|
16565
16372
|
const finalConfig = applyRuntimeEnvApiKeys(parseAndValidate(text, configPath));
|
|
16566
16373
|
let finalStat = stat;
|
|
16567
16374
|
try {
|
|
16568
|
-
finalStat =
|
|
16375
|
+
finalStat = fs10.statSync(configPath);
|
|
16569
16376
|
} catch {}
|
|
16570
16377
|
cachedConfig = {
|
|
16571
16378
|
config: finalConfig,
|
|
@@ -16625,7 +16432,9 @@ function maybeAutoMigrateConfigFile(configPath, text) {
|
|
|
16625
16432
|
} catch {
|
|
16626
16433
|
return text;
|
|
16627
16434
|
}
|
|
16628
|
-
|
|
16435
|
+
const onDiskVersion = obj.configVersion;
|
|
16436
|
+
const versionOrder = compareConfigVersion(onDiskVersion, CURRENT_CONFIG_VERSION);
|
|
16437
|
+
if (versionOrder === 1 || onDiskVersion !== undefined && versionOrder === undefined) {
|
|
16629
16438
|
return text;
|
|
16630
16439
|
}
|
|
16631
16440
|
const { changed, result } = migrateConfigShape(obj);
|
|
@@ -16669,7 +16478,7 @@ function saveConfig(config) {
|
|
|
16669
16478
|
cachedConfig = undefined;
|
|
16670
16479
|
const configPath = getConfigPath();
|
|
16671
16480
|
const dir = path10.dirname(configPath);
|
|
16672
|
-
|
|
16481
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
16673
16482
|
const sanitized = sanitizeConfigForWrite(config);
|
|
16674
16483
|
const parseResult = AkmConfigSchema.safeParse(sanitized);
|
|
16675
16484
|
if (!parseResult.success) {
|
|
@@ -16793,7 +16602,7 @@ function applyRuntimeEnvApiKeys(config) {
|
|
|
16793
16602
|
if (envKey)
|
|
16794
16603
|
next.embedding = { ...next.embedding, apiKey: envKey };
|
|
16795
16604
|
}
|
|
16796
|
-
const defaultProfile = next
|
|
16605
|
+
const defaultProfile = resolveDefaultLlmProfileName(next);
|
|
16797
16606
|
if (next.profiles?.llm) {
|
|
16798
16607
|
const updated = { ...next.profiles.llm };
|
|
16799
16608
|
let changed = false;
|
|
@@ -16829,7 +16638,7 @@ function warnIfProjectConfigPresent(startDir) {
|
|
|
16829
16638
|
}
|
|
16830
16639
|
function isFile(filePath) {
|
|
16831
16640
|
try {
|
|
16832
|
-
return
|
|
16641
|
+
return fs10.statSync(filePath).isFile();
|
|
16833
16642
|
} catch {
|
|
16834
16643
|
return false;
|
|
16835
16644
|
}
|
|
@@ -16885,8 +16694,9 @@ __export(exports_db, {
|
|
|
16885
16694
|
sanitizeFtsQuery: () => sanitizeFtsQuery,
|
|
16886
16695
|
relinkUsageEvents: () => relinkUsageEvents,
|
|
16887
16696
|
rebuildFts: () => rebuildFts,
|
|
16697
|
+
purgeEmbeddings: () => purgeEmbeddings,
|
|
16698
|
+
openIndexDatabase: () => openIndexDatabase,
|
|
16888
16699
|
openExistingDatabase: () => openExistingDatabase,
|
|
16889
|
-
openDatabase: () => openDatabase2,
|
|
16890
16700
|
isVecAvailable: () => isVecAvailable,
|
|
16891
16701
|
getZeroResultSearches: () => getZeroResultSearches,
|
|
16892
16702
|
getUtilityScoresByIds: () => getUtilityScoresByIds,
|
|
@@ -16932,20 +16742,20 @@ __export(exports_db, {
|
|
|
16932
16742
|
EMBEDDING_DIM: () => EMBEDDING_DIM,
|
|
16933
16743
|
DB_VERSION: () => DB_VERSION
|
|
16934
16744
|
});
|
|
16935
|
-
import
|
|
16745
|
+
import fs11 from "fs";
|
|
16936
16746
|
import { createRequire as createRequire3 } from "module";
|
|
16937
16747
|
import path11 from "path";
|
|
16938
|
-
function
|
|
16748
|
+
function openIndexDatabase(dbPath, options) {
|
|
16939
16749
|
const resolvedPath = dbPath ?? getDbPath();
|
|
16940
16750
|
const dir = path11.dirname(resolvedPath);
|
|
16941
|
-
if (!
|
|
16942
|
-
|
|
16751
|
+
if (!fs11.existsSync(dir)) {
|
|
16752
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
16943
16753
|
}
|
|
16944
16754
|
const db = openDatabase(resolvedPath);
|
|
16945
16755
|
applyStandardPragmas(db, { dataDir: dir });
|
|
16946
16756
|
loadVecExtension(db);
|
|
16947
16757
|
const resolvedDim = options?.embeddingDim ?? resolveConfiguredEmbeddingDim();
|
|
16948
|
-
ensureSchema(db, resolvedDim
|
|
16758
|
+
ensureSchema(db, resolvedDim);
|
|
16949
16759
|
warnIfVecMissing(db, { once: true });
|
|
16950
16760
|
return db;
|
|
16951
16761
|
}
|
|
@@ -17001,34 +16811,13 @@ function warnIfVecMissing(db, { once } = { once: false }) {
|
|
|
17001
16811
|
}
|
|
17002
16812
|
}, "embeddings table may not exist yet during init");
|
|
17003
16813
|
}
|
|
17004
|
-
function ensureSchema(db, embeddingDim
|
|
16814
|
+
function ensureSchema(db, embeddingDim) {
|
|
17005
16815
|
db.exec(`
|
|
17006
16816
|
CREATE TABLE IF NOT EXISTS index_meta (
|
|
17007
16817
|
key TEXT PRIMARY KEY,
|
|
17008
16818
|
value TEXT NOT NULL
|
|
17009
16819
|
);
|
|
17010
16820
|
`);
|
|
17011
|
-
if (options?.dataDir) {
|
|
17012
|
-
const storedVersionRaw = getMeta(db, "version");
|
|
17013
|
-
const storedVersion = storedVersionRaw !== undefined && storedVersionRaw !== "" ? Number.parseInt(storedVersionRaw, 10) : null;
|
|
17014
|
-
const willUpgrade = storedVersionRaw !== undefined && storedVersionRaw !== "" && storedVersionRaw !== String(DB_VERSION);
|
|
17015
|
-
if (willUpgrade) {
|
|
17016
|
-
try {
|
|
17017
|
-
const result = backupDataDir({
|
|
17018
|
-
dataDir: options.dataDir,
|
|
17019
|
-
sourceVersion: storedVersion !== null && !Number.isNaN(storedVersion) ? storedVersion : null,
|
|
17020
|
-
targetVersion: DB_VERSION,
|
|
17021
|
-
env: process.env
|
|
17022
|
-
});
|
|
17023
|
-
if (result) {
|
|
17024
|
-
warn("[akm] data directory backed up to %s before v%s\u2192v%d upgrade", result.path, storedVersionRaw, DB_VERSION);
|
|
17025
|
-
}
|
|
17026
|
-
} catch (err) {
|
|
17027
|
-
warn("[akm] pre-upgrade data dir backup raised an unexpected error \u2014 %s; upgrade will proceed without a snapshot", err instanceof Error ? err.message : String(err));
|
|
17028
|
-
}
|
|
17029
|
-
}
|
|
17030
|
-
}
|
|
17031
|
-
const usageBackup = handleVersionUpgrade(db);
|
|
17032
16821
|
db.exec(`
|
|
17033
16822
|
CREATE TABLE IF NOT EXISTS entries (
|
|
17034
16823
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -17231,10 +17020,7 @@ function ensureSchema(db, embeddingDim, options) {
|
|
|
17231
17020
|
if (dimExplicit) {
|
|
17232
17021
|
const storedDim = getMeta(db, "embeddingDim");
|
|
17233
17022
|
if (storedDim && storedDim !== String(embeddingDim)) {
|
|
17234
|
-
|
|
17235
|
-
bestEffort(() => db.exec("DROP TABLE IF EXISTS entries_vec"), "drop entries_vec on dim change");
|
|
17236
|
-
bestEffort(() => db.exec("DELETE FROM embeddings"), "delete stale embeddings on dim change");
|
|
17237
|
-
setMeta(db, "hasEmbeddings", "0");
|
|
17023
|
+
purgeEmbeddings(db, { dropVecTable: true });
|
|
17238
17024
|
}
|
|
17239
17025
|
}
|
|
17240
17026
|
const vecExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_vec'").get();
|
|
@@ -17256,109 +17042,20 @@ function ensureSchema(db, embeddingDim, options) {
|
|
|
17256
17042
|
if (dimExplicit) {
|
|
17257
17043
|
const storedDim = getMeta(db, "embeddingDim");
|
|
17258
17044
|
if (storedDim && storedDim !== String(embeddingDim)) {
|
|
17259
|
-
|
|
17260
|
-
bestEffort(() => db.exec("DELETE FROM embeddings"), "delete embeddings on explicit dim change");
|
|
17261
|
-
setMeta(db, "hasEmbeddings", "0");
|
|
17045
|
+
purgeEmbeddings(db);
|
|
17262
17046
|
}
|
|
17263
17047
|
setMeta(db, "embeddingDim", String(embeddingDim));
|
|
17264
17048
|
}
|
|
17265
17049
|
}
|
|
17266
17050
|
ensureUsageEventsSchema(db);
|
|
17267
17051
|
db.exec(REGISTRY_INDEX_CACHE_DDL);
|
|
17268
|
-
restoreUsageEventsBackup(db, usageBackup);
|
|
17269
|
-
}
|
|
17270
|
-
function handleVersionUpgrade(db) {
|
|
17271
|
-
const storedVersion = getMeta(db, "version");
|
|
17272
|
-
if (storedVersion === undefined || storedVersion === "" || storedVersion === String(DB_VERSION))
|
|
17273
|
-
return [];
|
|
17274
|
-
let usageBackup = [];
|
|
17275
|
-
bestEffort(() => {
|
|
17276
|
-
usageBackup = db.prepare("SELECT * FROM usage_events").all();
|
|
17277
|
-
}, "usage_events table may not exist in older versions");
|
|
17278
|
-
db.exec("DROP TABLE IF EXISTS utility_scores");
|
|
17279
|
-
db.exec("DROP TABLE IF EXISTS utility_scores_scoped");
|
|
17280
|
-
db.exec("DROP INDEX IF EXISTS idx_utility_scores_scoped_entry_id");
|
|
17281
|
-
db.exec("DROP TABLE IF EXISTS usage_events");
|
|
17282
|
-
db.exec("DROP TABLE IF EXISTS embeddings");
|
|
17283
|
-
db.exec("DROP TABLE IF EXISTS entries_vec");
|
|
17284
|
-
db.exec("DROP TABLE IF EXISTS entries_fts");
|
|
17285
|
-
db.exec("DROP TABLE IF EXISTS index_dir_state");
|
|
17286
|
-
db.exec("DROP TABLE IF EXISTS llm_enrichment_cache");
|
|
17287
|
-
db.exec("DROP INDEX IF EXISTS idx_llm_cache_updated");
|
|
17288
|
-
db.exec("DROP TABLE IF EXISTS graph_extraction_queue");
|
|
17289
|
-
db.exec("DROP TABLE IF EXISTS graph_file_relations");
|
|
17290
|
-
db.exec("DROP TABLE IF EXISTS graph_file_entities");
|
|
17291
|
-
db.exec("DROP TABLE IF EXISTS graph_files");
|
|
17292
|
-
db.exec("DROP TABLE IF EXISTS graph_meta");
|
|
17293
|
-
db.exec("DROP TABLE IF EXISTS graph_relations");
|
|
17294
|
-
db.exec("DROP TABLE IF EXISTS graph_entities");
|
|
17295
|
-
db.exec("DROP TABLE IF EXISTS graph_nodes");
|
|
17296
|
-
db.exec("DROP TABLE IF EXISTS graph_stashes");
|
|
17297
|
-
db.exec("DROP INDEX IF EXISTS idx_entries_dir");
|
|
17298
|
-
db.exec("DROP INDEX IF EXISTS idx_entries_type");
|
|
17299
|
-
db.exec("DROP TABLE IF EXISTS entries");
|
|
17300
|
-
db.exec("DELETE FROM index_meta");
|
|
17301
|
-
warn("[akm] Index rebuilt due to version upgrade. Run 'akm index' to repopulate.");
|
|
17302
|
-
return usageBackup;
|
|
17303
|
-
}
|
|
17304
|
-
function backupBeforeEmbeddingDimChange(dataDir, fromDim, toDim) {
|
|
17305
|
-
if (!dataDir)
|
|
17306
|
-
return;
|
|
17307
|
-
try {
|
|
17308
|
-
const result = backupDataDir({
|
|
17309
|
-
dataDir,
|
|
17310
|
-
sourceVersion: DB_VERSION,
|
|
17311
|
-
targetVersion: DB_VERSION,
|
|
17312
|
-
reason: EMBEDDING_DIM_CHANGE_REASON,
|
|
17313
|
-
env: process.env
|
|
17314
|
-
});
|
|
17315
|
-
if (result) {
|
|
17316
|
-
warn("[akm] embedding dimension changed %s\u2192%s; data directory backed up to %s; embeddings will be regenerated", fromDim, toDim, result.path);
|
|
17317
|
-
}
|
|
17318
|
-
} catch (err) {
|
|
17319
|
-
warn("[akm] pre-embedding-dim-change data dir backup raised an unexpected error \u2014 %s; embeddings will be regenerated without a snapshot", err instanceof Error ? err.message : String(err));
|
|
17320
|
-
}
|
|
17321
17052
|
}
|
|
17322
|
-
function
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
const targetCols = db.prepare("PRAGMA table_info(usage_events)").all().map((c) => c.name);
|
|
17327
|
-
if (targetCols.length === 0) {
|
|
17328
|
-
warn("[db] restoreUsageEventsBackup: usage_events table missing \u2014 discarding %d backup row(s)", backup.length);
|
|
17329
|
-
return;
|
|
17330
|
-
}
|
|
17331
|
-
const targetSet = new Set(targetCols);
|
|
17332
|
-
const backupCols = Object.keys(backup[0] ?? {});
|
|
17333
|
-
const projectedCols = backupCols.filter((c) => targetSet.has(c));
|
|
17334
|
-
const droppedCols = backupCols.filter((c) => !targetSet.has(c));
|
|
17335
|
-
if (projectedCols.length === 0) {
|
|
17336
|
-
warn("[db] restoreUsageEventsBackup: no overlapping columns between backup and current schema \u2014 discarding %d row(s); dropped: %s", backup.length, droppedCols.join(", ") || "(none)");
|
|
17337
|
-
return;
|
|
17338
|
-
}
|
|
17339
|
-
if (droppedCols.length > 0) {
|
|
17340
|
-
warn("[db] restoreUsageEventsBackup: dropping columns no longer in usage_events schema: %s", droppedCols.join(", "));
|
|
17341
|
-
}
|
|
17342
|
-
let restored = 0;
|
|
17343
|
-
let failed = 0;
|
|
17344
|
-
db.transaction(() => {
|
|
17345
|
-
const placeholders = projectedCols.map(() => "?").join(", ");
|
|
17346
|
-
const insert = db.prepare(`INSERT INTO usage_events (${projectedCols.join(", ")}) VALUES (${placeholders})`);
|
|
17347
|
-
for (const row of backup) {
|
|
17348
|
-
try {
|
|
17349
|
-
insert.run(...projectedCols.map((c) => row[c]));
|
|
17350
|
-
restored++;
|
|
17351
|
-
} catch {
|
|
17352
|
-
failed++;
|
|
17353
|
-
}
|
|
17354
|
-
}
|
|
17355
|
-
})();
|
|
17356
|
-
if (failed > 0) {
|
|
17357
|
-
warn("[db] restoreUsageEventsBackup: restored %d row(s); skipped %d incompatible row(s)", restored, failed);
|
|
17358
|
-
}
|
|
17359
|
-
} catch (err) {
|
|
17360
|
-
warn("[db] restoreUsageEventsBackup: discarded %d backup row(s) \u2014 %s", backup.length, err instanceof Error ? err.message : String(err));
|
|
17053
|
+
function purgeEmbeddings(db, opts) {
|
|
17054
|
+
bestEffort(() => db.exec("DELETE FROM embeddings"), "purge embeddings");
|
|
17055
|
+
if (isVecAvailable(db)) {
|
|
17056
|
+
bestEffort(() => db.exec(opts?.dropVecTable ? "DROP TABLE IF EXISTS entries_vec" : "DELETE FROM entries_vec"), "purge entries_vec");
|
|
17361
17057
|
}
|
|
17058
|
+
setMeta(db, "hasEmbeddings", "0");
|
|
17362
17059
|
}
|
|
17363
17060
|
function getMeta(db, key) {
|
|
17364
17061
|
const row = db.prepare("SELECT value FROM index_meta WHERE key = ?").get(key);
|
|
@@ -18282,18 +17979,27 @@ function collectTagSetFromEntries(db, entryType) {
|
|
|
18282
17979
|
}
|
|
18283
17980
|
return tags;
|
|
18284
17981
|
}
|
|
18285
|
-
var DB_VERSION = 17, EMBEDDING_DIM = 384, GRAPH_SCHEMA_VERSION = 4, vecStatus, VEC_DOCS_URL = "https://github.com/itlackey/akm/blob/main/docs/configuration.md#sqlite-vec-extension", VEC_FALLBACK_THRESHOLD = 1e4, vecInitWarnedDbs,
|
|
17982
|
+
var DB_VERSION = 17, EMBEDDING_DIM = 384, GRAPH_SCHEMA_VERSION = 4, vecStatus, VEC_DOCS_URL = "https://github.com/itlackey/akm/blob/main/docs/configuration.md#sqlite-vec-extension", VEC_FALLBACK_THRESHOLD = 1e4, vecInitWarnedDbs, REGISTRY_INDEX_CACHE_DDL = `
|
|
17983
|
+
CREATE TABLE IF NOT EXISTS registry_index_cache (
|
|
17984
|
+
registry_url TEXT PRIMARY KEY,
|
|
17985
|
+
fetched_at TEXT NOT NULL,
|
|
17986
|
+
etag TEXT,
|
|
17987
|
+
last_modified TEXT,
|
|
17988
|
+
index_json TEXT NOT NULL DEFAULT '{}'
|
|
17989
|
+
);
|
|
17990
|
+
|
|
17991
|
+
CREATE INDEX IF NOT EXISTS idx_registry_cache_fetched
|
|
17992
|
+
ON registry_index_cache(fetched_at);
|
|
17993
|
+
`, SQLITE_CHUNK_SIZE = 500, upsertStmtsByDb, FEEDBACK_LR = 0.1, FEEDBACK_REWARD_POSITIVE = 1, FEEDBACK_REWARD_NEGATIVE = 0, MAX_NEG_DELTA_PER_CALL = 0.15, UTILITY_REVIEW_THRESHOLD = 0.5, HIGH_UTILITY_THRESHOLD = 0.5;
|
|
18286
17994
|
var init_db = __esm(() => {
|
|
18287
17995
|
init_asset_ref();
|
|
18288
17996
|
init_best_effort();
|
|
18289
17997
|
init_paths();
|
|
18290
|
-
init_state_db();
|
|
18291
17998
|
init_warn();
|
|
18292
17999
|
init_types();
|
|
18293
18000
|
init_runtime();
|
|
18294
18001
|
init_database();
|
|
18295
18002
|
init_sqlite_pragmas();
|
|
18296
|
-
init_db_backup();
|
|
18297
18003
|
vecStatus = new WeakMap;
|
|
18298
18004
|
vecInitWarnedDbs = new WeakSet;
|
|
18299
18005
|
upsertStmtsByDb = new WeakMap;
|
|
@@ -18313,12 +18019,12 @@ __export(exports_graph_db, {
|
|
|
18313
18019
|
drainExtractionQueue: () => drainExtractionQueue,
|
|
18314
18020
|
deleteStoredGraph: () => deleteStoredGraph
|
|
18315
18021
|
});
|
|
18316
|
-
import
|
|
18022
|
+
import fs12 from "fs";
|
|
18317
18023
|
function withReadableGraphDb(db, fn) {
|
|
18318
18024
|
if (db)
|
|
18319
18025
|
return fn(db);
|
|
18320
18026
|
const dbPath = getDbPath();
|
|
18321
|
-
if (!
|
|
18027
|
+
if (!fs12.existsSync(dbPath))
|
|
18322
18028
|
throw new Error("GRAPH_DB_MISSING");
|
|
18323
18029
|
const opened = openExistingDatabase(dbPath);
|
|
18324
18030
|
try {
|
|
@@ -18662,7 +18368,7 @@ var init_graph_db = __esm(() => {
|
|
|
18662
18368
|
|
|
18663
18369
|
// scripts/migrate-storage.ts
|
|
18664
18370
|
init_paths();
|
|
18665
|
-
import
|
|
18371
|
+
import fs13 from "fs";
|
|
18666
18372
|
import os3 from "os";
|
|
18667
18373
|
import path12 from "path";
|
|
18668
18374
|
import readline from "readline";
|
|
@@ -18705,14 +18411,14 @@ var PATHS = {
|
|
|
18705
18411
|
};
|
|
18706
18412
|
var versionReports = [];
|
|
18707
18413
|
function ensureDir(dir) {
|
|
18708
|
-
if (!
|
|
18709
|
-
|
|
18414
|
+
if (!fs13.existsSync(dir)) {
|
|
18415
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
18710
18416
|
}
|
|
18711
18417
|
}
|
|
18712
18418
|
function copyAndVerify(src, dest) {
|
|
18713
|
-
|
|
18714
|
-
const srcStat =
|
|
18715
|
-
const destStat =
|
|
18419
|
+
fs13.copyFileSync(src, dest);
|
|
18420
|
+
const srcStat = fs13.statSync(src);
|
|
18421
|
+
const destStat = fs13.statSync(dest);
|
|
18716
18422
|
return destStat.size === srcStat.size;
|
|
18717
18423
|
}
|
|
18718
18424
|
function copyTree(src, dest, opts) {
|
|
@@ -18720,7 +18426,7 @@ function copyTree(src, dest, opts) {
|
|
|
18720
18426
|
let skipped = 0;
|
|
18721
18427
|
let failed = 0;
|
|
18722
18428
|
ensureDir(dest);
|
|
18723
|
-
for (const entry of
|
|
18429
|
+
for (const entry of fs13.readdirSync(src, { withFileTypes: true })) {
|
|
18724
18430
|
const srcEntry = path12.join(src, entry.name);
|
|
18725
18431
|
const destEntry = path12.join(dest, entry.name);
|
|
18726
18432
|
if (entry.isDirectory()) {
|
|
@@ -18730,7 +18436,7 @@ function copyTree(src, dest, opts) {
|
|
|
18730
18436
|
failed += sub.failed;
|
|
18731
18437
|
continue;
|
|
18732
18438
|
}
|
|
18733
|
-
if (!opts.clobber &&
|
|
18439
|
+
if (!opts.clobber && fs13.existsSync(destEntry)) {
|
|
18734
18440
|
skipped++;
|
|
18735
18441
|
continue;
|
|
18736
18442
|
}
|
|
@@ -18748,7 +18454,7 @@ function copyTree(src, dest, opts) {
|
|
|
18748
18454
|
}
|
|
18749
18455
|
function countFilesRecursive(dir) {
|
|
18750
18456
|
let count = 0;
|
|
18751
|
-
for (const entry of
|
|
18457
|
+
for (const entry of fs13.readdirSync(dir, { withFileTypes: true })) {
|
|
18752
18458
|
if (entry.isDirectory()) {
|
|
18753
18459
|
count += countFilesRecursive(path12.join(dir, entry.name));
|
|
18754
18460
|
} else {
|
|
@@ -18765,10 +18471,10 @@ async function runSteps(steps, ctx) {
|
|
|
18765
18471
|
function migrateDb(ctx, filename) {
|
|
18766
18472
|
const src = path12.join(ctx.paths.cacheDir, filename);
|
|
18767
18473
|
const dest = path12.join(ctx.paths.dataDir, filename);
|
|
18768
|
-
if (!
|
|
18474
|
+
if (!fs13.existsSync(src)) {
|
|
18769
18475
|
return { name: filename, status: "skipped", detail: "source not found" };
|
|
18770
18476
|
}
|
|
18771
|
-
if (
|
|
18477
|
+
if (fs13.existsSync(dest)) {
|
|
18772
18478
|
return { name: filename, status: "skipped", detail: "destination already exists" };
|
|
18773
18479
|
}
|
|
18774
18480
|
if (ctx.dryRun) {
|
|
@@ -18787,7 +18493,7 @@ function migrateDb(ctx, filename) {
|
|
|
18787
18493
|
}
|
|
18788
18494
|
async function migrateEventsJsonl(ctx) {
|
|
18789
18495
|
const src = path12.join(ctx.paths.cacheDir, "events.jsonl");
|
|
18790
|
-
if (!
|
|
18496
|
+
if (!fs13.existsSync(src)) {
|
|
18791
18497
|
return { name: "events.jsonl \u2192 state.db", status: "skipped", detail: "source not found" };
|
|
18792
18498
|
}
|
|
18793
18499
|
if (ctx.dryRun) {
|
|
@@ -18833,11 +18539,11 @@ async function migrateEventsJsonl(ctx) {
|
|
|
18833
18539
|
function migrateTaskHistory(ctx) {
|
|
18834
18540
|
const src = path12.join(ctx.paths.cacheDir, "tasks", "history");
|
|
18835
18541
|
const dest = path12.join(ctx.paths.stateDir, "tasks", "history");
|
|
18836
|
-
if (!
|
|
18542
|
+
if (!fs13.existsSync(src)) {
|
|
18837
18543
|
return { name: "tasks/history/", status: "skipped", detail: "source directory not found" };
|
|
18838
18544
|
}
|
|
18839
18545
|
try {
|
|
18840
|
-
const files =
|
|
18546
|
+
const files = fs13.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
|
|
18841
18547
|
if (files.length === 0) {
|
|
18842
18548
|
return { name: "tasks/history/", status: "skipped", detail: "no *.jsonl files found in source directory" };
|
|
18843
18549
|
}
|
|
@@ -18884,10 +18590,10 @@ function migrateTaskHistory(ctx) {
|
|
|
18884
18590
|
function migrateLockfile(ctx) {
|
|
18885
18591
|
const src = path12.join(ctx.paths.configDir, "akm.lock");
|
|
18886
18592
|
const dest = path12.join(ctx.paths.dataDir, "akm.lock");
|
|
18887
|
-
if (!
|
|
18593
|
+
if (!fs13.existsSync(src)) {
|
|
18888
18594
|
return { name: "akm.lock", status: "skipped", detail: "source not found" };
|
|
18889
18595
|
}
|
|
18890
|
-
if (
|
|
18596
|
+
if (fs13.existsSync(dest)) {
|
|
18891
18597
|
return { name: "akm.lock", status: "skipped", detail: "destination already exists" };
|
|
18892
18598
|
}
|
|
18893
18599
|
if (ctx.dryRun) {
|
|
@@ -18909,10 +18615,10 @@ function migrateLockfile(ctx) {
|
|
|
18909
18615
|
function migrateConfigBackups(ctx) {
|
|
18910
18616
|
const src = path12.join(ctx.paths.cacheDir, "config-backups");
|
|
18911
18617
|
const dest = path12.join(ctx.paths.dataDir, "config-backups");
|
|
18912
|
-
if (!
|
|
18618
|
+
if (!fs13.existsSync(src)) {
|
|
18913
18619
|
return { name: "config-backups/", status: "skipped", detail: "source directory not found" };
|
|
18914
18620
|
}
|
|
18915
|
-
if (
|
|
18621
|
+
if (fs13.existsSync(dest)) {
|
|
18916
18622
|
return { name: "config-backups/", status: "skipped", detail: "destination already exists" };
|
|
18917
18623
|
}
|
|
18918
18624
|
if (ctx.dryRun) {
|
|
@@ -18929,7 +18635,7 @@ function migrateConfigBackups(ctx) {
|
|
|
18929
18635
|
try {
|
|
18930
18636
|
const srcCount = countFilesRecursive(src);
|
|
18931
18637
|
const { copied, failed } = copyTree(src, dest, { clobber: true });
|
|
18932
|
-
const destCount =
|
|
18638
|
+
const destCount = fs13.existsSync(dest) ? countFilesRecursive(dest) : 0;
|
|
18933
18639
|
if (failed === 0 && destCount === srcCount) {
|
|
18934
18640
|
return {
|
|
18935
18641
|
name: "config-backups/",
|
|
@@ -18949,10 +18655,10 @@ function migrateConfigBackups(ctx) {
|
|
|
18949
18655
|
}
|
|
18950
18656
|
async function migrateTaskHistoryToDb(ctx) {
|
|
18951
18657
|
const src = path12.join(ctx.paths.cacheDir, "tasks", "history");
|
|
18952
|
-
if (!
|
|
18658
|
+
if (!fs13.existsSync(src)) {
|
|
18953
18659
|
return { name: "tasks/history/ \u2192 state.db", status: "skipped", detail: "source directory not found" };
|
|
18954
18660
|
}
|
|
18955
|
-
const files =
|
|
18661
|
+
const files = fs13.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
|
|
18956
18662
|
if (files.length === 0) {
|
|
18957
18663
|
return {
|
|
18958
18664
|
name: "tasks/history/ \u2192 state.db",
|
|
@@ -18976,7 +18682,7 @@ async function migrateTaskHistoryToDb(ctx) {
|
|
|
18976
18682
|
try {
|
|
18977
18683
|
for (const file of files) {
|
|
18978
18684
|
const filePath = path12.join(src, file);
|
|
18979
|
-
const text =
|
|
18685
|
+
const text = fs13.readFileSync(filePath, "utf8");
|
|
18980
18686
|
const lines = text.split(`
|
|
18981
18687
|
`).filter((l) => l.trim().length > 0);
|
|
18982
18688
|
db.exec("BEGIN");
|
|
@@ -19036,10 +18742,10 @@ async function migrateTaskHistoryToDb(ctx) {
|
|
|
19036
18742
|
}
|
|
19037
18743
|
function noteRegistryIndexCache(ctx) {
|
|
19038
18744
|
const src = path12.join(ctx.paths.cacheDir, "registry-index");
|
|
19039
|
-
if (!
|
|
18745
|
+
if (!fs13.existsSync(src)) {
|
|
19040
18746
|
return { name: "registry-index/ (note)", status: "skipped", detail: "no old $CACHE/registry-index/ directory found" };
|
|
19041
18747
|
}
|
|
19042
|
-
const legacyFiles =
|
|
18748
|
+
const legacyFiles = fs13.readdirSync(src).filter((f) => f.endsWith(".json") && !f.startsWith("website-"));
|
|
19043
18749
|
if (legacyFiles.length === 0) {
|
|
19044
18750
|
return { name: "registry-index/ (note)", status: "skipped", detail: "no old *.json cache files found" };
|
|
19045
18751
|
}
|
|
@@ -19071,7 +18777,7 @@ var v07To08Migration = {
|
|
|
19071
18777
|
path12.join(paths.cacheDir, "config-backups"),
|
|
19072
18778
|
path12.join(paths.cacheDir, "registry-index")
|
|
19073
18779
|
];
|
|
19074
|
-
return candidates.some((p) =>
|
|
18780
|
+
return candidates.some((p) => fs13.existsSync(p));
|
|
19075
18781
|
},
|
|
19076
18782
|
steps: [
|
|
19077
18783
|
{ id: "index-db", title: "index.db", run: (ctx) => migrateDb(ctx, "index.db") },
|
|
@@ -19093,13 +18799,13 @@ function legacyGraphCandidatePaths(ctx) {
|
|
|
19093
18799
|
}
|
|
19094
18800
|
function findLegacyGraphFile(ctx) {
|
|
19095
18801
|
for (const candidate of legacyGraphCandidatePaths(ctx)) {
|
|
19096
|
-
if (
|
|
18802
|
+
if (fs13.existsSync(candidate) && fs13.statSync(candidate).isFile())
|
|
19097
18803
|
return candidate;
|
|
19098
18804
|
}
|
|
19099
18805
|
const graphDir = path12.join(ctx.paths.cacheDir, "graph");
|
|
19100
|
-
if (
|
|
18806
|
+
if (fs13.existsSync(graphDir) && fs13.statSync(graphDir).isDirectory()) {
|
|
19101
18807
|
try {
|
|
19102
|
-
const json =
|
|
18808
|
+
const json = fs13.readdirSync(graphDir).filter((f) => f.endsWith(".json")).map((f) => path12.join(graphDir, f));
|
|
19103
18809
|
if (json.length > 0)
|
|
19104
18810
|
return json[0];
|
|
19105
18811
|
} catch {}
|
|
@@ -19135,7 +18841,7 @@ async function migrateGraphFileToDb(ctx) {
|
|
|
19135
18841
|
if (!legacyFile) {
|
|
19136
18842
|
return { name, status: "skipped", detail: "no legacy graph file found" };
|
|
19137
18843
|
}
|
|
19138
|
-
if (!
|
|
18844
|
+
if (!fs13.existsSync(ctx.paths.indexDbPath)) {
|
|
19139
18845
|
return {
|
|
19140
18846
|
name,
|
|
19141
18847
|
status: "skipped",
|
|
@@ -19150,7 +18856,7 @@ async function migrateGraphFileToDb(ctx) {
|
|
|
19150
18856
|
};
|
|
19151
18857
|
}
|
|
19152
18858
|
try {
|
|
19153
|
-
const raw =
|
|
18859
|
+
const raw = fs13.readFileSync(legacyFile, "utf8");
|
|
19154
18860
|
let parsed;
|
|
19155
18861
|
try {
|
|
19156
18862
|
parsed = JSON.parse(raw);
|
|
@@ -19227,7 +18933,7 @@ async function migrateGraphFileToDb(ctx) {
|
|
|
19227
18933
|
}
|
|
19228
18934
|
const renamed = `${legacyFile}.migrated`;
|
|
19229
18935
|
try {
|
|
19230
|
-
|
|
18936
|
+
fs13.renameSync(legacyFile, renamed);
|
|
19231
18937
|
} catch (err) {
|
|
19232
18938
|
const msg = err instanceof Error ? err.message : String(err);
|
|
19233
18939
|
return {
|
|
@@ -19252,8 +18958,8 @@ async function migrateGraphFileToDb(ctx) {
|
|
|
19252
18958
|
function resolvePrimaryStashDir(configDirPath) {
|
|
19253
18959
|
try {
|
|
19254
18960
|
const cfgPath = path12.join(configDirPath, "config.json");
|
|
19255
|
-
if (
|
|
19256
|
-
const cfg = JSON.parse(
|
|
18961
|
+
if (fs13.existsSync(cfgPath)) {
|
|
18962
|
+
const cfg = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
|
|
19257
18963
|
if (typeof cfg.stashDir === "string" && cfg.stashDir.length > 0)
|
|
19258
18964
|
return cfg.stashDir;
|
|
19259
18965
|
}
|
|
@@ -19262,7 +18968,7 @@ function resolvePrimaryStashDir(configDirPath) {
|
|
|
19262
18968
|
}
|
|
19263
18969
|
function countEnvFilesRecursive(dir) {
|
|
19264
18970
|
let count = 0;
|
|
19265
|
-
for (const entry of
|
|
18971
|
+
for (const entry of fs13.readdirSync(dir, { withFileTypes: true })) {
|
|
19266
18972
|
const full = path12.join(dir, entry.name);
|
|
19267
18973
|
if (entry.isDirectory()) {
|
|
19268
18974
|
count += countEnvFilesRecursive(full);
|
|
@@ -19275,12 +18981,12 @@ function countEnvFilesRecursive(dir) {
|
|
|
19275
18981
|
function chmodTreeSecure(dir) {
|
|
19276
18982
|
const isWin = process.platform === "win32";
|
|
19277
18983
|
try {
|
|
19278
|
-
|
|
18984
|
+
fs13.chmodSync(dir, 448);
|
|
19279
18985
|
} catch (err) {
|
|
19280
18986
|
if (!isWin)
|
|
19281
18987
|
return { ok: false, detail: `chmod 0700 ${dir} failed: ${err instanceof Error ? err.message : err}` };
|
|
19282
18988
|
}
|
|
19283
|
-
for (const entry of
|
|
18989
|
+
for (const entry of fs13.readdirSync(dir, { withFileTypes: true })) {
|
|
19284
18990
|
const full = path12.join(dir, entry.name);
|
|
19285
18991
|
if (entry.isDirectory()) {
|
|
19286
18992
|
const sub = chmodTreeSecure(full);
|
|
@@ -19289,8 +18995,8 @@ function chmodTreeSecure(dir) {
|
|
|
19289
18995
|
continue;
|
|
19290
18996
|
}
|
|
19291
18997
|
try {
|
|
19292
|
-
|
|
19293
|
-
if (!isWin && (
|
|
18998
|
+
fs13.chmodSync(full, 384);
|
|
18999
|
+
if (!isWin && (fs13.statSync(full).mode & 511) !== 384) {
|
|
19294
19000
|
return { ok: false, detail: `mode verification failed for ${full}` };
|
|
19295
19001
|
}
|
|
19296
19002
|
} catch (err) {
|
|
@@ -19306,10 +19012,10 @@ async function migrateVaultsToEnv(ctx) {
|
|
|
19306
19012
|
const vaultsDir = path12.join(stashDir, "vaults");
|
|
19307
19013
|
const envDir = path12.join(stashDir, "env");
|
|
19308
19014
|
const marker = path12.join(vaultsDir, MIGRATED_MARKER);
|
|
19309
|
-
if (!
|
|
19015
|
+
if (!fs13.existsSync(vaultsDir)) {
|
|
19310
19016
|
return { name, status: "skipped", detail: `no vaults/ directory under ${stashDir}` };
|
|
19311
19017
|
}
|
|
19312
|
-
if (
|
|
19018
|
+
if (fs13.existsSync(marker)) {
|
|
19313
19019
|
return { name, status: "skipped", detail: "already migrated (.migrated marker present)" };
|
|
19314
19020
|
}
|
|
19315
19021
|
const vaultEnvCount = countEnvFilesRecursive(vaultsDir);
|
|
@@ -19340,7 +19046,7 @@ async function migrateVaultsToEnv(ctx) {
|
|
|
19340
19046
|
detail: `post-copy count ${envEnvCount} < source ${vaultEnvCount}; no marker written`
|
|
19341
19047
|
};
|
|
19342
19048
|
}
|
|
19343
|
-
|
|
19049
|
+
fs13.writeFileSync(marker, `migrated vaults/ -> env/ at ${new Date().toISOString()}
|
|
19344
19050
|
`, { mode: 384 });
|
|
19345
19051
|
return {
|
|
19346
19052
|
name,
|