akm-cli 0.9.0-beta.1 → 0.9.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +469 -0
- package/dist/assets/templates/html/default.html +78 -0
- package/dist/assets/templates/html/health.html +732 -0
- package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
- package/dist/cli/shared.js +21 -5
- package/dist/cli.js +47 -5
- package/dist/commands/config-cli.js +0 -10
- package/dist/commands/feedback-cli.js +42 -37
- package/dist/commands/graph/graph.js +75 -71
- package/dist/commands/health/checks.js +48 -0
- package/dist/commands/health/html-report.js +666 -0
- package/dist/commands/health.js +186 -13
- package/dist/commands/improve/consolidate.js +39 -6
- package/dist/commands/improve/distill.js +26 -5
- package/dist/commands/improve/extract-prompt.js +1 -1
- package/dist/commands/improve/extract.js +52 -8
- package/dist/commands/improve/improve-auto-accept.js +33 -1
- package/dist/commands/improve/improve-cli.js +7 -0
- package/dist/commands/improve/improve-profiles.js +4 -0
- package/dist/commands/improve/improve.js +874 -447
- package/dist/commands/improve/proactive-maintenance.js +113 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +31 -0
- package/dist/commands/proposal/drain.js +73 -6
- package/dist/commands/proposal/proposal-cli.js +22 -10
- package/dist/commands/proposal/proposal.js +17 -1
- package/dist/commands/proposal/validators/proposals.js +365 -329
- package/dist/commands/read/curate.js +17 -0
- package/dist/commands/remember.js +6 -2
- package/dist/commands/sources/stash-cli.js +10 -2
- package/dist/commands/tasks/tasks.js +32 -8
- package/dist/core/config/config-schema.js +30 -0
- package/dist/core/logs-db.js +304 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/state-db.js +152 -14
- package/dist/indexer/db/db.js +99 -13
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +114 -111
- package/dist/indexer/passes/memory-inference.js +61 -22
- package/dist/integrations/harnesses/claude/session-log.js +17 -5
- package/dist/llm/client.js +38 -4
- package/dist/llm/usage-persist.js +77 -0
- package/dist/llm/usage-telemetry.js +103 -0
- package/dist/output/context.js +3 -2
- package/dist/output/html-render.js +73 -0
- package/dist/output/shapes/helpers.js +17 -1
- package/dist/output/text/helpers.js +69 -1
- package/dist/scripts/migrate-storage.js +153 -25
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +21 -2
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/tasks/backends/cron.js +46 -9
- package/dist/tasks/runner.js +99 -16
- package/dist/workflows/db.js +4 -0
- package/package.json +3 -2
- package/dist/commands/config-edit.js +0 -344
|
@@ -8882,12 +8882,14 @@ var init_improve_types = () => {};
|
|
|
8882
8882
|
// src/core/state-db.ts
|
|
8883
8883
|
var exports_state_db = {};
|
|
8884
8884
|
__export(exports_state_db, {
|
|
8885
|
+
withImmediateTransaction: () => withImmediateTransaction,
|
|
8885
8886
|
upsertTaskHistory: () => upsertTaskHistory,
|
|
8886
8887
|
upsertProposal: () => upsertProposal,
|
|
8887
8888
|
upsertExtractedSession: () => upsertExtractedSession,
|
|
8888
8889
|
shouldSkipAlreadyExtractedSession: () => shouldSkipAlreadyExtractedSession,
|
|
8889
8890
|
runMigrations: () => runMigrations2,
|
|
8890
8891
|
recordImproveRun: () => recordImproveRun,
|
|
8892
|
+
recordFsProposalsImport: () => recordFsProposalsImport,
|
|
8891
8893
|
readStateEvents: () => readStateEvents,
|
|
8892
8894
|
queryTaskHistory: () => queryTaskHistory,
|
|
8893
8895
|
queryImproveRuns: () => queryImproveRuns,
|
|
@@ -8898,9 +8900,12 @@ __export(exports_state_db, {
|
|
|
8898
8900
|
proposalRowToProposal: () => proposalRowToProposal,
|
|
8899
8901
|
openStateDatabase: () => openStateDatabase,
|
|
8900
8902
|
listStateProposals: () => listStateProposals,
|
|
8903
|
+
listStateProposalIdsByPrefix: () => listStateProposalIdsByPrefix,
|
|
8901
8904
|
listExistingTableNames: () => listExistingTableNames,
|
|
8905
|
+
insertProposalIfAbsent: () => insertProposalIfAbsent,
|
|
8902
8906
|
insertEvent: () => insertEvent,
|
|
8903
8907
|
importEventsJsonl: () => importEventsJsonl,
|
|
8908
|
+
hasImportedFsProposals: () => hasImportedFsProposals,
|
|
8904
8909
|
getTaskHistoryRuns: () => getTaskHistoryRuns,
|
|
8905
8910
|
getTaskHistory: () => getTaskHistory,
|
|
8906
8911
|
getStateProposal: () => getStateProposal,
|
|
@@ -8925,7 +8930,7 @@ function openStateDatabase(dbPath) {
|
|
|
8925
8930
|
const db = openDatabase(resolvedPath);
|
|
8926
8931
|
db.exec("PRAGMA journal_mode = WAL");
|
|
8927
8932
|
db.exec("PRAGMA foreign_keys = ON");
|
|
8928
|
-
db.exec("PRAGMA busy_timeout =
|
|
8933
|
+
db.exec("PRAGMA busy_timeout = 30000");
|
|
8929
8934
|
runMigrations2(db);
|
|
8930
8935
|
return db;
|
|
8931
8936
|
}
|
|
@@ -8972,7 +8977,11 @@ function proposalRowToProposal(row) {
|
|
|
8972
8977
|
content: row.content,
|
|
8973
8978
|
...frontmatter !== undefined ? { frontmatter } : {}
|
|
8974
8979
|
},
|
|
8975
|
-
...meta.review !== undefined ? { review: meta.review } : {}
|
|
8980
|
+
...meta.review !== undefined ? { review: meta.review } : {},
|
|
8981
|
+
...typeof meta.confidence === "number" ? { confidence: meta.confidence } : {},
|
|
8982
|
+
...meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {},
|
|
8983
|
+
...typeof meta.backupContent === "string" ? { backupContent: meta.backupContent } : {},
|
|
8984
|
+
...typeof meta.eligibilitySource === "string" ? { eligibilitySource: meta.eligibilitySource } : {}
|
|
8976
8985
|
};
|
|
8977
8986
|
}
|
|
8978
8987
|
function proposalToRowValues(proposal, stashDir) {
|
|
@@ -8981,6 +8990,14 @@ function proposalToRowValues(proposal, stashDir) {
|
|
|
8981
8990
|
metaObj.sourceRun = proposal.sourceRun;
|
|
8982
8991
|
if (proposal.review !== undefined)
|
|
8983
8992
|
metaObj.review = proposal.review;
|
|
8993
|
+
if (proposal.confidence !== undefined)
|
|
8994
|
+
metaObj.confidence = proposal.confidence;
|
|
8995
|
+
if (proposal.gateDecision !== undefined)
|
|
8996
|
+
metaObj.gateDecision = proposal.gateDecision;
|
|
8997
|
+
if (proposal.backupContent !== undefined)
|
|
8998
|
+
metaObj.backupContent = proposal.backupContent;
|
|
8999
|
+
if (proposal.eligibilitySource !== undefined)
|
|
9000
|
+
metaObj.eligibilitySource = proposal.eligibilitySource;
|
|
8984
9001
|
return {
|
|
8985
9002
|
id: proposal.id,
|
|
8986
9003
|
stash_dir: stashDir,
|
|
@@ -9074,15 +9091,52 @@ function listStateProposals(db, options = {}) {
|
|
|
9074
9091
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9075
9092
|
const rows = db.prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
9076
9093
|
content, frontmatter_json, metadata_json
|
|
9077
|
-
FROM proposals ${where} ORDER BY created_at ASC`).all(...params);
|
|
9094
|
+
FROM proposals ${where} ORDER BY created_at ASC, rowid ASC`).all(...params);
|
|
9078
9095
|
return rows.map(proposalRowToProposal);
|
|
9079
9096
|
}
|
|
9080
|
-
function getStateProposal(db, id) {
|
|
9081
|
-
const
|
|
9097
|
+
function getStateProposal(db, id, stashDir) {
|
|
9098
|
+
const sql = `SELECT id, stash_dir, ref, status, source, created_at, updated_at,
|
|
9082
9099
|
content, frontmatter_json, metadata_json
|
|
9083
|
-
FROM proposals WHERE id =
|
|
9100
|
+
FROM proposals WHERE id = ?${stashDir ? " AND stash_dir = ?" : ""}`;
|
|
9101
|
+
const row = stashDir ? db.prepare(sql).get(id, stashDir) : db.prepare(sql).get(id);
|
|
9084
9102
|
return row ? proposalRowToProposal(row) : undefined;
|
|
9085
9103
|
}
|
|
9104
|
+
function listStateProposalIdsByPrefix(db, stashDir, idPrefix) {
|
|
9105
|
+
const escaped = idPrefix.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
9106
|
+
const rows = db.prepare(`SELECT id FROM proposals
|
|
9107
|
+
WHERE stash_dir = ? AND status = 'pending' AND id LIKE ? ESCAPE '\\'
|
|
9108
|
+
ORDER BY id ASC`).all(stashDir, `${escaped}%`);
|
|
9109
|
+
return rows.map((r) => r.id);
|
|
9110
|
+
}
|
|
9111
|
+
function hasImportedFsProposals(db, stashDir) {
|
|
9112
|
+
return Boolean(db.prepare("SELECT 1 FROM proposal_fs_imports WHERE stash_dir = ?").get(stashDir));
|
|
9113
|
+
}
|
|
9114
|
+
function recordFsProposalsImport(db, stashDir, importedCount) {
|
|
9115
|
+
db.prepare("INSERT OR REPLACE INTO proposal_fs_imports (stash_dir, imported_at, imported_count) VALUES (?, ?, ?)").run(stashDir, new Date().toISOString(), importedCount);
|
|
9116
|
+
}
|
|
9117
|
+
function insertProposalIfAbsent(db, proposal, stashDir) {
|
|
9118
|
+
const v = proposalToRowValues(proposal, stashDir);
|
|
9119
|
+
const result = db.prepare(`
|
|
9120
|
+
INSERT OR IGNORE INTO proposals
|
|
9121
|
+
(id, stash_dir, ref, status, source, created_at, updated_at, content, frontmatter_json, metadata_json)
|
|
9122
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9123
|
+
`).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);
|
|
9124
|
+
const changes = result.changes ?? 0;
|
|
9125
|
+
return Number(changes) > 0;
|
|
9126
|
+
}
|
|
9127
|
+
function withImmediateTransaction(db, fn) {
|
|
9128
|
+
db.exec("BEGIN IMMEDIATE");
|
|
9129
|
+
try {
|
|
9130
|
+
const result = fn();
|
|
9131
|
+
db.exec("COMMIT");
|
|
9132
|
+
return result;
|
|
9133
|
+
} catch (err) {
|
|
9134
|
+
try {
|
|
9135
|
+
db.exec("ROLLBACK");
|
|
9136
|
+
} catch {}
|
|
9137
|
+
throw err;
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9086
9140
|
function upsertTaskHistory(db, row) {
|
|
9087
9141
|
db.prepare(`
|
|
9088
9142
|
INSERT OR IGNORE INTO task_history
|
|
@@ -9363,7 +9417,9 @@ var init_state_db = __esm(() => {
|
|
|
9363
9417
|
--
|
|
9364
9418
|
-- Extensible (metadata_json) columns:
|
|
9365
9419
|
-- metadata_json TEXT \u2014 JSON object for future proposal fields.
|
|
9366
|
-
-- Current fields stored here: sourceRun,
|
|
9420
|
+
-- Current fields stored here: sourceRun,
|
|
9421
|
+
-- review, confidence, gateDecision (#577),
|
|
9422
|
+
-- backupContent, eligibilitySource.
|
|
9367
9423
|
--
|
|
9368
9424
|
-- ADD COLUMN extension points (future migrations):
|
|
9369
9425
|
-- ALTER TABLE proposals ADD COLUMN source_run TEXT DEFAULT NULL;
|
|
@@ -9550,6 +9606,23 @@ var init_state_db = __esm(() => {
|
|
|
9550
9606
|
CREATE INDEX IF NOT EXISTS idx_extract_sessions_processed
|
|
9551
9607
|
ON extract_sessions_seen(processed_at);
|
|
9552
9608
|
`
|
|
9609
|
+
},
|
|
9610
|
+
{
|
|
9611
|
+
id: "005-proposal-fs-imports",
|
|
9612
|
+
up: `
|
|
9613
|
+
CREATE TABLE IF NOT EXISTS proposal_fs_imports (
|
|
9614
|
+
stash_dir TEXT PRIMARY KEY,
|
|
9615
|
+
imported_at TEXT NOT NULL,
|
|
9616
|
+
imported_count INTEGER NOT NULL DEFAULT 0
|
|
9617
|
+
);
|
|
9618
|
+
`
|
|
9619
|
+
},
|
|
9620
|
+
{
|
|
9621
|
+
id: "006-proposals-pending-ref-source",
|
|
9622
|
+
up: `
|
|
9623
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_stash_status_ref_source
|
|
9624
|
+
ON proposals(stash_dir, status, ref, source);
|
|
9625
|
+
`
|
|
9553
9626
|
}
|
|
9554
9627
|
];
|
|
9555
9628
|
});
|
|
@@ -10238,6 +10311,9 @@ var init_inline_refs = __esm(() => {
|
|
|
10238
10311
|
import fs9 from "fs";
|
|
10239
10312
|
import os from "os";
|
|
10240
10313
|
import path8 from "path";
|
|
10314
|
+
function claudeProjectsDir() {
|
|
10315
|
+
return process.env.AKM_CLAUDE_PROJECTS_DIR ?? path8.join(os.homedir(), ".claude", "projects");
|
|
10316
|
+
}
|
|
10241
10317
|
function parseClaudeEvent(entry, sessionId, filePath, fallbackTsMs) {
|
|
10242
10318
|
if (!entry || typeof entry !== "object")
|
|
10243
10319
|
return;
|
|
@@ -10294,11 +10370,11 @@ function parseClaudeEvent(entry, sessionId, filePath, fallbackTsMs) {
|
|
|
10294
10370
|
class ClaudeCodeProvider {
|
|
10295
10371
|
name = "claude-code";
|
|
10296
10372
|
isAvailable() {
|
|
10297
|
-
return fs9.existsSync(
|
|
10373
|
+
return fs9.existsSync(claudeProjectsDir());
|
|
10298
10374
|
}
|
|
10299
10375
|
*readEvents(input) {
|
|
10300
10376
|
try {
|
|
10301
|
-
for (const jsonlPath of this.#walkJsonl(
|
|
10377
|
+
for (const jsonlPath of this.#walkJsonl(claudeProjectsDir())) {
|
|
10302
10378
|
const stat = fs9.statSync(jsonlPath);
|
|
10303
10379
|
if (stat.mtimeMs < input.sinceMs)
|
|
10304
10380
|
continue;
|
|
@@ -10326,7 +10402,7 @@ class ClaudeCodeProvider {
|
|
|
10326
10402
|
}
|
|
10327
10403
|
}
|
|
10328
10404
|
listSessions(input = {}) {
|
|
10329
|
-
const root = input.location ??
|
|
10405
|
+
const root = input.location ?? claudeProjectsDir();
|
|
10330
10406
|
const sinceMs = input.sinceMs ?? 0;
|
|
10331
10407
|
const summaries = [];
|
|
10332
10408
|
try {
|
|
@@ -10460,16 +10536,14 @@ class ClaudeCodeProvider {
|
|
|
10460
10536
|
const full = path8.join(dir, entry.name);
|
|
10461
10537
|
if (entry.isDirectory())
|
|
10462
10538
|
yield* this.#walkJsonl(full);
|
|
10463
|
-
else if (entry.name.endsWith(".jsonl"))
|
|
10539
|
+
else if (entry.name.endsWith(".jsonl") && entry.name !== "journal.jsonl")
|
|
10464
10540
|
yield full;
|
|
10465
10541
|
}
|
|
10466
10542
|
} catch {}
|
|
10467
10543
|
}
|
|
10468
10544
|
}
|
|
10469
|
-
var CLAUDE_PROJECTS_DIR;
|
|
10470
10545
|
var init_session_log = __esm(() => {
|
|
10471
10546
|
init_inline_refs();
|
|
10472
|
-
CLAUDE_PROJECTS_DIR = path8.join(os.homedir(), ".claude", "projects");
|
|
10473
10547
|
});
|
|
10474
10548
|
|
|
10475
10549
|
// src/integrations/harnesses/claude/index.ts
|
|
@@ -15458,7 +15532,16 @@ var init_config_schema = __esm(() => {
|
|
|
15458
15532
|
contradictionDetection: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
|
|
15459
15533
|
defaultSince: exports_external.string().min(1).optional(),
|
|
15460
15534
|
maxTotalChars: positiveInt.optional(),
|
|
15535
|
+
minContentChars: exports_external.number().int().min(0).optional(),
|
|
15461
15536
|
maxChunkSize: exports_external.number().int().min(1).max(50).optional(),
|
|
15537
|
+
incrementalSince: exports_external.string().optional(),
|
|
15538
|
+
limit: positiveInt.optional(),
|
|
15539
|
+
neighborsPerChanged: exports_external.number().int().min(1).optional(),
|
|
15540
|
+
requirePlannedRefs: exports_external.boolean().optional(),
|
|
15541
|
+
dueDays: exports_external.number().int().min(0).optional(),
|
|
15542
|
+
maxPerRun: positiveInt.optional(),
|
|
15543
|
+
importanceWeights: exports_external.record(exports_external.string().min(1), exports_external.number()).optional(),
|
|
15544
|
+
minPendingCount: exports_external.number().int().min(0).optional(),
|
|
15462
15545
|
minNewSessions: exports_external.number().int().min(0).optional(),
|
|
15463
15546
|
indexSessions: exports_external.boolean().optional(),
|
|
15464
15547
|
minSessionDuration: exports_external.number().min(0).optional(),
|
|
@@ -15480,7 +15563,8 @@ var init_config_schema = __esm(() => {
|
|
|
15480
15563
|
memoryInference: ImproveProcessConfigSchema.optional(),
|
|
15481
15564
|
graphExtraction: ImproveProcessConfigSchema.optional(),
|
|
15482
15565
|
validation: ImproveProcessConfigSchema.optional(),
|
|
15483
|
-
triage: ImproveProcessConfigSchema.optional()
|
|
15566
|
+
triage: ImproveProcessConfigSchema.optional(),
|
|
15567
|
+
proactiveMaintenance: ImproveProcessConfigSchema.optional()
|
|
15484
15568
|
}).passthrough().superRefine((val, ctx) => {
|
|
15485
15569
|
const raw = val;
|
|
15486
15570
|
if ("feedbackDistillation" in raw) {
|
|
@@ -15498,7 +15582,8 @@ var init_config_schema = __esm(() => {
|
|
|
15498
15582
|
"graphExtraction",
|
|
15499
15583
|
"validation",
|
|
15500
15584
|
"extract",
|
|
15501
|
-
"triage"
|
|
15585
|
+
"triage",
|
|
15586
|
+
"proactiveMaintenance"
|
|
15502
15587
|
]);
|
|
15503
15588
|
for (const k of Object.keys(raw)) {
|
|
15504
15589
|
if (!allowed.has(k)) {
|
|
@@ -16237,6 +16322,7 @@ __export(exports_db, {
|
|
|
16237
16322
|
getMeta: () => getMeta,
|
|
16238
16323
|
getLlmCacheEntry: () => getLlmCacheEntry,
|
|
16239
16324
|
getLlmCacheEntriesByRefs: () => getLlmCacheEntriesByRefs,
|
|
16325
|
+
getIndexedFilePaths: () => getIndexedFilePaths,
|
|
16240
16326
|
getIndexDirState: () => getIndexDirState,
|
|
16241
16327
|
getEntryRefRowsForStashRoot: () => getEntryRefRowsForStashRoot,
|
|
16242
16328
|
getEntryIdByFilePath: () => getEntryIdByFilePath,
|
|
@@ -16280,7 +16366,7 @@ function openDatabase2(dbPath, options) {
|
|
|
16280
16366
|
}
|
|
16281
16367
|
const db = openDatabase(resolvedPath);
|
|
16282
16368
|
db.exec("PRAGMA journal_mode = WAL");
|
|
16283
|
-
db.exec("PRAGMA busy_timeout =
|
|
16369
|
+
db.exec("PRAGMA busy_timeout = 30000");
|
|
16284
16370
|
db.exec("PRAGMA foreign_keys = ON");
|
|
16285
16371
|
loadVecExtension(db);
|
|
16286
16372
|
const resolvedDim = options?.embeddingDim ?? resolveConfiguredEmbeddingDim();
|
|
@@ -16304,7 +16390,7 @@ function openExistingDatabase(dbPath) {
|
|
|
16304
16390
|
const resolvedPath = dbPath ?? getDbPath();
|
|
16305
16391
|
const db = openDatabase(resolvedPath);
|
|
16306
16392
|
db.exec("PRAGMA journal_mode = WAL");
|
|
16307
|
-
db.exec("PRAGMA busy_timeout =
|
|
16393
|
+
db.exec("PRAGMA busy_timeout = 30000");
|
|
16308
16394
|
db.exec("PRAGMA foreign_keys = ON");
|
|
16309
16395
|
loadVecExtension(db);
|
|
16310
16396
|
return db;
|
|
@@ -17136,6 +17222,10 @@ function getEntryIdByFilePath(db, filePath) {
|
|
|
17136
17222
|
const row = db.prepare("SELECT id FROM entries WHERE file_path = ? LIMIT 1").get(filePath);
|
|
17137
17223
|
return row?.id;
|
|
17138
17224
|
}
|
|
17225
|
+
function getIndexedFilePaths(db) {
|
|
17226
|
+
const rows = db.prepare("SELECT DISTINCT file_path FROM entries WHERE file_path IS NOT NULL AND file_path <> ''").all();
|
|
17227
|
+
return new Set(rows.map((r) => r.file_path));
|
|
17228
|
+
}
|
|
17139
17229
|
function getEntryFilePathById(db, id) {
|
|
17140
17230
|
const row = db.prepare("SELECT file_path FROM entries WHERE id = ?").get(id);
|
|
17141
17231
|
return row?.file_path;
|
|
@@ -17263,18 +17353,56 @@ function clearStaleCacheEntries(db) {
|
|
|
17263
17353
|
function computeBodyHash(body) {
|
|
17264
17354
|
return sha256Hex(body);
|
|
17265
17355
|
}
|
|
17356
|
+
function bareRef(ref) {
|
|
17357
|
+
try {
|
|
17358
|
+
const parsed = parseAssetRef(ref);
|
|
17359
|
+
return `${parsed.type}:${parsed.name}`;
|
|
17360
|
+
} catch {
|
|
17361
|
+
return ref;
|
|
17362
|
+
}
|
|
17363
|
+
}
|
|
17266
17364
|
function getRetrievalCounts(db, refs) {
|
|
17267
17365
|
if (refs.length === 0)
|
|
17268
17366
|
return new Map;
|
|
17269
|
-
const
|
|
17270
|
-
for (
|
|
17271
|
-
const
|
|
17367
|
+
const bareToInputs = new Map;
|
|
17368
|
+
for (const ref of refs) {
|
|
17369
|
+
const bare = bareRef(ref);
|
|
17370
|
+
const existing = bareToInputs.get(bare);
|
|
17371
|
+
if (existing)
|
|
17372
|
+
existing.push(ref);
|
|
17373
|
+
else
|
|
17374
|
+
bareToInputs.set(bare, [ref]);
|
|
17375
|
+
}
|
|
17376
|
+
const bareForms = [...bareToInputs.keys()];
|
|
17377
|
+
const countsByBare = new Map;
|
|
17378
|
+
for (let i = 0;i < bareForms.length; i += SQLITE_CHUNK_SIZE) {
|
|
17379
|
+
const chunk = bareForms.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
17272
17380
|
const placeholders = chunk.map(() => "?").join(", ");
|
|
17273
|
-
const rows = db.prepare(`SELECT
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17381
|
+
const rows = db.prepare(`SELECT
|
|
17382
|
+
CASE
|
|
17383
|
+
WHEN instr(entry_ref, '//') > 0
|
|
17384
|
+
THEN substr(entry_ref, instr(entry_ref, '//') + 2)
|
|
17385
|
+
ELSE entry_ref
|
|
17386
|
+
END AS bare_ref,
|
|
17387
|
+
COUNT(*) AS cnt
|
|
17388
|
+
FROM usage_events
|
|
17389
|
+
WHERE event_type IN ('search','show','curate')
|
|
17390
|
+
AND entry_ref IS NOT NULL
|
|
17391
|
+
AND CASE
|
|
17392
|
+
WHEN instr(entry_ref, '//') > 0
|
|
17393
|
+
THEN substr(entry_ref, instr(entry_ref, '//') + 2)
|
|
17394
|
+
ELSE entry_ref
|
|
17395
|
+
END IN (${placeholders})
|
|
17396
|
+
GROUP BY bare_ref`).all(...chunk);
|
|
17397
|
+
for (const r of rows) {
|
|
17398
|
+
countsByBare.set(r.bare_ref, (countsByBare.get(r.bare_ref) ?? 0) + r.cnt);
|
|
17399
|
+
}
|
|
17400
|
+
}
|
|
17401
|
+
const result = new Map;
|
|
17402
|
+
for (const [bare, count] of countsByBare) {
|
|
17403
|
+
for (const input of bareToInputs.get(bare) ?? []) {
|
|
17404
|
+
result.set(input, count);
|
|
17405
|
+
}
|
|
17278
17406
|
}
|
|
17279
17407
|
return result;
|
|
17280
17408
|
}
|
|
@@ -8631,7 +8631,7 @@ function openStateDatabase(dbPath) {
|
|
|
8631
8631
|
const db = openDatabase(resolvedPath);
|
|
8632
8632
|
db.exec("PRAGMA journal_mode = WAL");
|
|
8633
8633
|
db.exec("PRAGMA foreign_keys = ON");
|
|
8634
|
-
db.exec("PRAGMA busy_timeout =
|
|
8634
|
+
db.exec("PRAGMA busy_timeout = 30000");
|
|
8635
8635
|
runMigrations2(db);
|
|
8636
8636
|
return db;
|
|
8637
8637
|
}
|
|
@@ -8706,7 +8706,9 @@ var MIGRATIONS = [
|
|
|
8706
8706
|
--
|
|
8707
8707
|
-- Extensible (metadata_json) columns:
|
|
8708
8708
|
-- metadata_json TEXT \u2014 JSON object for future proposal fields.
|
|
8709
|
-
-- Current fields stored here: sourceRun,
|
|
8709
|
+
-- Current fields stored here: sourceRun,
|
|
8710
|
+
-- review, confidence, gateDecision (#577),
|
|
8711
|
+
-- backupContent, eligibilitySource.
|
|
8710
8712
|
--
|
|
8711
8713
|
-- ADD COLUMN extension points (future migrations):
|
|
8712
8714
|
-- ALTER TABLE proposals ADD COLUMN source_run TEXT DEFAULT NULL;
|
|
@@ -8893,6 +8895,23 @@ var MIGRATIONS = [
|
|
|
8893
8895
|
CREATE INDEX IF NOT EXISTS idx_extract_sessions_processed
|
|
8894
8896
|
ON extract_sessions_seen(processed_at);
|
|
8895
8897
|
`
|
|
8898
|
+
},
|
|
8899
|
+
{
|
|
8900
|
+
id: "005-proposal-fs-imports",
|
|
8901
|
+
up: `
|
|
8902
|
+
CREATE TABLE IF NOT EXISTS proposal_fs_imports (
|
|
8903
|
+
stash_dir TEXT PRIMARY KEY,
|
|
8904
|
+
imported_at TEXT NOT NULL,
|
|
8905
|
+
imported_count INTEGER NOT NULL DEFAULT 0
|
|
8906
|
+
);
|
|
8907
|
+
`
|
|
8908
|
+
},
|
|
8909
|
+
{
|
|
8910
|
+
id: "006-proposals-pending-ref-source",
|
|
8911
|
+
up: `
|
|
8912
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_stash_status_ref_source
|
|
8913
|
+
ON proposals(stash_dir, status, ref, source);
|
|
8914
|
+
`
|
|
8896
8915
|
}
|
|
8897
8916
|
];
|
|
8898
8917
|
function runMigrations2(db) {
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* providers that fetch tarballs (currently `NpmSourceProvider` and the
|
|
13
13
|
* registry index builder).
|
|
14
14
|
*/
|
|
15
|
-
import { spawnSync } from "node:child_process";
|
|
16
15
|
import { createHash } from "node:crypto";
|
|
17
16
|
import fs from "node:fs";
|
|
18
17
|
import path from "node:path";
|
|
19
18
|
import { isWithin } from "../../core/common.js";
|
|
20
19
|
import { warn } from "../../core/warn.js";
|
|
20
|
+
import { spawnSync } from "../../runtime.js";
|
|
21
21
|
/**
|
|
22
22
|
* Verify an archive's integrity against a known hash. Throws and removes
|
|
23
23
|
* the archive when verification fails.
|
|
@@ -65,17 +65,25 @@ export function verifyArchiveIntegrity(archivePath, expected, source) {
|
|
|
65
65
|
* tree for symlinks that would escape the destination.
|
|
66
66
|
*/
|
|
67
67
|
export function extractTarGzSecure(archivePath, destinationDir) {
|
|
68
|
-
const listResult = spawnSync("tar",
|
|
69
|
-
if (listResult.
|
|
70
|
-
const err = listResult.stderr?.trim() ||
|
|
68
|
+
const listResult = spawnSync(["tar", "tzf", archivePath]);
|
|
69
|
+
if (!listResult.success) {
|
|
70
|
+
const err = listResult.stderr?.toString().trim() || "unknown error";
|
|
71
71
|
throw new Error(`Failed to inspect archive ${archivePath}: ${err}`);
|
|
72
72
|
}
|
|
73
|
-
validateTarEntries(listResult.stdout);
|
|
73
|
+
validateTarEntries(listResult.stdout.toString());
|
|
74
74
|
fs.rmSync(destinationDir, { recursive: true, force: true });
|
|
75
75
|
fs.mkdirSync(destinationDir, { recursive: true });
|
|
76
|
-
const extractResult = spawnSync(
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
const extractResult = spawnSync([
|
|
77
|
+
"tar",
|
|
78
|
+
"xzf",
|
|
79
|
+
archivePath,
|
|
80
|
+
"--no-same-owner",
|
|
81
|
+
"--strip-components=1",
|
|
82
|
+
"-C",
|
|
83
|
+
destinationDir,
|
|
84
|
+
]);
|
|
85
|
+
if (!extractResult.success) {
|
|
86
|
+
const err = extractResult.stderr?.toString().trim() || "unknown error";
|
|
79
87
|
throw new Error(`Failed to extract archive ${archivePath}: ${err}`);
|
|
80
88
|
}
|
|
81
89
|
// Post-extraction scan: verify all extracted files are within destinationDir
|
|
@@ -64,13 +64,11 @@ export function CRON_BACKEND(options = {}) {
|
|
|
64
64
|
},
|
|
65
65
|
list() {
|
|
66
66
|
const existing = readCrontab(exec);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
return ids.map((id) => ({ id }));
|
|
67
|
+
return listBlocks(existing).map(({ id, body }) => ({ id, signature: normalizeSignature(body) }));
|
|
68
|
+
},
|
|
69
|
+
expectedSignature(task) {
|
|
70
|
+
const cronLine = buildCronLine(task, akmArgv, logDir);
|
|
71
|
+
return normalizeSignature(cronBlockBody(cronLine, task.enabled));
|
|
74
72
|
},
|
|
75
73
|
};
|
|
76
74
|
}
|
|
@@ -82,9 +80,48 @@ export function buildCronLine(task, akmArgv, logDir) {
|
|
|
82
80
|
const cmd = [...akmArgv, "tasks", "run", task.id].map((part) => quoteForCron(part)).join(" ");
|
|
83
81
|
return `${cronExpr} ${cmd} >> ${quoteForCron(logPath)} 2>&1`;
|
|
84
82
|
}
|
|
83
|
+
/** The crontab line as it appears inside a block — commented when disabled. */
|
|
84
|
+
export function cronBlockBody(cronLine, enabled) {
|
|
85
|
+
return enabled ? cronLine : `${DISABLED_PREFIX}${cronLine}`;
|
|
86
|
+
}
|
|
85
87
|
export function renderBlock(id, cronLine, enabled) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
return [BEGIN(id), cronBlockBody(cronLine, enabled), END(id)].join("\n");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parse the akm-owned blocks out of a crontab, returning each task id with the
|
|
92
|
+
* raw body line(s) between its BEGIN/END markers. Used by `list()` to build a
|
|
93
|
+
* drift signature, and exported for tests.
|
|
94
|
+
*/
|
|
95
|
+
export function listBlocks(existing) {
|
|
96
|
+
const out = [];
|
|
97
|
+
const lines = existing.split(/\r?\n/);
|
|
98
|
+
let currentId = null;
|
|
99
|
+
let body = [];
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
const begin = line.match(BLOCK_RE);
|
|
102
|
+
if (begin) {
|
|
103
|
+
currentId = begin[1];
|
|
104
|
+
body = [];
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (currentId !== null && line === END(currentId)) {
|
|
108
|
+
out.push({ id: currentId, body: body.join("\n") });
|
|
109
|
+
currentId = null;
|
|
110
|
+
body = [];
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (currentId !== null)
|
|
114
|
+
body.push(line);
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/** Collapse incidental whitespace so signature comparison ignores it. */
|
|
119
|
+
function normalizeSignature(body) {
|
|
120
|
+
return body
|
|
121
|
+
.split(/\r?\n/)
|
|
122
|
+
.map((l) => l.trim())
|
|
123
|
+
.filter((l) => l.length > 0)
|
|
124
|
+
.join("\n");
|
|
88
125
|
}
|
|
89
126
|
export function upsertBlock(existing, id, block) {
|
|
90
127
|
const trimmed = existing.replace(/\s+$/g, "");
|