@swestash/agent-lens 0.8.2 → 0.9.0
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/agent-lens.js +332 -70
- package/dist/agent-lens.js.map +1 -1
- package/package.json +1 -1
- package/web/assets/{CopyButton-CQA_09tY.js → CopyButton-CLyPzvYe.js} +1 -1
- package/web/assets/{Dashboard-CtAZNySe.js → Dashboard-De60eF84.js} +1 -1
- package/web/assets/FileView-BbQOT43z.js +1 -0
- package/web/assets/FilesView-Ce3W2j5_.js +1 -0
- package/web/assets/{SecurityView-Dg0hnxA-.js → SecurityView-B2czCQsw.js} +1 -1
- package/web/assets/SessionView-CpmvQW2v.js +12 -0
- package/web/assets/{SkillView-BfEFS2DU.js → SkillView-DIxKdBD4.js} +1 -1
- package/web/assets/{SkillsView-CG8HFyyk.js → SkillsView-DF66QBkB.js} +1 -1
- package/web/assets/{WorkflowView-B6_1UcYD.js → WorkflowView-BWB4Z_bX.js} +1 -1
- package/web/assets/{index-B9PbFrlb.js → index-D2UV_n-R.js} +13 -13
- package/web/assets/{index-ksYuAXX3.js → index-bz6hQeaj.js} +1 -1
- package/web/assets/{severity-CZIEtWx1.js → severity-CVXkbfrj.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/SessionView-Ddk_CWjP.js +0 -12
package/dist/agent-lens.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
5
|
-
import { dirname as dirname6, join as
|
|
5
|
+
import { dirname as dirname6, join as join12 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7
7
|
|
|
8
8
|
// ../../node_modules/.pnpm/cac@6.7.14/node_modules/cac/dist/index.mjs
|
|
@@ -591,7 +591,7 @@ var CAC = class extends EventEmitter {
|
|
|
591
591
|
var cac = (name = "") => new CAC(name);
|
|
592
592
|
|
|
593
593
|
// ../core/dist/schema.js
|
|
594
|
-
var SCHEMA_VERSION =
|
|
594
|
+
var SCHEMA_VERSION = 14;
|
|
595
595
|
var SCHEMA_SQL = (
|
|
596
596
|
/* sql */
|
|
597
597
|
`
|
|
@@ -811,6 +811,29 @@ CREATE INDEX IF NOT EXISTS idx_findings_category ON findings(category);
|
|
|
811
811
|
CREATE INDEX IF NOT EXISTS idx_findings_rule ON findings(rule_id);
|
|
812
812
|
CREATE INDEX IF NOT EXISTS idx_findings_tool_call ON findings(tool_call_id);
|
|
813
813
|
|
|
814
|
+
-- File modifications derived from tool calls (ADR-022). One row per (tool_call, file): today
|
|
815
|
+
-- Edit/Write/NotebookEdit are 1:1, but the hash id leaves room for multi-file sources (e.g. a
|
|
816
|
+
-- future Bash-write heuristic) without a schema break. Failed calls (status='error') are excluded
|
|
817
|
+
-- at derivation \u2014 they didn't change the file. Re-runnable like findings: incremental runs DELETE
|
|
818
|
+
-- the dirty sessions' rows and re-INSERT (delete-then-insert). Populated by ingest's
|
|
819
|
+
-- deriveFileChanges (filechanges.ts).
|
|
820
|
+
CREATE TABLE IF NOT EXISTS file_changes (
|
|
821
|
+
id TEXT PRIMARY KEY, -- sha1(tool_call_id + NUL + file_path), 16 hex
|
|
822
|
+
tool_call_id TEXT NOT NULL REFERENCES tool_calls(id),
|
|
823
|
+
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
824
|
+
turn_id TEXT, -- no FK: rebuildDerived recreates turns (same stance as findings)
|
|
825
|
+
event_uuid TEXT, -- jump-to-transcript anchor (#ev-<uuid>)
|
|
826
|
+
project_id TEXT REFERENCES projects(id), -- from the session, for per-project grouping
|
|
827
|
+
file_path TEXT NOT NULL, -- normalized absolute path
|
|
828
|
+
tool_name TEXT NOT NULL, -- Edit | Write | NotebookEdit
|
|
829
|
+
lines_added INTEGER, -- newline-count delta (NULL when unknowable)
|
|
830
|
+
lines_removed INTEGER,
|
|
831
|
+
timestamp TEXT, -- ISO8601, from the tool call's event
|
|
832
|
+
derive_version INTEGER NOT NULL DEFAULT 1
|
|
833
|
+
);
|
|
834
|
+
CREATE INDEX IF NOT EXISTS idx_file_changes_session ON file_changes(session_id);
|
|
835
|
+
CREATE INDEX IF NOT EXISTS idx_file_changes_file ON file_changes(project_id, file_path, timestamp);
|
|
836
|
+
|
|
814
837
|
-- Workflow-tool run results, ingested from the sidecar JSON the runner writes next to the launching
|
|
815
838
|
-- session (\u2026/projects/<enc>/<sessionId>/workflows/wf_<id>.json). The transcript only carries the
|
|
816
839
|
-- "launched in background" ack for an async run; this sidecar is the authoritative record of how the
|
|
@@ -1315,6 +1338,12 @@ function resolveDataDir(repoRoot = findRepoRoot()) {
|
|
|
1315
1338
|
return join(repoRoot, "data");
|
|
1316
1339
|
return userDataDir();
|
|
1317
1340
|
}
|
|
1341
|
+
function resolveArchiveDir(repoRoot = findRepoRoot()) {
|
|
1342
|
+
return join(resolveDataDir(repoRoot), "archive");
|
|
1343
|
+
}
|
|
1344
|
+
function triageDbFor(dbPath) {
|
|
1345
|
+
return join(dirname(dbPath), "triage.db");
|
|
1346
|
+
}
|
|
1318
1347
|
function resolveConfigFile(repoRoot, dataDir) {
|
|
1319
1348
|
const candidates = [
|
|
1320
1349
|
process.env.AGENT_LENS_CONFIG,
|
|
@@ -1662,7 +1691,7 @@ function collectSource(source, archiveBase, excludedDirs, runTs, stats, log) {
|
|
|
1662
1691
|
}
|
|
1663
1692
|
function collectAll(opts = {}) {
|
|
1664
1693
|
const log = opts.log ?? ((m) => console.log(m));
|
|
1665
|
-
const archiveBase = opts.archiveBase ??
|
|
1694
|
+
const archiveBase = opts.archiveBase ?? resolveArchiveDir();
|
|
1666
1695
|
const sources = opts.sources ?? loadSources();
|
|
1667
1696
|
const excludedDirs = (opts.excludes ?? loadExcludes()).map(encodeProjectPath);
|
|
1668
1697
|
const runTs = opts.runTimestamp ?? collectTimestamp();
|
|
@@ -2082,6 +2111,7 @@ function resetSchema(db) {
|
|
|
2082
2111
|
DROP TRIGGER IF EXISTS events_ad;
|
|
2083
2112
|
DROP TRIGGER IF EXISTS events_ai;
|
|
2084
2113
|
DROP TABLE IF EXISTS events_fts;
|
|
2114
|
+
DROP TABLE IF EXISTS file_changes;
|
|
2085
2115
|
DROP TABLE IF EXISTS findings;
|
|
2086
2116
|
DROP TABLE IF EXISTS classifications;
|
|
2087
2117
|
DROP TABLE IF EXISTS token_usage;
|
|
@@ -2170,13 +2200,13 @@ function asString(v) {
|
|
|
2170
2200
|
function asInt(v) {
|
|
2171
2201
|
return typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : 0;
|
|
2172
2202
|
}
|
|
2173
|
-
function parentSessionFromPath(
|
|
2174
|
-
const parts =
|
|
2203
|
+
function parentSessionFromPath(path3) {
|
|
2204
|
+
const parts = path3.split("/");
|
|
2175
2205
|
const i = parts.indexOf("subagents");
|
|
2176
2206
|
return i > 0 ? parts[i - 1] : null;
|
|
2177
2207
|
}
|
|
2178
|
-
function workflowRunFromPath(
|
|
2179
|
-
const parts =
|
|
2208
|
+
function workflowRunFromPath(path3) {
|
|
2209
|
+
const parts = path3.split("/");
|
|
2180
2210
|
const i = parts.indexOf("workflows");
|
|
2181
2211
|
return i >= 0 && i + 1 < parts.length ? parts[i + 1] : null;
|
|
2182
2212
|
}
|
|
@@ -2204,18 +2234,18 @@ var ClaudeCodeAdapter = class {
|
|
|
2204
2234
|
const addFrom = (projectsDir, isVersion) => {
|
|
2205
2235
|
const found = [];
|
|
2206
2236
|
recurseJsonl(projectsDir, found);
|
|
2207
|
-
for (const
|
|
2208
|
-
const parts =
|
|
2209
|
-
const sessionId = basename2(
|
|
2237
|
+
for (const path3 of found) {
|
|
2238
|
+
const parts = path3.split("/");
|
|
2239
|
+
const sessionId = basename2(path3, ".jsonl");
|
|
2210
2240
|
const encodedDir = parts[parts.length - 2] ?? "";
|
|
2211
2241
|
files.push({
|
|
2212
|
-
path:
|
|
2242
|
+
path: path3,
|
|
2213
2243
|
sessionId,
|
|
2214
2244
|
encodedDir,
|
|
2215
2245
|
isVersion,
|
|
2216
2246
|
sourceId,
|
|
2217
|
-
parentSessionId: parentSessionFromPath(
|
|
2218
|
-
workflowRunId: workflowRunFromPath(
|
|
2247
|
+
parentSessionId: parentSessionFromPath(path3),
|
|
2248
|
+
workflowRunId: workflowRunFromPath(path3)
|
|
2219
2249
|
});
|
|
2220
2250
|
}
|
|
2221
2251
|
};
|
|
@@ -2653,8 +2683,8 @@ function execGeneratedScript(cmd) {
|
|
|
2653
2683
|
const e = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2654
2684
|
const interp = new RegExp(`\\b(?:sh|bash|zsh|ksh|dash|source)\\s+["']?${e}\\b|(?:^|[\\n;|&(])\\s*\\.\\s+["']?${e}\\b`);
|
|
2655
2685
|
const hasPath = /^(\.\/|\/|~\/)/.test(t);
|
|
2656
|
-
const
|
|
2657
|
-
if (interp.test(cmd) ||
|
|
2686
|
+
const path3 = hasPath ? new RegExp(`(?:^|[\\n;|&(])\\s*${e}(?:\\s|$|[;&|<>])`) : new RegExp(`(?:^|[\\n;|&(])\\s*\\.\\/${e}\\b`);
|
|
2687
|
+
if (interp.test(cmd) || path3.test(cmd))
|
|
2658
2688
|
return t;
|
|
2659
2689
|
}
|
|
2660
2690
|
return null;
|
|
@@ -3194,8 +3224,107 @@ function classifyErrors(db, dirty) {
|
|
|
3194
3224
|
return { count, version: ERROR_CLASSIFIER_VERSION };
|
|
3195
3225
|
}
|
|
3196
3226
|
|
|
3197
|
-
// ../ingest/dist/
|
|
3227
|
+
// ../ingest/dist/filechanges.js
|
|
3198
3228
|
import { createHash as createHash3 } from "crypto";
|
|
3229
|
+
import { posix as path2 } from "path";
|
|
3230
|
+
var FILECHANGES_VERSION = 1;
|
|
3231
|
+
var FILE_TOOLS = ["Edit", "Write", "NotebookEdit"];
|
|
3232
|
+
function fileChangeId(toolCallId, filePath) {
|
|
3233
|
+
return createHash3("sha1").update(toolCallId).update("\0").update(filePath).digest("hex").slice(0, 16);
|
|
3234
|
+
}
|
|
3235
|
+
function normalizeFilePath(filePath, projectPath) {
|
|
3236
|
+
if (typeof filePath !== "string" || !filePath)
|
|
3237
|
+
return null;
|
|
3238
|
+
if (filePath.startsWith("/"))
|
|
3239
|
+
return path2.normalize(filePath);
|
|
3240
|
+
if (!projectPath)
|
|
3241
|
+
return null;
|
|
3242
|
+
return path2.normalize(projectPath.replace(/\/$/, "") + "/" + filePath);
|
|
3243
|
+
}
|
|
3244
|
+
function lineCount(s) {
|
|
3245
|
+
if (typeof s !== "string")
|
|
3246
|
+
return null;
|
|
3247
|
+
if (s === "")
|
|
3248
|
+
return 0;
|
|
3249
|
+
let n = 1;
|
|
3250
|
+
for (let i = 0; i < s.length; i++)
|
|
3251
|
+
if (s.charCodeAt(i) === 10)
|
|
3252
|
+
n++;
|
|
3253
|
+
return n;
|
|
3254
|
+
}
|
|
3255
|
+
function deriveFileChanges(db, dirty) {
|
|
3256
|
+
const incremental = dirty != null;
|
|
3257
|
+
if (incremental) {
|
|
3258
|
+
db.exec("DROP TABLE IF EXISTS _dirty_fc");
|
|
3259
|
+
db.exec("CREATE TEMP TABLE _dirty_fc (id TEXT PRIMARY KEY)");
|
|
3260
|
+
const ins = db.prepare("INSERT OR IGNORE INTO _dirty_fc (id) VALUES (?)");
|
|
3261
|
+
db.transaction((ids) => {
|
|
3262
|
+
for (const id of ids)
|
|
3263
|
+
ins.run(id);
|
|
3264
|
+
})(dirty);
|
|
3265
|
+
}
|
|
3266
|
+
const scope = incremental ? " AND tc.session_id IN (SELECT id FROM _dirty_fc)" : "";
|
|
3267
|
+
const proj = /* @__PURE__ */ new Map();
|
|
3268
|
+
for (const r of db.prepare(`SELECT s.id id, p.id pid, p.path path FROM sessions s JOIN projects p ON p.id = s.project_id${incremental ? " WHERE s.id IN (SELECT id FROM _dirty_fc)" : ""}`).all()) {
|
|
3269
|
+
proj.set(r.id, { id: r.pid, path: r.path });
|
|
3270
|
+
}
|
|
3271
|
+
const rows = db.prepare(`SELECT tc.id, tc.session_id, tc.turn_id, tc.event_uuid, tc.tool_name, tc.input_json, tc.status, e.timestamp
|
|
3272
|
+
FROM tool_calls tc LEFT JOIN events e ON e.uuid = tc.event_uuid
|
|
3273
|
+
WHERE tc.tool_name IN (${FILE_TOOLS.map(() => "?").join(",")})${scope}`).all(...FILE_TOOLS);
|
|
3274
|
+
const insert = db.prepare(`INSERT INTO file_changes (id, tool_call_id, session_id, turn_id, event_uuid, project_id, file_path, tool_name, lines_added, lines_removed, timestamp, derive_version)
|
|
3275
|
+
VALUES (@id, @tool_call_id, @session_id, @turn_id, @event_uuid, @project_id, @file_path, @tool_name, @lines_added, @lines_removed, @timestamp, @derive_version)
|
|
3276
|
+
ON CONFLICT(id) DO NOTHING`);
|
|
3277
|
+
const delScope = incremental ? "DELETE FROM file_changes WHERE session_id IN (SELECT id FROM _dirty_fc)" : "DELETE FROM file_changes";
|
|
3278
|
+
const tx = db.transaction(() => {
|
|
3279
|
+
db.exec(delScope);
|
|
3280
|
+
for (const row of rows) {
|
|
3281
|
+
if (row.status === "error")
|
|
3282
|
+
continue;
|
|
3283
|
+
let input = null;
|
|
3284
|
+
try {
|
|
3285
|
+
input = row.input_json ? JSON.parse(row.input_json) : null;
|
|
3286
|
+
} catch {
|
|
3287
|
+
input = null;
|
|
3288
|
+
}
|
|
3289
|
+
if (!input)
|
|
3290
|
+
continue;
|
|
3291
|
+
const p = proj.get(row.session_id);
|
|
3292
|
+
const filePath = normalizeFilePath(input.file_path ?? input.notebook_path, p?.path ?? null);
|
|
3293
|
+
if (!filePath)
|
|
3294
|
+
continue;
|
|
3295
|
+
let added = null;
|
|
3296
|
+
let removed = null;
|
|
3297
|
+
if (row.tool_name === "Edit") {
|
|
3298
|
+
added = lineCount(input.new_string);
|
|
3299
|
+
removed = lineCount(input.old_string);
|
|
3300
|
+
} else if (row.tool_name === "Write") {
|
|
3301
|
+
added = lineCount(input.content);
|
|
3302
|
+
}
|
|
3303
|
+
insert.run({
|
|
3304
|
+
id: fileChangeId(row.id, filePath),
|
|
3305
|
+
tool_call_id: row.id,
|
|
3306
|
+
session_id: row.session_id,
|
|
3307
|
+
turn_id: row.turn_id,
|
|
3308
|
+
event_uuid: row.event_uuid,
|
|
3309
|
+
project_id: p?.id ?? null,
|
|
3310
|
+
file_path: filePath,
|
|
3311
|
+
tool_name: row.tool_name,
|
|
3312
|
+
lines_added: added,
|
|
3313
|
+
lines_removed: removed,
|
|
3314
|
+
timestamp: row.timestamp,
|
|
3315
|
+
derive_version: FILECHANGES_VERSION
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
});
|
|
3319
|
+
tx();
|
|
3320
|
+
if (incremental)
|
|
3321
|
+
db.exec("DROP TABLE IF EXISTS _dirty_fc");
|
|
3322
|
+
const count = db.prepare("SELECT COUNT(*) n FROM file_changes").get().n;
|
|
3323
|
+
return { count, version: FILECHANGES_VERSION };
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
// ../ingest/dist/pipeline.js
|
|
3327
|
+
import { createHash as createHash4 } from "crypto";
|
|
3199
3328
|
function newStats() {
|
|
3200
3329
|
return { files: 0, skipped: 0, malformed: 0, newEvents: 0 };
|
|
3201
3330
|
}
|
|
@@ -3233,7 +3362,7 @@ function skillSummary(body) {
|
|
|
3233
3362
|
return null;
|
|
3234
3363
|
}
|
|
3235
3364
|
function skillVersionId(name, body) {
|
|
3236
|
-
return
|
|
3365
|
+
return createHash4("sha1").update(name).update("\0").update(body).digest("hex").slice(0, 16);
|
|
3237
3366
|
}
|
|
3238
3367
|
function pruneExcluded(db, excludedPaths) {
|
|
3239
3368
|
if (!excludedPaths.length)
|
|
@@ -3268,6 +3397,7 @@ function pruneExcluded(db, excludedPaths) {
|
|
|
3268
3397
|
db.pragma("foreign_keys = OFF");
|
|
3269
3398
|
db.transaction(() => {
|
|
3270
3399
|
db.exec("DELETE FROM findings WHERE session_id IN (SELECT id FROM _prune)");
|
|
3400
|
+
db.exec("DELETE FROM file_changes WHERE session_id IN (SELECT id FROM _prune)");
|
|
3271
3401
|
for (const t of ["token_usage", "tool_calls", "turns", "events"])
|
|
3272
3402
|
db.exec(`DELETE FROM ${t} WHERE session_id IN (SELECT id FROM _prune)`);
|
|
3273
3403
|
db.exec("DELETE FROM classifications WHERE scope = 'session' AND target_id IN (SELECT id FROM _prune)");
|
|
@@ -3501,6 +3631,7 @@ function rebuildDerived(db, dirty) {
|
|
|
3501
3631
|
db.exec(`DELETE FROM sessions WHERE event_count = 0${andId}`);
|
|
3502
3632
|
db.exec("DELETE FROM classifications WHERE scope = 'session' AND target_id NOT IN (SELECT id FROM sessions)");
|
|
3503
3633
|
db.exec("DELETE FROM findings WHERE session_id NOT IN (SELECT id FROM sessions)");
|
|
3634
|
+
db.exec("DELETE FROM file_changes WHERE session_id NOT IN (SELECT id FROM sessions)");
|
|
3504
3635
|
if (!incremental) {
|
|
3505
3636
|
db.exec("DELETE FROM skills WHERE id NOT IN (SELECT skill_id FROM tool_calls WHERE skill_id IS NOT NULL)");
|
|
3506
3637
|
}
|
|
@@ -3615,7 +3746,7 @@ function ingestFile(db, stmts, adapter, file, lines, meta, now2, stats) {
|
|
|
3615
3746
|
}
|
|
3616
3747
|
let projectId = null;
|
|
3617
3748
|
if (sessionMeta.cwd) {
|
|
3618
|
-
projectId =
|
|
3749
|
+
projectId = createHash4("sha1").update(`${adapter.agentId}\0${sessionMeta.cwd}`).digest("hex").slice(0, 16);
|
|
3619
3750
|
stmts.upsertProject.run({
|
|
3620
3751
|
id: projectId,
|
|
3621
3752
|
agent_id: adapter.agentId,
|
|
@@ -3654,7 +3785,7 @@ import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as
|
|
|
3654
3785
|
import { basename as basename3, join as join6 } from "path";
|
|
3655
3786
|
|
|
3656
3787
|
// ../ingest/dist/redact.js
|
|
3657
|
-
import { createHash as
|
|
3788
|
+
import { createHash as createHash5, randomBytes } from "crypto";
|
|
3658
3789
|
function parseExcludes(csv) {
|
|
3659
3790
|
return (csv ?? "").split(",").map((s) => s.trim()).filter(Boolean).map(encodeProjectPath);
|
|
3660
3791
|
}
|
|
@@ -3663,17 +3794,17 @@ function isExcludedArchivePath(filePath, excluded) {
|
|
|
3663
3794
|
}
|
|
3664
3795
|
|
|
3665
3796
|
// ../ingest/dist/fileread.js
|
|
3666
|
-
import { createHash as
|
|
3797
|
+
import { createHash as createHash6 } from "crypto";
|
|
3667
3798
|
import { openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
3668
3799
|
import { StringDecoder } from "string_decoder";
|
|
3669
3800
|
var STREAM_THRESHOLD = 8 * 1024 * 1024;
|
|
3670
3801
|
var CHUNK2 = 64 * 1024;
|
|
3671
3802
|
function sha256(buf) {
|
|
3672
|
-
return
|
|
3803
|
+
return createHash6("sha256").update(buf).digest("hex");
|
|
3673
3804
|
}
|
|
3674
|
-
function sha256File(
|
|
3675
|
-
const fd = openSync2(
|
|
3676
|
-
const h =
|
|
3805
|
+
function sha256File(path3) {
|
|
3806
|
+
const fd = openSync2(path3, "r");
|
|
3807
|
+
const h = createHash6("sha256");
|
|
3677
3808
|
const buf = Buffer.allocUnsafe(CHUNK2);
|
|
3678
3809
|
try {
|
|
3679
3810
|
let n;
|
|
@@ -3684,8 +3815,8 @@ function sha256File(path2) {
|
|
|
3684
3815
|
}
|
|
3685
3816
|
return h.digest("hex");
|
|
3686
3817
|
}
|
|
3687
|
-
function* streamLines(
|
|
3688
|
-
const fd = openSync2(
|
|
3818
|
+
function* streamLines(path3) {
|
|
3819
|
+
const fd = openSync2(path3, "r");
|
|
3689
3820
|
const decoder = new StringDecoder("utf8");
|
|
3690
3821
|
const buf = Buffer.allocUnsafe(CHUNK2);
|
|
3691
3822
|
let leftover = "";
|
|
@@ -3737,8 +3868,8 @@ function discoverSidecars(sourceArchiveDir) {
|
|
|
3737
3868
|
}
|
|
3738
3869
|
return out;
|
|
3739
3870
|
}
|
|
3740
|
-
function sessionFromPath(
|
|
3741
|
-
const parts =
|
|
3871
|
+
function sessionFromPath(path3) {
|
|
3872
|
+
const parts = path3.split("/");
|
|
3742
3873
|
const i = parts.indexOf("workflows");
|
|
3743
3874
|
return i > 0 ? parts[i - 1] : null;
|
|
3744
3875
|
}
|
|
@@ -3785,25 +3916,25 @@ function ingestWorkflowResults(db, sourceArchiveDir, sourceId, excludedDirs, now
|
|
|
3785
3916
|
total_tokens=excluded.total_tokens, total_tool_calls=excluded.total_tool_calls,
|
|
3786
3917
|
duration_ms=excluded.duration_ms, started_at=excluded.started_at, ended_at=excluded.ended_at,
|
|
3787
3918
|
ingested_at=excluded.ingested_at`);
|
|
3788
|
-
for (const
|
|
3919
|
+
for (const path3 of paths) {
|
|
3789
3920
|
stats.files++;
|
|
3790
|
-
const st = statSync2(
|
|
3921
|
+
const st = statSync2(path3);
|
|
3791
3922
|
const mtimeMs = Math.trunc(st.mtimeMs);
|
|
3792
|
-
const prev = full ? void 0 : getState.get(
|
|
3923
|
+
const prev = full ? void 0 : getState.get(path3);
|
|
3793
3924
|
if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {
|
|
3794
3925
|
stats.skipped++;
|
|
3795
3926
|
continue;
|
|
3796
3927
|
}
|
|
3797
3928
|
let buf;
|
|
3798
3929
|
try {
|
|
3799
|
-
buf = readFileSync4(
|
|
3930
|
+
buf = readFileSync4(path3);
|
|
3800
3931
|
} catch {
|
|
3801
3932
|
stats.malformed++;
|
|
3802
3933
|
continue;
|
|
3803
3934
|
}
|
|
3804
3935
|
const hash2 = sha256(buf);
|
|
3805
3936
|
if (prev && prev.sha256 === hash2) {
|
|
3806
|
-
setState.run({ file_path:
|
|
3937
|
+
setState.run({ file_path: path3, size: st.size, mtime_ms: mtimeMs, sha256: hash2, ingested_at: now2 });
|
|
3807
3938
|
stats.skipped++;
|
|
3808
3939
|
continue;
|
|
3809
3940
|
}
|
|
@@ -3818,14 +3949,14 @@ function ingestWorkflowResults(db, sourceArchiveDir, sourceId, excludedDirs, now
|
|
|
3818
3949
|
stats.malformed++;
|
|
3819
3950
|
continue;
|
|
3820
3951
|
}
|
|
3821
|
-
const runId = strOrNull(d.runId) ?? basename3(
|
|
3952
|
+
const runId = strOrNull(d.runId) ?? basename3(path3, ".json");
|
|
3822
3953
|
const startedAt = toIso(d.startTime) ?? toIso(d.timestamp);
|
|
3823
3954
|
const durationMs2 = intOrNull(d.durationMs);
|
|
3824
3955
|
const endedAt = startedAt && durationMs2 != null ? new Date(Date.parse(startedAt) + durationMs2).toISOString() : null;
|
|
3825
3956
|
upsert.run({
|
|
3826
3957
|
run_id: runId,
|
|
3827
3958
|
source_id: sourceId,
|
|
3828
|
-
session_id: sessionFromPath(
|
|
3959
|
+
session_id: sessionFromPath(path3),
|
|
3829
3960
|
task_id: strOrNull(d.taskId),
|
|
3830
3961
|
workflow_name: strOrNull(d.workflowName),
|
|
3831
3962
|
status: strOrNull(d.status),
|
|
@@ -3843,7 +3974,7 @@ function ingestWorkflowResults(db, sourceArchiveDir, sourceId, excludedDirs, now
|
|
|
3843
3974
|
ended_at: endedAt,
|
|
3844
3975
|
ingested_at: now2
|
|
3845
3976
|
});
|
|
3846
|
-
setState.run({ file_path:
|
|
3977
|
+
setState.run({ file_path: path3, size: st.size, mtime_ms: mtimeMs, sha256: hash2, ingested_at: now2 });
|
|
3847
3978
|
stats.upserted++;
|
|
3848
3979
|
}
|
|
3849
3980
|
}
|
|
@@ -3899,25 +4030,25 @@ function ingestSubagentMeta(db, sourceArchiveDir, sourceId, excludedDirs, now2,
|
|
|
3899
4030
|
source_id=excluded.source_id, agent_type=excluded.agent_type,
|
|
3900
4031
|
agent_description=excluded.agent_description, spawn_depth=excluded.spawn_depth,
|
|
3901
4032
|
tool_use_id=excluded.tool_use_id, ingested_at=excluded.ingested_at`);
|
|
3902
|
-
for (const
|
|
4033
|
+
for (const path3 of paths) {
|
|
3903
4034
|
stats.files++;
|
|
3904
|
-
const st = statSync3(
|
|
4035
|
+
const st = statSync3(path3);
|
|
3905
4036
|
const mtimeMs = Math.trunc(st.mtimeMs);
|
|
3906
|
-
const prev = full ? void 0 : getState.get(
|
|
4037
|
+
const prev = full ? void 0 : getState.get(path3);
|
|
3907
4038
|
if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {
|
|
3908
4039
|
stats.skipped++;
|
|
3909
4040
|
continue;
|
|
3910
4041
|
}
|
|
3911
4042
|
let buf;
|
|
3912
4043
|
try {
|
|
3913
|
-
buf = readFileSync5(
|
|
4044
|
+
buf = readFileSync5(path3);
|
|
3914
4045
|
} catch {
|
|
3915
4046
|
stats.malformed++;
|
|
3916
4047
|
continue;
|
|
3917
4048
|
}
|
|
3918
4049
|
const hash2 = sha256(buf);
|
|
3919
4050
|
if (prev && prev.sha256 === hash2) {
|
|
3920
|
-
setState.run({ file_path:
|
|
4051
|
+
setState.run({ file_path: path3, size: st.size, mtime_ms: mtimeMs, sha256: hash2, ingested_at: now2 });
|
|
3921
4052
|
stats.skipped++;
|
|
3922
4053
|
continue;
|
|
3923
4054
|
}
|
|
@@ -3932,7 +4063,7 @@ function ingestSubagentMeta(db, sourceArchiveDir, sourceId, excludedDirs, now2,
|
|
|
3932
4063
|
stats.malformed++;
|
|
3933
4064
|
continue;
|
|
3934
4065
|
}
|
|
3935
|
-
const sessionId = basename4(
|
|
4066
|
+
const sessionId = basename4(path3, ".meta.json");
|
|
3936
4067
|
upsert.run({
|
|
3937
4068
|
session_id: sessionId,
|
|
3938
4069
|
source_id: sourceId,
|
|
@@ -3942,7 +4073,7 @@ function ingestSubagentMeta(db, sourceArchiveDir, sourceId, excludedDirs, now2,
|
|
|
3942
4073
|
tool_use_id: strOrNull2(d.toolUseId),
|
|
3943
4074
|
ingested_at: now2
|
|
3944
4075
|
});
|
|
3945
|
-
setState.run({ file_path:
|
|
4076
|
+
setState.run({ file_path: path3, size: st.size, mtime_ms: mtimeMs, sha256: hash2, ingested_at: now2 });
|
|
3946
4077
|
stats.upserted++;
|
|
3947
4078
|
}
|
|
3948
4079
|
}
|
|
@@ -3979,8 +4110,8 @@ function discoverResults(sourceArchiveDir) {
|
|
|
3979
4110
|
}
|
|
3980
4111
|
return out;
|
|
3981
4112
|
}
|
|
3982
|
-
function sessionFromPath2(
|
|
3983
|
-
const parts =
|
|
4113
|
+
function sessionFromPath2(path3) {
|
|
4114
|
+
const parts = path3.split("/");
|
|
3984
4115
|
const i = parts.indexOf("tool-results");
|
|
3985
4116
|
return i > 0 ? parts[i - 1] : null;
|
|
3986
4117
|
}
|
|
@@ -3997,40 +4128,40 @@ function ingestToolResults(db, sourceArchiveDir, excludedDirs, now2, stats, full
|
|
|
3997
4128
|
VALUES (@session_id, @name, @path, @bytes, @text, @ingested_at)
|
|
3998
4129
|
ON CONFLICT(session_id, name) DO UPDATE SET
|
|
3999
4130
|
path=excluded.path, bytes=excluded.bytes, text=excluded.text, ingested_at=excluded.ingested_at`);
|
|
4000
|
-
for (const
|
|
4001
|
-
const sessionId = sessionFromPath2(
|
|
4131
|
+
for (const path3 of paths) {
|
|
4132
|
+
const sessionId = sessionFromPath2(path3);
|
|
4002
4133
|
if (!sessionId)
|
|
4003
4134
|
continue;
|
|
4004
4135
|
stats.files++;
|
|
4005
|
-
const st = statSync4(
|
|
4136
|
+
const st = statSync4(path3);
|
|
4006
4137
|
const mtimeMs = Math.trunc(st.mtimeMs);
|
|
4007
|
-
const prev = full ? void 0 : getState.get(
|
|
4138
|
+
const prev = full ? void 0 : getState.get(path3);
|
|
4008
4139
|
if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {
|
|
4009
4140
|
stats.skipped++;
|
|
4010
4141
|
continue;
|
|
4011
4142
|
}
|
|
4012
4143
|
let buf;
|
|
4013
4144
|
try {
|
|
4014
|
-
buf = readFileSync6(
|
|
4145
|
+
buf = readFileSync6(path3);
|
|
4015
4146
|
} catch {
|
|
4016
4147
|
stats.malformed++;
|
|
4017
4148
|
continue;
|
|
4018
4149
|
}
|
|
4019
4150
|
const hash2 = sha256(buf);
|
|
4020
4151
|
if (prev && prev.sha256 === hash2) {
|
|
4021
|
-
setState.run({ file_path:
|
|
4152
|
+
setState.run({ file_path: path3, size: st.size, mtime_ms: mtimeMs, sha256: hash2, ingested_at: now2 });
|
|
4022
4153
|
stats.skipped++;
|
|
4023
4154
|
continue;
|
|
4024
4155
|
}
|
|
4025
4156
|
upsert.run({
|
|
4026
4157
|
session_id: sessionId,
|
|
4027
|
-
name: basename5(
|
|
4028
|
-
path:
|
|
4158
|
+
name: basename5(path3, ".txt"),
|
|
4159
|
+
path: path3,
|
|
4029
4160
|
bytes: st.size,
|
|
4030
4161
|
text: buf.toString("utf8"),
|
|
4031
4162
|
ingested_at: now2
|
|
4032
4163
|
});
|
|
4033
|
-
setState.run({ file_path:
|
|
4164
|
+
setState.run({ file_path: path3, size: st.size, mtime_ms: mtimeMs, sha256: hash2, ingested_at: now2 });
|
|
4034
4165
|
stats.upserted++;
|
|
4035
4166
|
}
|
|
4036
4167
|
}
|
|
@@ -4050,8 +4181,7 @@ function parseIngestArgs(argv) {
|
|
|
4050
4181
|
}
|
|
4051
4182
|
function runIngest(argv = process.argv.slice(2)) {
|
|
4052
4183
|
const args = parseIngestArgs(argv);
|
|
4053
|
-
const
|
|
4054
|
-
const archiveRoot = args.archive || process.env.AGENT_LENS_ARCHIVE || join9(dataDir, "archive");
|
|
4184
|
+
const archiveRoot = args.archive || resolveArchiveDir();
|
|
4055
4185
|
const { path: dbPath } = resolveDbPath(args.db);
|
|
4056
4186
|
if (!existsSync4(archiveRoot)) {
|
|
4057
4187
|
console.error(`agent-lens-ingest: archive not found: ${archiveRoot} (run 'agent-lens collect' first)`);
|
|
@@ -4130,6 +4260,7 @@ function runIngest(argv = process.argv.slice(2)) {
|
|
|
4130
4260
|
const classified = classify(db, args.full ? null : expanded);
|
|
4131
4261
|
const detected = detect(db, args.full ? null : expanded);
|
|
4132
4262
|
classifyErrors(db, args.full ? null : expanded);
|
|
4263
|
+
const fileChanges = deriveFileChanges(db, args.full ? null : expanded);
|
|
4133
4264
|
const count = (sql) => db.prepare(sql).get().n;
|
|
4134
4265
|
const sessions = count("SELECT COUNT(*) n FROM sessions");
|
|
4135
4266
|
const turns = count("SELECT COUNT(*) n FROM turns");
|
|
@@ -4155,6 +4286,7 @@ function runIngest(argv = process.argv.slice(2)) {
|
|
|
4155
4286
|
session_meta=${metaRows} (upserted=${metaStats.upserted} skipped=${metaStats.skipped} malformed=${metaStats.malformed})
|
|
4156
4287
|
tool_results=${trRows} (upserted=${trStats.upserted} skipped=${trStats.skipped} malformed=${trStats.malformed})
|
|
4157
4288
|
findings=${detected.count} (${sevLine})
|
|
4289
|
+
file_changes=${fileChanges.count}
|
|
4158
4290
|
tokens=${totalTokens.toLocaleString()} est_cost=$${cost.toFixed(2)} db=${dbPath}`);
|
|
4159
4291
|
}
|
|
4160
4292
|
function runMetrics(argv = process.argv.slice(2)) {
|
|
@@ -4171,13 +4303,14 @@ function runMetrics(argv = process.argv.slice(2)) {
|
|
|
4171
4303
|
const r = classify(db);
|
|
4172
4304
|
const d = detect(db);
|
|
4173
4305
|
const e = classifyErrors(db);
|
|
4306
|
+
const fc = deriveFileChanges(db);
|
|
4174
4307
|
db.close();
|
|
4175
|
-
console.log(`agent-lens-metrics: classified=${r.count} classifier_version=${CLASSIFIER_VERSION} findings=${d.count} detector_version=${DETECTOR_VERSION} errors_classified=${e.count} error_classifier_version=${e.version} db=${dbPath}`);
|
|
4308
|
+
console.log(`agent-lens-metrics: classified=${r.count} classifier_version=${CLASSIFIER_VERSION} findings=${d.count} detector_version=${DETECTOR_VERSION} errors_classified=${e.count} error_classifier_version=${e.version} file_changes=${fc.count} filechanges_version=${FILECHANGES_VERSION} db=${dbPath}`);
|
|
4176
4309
|
}
|
|
4177
4310
|
|
|
4178
4311
|
// ../server/dist/run.js
|
|
4179
4312
|
import { existsSync as existsSync7 } from "fs";
|
|
4180
|
-
import { dirname as dirname5
|
|
4313
|
+
import { dirname as dirname5 } from "path";
|
|
4181
4314
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4182
4315
|
|
|
4183
4316
|
// ../server/dist/db.js
|
|
@@ -4503,7 +4636,13 @@ function getSession(db, id) {
|
|
|
4503
4636
|
session.tool_error_count = toolRows.filter((t) => t.status === "error").length;
|
|
4504
4637
|
session.tool_rejection_count = toolRows.filter((t) => t.status === "error" && t.error_type && errorKind(t.error_type) === "rejection").length;
|
|
4505
4638
|
session.tool_failure_count = session.tool_error_count - session.tool_rejection_count;
|
|
4506
|
-
|
|
4639
|
+
let fileChanges = [];
|
|
4640
|
+
if (tableExists(db, "file_changes")) {
|
|
4641
|
+
fileChanges = db.prepare(`SELECT fc.id, fc.tool_call_id, fc.turn_id, fc.event_uuid, fc.file_path, fc.tool_name,
|
|
4642
|
+
fc.lines_added, fc.lines_removed, fc.timestamp
|
|
4643
|
+
FROM file_changes fc WHERE fc.session_id = ? ORDER BY fc.timestamp`).all(id);
|
|
4644
|
+
}
|
|
4645
|
+
return { session, turns, events, classification, parent, children, workflow_runs, findings: sessionFindings, file_changes: fileChanges };
|
|
4507
4646
|
}
|
|
4508
4647
|
function xmlTag(text, tag) {
|
|
4509
4648
|
return text.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`))?.[1]?.trim() ?? null;
|
|
@@ -4639,6 +4778,107 @@ function listSkills(db, f = {}) {
|
|
|
4639
4778
|
r.sources = r.sources ? String(r.sources).split(",").filter(Boolean) : [];
|
|
4640
4779
|
return rows;
|
|
4641
4780
|
}
|
|
4781
|
+
function listFiles(db, f = {}) {
|
|
4782
|
+
if (!tableExists(db, "file_changes"))
|
|
4783
|
+
return { total: 0, files: [] };
|
|
4784
|
+
const where = [];
|
|
4785
|
+
const params = [];
|
|
4786
|
+
if (f.q && f.q.trim())
|
|
4787
|
+
where.push("fc.file_path LIKE ?"), params.push(`%${f.q.trim()}%`);
|
|
4788
|
+
if (f.project)
|
|
4789
|
+
where.push("fc.project_id = ?"), params.push(f.project);
|
|
4790
|
+
if (f.source)
|
|
4791
|
+
where.push("EXISTS (SELECT 1 FROM sessions s WHERE s.id = fc.session_id AND s.source_id = ?)"), params.push(f.source);
|
|
4792
|
+
const whereSql = where.length ? ` WHERE ${where.join(" AND ")}` : "";
|
|
4793
|
+
const grouped = `SELECT fc.project_id, fc.file_path,
|
|
4794
|
+
COUNT(DISTINCT fc.session_id) AS sessions,
|
|
4795
|
+
COUNT(*) AS changes,
|
|
4796
|
+
SUM(fc.lines_added) AS lines_added,
|
|
4797
|
+
SUM(fc.lines_removed) AS lines_removed,
|
|
4798
|
+
MIN(fc.timestamp) AS first_ts,
|
|
4799
|
+
MAX(fc.timestamp) AS last_ts
|
|
4800
|
+
FROM file_changes fc${whereSql}
|
|
4801
|
+
GROUP BY fc.project_id, fc.file_path`;
|
|
4802
|
+
const total = db.prepare(`SELECT COUNT(*) n FROM (${grouped})`).get(...params).n;
|
|
4803
|
+
const ORDER = {
|
|
4804
|
+
last_ts: "last_ts",
|
|
4805
|
+
first_ts: "first_ts",
|
|
4806
|
+
changes: "changes",
|
|
4807
|
+
sessions: "sessions",
|
|
4808
|
+
path: "file_path"
|
|
4809
|
+
};
|
|
4810
|
+
const col = ORDER[f.sort ?? "last_ts"] ?? "last_ts";
|
|
4811
|
+
const dir = f.dir === "asc" ? "ASC" : "DESC";
|
|
4812
|
+
const limit = Math.min(f.limit ?? 50, 200);
|
|
4813
|
+
const offset = f.offset ?? 0;
|
|
4814
|
+
const files = db.prepare(`SELECT g.*, p.path AS project_path FROM (${grouped}) g
|
|
4815
|
+
LEFT JOIN projects p ON p.id = g.project_id
|
|
4816
|
+
ORDER BY ${col} ${dir}, g.file_path ASC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
4817
|
+
return { total, files };
|
|
4818
|
+
}
|
|
4819
|
+
function getFileTimeline(db, filePath, projectId) {
|
|
4820
|
+
if (!tableExists(db, "file_changes"))
|
|
4821
|
+
return null;
|
|
4822
|
+
const params = [filePath];
|
|
4823
|
+
let scope = "fc.file_path = ?";
|
|
4824
|
+
if (projectId)
|
|
4825
|
+
scope += " AND fc.project_id = ?", params.push(projectId);
|
|
4826
|
+
const rows = db.prepare(`SELECT fc.id, fc.session_id, fc.turn_id, fc.event_uuid, fc.tool_name, fc.lines_added,
|
|
4827
|
+
fc.lines_removed, fc.timestamp, fc.project_id,
|
|
4828
|
+
s.ai_title, s.slug, s.source_id, s.started_at, t.seq AS turn_seq, t.prompt_preview,
|
|
4829
|
+
p.path AS project_path
|
|
4830
|
+
FROM file_changes fc
|
|
4831
|
+
JOIN sessions s ON s.id = fc.session_id
|
|
4832
|
+
LEFT JOIN turns t ON t.id = fc.turn_id
|
|
4833
|
+
LEFT JOIN projects p ON p.id = fc.project_id
|
|
4834
|
+
WHERE ${scope} ORDER BY fc.timestamp`).all(...params);
|
|
4835
|
+
if (!rows.length)
|
|
4836
|
+
return null;
|
|
4837
|
+
const cat = tableExists(db, "classifications") ? db.prepare("SELECT category FROM classifications WHERE scope = 'session' AND target_id = ?") : null;
|
|
4838
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
4839
|
+
for (const r of rows) {
|
|
4840
|
+
let g = bySession.get(r.session_id);
|
|
4841
|
+
if (!g) {
|
|
4842
|
+
g = {
|
|
4843
|
+
session_id: r.session_id,
|
|
4844
|
+
title: r.ai_title || r.slug || null,
|
|
4845
|
+
source_id: r.source_id,
|
|
4846
|
+
started_at: r.started_at,
|
|
4847
|
+
category: cat ? cat.get(r.session_id)?.category ?? null : null,
|
|
4848
|
+
changes: []
|
|
4849
|
+
};
|
|
4850
|
+
bySession.set(r.session_id, g);
|
|
4851
|
+
}
|
|
4852
|
+
g.changes.push({
|
|
4853
|
+
id: r.id,
|
|
4854
|
+
turn_id: r.turn_id,
|
|
4855
|
+
turn_seq: r.turn_seq,
|
|
4856
|
+
prompt_preview: r.prompt_preview,
|
|
4857
|
+
event_uuid: r.event_uuid,
|
|
4858
|
+
tool_name: r.tool_name,
|
|
4859
|
+
lines_added: r.lines_added,
|
|
4860
|
+
lines_removed: r.lines_removed,
|
|
4861
|
+
timestamp: r.timestamp
|
|
4862
|
+
});
|
|
4863
|
+
}
|
|
4864
|
+
const sessions = [...bySession.values()].sort((a, b) => {
|
|
4865
|
+
const la = a.changes[a.changes.length - 1].timestamp ?? "";
|
|
4866
|
+
const lb = b.changes[b.changes.length - 1].timestamp ?? "";
|
|
4867
|
+
return la < lb ? 1 : la > lb ? -1 : 0;
|
|
4868
|
+
});
|
|
4869
|
+
return {
|
|
4870
|
+
file_path: filePath,
|
|
4871
|
+
project_id: rows[0].project_id ?? null,
|
|
4872
|
+
project_path: rows[0].project_path ?? null,
|
|
4873
|
+
sessions_count: sessions.length,
|
|
4874
|
+
changes_count: rows.length,
|
|
4875
|
+
lines_added: rows.reduce((a, r) => a + (r.lines_added ?? 0), 0),
|
|
4876
|
+
lines_removed: rows.reduce((a, r) => a + (r.lines_removed ?? 0), 0),
|
|
4877
|
+
first_ts: rows[0].timestamp ?? null,
|
|
4878
|
+
last_ts: rows[rows.length - 1].timestamp ?? null,
|
|
4879
|
+
sessions
|
|
4880
|
+
};
|
|
4881
|
+
}
|
|
4642
4882
|
function getSkill(db, name) {
|
|
4643
4883
|
const versions = db.prepare(`SELECT sk.id, sk.base_dir, sk.summary, sk.body, sk.body_bytes, sk.first_seen, sk.last_seen,
|
|
4644
4884
|
(SELECT COUNT(*) FROM tool_calls tc WHERE tc.skill_id = sk.id) AS call_count
|
|
@@ -5059,7 +5299,7 @@ function runRefresh() {
|
|
|
5059
5299
|
return null;
|
|
5060
5300
|
try {
|
|
5061
5301
|
const collected = collectAll();
|
|
5062
|
-
const archiveRoot =
|
|
5302
|
+
const archiveRoot = resolveArchiveDir();
|
|
5063
5303
|
if (!existsSync5(archiveRoot))
|
|
5064
5304
|
mkdirSync5(archiveRoot, { recursive: true });
|
|
5065
5305
|
runIngest([]);
|
|
@@ -5355,6 +5595,28 @@ async function createApp(db, opts = {}) {
|
|
|
5355
5595
|
return reply.code(404).send({ error: "not found" });
|
|
5356
5596
|
return result;
|
|
5357
5597
|
});
|
|
5598
|
+
app.get("/api/files", async (req) => {
|
|
5599
|
+
const q = req.query;
|
|
5600
|
+
const sort = ["last_ts", "first_ts", "changes", "sessions", "path"].find((s) => s === q.sort);
|
|
5601
|
+
return listFiles(db, {
|
|
5602
|
+
q: q.q,
|
|
5603
|
+
source: q.source,
|
|
5604
|
+
project: q.project,
|
|
5605
|
+
sort,
|
|
5606
|
+
dir: q.dir === "asc" ? "asc" : "desc",
|
|
5607
|
+
limit: Math.min(Number(q.limit) || 50, 200),
|
|
5608
|
+
offset: Number(q.offset) || 0
|
|
5609
|
+
});
|
|
5610
|
+
});
|
|
5611
|
+
app.get("/api/file", async (req, reply) => {
|
|
5612
|
+
const q = req.query;
|
|
5613
|
+
if (!q.path)
|
|
5614
|
+
return reply.code(400).send({ error: "missing path" });
|
|
5615
|
+
const result = getFileTimeline(db, q.path, q.project || void 0);
|
|
5616
|
+
if (!result)
|
|
5617
|
+
return reply.code(404).send({ error: "not found" });
|
|
5618
|
+
return result;
|
|
5619
|
+
});
|
|
5358
5620
|
app.get("/api/sessions/:id/export.md", async (req, reply) => {
|
|
5359
5621
|
const { id } = req.params;
|
|
5360
5622
|
const level = parseRedactionLevel(req.query.redact);
|
|
@@ -5389,7 +5651,7 @@ async function startServer(overrides = {}) {
|
|
|
5389
5651
|
console.error(`agent-lens-server: db not found: ${dbPath} (run ingest first)`);
|
|
5390
5652
|
process.exit(1);
|
|
5391
5653
|
}
|
|
5392
|
-
const triageDbPath =
|
|
5654
|
+
const triageDbPath = triageDbFor(dbPath);
|
|
5393
5655
|
const db = openReadonly(dbPath);
|
|
5394
5656
|
const enforceLoopbackHost = host === "127.0.0.1" || host === "localhost";
|
|
5395
5657
|
const app = await createApp(db, { webDist, triageDbPath, enforceLoopbackHost });
|
|
@@ -5406,11 +5668,11 @@ async function startServer(overrides = {}) {
|
|
|
5406
5668
|
|
|
5407
5669
|
// src/watch.ts
|
|
5408
5670
|
import { existsSync as existsSync8 } from "fs";
|
|
5409
|
-
import { join as
|
|
5671
|
+
import { join as join11 } from "path";
|
|
5410
5672
|
import chokidar from "chokidar";
|
|
5411
5673
|
async function runWatch(opts = {}) {
|
|
5412
5674
|
const sources = loadSources();
|
|
5413
|
-
const lockPath =
|
|
5675
|
+
const lockPath = join11(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
|
|
5414
5676
|
let running = false;
|
|
5415
5677
|
let pending = false;
|
|
5416
5678
|
const cycle = () => {
|
|
@@ -5443,7 +5705,7 @@ async function runWatch(opts = {}) {
|
|
|
5443
5705
|
}
|
|
5444
5706
|
};
|
|
5445
5707
|
cycle();
|
|
5446
|
-
const watchPaths = sources.map((s) =>
|
|
5708
|
+
const watchPaths = sources.map((s) => join11(s.configDir, "projects")).filter((p) => existsSync8(p));
|
|
5447
5709
|
if (watchPaths.length === 0) {
|
|
5448
5710
|
console.error("agent-lens watch: no source 'projects' directories found \u2014 nothing to watch");
|
|
5449
5711
|
}
|
|
@@ -5471,14 +5733,14 @@ async function runWatch(opts = {}) {
|
|
|
5471
5733
|
// src/index.ts
|
|
5472
5734
|
function version() {
|
|
5473
5735
|
try {
|
|
5474
|
-
const pkg = JSON.parse(readFileSync8(
|
|
5736
|
+
const pkg = JSON.parse(readFileSync8(join12(dirname6(fileURLToPath3(import.meta.url)), "../package.json"), "utf8"));
|
|
5475
5737
|
return pkg.version ?? "0.0.0";
|
|
5476
5738
|
} catch {
|
|
5477
5739
|
return "0.0.0";
|
|
5478
5740
|
}
|
|
5479
5741
|
}
|
|
5480
5742
|
function withLock(fn) {
|
|
5481
|
-
const lockPath =
|
|
5743
|
+
const lockPath = join12(resolveDataDir(findRepoRoot()), ".agent-lens.lock");
|
|
5482
5744
|
const lock = acquireLock(lockPath);
|
|
5483
5745
|
if (!lock) {
|
|
5484
5746
|
console.error("agent-lens: another collect/ingest run is in progress \u2014 skipping");
|
|
@@ -5561,17 +5823,17 @@ cli.command("config", "Show the effective configuration (sources, paths, server)
|
|
|
5561
5823
|
}
|
|
5562
5824
|
const { port, host, portOrigin, hostOrigin } = server;
|
|
5563
5825
|
const { path: dbPath, origin: dbOrigin } = resolveDbPath();
|
|
5564
|
-
const archive =
|
|
5565
|
-
const triageDb =
|
|
5826
|
+
const archive = resolveArchiveDir(repoRoot);
|
|
5827
|
+
const triageDb = triageDbFor(dbPath);
|
|
5566
5828
|
const keepDays = process.env.AGENT_LENS_VERSIONS_KEEP_DAYS || "90";
|
|
5567
5829
|
const keepOrigin = process.env.AGENT_LENS_VERSIONS_KEEP_DAYS ? "env" : "default";
|
|
5568
5830
|
console.log("agent-lens config (precedence: flag > env > config file > default)\n");
|
|
5569
5831
|
console.log("Paths");
|
|
5570
5832
|
console.log(` config file ${configFile ?? "(none \u2014 using built-in default source)"}`);
|
|
5571
5833
|
console.log(` data dir ${dataDir}${process.env.AGENT_LENS_DATA ? " [env]" : ""}`);
|
|
5572
|
-
console.log(` archive ${archive}
|
|
5834
|
+
console.log(` archive ${archive} [fixed: <data dir>/archive]`);
|
|
5573
5835
|
console.log(` db ${dbPath} [${dbOrigin}]`);
|
|
5574
|
-
console.log(` triage db ${triageDb}
|
|
5836
|
+
console.log(` triage db ${triageDb} [fixed: beside the db]`);
|
|
5575
5837
|
console.log("\nServer");
|
|
5576
5838
|
console.log(` host ${host} [${hostOrigin}]`);
|
|
5577
5839
|
console.log(` port ${port} [${portOrigin}]`);
|