@swestash/agent-lens 0.5.4 → 0.6.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/cac@6.7.14/node_modules/cac/dist/index.mjs","../../core/src/schema.ts","../../core/src/security.ts","../../core/src/pricing.ts","../../core/src/markdown.ts","../../core/src/rawjson.ts","../../core/src/paths.ts","../../core/src/sources.ts","../../core/src/projects.ts","../../core/src/collect.ts","../../core/src/lock.ts","../../core/src/service.ts","../../ingest/src/run.ts","../../ingest/src/db.ts","../../ingest/src/adapters/claude-code.ts","../../ingest/src/classify.ts","../../ingest/src/detect.ts","../../ingest/src/pipeline.ts","../../ingest/src/workflows.ts","../../ingest/src/redact.ts","../../ingest/src/fileread.ts","../../ingest/src/meta.ts","../../ingest/src/toolResults.ts","../../server/src/run.ts","../../server/src/db.ts","../../server/src/app.ts","../../server/src/dashboard.ts","../../server/src/refresh.ts","../../server/src/triage.ts","../src/watch.ts"],"sourcesContent":["/**\n * `agent-lens` — the unified CLI. One binary dispatching the pipeline: collect → ingest → serve,\n * plus watch (resident collect+ingest) and service (install collect+ingest and/or serve as OS\n * services). Bundled into a single file by tsup so it installs as one npm package (ADR-010).\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { cac } from \"cac\";\nimport { acquireLock, collectAll, findRepoRoot, parseHours, parseTargets, resolveDataDir, runService } from \"@agent-lens/core\";\nimport { runIngest, runMetrics } from \"@agent-lens/ingest\";\nimport { startServer } from \"@agent-lens/server\";\nimport { runWatch } from \"./watch.js\";\n\nfunction version(): string {\n try {\n const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), \"../package.json\"), \"utf8\"));\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/** Run `fn` under the single-instance lock so collect/ingest never overlap another run. */\nfunction withLock(fn: () => void): void {\n const lockPath = join(resolveDataDir(findRepoRoot()), \".agent-lens.lock\");\n const lock = acquireLock(lockPath);\n if (!lock) {\n console.error(\"agent-lens: another collect/ingest run is in progress — skipping\");\n return;\n }\n try {\n fn();\n } finally {\n lock.release();\n }\n}\n\nconst cli = cac(\"agent-lens\");\n\ncli\n .command(\"collect\", \"Copy new session traces into the local archive\")\n .option(\"--then-ingest\", \"Run ingest immediately after collecting\")\n .action((opts: { thenIngest?: boolean }) => {\n withLock(() => {\n const s = collectAll();\n console.log(\n `agent-lens: collect done — ${s.sources} source(s), ${s.scanned} scanned, ${s.copied} copied, ` +\n `${s.appended} appended, ${s.diverged} diverged, ${s.compacted} compacted, ${s.snapshots} snapshots`,\n );\n if (opts.thenIngest) runIngest([]);\n });\n });\n\ncli\n .command(\"ingest\", \"Build/update the SQLite projection from the archive\")\n .option(\"--full\", \"Ignore incremental state and re-read every file\")\n .option(\"--db <path>\", \"SQLite DB path\")\n .option(\"--archive <path>\", \"Archive directory\")\n .action((opts: { full?: boolean; db?: string; archive?: string }) => {\n const argv: string[] = [];\n if (opts.full) argv.push(\"--full\");\n if (opts.db) argv.push(\"--db\", opts.db);\n if (opts.archive) argv.push(\"--archive\", opts.archive);\n withLock(() => runIngest(argv));\n });\n\ncli\n .command(\"serve\", \"Serve the web UI + read-only API on 127.0.0.1\")\n .action(async () => {\n await startServer();\n });\n\ncli\n .command(\"watch\", \"Watch sources and collect + ingest on change (resident process)\")\n .option(\"--interval <sec>\", \"Also run on a fixed interval (seconds)\")\n .option(\"--poll\", \"Use polling — for network filesystems where fs events don't fire\")\n .action(async (opts: { interval?: string; poll?: boolean }) => {\n await runWatch({ intervalSec: opts.interval ? Number(opts.interval) : undefined, poll: !!opts.poll });\n });\n\ncli\n .command(\"metrics\", \"Re-run classification over an already-ingested DB\")\n .option(\"--db <path>\", \"SQLite DB path\")\n .action((opts: { db?: string }) => {\n runMetrics(opts.db ? [\"--db\", opts.db] : []);\n });\n\ncli\n .command(\n \"service <action> [target]\",\n \"Install/uninstall/status OS services — action: install|uninstall|status; target: collector|server|all (default all)\",\n )\n .option(\"--times <hours>\", \"Collector cadence: comma-separated hours 0-23 (default: 9,13,17,21)\")\n .action((action: string, target: string | undefined, opts: { times?: string }) => {\n const targets = parseTargets(target);\n const hours = action === \"install\" ? parseHours(opts.times) : undefined;\n // The bundled CLI file is this module; bake its absolute path + node into every unit/plist/task.\n runService(action, { cliEntry: fileURLToPath(import.meta.url), hours, targets });\n });\n\ncli.help();\ncli.version(version());\ncli.parse();\n","import { EventEmitter } from 'events';\n\nfunction toArr(any) {\n\treturn any == null ? [] : Array.isArray(any) ? any : [any];\n}\n\nfunction toVal(out, key, val, opts) {\n\tvar x, old=out[key], nxt=(\n\t\t!!~opts.string.indexOf(key) ? (val == null || val === true ? '' : String(val))\n\t\t: typeof val === 'boolean' ? val\n\t\t: !!~opts.boolean.indexOf(key) ? (val === 'false' ? false : val === 'true' || (out._.push((x = +val,x * 0 === 0) ? x : val),!!val))\n\t\t: (x = +val,x * 0 === 0) ? x : val\n\t);\n\tout[key] = old == null ? nxt : (Array.isArray(old) ? old.concat(nxt) : [old, nxt]);\n}\n\nfunction mri2 (args, opts) {\n\targs = args || [];\n\topts = opts || {};\n\n\tvar k, arr, arg, name, val, out={ _:[] };\n\tvar i=0, j=0, idx=0, len=args.length;\n\n\tconst alibi = opts.alias !== void 0;\n\tconst strict = opts.unknown !== void 0;\n\tconst defaults = opts.default !== void 0;\n\n\topts.alias = opts.alias || {};\n\topts.string = toArr(opts.string);\n\topts.boolean = toArr(opts.boolean);\n\n\tif (alibi) {\n\t\tfor (k in opts.alias) {\n\t\t\tarr = opts.alias[k] = toArr(opts.alias[k]);\n\t\t\tfor (i=0; i < arr.length; i++) {\n\t\t\t\t(opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (i=opts.boolean.length; i-- > 0;) {\n\t\tarr = opts.alias[opts.boolean[i]] || [];\n\t\tfor (j=arr.length; j-- > 0;) opts.boolean.push(arr[j]);\n\t}\n\n\tfor (i=opts.string.length; i-- > 0;) {\n\t\tarr = opts.alias[opts.string[i]] || [];\n\t\tfor (j=arr.length; j-- > 0;) opts.string.push(arr[j]);\n\t}\n\n\tif (defaults) {\n\t\tfor (k in opts.default) {\n\t\t\tname = typeof opts.default[k];\n\t\t\tarr = opts.alias[k] = opts.alias[k] || [];\n\t\t\tif (opts[name] !== void 0) {\n\t\t\t\topts[name].push(k);\n\t\t\t\tfor (i=0; i < arr.length; i++) {\n\t\t\t\t\topts[name].push(arr[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst keys = strict ? Object.keys(opts.alias) : [];\n\n\tfor (i=0; i < len; i++) {\n\t\targ = args[i];\n\n\t\tif (arg === '--') {\n\t\t\tout._ = out._.concat(args.slice(++i));\n\t\t\tbreak;\n\t\t}\n\n\t\tfor (j=0; j < arg.length; j++) {\n\t\t\tif (arg.charCodeAt(j) !== 45) break; // \"-\"\n\t\t}\n\n\t\tif (j === 0) {\n\t\t\tout._.push(arg);\n\t\t} else if (arg.substring(j, j + 3) === 'no-') {\n\t\t\tname = arg.substring(j + 3);\n\t\t\tif (strict && !~keys.indexOf(name)) {\n\t\t\t\treturn opts.unknown(arg);\n\t\t\t}\n\t\t\tout[name] = false;\n\t\t} else {\n\t\t\tfor (idx=j+1; idx < arg.length; idx++) {\n\t\t\t\tif (arg.charCodeAt(idx) === 61) break; // \"=\"\n\t\t\t}\n\n\t\t\tname = arg.substring(j, idx);\n\t\t\tval = arg.substring(++idx) || (i+1 === len || (''+args[i+1]).charCodeAt(0) === 45 || args[++i]);\n\t\t\tarr = (j === 2 ? [name] : name);\n\n\t\t\tfor (idx=0; idx < arr.length; idx++) {\n\t\t\t\tname = arr[idx];\n\t\t\t\tif (strict && !~keys.indexOf(name)) return opts.unknown('-'.repeat(j) + name);\n\t\t\t\ttoVal(out, name, (idx + 1 < arr.length) || val, opts);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (defaults) {\n\t\tfor (k in opts.default) {\n\t\t\tif (out[k] === void 0) {\n\t\t\t\tout[k] = opts.default[k];\n\t\t\t}\n\t\t}\n\t}\n\n\tif (alibi) {\n\t\tfor (k in out) {\n\t\t\tarr = opts.alias[k] || [];\n\t\t\twhile (arr.length > 0) {\n\t\t\t\tout[arr.shift()] = out[k];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out;\n}\n\nconst removeBrackets = (v) => v.replace(/[<[].+/, \"\").trim();\nconst findAllBrackets = (v) => {\n const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;\n const SQUARE_BRACKET_RE_GLOBAL = /\\[([^\\]]+)\\]/g;\n const res = [];\n const parse = (match) => {\n let variadic = false;\n let value = match[1];\n if (value.startsWith(\"...\")) {\n value = value.slice(3);\n variadic = true;\n }\n return {\n required: match[0].startsWith(\"<\"),\n value,\n variadic\n };\n };\n let angledMatch;\n while (angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v)) {\n res.push(parse(angledMatch));\n }\n let squareMatch;\n while (squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v)) {\n res.push(parse(squareMatch));\n }\n return res;\n};\nconst getMriOptions = (options) => {\n const result = {alias: {}, boolean: []};\n for (const [index, option] of options.entries()) {\n if (option.names.length > 1) {\n result.alias[option.names[0]] = option.names.slice(1);\n }\n if (option.isBoolean) {\n if (option.negated) {\n const hasStringTypeOption = options.some((o, i) => {\n return i !== index && o.names.some((name) => option.names.includes(name)) && typeof o.required === \"boolean\";\n });\n if (!hasStringTypeOption) {\n result.boolean.push(option.names[0]);\n }\n } else {\n result.boolean.push(option.names[0]);\n }\n }\n }\n return result;\n};\nconst findLongest = (arr) => {\n return arr.sort((a, b) => {\n return a.length > b.length ? -1 : 1;\n })[0];\n};\nconst padRight = (str, length) => {\n return str.length >= length ? str : `${str}${\" \".repeat(length - str.length)}`;\n};\nconst camelcase = (input) => {\n return input.replace(/([a-z])-([a-z])/g, (_, p1, p2) => {\n return p1 + p2.toUpperCase();\n });\n};\nconst setDotProp = (obj, keys, val) => {\n let i = 0;\n let length = keys.length;\n let t = obj;\n let x;\n for (; i < length; ++i) {\n x = t[keys[i]];\n t = t[keys[i]] = i === length - 1 ? val : x != null ? x : !!~keys[i + 1].indexOf(\".\") || !(+keys[i + 1] > -1) ? {} : [];\n }\n};\nconst setByType = (obj, transforms) => {\n for (const key of Object.keys(transforms)) {\n const transform = transforms[key];\n if (transform.shouldTransform) {\n obj[key] = Array.prototype.concat.call([], obj[key]);\n if (typeof transform.transformFunction === \"function\") {\n obj[key] = obj[key].map(transform.transformFunction);\n }\n }\n }\n};\nconst getFileName = (input) => {\n const m = /([^\\\\\\/]+)$/.exec(input);\n return m ? m[1] : \"\";\n};\nconst camelcaseOptionName = (name) => {\n return name.split(\".\").map((v, i) => {\n return i === 0 ? camelcase(v) : v;\n }).join(\".\");\n};\nclass CACError extends Error {\n constructor(message) {\n super(message);\n this.name = this.constructor.name;\n if (typeof Error.captureStackTrace === \"function\") {\n Error.captureStackTrace(this, this.constructor);\n } else {\n this.stack = new Error(message).stack;\n }\n }\n}\n\nclass Option {\n constructor(rawName, description, config) {\n this.rawName = rawName;\n this.description = description;\n this.config = Object.assign({}, config);\n rawName = rawName.replace(/\\.\\*/g, \"\");\n this.negated = false;\n this.names = removeBrackets(rawName).split(\",\").map((v) => {\n let name = v.trim().replace(/^-{1,2}/, \"\");\n if (name.startsWith(\"no-\")) {\n this.negated = true;\n name = name.replace(/^no-/, \"\");\n }\n return camelcaseOptionName(name);\n }).sort((a, b) => a.length > b.length ? 1 : -1);\n this.name = this.names[this.names.length - 1];\n if (this.negated && this.config.default == null) {\n this.config.default = true;\n }\n if (rawName.includes(\"<\")) {\n this.required = true;\n } else if (rawName.includes(\"[\")) {\n this.required = false;\n } else {\n this.isBoolean = true;\n }\n }\n}\n\nconst processArgs = process.argv;\nconst platformInfo = `${process.platform}-${process.arch} node-${process.version}`;\n\nclass Command {\n constructor(rawName, description, config = {}, cli) {\n this.rawName = rawName;\n this.description = description;\n this.config = config;\n this.cli = cli;\n this.options = [];\n this.aliasNames = [];\n this.name = removeBrackets(rawName);\n this.args = findAllBrackets(rawName);\n this.examples = [];\n }\n usage(text) {\n this.usageText = text;\n return this;\n }\n allowUnknownOptions() {\n this.config.allowUnknownOptions = true;\n return this;\n }\n ignoreOptionDefaultValue() {\n this.config.ignoreOptionDefaultValue = true;\n return this;\n }\n version(version, customFlags = \"-v, --version\") {\n this.versionNumber = version;\n this.option(customFlags, \"Display version number\");\n return this;\n }\n example(example) {\n this.examples.push(example);\n return this;\n }\n option(rawName, description, config) {\n const option = new Option(rawName, description, config);\n this.options.push(option);\n return this;\n }\n alias(name) {\n this.aliasNames.push(name);\n return this;\n }\n action(callback) {\n this.commandAction = callback;\n return this;\n }\n isMatched(name) {\n return this.name === name || this.aliasNames.includes(name);\n }\n get isDefaultCommand() {\n return this.name === \"\" || this.aliasNames.includes(\"!\");\n }\n get isGlobalCommand() {\n return this instanceof GlobalCommand;\n }\n hasOption(name) {\n name = name.split(\".\")[0];\n return this.options.find((option) => {\n return option.names.includes(name);\n });\n }\n outputHelp() {\n const {name, commands} = this.cli;\n const {\n versionNumber,\n options: globalOptions,\n helpCallback\n } = this.cli.globalCommand;\n let sections = [\n {\n body: `${name}${versionNumber ? `/${versionNumber}` : \"\"}`\n }\n ];\n sections.push({\n title: \"Usage\",\n body: ` $ ${name} ${this.usageText || this.rawName}`\n });\n const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;\n if (showCommands) {\n const longestCommandName = findLongest(commands.map((command) => command.rawName));\n sections.push({\n title: \"Commands\",\n body: commands.map((command) => {\n return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;\n }).join(\"\\n\")\n });\n sections.push({\n title: `For more info, run any command with the \\`--help\\` flag`,\n body: commands.map((command) => ` $ ${name}${command.name === \"\" ? \"\" : ` ${command.name}`} --help`).join(\"\\n\")\n });\n }\n let options = this.isGlobalCommand ? globalOptions : [...this.options, ...globalOptions || []];\n if (!this.isGlobalCommand && !this.isDefaultCommand) {\n options = options.filter((option) => option.name !== \"version\");\n }\n if (options.length > 0) {\n const longestOptionName = findLongest(options.map((option) => option.rawName));\n sections.push({\n title: \"Options\",\n body: options.map((option) => {\n return ` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${option.config.default === void 0 ? \"\" : `(default: ${option.config.default})`}`;\n }).join(\"\\n\")\n });\n }\n if (this.examples.length > 0) {\n sections.push({\n title: \"Examples\",\n body: this.examples.map((example) => {\n if (typeof example === \"function\") {\n return example(name);\n }\n return example;\n }).join(\"\\n\")\n });\n }\n if (helpCallback) {\n sections = helpCallback(sections) || sections;\n }\n console.log(sections.map((section) => {\n return section.title ? `${section.title}:\n${section.body}` : section.body;\n }).join(\"\\n\\n\"));\n }\n outputVersion() {\n const {name} = this.cli;\n const {versionNumber} = this.cli.globalCommand;\n if (versionNumber) {\n console.log(`${name}/${versionNumber} ${platformInfo}`);\n }\n }\n checkRequiredArgs() {\n const minimalArgsCount = this.args.filter((arg) => arg.required).length;\n if (this.cli.args.length < minimalArgsCount) {\n throw new CACError(`missing required args for command \\`${this.rawName}\\``);\n }\n }\n checkUnknownOptions() {\n const {options, globalCommand} = this.cli;\n if (!this.config.allowUnknownOptions) {\n for (const name of Object.keys(options)) {\n if (name !== \"--\" && !this.hasOption(name) && !globalCommand.hasOption(name)) {\n throw new CACError(`Unknown option \\`${name.length > 1 ? `--${name}` : `-${name}`}\\``);\n }\n }\n }\n }\n checkOptionValue() {\n const {options: parsedOptions, globalCommand} = this.cli;\n const options = [...globalCommand.options, ...this.options];\n for (const option of options) {\n const value = parsedOptions[option.name.split(\".\")[0]];\n if (option.required) {\n const hasNegated = options.some((o) => o.negated && o.names.includes(option.name));\n if (value === true || value === false && !hasNegated) {\n throw new CACError(`option \\`${option.rawName}\\` value is missing`);\n }\n }\n }\n }\n}\nclass GlobalCommand extends Command {\n constructor(cli) {\n super(\"@@global@@\", \"\", {}, cli);\n }\n}\n\nvar __assign = Object.assign;\nclass CAC extends EventEmitter {\n constructor(name = \"\") {\n super();\n this.name = name;\n this.commands = [];\n this.rawArgs = [];\n this.args = [];\n this.options = {};\n this.globalCommand = new GlobalCommand(this);\n this.globalCommand.usage(\"<command> [options]\");\n }\n usage(text) {\n this.globalCommand.usage(text);\n return this;\n }\n command(rawName, description, config) {\n const command = new Command(rawName, description || \"\", config, this);\n command.globalCommand = this.globalCommand;\n this.commands.push(command);\n return command;\n }\n option(rawName, description, config) {\n this.globalCommand.option(rawName, description, config);\n return this;\n }\n help(callback) {\n this.globalCommand.option(\"-h, --help\", \"Display this message\");\n this.globalCommand.helpCallback = callback;\n this.showHelpOnExit = true;\n return this;\n }\n version(version, customFlags = \"-v, --version\") {\n this.globalCommand.version(version, customFlags);\n this.showVersionOnExit = true;\n return this;\n }\n example(example) {\n this.globalCommand.example(example);\n return this;\n }\n outputHelp() {\n if (this.matchedCommand) {\n this.matchedCommand.outputHelp();\n } else {\n this.globalCommand.outputHelp();\n }\n }\n outputVersion() {\n this.globalCommand.outputVersion();\n }\n setParsedInfo({args, options}, matchedCommand, matchedCommandName) {\n this.args = args;\n this.options = options;\n if (matchedCommand) {\n this.matchedCommand = matchedCommand;\n }\n if (matchedCommandName) {\n this.matchedCommandName = matchedCommandName;\n }\n return this;\n }\n unsetMatchedCommand() {\n this.matchedCommand = void 0;\n this.matchedCommandName = void 0;\n }\n parse(argv = processArgs, {\n run = true\n } = {}) {\n this.rawArgs = argv;\n if (!this.name) {\n this.name = argv[1] ? getFileName(argv[1]) : \"cli\";\n }\n let shouldParse = true;\n for (const command of this.commands) {\n const parsed = this.mri(argv.slice(2), command);\n const commandName = parsed.args[0];\n if (command.isMatched(commandName)) {\n shouldParse = false;\n const parsedInfo = __assign(__assign({}, parsed), {\n args: parsed.args.slice(1)\n });\n this.setParsedInfo(parsedInfo, command, commandName);\n this.emit(`command:${commandName}`, command);\n }\n }\n if (shouldParse) {\n for (const command of this.commands) {\n if (command.name === \"\") {\n shouldParse = false;\n const parsed = this.mri(argv.slice(2), command);\n this.setParsedInfo(parsed, command);\n this.emit(`command:!`, command);\n }\n }\n }\n if (shouldParse) {\n const parsed = this.mri(argv.slice(2));\n this.setParsedInfo(parsed);\n }\n if (this.options.help && this.showHelpOnExit) {\n this.outputHelp();\n run = false;\n this.unsetMatchedCommand();\n }\n if (this.options.version && this.showVersionOnExit && this.matchedCommandName == null) {\n this.outputVersion();\n run = false;\n this.unsetMatchedCommand();\n }\n const parsedArgv = {args: this.args, options: this.options};\n if (run) {\n this.runMatchedCommand();\n }\n if (!this.matchedCommand && this.args[0]) {\n this.emit(\"command:*\");\n }\n return parsedArgv;\n }\n mri(argv, command) {\n const cliOptions = [\n ...this.globalCommand.options,\n ...command ? command.options : []\n ];\n const mriOptions = getMriOptions(cliOptions);\n let argsAfterDoubleDashes = [];\n const doubleDashesIndex = argv.indexOf(\"--\");\n if (doubleDashesIndex > -1) {\n argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);\n argv = argv.slice(0, doubleDashesIndex);\n }\n let parsed = mri2(argv, mriOptions);\n parsed = Object.keys(parsed).reduce((res, name) => {\n return __assign(__assign({}, res), {\n [camelcaseOptionName(name)]: parsed[name]\n });\n }, {_: []});\n const args = parsed._;\n const options = {\n \"--\": argsAfterDoubleDashes\n };\n const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;\n let transforms = Object.create(null);\n for (const cliOption of cliOptions) {\n if (!ignoreDefault && cliOption.config.default !== void 0) {\n for (const name of cliOption.names) {\n options[name] = cliOption.config.default;\n }\n }\n if (Array.isArray(cliOption.config.type)) {\n if (transforms[cliOption.name] === void 0) {\n transforms[cliOption.name] = Object.create(null);\n transforms[cliOption.name][\"shouldTransform\"] = true;\n transforms[cliOption.name][\"transformFunction\"] = cliOption.config.type[0];\n }\n }\n }\n for (const key of Object.keys(parsed)) {\n if (key !== \"_\") {\n const keys = key.split(\".\");\n setDotProp(options, keys, parsed[key]);\n setByType(options, transforms);\n }\n }\n return {\n args,\n options\n };\n }\n runMatchedCommand() {\n const {args, options, matchedCommand: command} = this;\n if (!command || !command.commandAction)\n return;\n command.checkUnknownOptions();\n command.checkOptionValue();\n command.checkRequiredArgs();\n const actionArgs = [];\n command.args.forEach((arg, index) => {\n if (arg.variadic) {\n actionArgs.push(args.slice(index));\n } else {\n actionArgs.push(args[index]);\n }\n });\n actionArgs.push(options);\n return command.commandAction.apply(this, actionArgs);\n }\n}\n\nconst cac = (name = \"\") => new CAC(name);\n\nexport default cac;\nexport { CAC, Command, cac };\n","/**\n * Agent Lens — normalized, agent-agnostic data model (SQLite DDL).\n *\n * Design notes\n * - Agent-agnostic: Claude Code is one *source adapter*. Adding another agent later means a new\n * adapter that emits rows for these same tables — no schema change (see ADR-003).\n * - Event-grained source of truth: every transcript line becomes a row in `events`, keyed by its\n * stable `uuid`. Re-ingesting the same event is an UPSERT, so ingesting the mirror plus all\n * `.versions/` divergence backups deduplicates to the *maximal* history and recovers\n * compaction-dropped events (see ADR-001 / ADR-002).\n * - Hierarchy: session › turn › event. A \"turn\" = one user prompt → assistant completion.\n * - `raw_json` keeps the original line verbatim for lossless re-derivation; structured columns are\n * projections for querying/dashboards. It is stored gzip-compressed as a BLOB (ADR-011) — write via\n * `packRaw`, read via `unpackRaw` (both in `rawjson.ts`).\n *\n * This module only declares the schema. Connection handling and migrations live in `db.ts`\n * (Phase 2). Bump SCHEMA_VERSION on any DDL change.\n */\n\nexport const SCHEMA_VERSION = 12;\n\nexport const SCHEMA_SQL = /* sql */ `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\n-- Schema/version bookkeeping ------------------------------------------------\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- An agent *type* (e.g. 'claude-code'). Extensible to other agents. ---------\nCREATE TABLE IF NOT EXISTS agents (\n id TEXT PRIMARY KEY, -- 'claude-code'\n name TEXT NOT NULL, -- 'Claude Code CLI'\n kind TEXT NOT NULL DEFAULT 'cli'\n);\n\n-- A configured source = a labeled agent *instance* (e.g. account 'personal' --\n-- of agent 'claude-code' rooted at ~/.claude). Sessions belong to a source. --\nCREATE TABLE IF NOT EXISTS sources (\n id TEXT PRIMARY KEY, -- the (unique) label, e.g. 'personal'\n label TEXT NOT NULL,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n config_dir TEXT\n);\n\n-- A project = a working directory a session ran in. ------------------------\nCREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY, -- stable hash of (agent_id, path)\n agent_id TEXT NOT NULL REFERENCES agents(id),\n path TEXT NOT NULL, -- decoded absolute cwd\n encoded_dir TEXT, -- original '-home-...' archive dir name\n first_seen TEXT, -- ISO8601\n last_seen TEXT,\n UNIQUE (agent_id, path)\n);\n\n-- A session = one transcript file (sessionId). -----------------------------\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY, -- sessionId (UUID)\n agent_id TEXT NOT NULL REFERENCES agents(id),\n source_id TEXT REFERENCES sources(id),\n project_id TEXT REFERENCES projects(id),\n slug TEXT,\n ai_title TEXT,\n cli_version TEXT,\n entrypoint TEXT,\n git_branch TEXT,\n is_sidechain INTEGER NOT NULL DEFAULT 0, -- 1 = subagent thread\n spawn_parent_id TEXT, -- structural parent from path (<parent>/subagents/…); raw link hint, set at ingest\n workflow_run_id TEXT, -- for a Workflow-tool subagent: its run id (wf_<id>) from the path; groups fan-out by run\n parent_session_id TEXT REFERENCES sessions(id), -- spawning session (subagents only); derived in rebuildDerived\n parent_turn_id TEXT REFERENCES turns(id), -- the turn that spawned this subagent\n started_at TEXT, -- ISO8601 of first event\n ended_at TEXT, -- ISO8601 of last event\n duration_ms INTEGER,\n event_count INTEGER NOT NULL DEFAULT 0,\n turn_count INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_source ON sessions(source_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at);\nCREATE INDEX IF NOT EXISTS idx_sessions_parent_session ON sessions(parent_session_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_workflow_run ON sessions(workflow_run_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_parent_turn ON sessions(parent_turn_id);\n\n-- A turn = user prompt -> assistant completion within a session. ------------\nCREATE TABLE IF NOT EXISTS turns (\n id TEXT PRIMARY KEY, -- hash(session_id, seq)\n session_id TEXT NOT NULL REFERENCES sessions(id),\n seq INTEGER NOT NULL, -- 0-based order within session\n user_event_uuid TEXT, -- the originating user event\n prompt_preview TEXT, -- short, for lists (full text in events)\n model TEXT, -- model that answered this turn\n started_at TEXT,\n ended_at TEXT,\n duration_ms INTEGER,\n UNIQUE (session_id, seq)\n);\nCREATE INDEX IF NOT EXISTS idx_turns_session ON turns(session_id);\n\n-- Every transcript line. uuid is the dedup key across archive versions. -----\nCREATE TABLE IF NOT EXISTS events (\n uuid TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n turn_id TEXT REFERENCES turns(id),\n parent_uuid TEXT,\n seq INTEGER, -- line order as first observed\n type TEXT NOT NULL, -- user | assistant | system | attachment | ...\n role TEXT, -- user | assistant (message.role)\n timestamp TEXT, -- ISO8601\n model TEXT,\n is_sidechain INTEGER NOT NULL DEFAULT 0,\n is_meta INTEGER NOT NULL DEFAULT 0,\n text TEXT, -- flattened text/thinking for search + preview\n raw_json BLOB NOT NULL, -- original line, verbatim, gzip-compressed (ADR-011); read via unpackRaw()\n source_file TEXT -- archive path the canonical copy came from\n);\nCREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_turn ON events(turn_id);\nCREATE INDEX IF NOT EXISTS idx_events_type ON events(type);\nCREATE INDEX IF NOT EXISTS idx_events_ts ON events(timestamp);\n\n-- Tool invocations (incl. subagents / Task calls). -------------------------\nCREATE TABLE IF NOT EXISTS tool_calls (\n id TEXT PRIMARY KEY, -- tool_use id, or hash\n event_uuid TEXT REFERENCES events(uuid),\n session_id TEXT NOT NULL REFERENCES sessions(id),\n turn_id TEXT REFERENCES turns(id),\n tool_name TEXT NOT NULL, -- Edit, Write, Bash, Skill, Task, ...\n caller TEXT,\n skill_name TEXT, -- when tool_name = Skill\n skill_id TEXT REFERENCES skills(id), -- the specific skill *version* this call fired (content hash); set in rebuildDerived\n agent_type TEXT, -- subagent type (toolUseResult.agentType)\n spawned_session_id TEXT, -- for Task/Agent: the subagent session id ('agent-'||agentId)\n workflow_run_id TEXT, -- for Workflow: the run id (toolUseResult.runId, e.g. wf_<id>) — ties the run to its subagents\n workflow_name TEXT, -- for Workflow: the workflow name (toolUseResult.workflowName)\n resolved_model TEXT,\n status TEXT, -- success | error | ...\n total_duration_ms INTEGER,\n total_tokens INTEGER,\n total_tool_use_count INTEGER,\n input_json TEXT, -- tool input, verbatim\n result_summary TEXT -- short result projection (no secrets dumped wholesale)\n);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_tool ON tool_calls(tool_name);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_skill ON tool_calls(skill_name);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_skill_id ON tool_calls(skill_id);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_spawned ON tool_calls(spawned_session_id);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_workflow_run ON tool_calls(workflow_run_id);\n\n-- Skill *versions* (content-addressed). A skill firing injects the full SKILL.md body into the\n-- transcript as an isMeta user event (begins \"Base directory for this skill: …\", then the body,\n-- then a trailing \"ARGUMENTS: …\" block). That body is the only real skill content available (the\n-- trace carries no description/created_at). We identify a version by hashing the *normalized* body\n-- (Base-directory line + per-call ARGUMENTS block stripped, trailing whitespace trimmed) so the same\n-- content from either install / any args is one version, and a content change shows as a new row.\n-- Linked from tool_calls.skill_id in rebuildDerived. See plan: content-addressed skill versioning.\nCREATE TABLE IF NOT EXISTS skills (\n id TEXT PRIMARY KEY, -- sha1 of name + NUL + normalized_body\n name TEXT NOT NULL,\n base_dir TEXT, -- last-seen base directory (informational; NOT part of the hash)\n body TEXT NOT NULL, -- normalized SKILL.md body (Base-dir line + ARGUMENTS block stripped)\n summary TEXT, -- first heading/intro line, for list + detail display\n body_bytes INTEGER,\n first_seen TEXT, -- earliest firing timestamp (MIN across firings)\n last_seen TEXT -- latest firing timestamp (MAX across firings)\n);\nCREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);\n\n-- Token usage at the assistant-response grain. Cost derived later from model. --\n-- One API response is logged across multiple JSONL lines (one per content block: text + each\n-- tool_use), and Claude Code stamps the SAME usage object on every line. We dedup at the response\n-- grain via the unique (session_id, message_id) index so a response's tokens are counted ONCE, not\n-- once per content block (which over-counted tokens ~2.3x and cost ~3x). event_uuid stays the PK\n-- (the row is attached to whichever content-block event won the insert). --\nCREATE TABLE IF NOT EXISTS token_usage (\n event_uuid TEXT PRIMARY KEY REFERENCES events(uuid),\n session_id TEXT NOT NULL REFERENCES sessions(id),\n turn_id TEXT REFERENCES turns(id),\n message_id TEXT, -- Anthropic response id (msg_…); dedup key\n model TEXT,\n input_tokens INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_input_tokens INTEGER NOT NULL DEFAULT 0,\n cache_read_input_tokens INTEGER NOT NULL DEFAULT 0,\n service_tier TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_token_usage_session ON token_usage(session_id);\n-- Partial so rare usage rows lacking a message_id (e.g. <synthetic>) don't collide on NULL. --\nCREATE UNIQUE INDEX IF NOT EXISTS idx_token_usage_msg\n ON token_usage(session_id, message_id) WHERE message_id IS NOT NULL;\n\n-- Heuristic classification (category + complexity), re-runnable. -----------\n-- scope: 'session' | 'turn'; target_id references that entity. -------------\nCREATE TABLE IF NOT EXISTS classifications (\n scope TEXT NOT NULL, -- 'session' | 'turn'\n target_id TEXT NOT NULL,\n category TEXT, -- feature | bugfix | refactor | docs | ops | review | ...\n complexity_score REAL,\n complexity_band TEXT, -- trivial | small | medium | large | xl\n signals_json TEXT, -- inputs used, for transparency/repro\n classifier_version INTEGER NOT NULL DEFAULT 1,\n PRIMARY KEY (scope, target_id)\n);\n\n-- Security findings (ADR-017), re-runnable. A deterministic rule engine (detect.ts) scans each\n-- tool call's verbatim input/result for risky operations — destructive/data-loss, credential access,\n-- data exfiltration, privilege/guardrail bypass — and records one row per (tool_call, rule) match.\n-- Findings are 0..N per tool call (unlike classifications, which are 1-per-session), so an incremental\n-- re-run DELETEs the touched sessions' rows and re-INSERTs (delete-then-insert) rather than upserting.\n-- Every input/modifier is captured in signals_json so a finding is fully explainable; framework_ref\n-- anchors it to OWASP Agentic / MITRE ATLAS. detector_version lets a future engine supersede rows.\nCREATE TABLE IF NOT EXISTS findings (\n id TEXT PRIMARY KEY, -- deterministic hash(tool_call_id, rule_id)\n session_id TEXT NOT NULL REFERENCES sessions(id),\n tool_call_id TEXT REFERENCES tool_calls(id),\n event_uuid TEXT, -- the tool call's event, for jump-to-transcript\n turn_id TEXT,\n rule_id TEXT NOT NULL, -- 'destructive.rm_rf'\n category TEXT NOT NULL, -- destructive | credential-access | exfiltration | privilege-bypass\n framework_ref TEXT, -- 'OWASP ASI02' | 'MITRE ATLAS AML.T0086' | ...\n severity TEXT NOT NULL, -- info | low | medium | high | critical\n title TEXT,\n evidence TEXT, -- matched snippet, truncated (no wholesale secret dump)\n signals_json TEXT, -- why: rule, matched pattern, context modifiers\n detector_version INTEGER NOT NULL DEFAULT 1\n);\nCREATE INDEX IF NOT EXISTS idx_findings_session ON findings(session_id);\nCREATE INDEX IF NOT EXISTS idx_findings_severity ON findings(severity);\nCREATE INDEX IF NOT EXISTS idx_findings_category ON findings(category);\nCREATE INDEX IF NOT EXISTS idx_findings_rule ON findings(rule_id);\nCREATE INDEX IF NOT EXISTS idx_findings_tool_call ON findings(tool_call_id);\n\n-- Workflow-tool run results, ingested from the sidecar JSON the runner writes next to the launching\n-- session (…/projects/<enc>/<sessionId>/workflows/wf_<id>.json). The transcript only carries the\n-- \"launched in background\" ack for an async run; this sidecar is the authoritative record of how the\n-- run finished — status (completed/failed), summary, the returned result payload, and roll-up\n-- tokens/tool-calls/duration/agent-count. One row per run id; UPSERT on re-ingest. Stored verbatim\n-- (like tool_calls.input_json) — the DB is local; redaction is an export-time concern. --------------\nCREATE TABLE IF NOT EXISTS workflow_results (\n run_id TEXT PRIMARY KEY, -- wf_<id> (matches tool_calls.workflow_run_id / sessions.workflow_run_id)\n source_id TEXT, -- which configured source (account) it came from\n session_id TEXT, -- launching session (path segment before /workflows/)\n task_id TEXT, -- async task id (the \"Task ID: …\" in the launch ack)\n workflow_name TEXT,\n status TEXT, -- completed | failed | running | ...\n summary TEXT,\n default_model TEXT, -- model the run executed on (e.g. claude-fable-5)\n result_json TEXT, -- the returned result object, verbatim JSON (null when none)\n phases_json TEXT, -- [{title}] the run's phase structure (e.g. Generate → Judge)\n logs_json TEXT, -- concise per-item outcome lines the runner emitted\n progress_json TEXT, -- workflowProgress: the phase/agent event timeline\n agent_count INTEGER, -- agents the run reports having fanned out to\n total_tokens INTEGER,\n total_tool_calls INTEGER,\n duration_ms INTEGER,\n started_at TEXT, -- ISO, from the sidecar startTime/timestamp\n ended_at TEXT, -- ISO, started_at + duration\n ingested_at TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_workflow_results_session ON workflow_results(session_id);\n\n-- Per-subagent metadata sidecar (subagents/agent-<id>.meta.json). One row per subagent session,\n-- joined at read time onto sessions (LEFT JOIN, may precede the session row's arrival). Supplies the\n-- authoritative agentType/description/spawnDepth — Workflow fan-out subagents carry none in-transcript.\nCREATE TABLE IF NOT EXISTS session_meta (\n session_id TEXT PRIMARY KEY, -- 'agent-<id>' = the subagent session id (meta filename stem)\n source_id TEXT,\n agent_type TEXT, -- meta.agentType (e.g. 'Explore', 'claude-code-guide')\n agent_description TEXT, -- meta.description (a real human title)\n spawn_depth INTEGER, -- meta.spawnDepth (optional; absent ⇒ top-level)\n tool_use_id TEXT, -- meta.toolUseId (the spawning tool_call)\n ingested_at TEXT\n);\n\n-- Full un-truncated tool outputs spilled to disk (tool-results/<name>.txt) when a result was too large\n-- for the transcript, which keeps only a 280-char summary naming the file. Joined at read time by\n-- parsing that summary's '…/tool-results/<name>.txt' marker back to (session_id, name).\nCREATE TABLE IF NOT EXISTS tool_results (\n session_id TEXT, -- session the tool ran in (from the archive path)\n name TEXT, -- file basename without .txt (matches the marker in tool_calls.result_summary)\n path TEXT, -- archive path (provenance)\n bytes INTEGER, -- original size, for the UI\n text TEXT, -- full un-truncated output, stored inline (self-contained DB)\n ingested_at TEXT,\n PRIMARY KEY (session_id, name)\n);\n\n-- Idempotent ingest bookkeeping per archive file. --------------------------\nCREATE TABLE IF NOT EXISTS ingest_state (\n file_path TEXT PRIMARY KEY,\n size INTEGER,\n mtime_ms INTEGER,\n sha256 TEXT,\n events_ingested INTEGER NOT NULL DEFAULT 0,\n ingested_at TEXT\n);\n\n-- Full-text search over event text (contentless external-content FTS). ------\nCREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(\n text,\n content='events',\n content_rowid='rowid'\n);\n\n-- Keep FTS in sync with events. --------------------------------------------\nCREATE TRIGGER IF NOT EXISTS events_ai AFTER INSERT ON events BEGIN\n INSERT INTO events_fts(rowid, text) VALUES (new.rowid, new.text);\nEND;\nCREATE TRIGGER IF NOT EXISTS events_ad AFTER DELETE ON events BEGIN\n INSERT INTO events_fts(events_fts, rowid, text) VALUES ('delete', old.rowid, old.text);\nEND;\nCREATE TRIGGER IF NOT EXISTS events_au AFTER UPDATE ON events\nWHEN old.text IS NOT new.text BEGIN\n INSERT INTO events_fts(events_fts, rowid, text) VALUES ('delete', old.rowid, old.text);\n INSERT INTO events_fts(rowid, text) VALUES (new.rowid, new.text);\nEND;\n`;\n","/**\n * Agent Lens — security findings: shared severity model + framework-anchored reference content\n * (ADR-017). This is the low-level, dependency-free source of truth so the ingest detector\n * (packages/ingest/src/detect.ts), the server queries, and the web UI all agree on the severity\n * ordering and the category taxonomy. The reference content also powers the \"what & why\" explainer\n * blocks on the /security page.\n */\n\nexport type Severity = \"info\" | \"low\" | \"medium\" | \"high\" | \"critical\";\n\n/** Ascending order of concern; index doubles as the numeric rank (info=0 … critical=4). */\nexport const SEVERITY_ORDER: Severity[] = [\"info\", \"low\", \"medium\", \"high\", \"critical\"];\n\n/** Numeric rank of a severity (info=0 … critical=4); -1 for an unknown value. */\nexport function severityRank(s: string): number {\n return SEVERITY_ORDER.indexOf(s as Severity);\n}\n\n/** Shift a severity by `delta` bands, clamped to the [info, critical] range. */\nexport function bumpSeverity(s: Severity, delta: number): Severity {\n const i = SEVERITY_ORDER.indexOf(s);\n const j = Math.max(0, Math.min(SEVERITY_ORDER.length - 1, i + delta));\n return SEVERITY_ORDER[j];\n}\n\n/** The four v1 risk families a finding is categorized into. */\nexport type SecurityCategoryKey = \"destructive\" | \"credential-access\" | \"exfiltration\" | \"privilege-bypass\";\n\nexport interface SecurityCategoryRef {\n key: SecurityCategoryKey;\n title: string;\n /** Short framework anchor shown as a chip (matches findings.framework_ref). */\n framework_ref: string;\n framework_url: string;\n /** What the detector looks for. */\n what: string;\n /** Why it matters for a host running an autonomous agent. */\n why: string;\n /** What to do when you see one. */\n remediation: string;\n}\n\n/**\n * Reference content for each category — the browsable \"security page with info\" the feature was scoped\n * around. Anchored to OWASP Top 10 for Agentic Apps 2026 and MITRE ATLAS. Kept deliberately concise;\n * the point is orientation, not a full framework reproduction.\n */\nexport const SECURITY_CATEGORIES: SecurityCategoryRef[] = [\n {\n key: \"destructive\",\n title: \"Destructive & data-loss operations\",\n framework_ref: \"OWASP ASI02\",\n framework_url: \"https://genai.owasp.org/\",\n what: \"Commands or edits that irreversibly delete, overwrite, or truncate data — rm -rf, git reset --hard, force-pushes, DROP/TRUNCATE, disk writes (dd/mkfs/shred), and overwrites of critical project files (lockfiles, CI config).\",\n why: \"An agent acting on a bad plan or a poisoned instruction can wipe work, history, or a database with a single tool call. Because agent-lens sees sessions after the fact, this is how you notice the damage — and the blast radius — even when nobody was watching live.\",\n remediation: \"Open the flagged session, confirm the target was intended, and check the tool result for whether it succeeded. Restore from git/backups if needed, and consider a PreToolUse hook to block the pattern going forward.\",\n },\n {\n key: \"credential-access\",\n title: \"Secret & credential access\",\n framework_ref: \"MITRE ATLAS: Credential Access\",\n framework_url: \"https://atlas.mitre.org/\",\n what: \"Reads of secret-bearing files (.env, ~/.ssh keys, ~/.aws/credentials, .npmrc, .pem) or secret-looking values (private keys, AWS keys, GitHub/Slack tokens) appearing in a command or a captured tool result.\",\n why: \"Credentials the agent touched may have been exposed to the model context, logs, or a downstream tool — and could be leaked or reused. Retrospective visibility tells you which secrets to rotate.\",\n remediation: \"Treat any accessed secret as potentially exposed: rotate it. Verify the access was purposeful, and keep secrets out of the working tree the agent operates on.\",\n },\n {\n key: \"exfiltration\",\n title: \"Data exfiltration\",\n framework_ref: \"MITRE ATLAS AML.T0086\",\n framework_url: \"https://atlas.mitre.org/techniques/AML.T0086\",\n what: \"Local data flowing outbound: curl/wget uploads to external hosts, piping files into a network command, netcat/reverse-shell patterns, and other ways an agent's write-capable tools become an exfiltration channel.\",\n why: \"Exfiltration via the agent's own tool calls (AML.T0086) is a primary agentic attack path — a prompt-injected agent can POST your files or secrets to an attacker with one command. These are the highest-signal findings to review.\",\n remediation: \"Inspect the destination host and the data referenced in the command. If it was not you, treat it as an incident: rotate anything referenced, review what left the host, and audit the session for the injection source.\",\n },\n {\n key: \"privilege-bypass\",\n title: \"Privilege escalation & guardrail bypass\",\n framework_ref: \"OWASP LLM06\",\n framework_url: \"https://genai.owasp.org/\",\n what: \"Actions that widen the agent's authority or sidestep safety: sudo, chmod 777, curl | sh, --dangerously-skip-permissions, writes outside the working project, and persistence mechanisms (cron, systemd, shell rc files).\",\n why: \"Excessive agency (LLM06) is the root cause behind most agent incidents — every disabled guardrail or out-of-scope write removes a barrier the design relied on. Persistence in particular outlives the session that created it.\",\n remediation: \"Confirm the escalation was necessary and scoped. Remove any persistence the agent added, re-tighten permissions, and avoid running agents with skipped permission prompts on a trusted host.\",\n },\n];\n\n/** Quick lookup of a category's reference content by key. */\nexport function securityCategory(key: string): SecurityCategoryRef | undefined {\n return SECURITY_CATEGORIES.find((c) => c.key === key);\n}\n","/**\n * Model pricing for deriving cost from token counts (ADR-003: no cost is stored in traces).\n * USD per 1M tokens. Cache-read is the discounted rate; cache-write the premium rate.\n *\n * Rates are matched by prefix so dated/variant model ids resolve (e.g. 'claude-opus-4-8[1m]').\n * Update as pricing changes; unknown models cost 0 and are reported separately.\n */\n\nexport interface Rate {\n input: number;\n output: number;\n cacheWrite: number;\n cacheRead: number;\n}\n\nexport interface UsageTokens {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n}\n\n/**\n * Prefix → rate. Longest matching prefix wins, so dated/variant ids resolve to the most specific\n * entry (e.g. 'claude-opus-4-8' beats the legacy 'claude-opus-4' fallback).\n * USD per 1M tokens. Convention: cacheWrite (5-minute) = 1.25× input, cacheRead = 0.1× input.\n */\nexport const PRICE_TABLE: Record<string, Rate> = {\n // Opus 4.0 / 4.1 launched at $15/$75; Opus 4.5 cut the Opus tier to $5/$25 and 4.6/4.7/4.8 kept it.\n // The bare 'claude-opus-4' fallback stays at the legacy rate for 4.0/4.1; 4.5+ override below.\n \"claude-opus-4\": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },\n \"claude-opus-4-5\": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },\n \"claude-opus-4-6\": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },\n \"claude-opus-4-7\": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },\n \"claude-opus-4-8\": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },\n \"claude-fable-5\": { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1.0 },\n \"claude-sonnet-4\": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },\n \"claude-haiku-4\": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },\n \"claude-3-5-haiku\": { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },\n \"claude-3-5-sonnet\": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },\n};\n\nexport function rateForModel(model: string | null | undefined): Rate | null {\n if (!model) return null;\n let best: { len: number; rate: Rate } | null = null;\n for (const [prefix, rate] of Object.entries(PRICE_TABLE)) {\n if (model.startsWith(prefix) && (!best || prefix.length > best.len)) {\n best = { len: prefix.length, rate };\n }\n }\n return best?.rate ?? null;\n}\n\n/** Cost in USD for one usage record. Returns 0 for unknown models. */\nexport function costForUsage(model: string | null | undefined, u: UsageTokens): number {\n const r = rateForModel(model);\n if (!r) return 0;\n const M = 1_000_000;\n return (\n (u.input_tokens * r.input +\n u.output_tokens * r.output +\n u.cache_creation_input_tokens * r.cacheWrite +\n u.cache_read_input_tokens * r.cacheRead) /\n M\n );\n}\n","/** Render a session to Markdown — shared by the export endpoint (ADR: data exportable as markdown). */\n\nexport interface MarkdownSession {\n id: string;\n title: string | null;\n source: string | null;\n project: string | null;\n model: string | null;\n started_at: string | null;\n ended_at: string | null;\n}\n\nexport interface MarkdownToolCall {\n tool_name: string;\n skill_name: string | null;\n agent_type: string | null;\n input_json: string | null;\n status: string | null;\n}\n\nexport interface MarkdownEvent {\n type: string;\n role: string | null;\n timestamp: string | null;\n text: string | null;\n thinking: string | null;\n toolCalls: MarkdownToolCall[];\n}\n\nfunction fence(s: string): string {\n return s.length > 2000 ? s.slice(0, 2000) + \"\\n… (truncated)\" : s;\n}\n\nexport function sessionToMarkdown(s: MarkdownSession, events: MarkdownEvent[]): string {\n const out: string[] = [];\n out.push(`# ${s.title || \"Session \" + s.id.slice(0, 8)}`);\n out.push(\"\");\n const meta = [\n [\"Session\", s.id],\n [\"Source\", s.source],\n [\"Project\", s.project],\n [\"Model\", s.model],\n [\"Started\", s.started_at],\n [\"Ended\", s.ended_at],\n ].filter(([, v]) => v);\n for (const [k, v] of meta) out.push(`- **${k}:** ${v}`);\n out.push(\"\");\n out.push(\"---\");\n out.push(\"\");\n\n for (const e of events) {\n const who = e.role || e.type;\n const ts = e.timestamp ? ` _(${e.timestamp})_` : \"\";\n if (e.thinking) {\n out.push(`### 🧠 thinking${ts}`);\n out.push(\"\");\n out.push(fence(e.thinking));\n out.push(\"\");\n }\n if (e.text) {\n const icon = who === \"user\" ? \"👤\" : who === \"assistant\" ? \"🤖\" : \"⚙️\";\n out.push(`### ${icon} ${who}${ts}`);\n out.push(\"\");\n out.push(fence(e.text));\n out.push(\"\");\n }\n for (const tc of e.toolCalls) {\n const label = tc.skill_name\n ? `Skill: ${tc.skill_name}`\n : tc.agent_type\n ? `${tc.tool_name} → ${tc.agent_type}`\n : tc.tool_name;\n const status = tc.status ? ` _(${tc.status})_` : \"\";\n out.push(`> 🔧 **${label}**${status}`);\n if (tc.input_json && tc.input_json !== \"{}\") {\n out.push(\"```json\");\n out.push(fence(tc.input_json));\n out.push(\"```\");\n }\n out.push(\"\");\n }\n }\n return out.join(\"\\n\");\n}\n","/**\n * Agent Lens — `events.raw_json` storage codec (ADR-011).\n *\n * Every transcript line is kept verbatim in `events.raw_json` for lossless re-derivation. That column\n * dominates DB growth, so it is stored gzip-compressed as a BLOB. This module is the single chokepoint:\n * the ingest writer compresses with `packRaw`, the server reader decompresses with `unpackRaw`.\n *\n * `unpackRaw` sniffs the gzip magic bytes (0x1f 0x8b) so it transparently handles a mix of compressed\n * BLOBs and legacy plain-text rows (a DB written before the migration, or read before `--full` runs).\n * JSON always begins with `{`/`[`/`\"`/whitespace, never 0x1f, so the sniff is unambiguous.\n */\nimport { gzipSync, gunzipSync } from \"node:zlib\";\n\n/** Compress a raw transcript line for storage in `events.raw_json` (BLOB). */\nexport function packRaw(json: string): Buffer {\n return gzipSync(Buffer.from(json, \"utf8\"));\n}\n\n/** Decompress a stored `raw_json` value. Accepts gzip BLOBs and legacy plain text/strings. */\nexport function unpackRaw(value: string | Buffer): string {\n if (Buffer.isBuffer(value)) {\n if (value.length >= 2 && value[0] === 0x1f && value[1] === 0x8b) {\n return gunzipSync(value).toString(\"utf8\");\n }\n return value.toString(\"utf8\");\n }\n return value;\n}\n","/**\n * Path resolution shared by ingest, server, and the CLI. The goal is that agent-lens works both\n * from the dev monorepo (data under <repo>/data, config at the repo root) AND when installed as a\n * standalone package under node_modules (per-user OS data dir, no repo layout to assume).\n *\n * Nothing here reads process state beyond env vars + the filesystem, so it is safe to call from any\n * entrypoint. `import.meta.url`-relative resolution replaces the old hardcoded \"../../..\" repo anchor.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { homedir, platform } from \"node:os\";\n\n/** Expand a leading `~` or `$HOME` and resolve to an absolute path. Empty input passes through. */\nexport function expandPath(p: string): string {\n if (!p) return p;\n let out = p;\n if (out === \"~\") out = homedir();\n else if (out.startsWith(\"~/\")) out = join(homedir(), out.slice(2));\n out = out.replace(/\\$HOME/g, homedir());\n return resolve(out);\n}\n\nconst moduleDir = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Find the agent-lens monorepo root by walking up from `from` (default: this module's location).\n * A directory qualifies only if it has BOTH a `pnpm-workspace.yaml` AND a `package.json` named\n * \"agent-lens\" — so an unrelated pnpm workspace never matches, and the published/bundled package\n * (which ships neither) resolves to `null`, sending data to the per-user dir instead.\n */\nexport function findRepoRoot(from: string = moduleDir): string | null {\n let dir = resolve(from);\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, \"package.json\"), \"utf8\"));\n if (pkg?.name === \"agent-lens\") return dir;\n } catch {\n /* not the file we want — keep walking */\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/** Per-user data directory used when not running from the repo (installed CLI). */\nexport function userDataDir(): string {\n const home = homedir();\n if (platform() === \"win32\") {\n return join(process.env.LOCALAPPDATA || join(home, \"AppData\", \"Local\"), \"agent-lens\");\n }\n if (platform() === \"darwin\") {\n return join(home, \"Library\", \"Application Support\", \"agent-lens\");\n }\n return join(process.env.XDG_DATA_HOME || join(home, \".local\", \"share\"), \"agent-lens\");\n}\n\n/** The data dir: `AGENT_LENS_DATA` → `<repo>/data` (dev) → per-user OS dir (installed). */\nexport function resolveDataDir(repoRoot: string | null = findRepoRoot()): string {\n if (process.env.AGENT_LENS_DATA) return resolve(process.env.AGENT_LENS_DATA);\n if (repoRoot) return join(repoRoot, \"data\");\n return userDataDir();\n}\n\n/**\n * First existing config file, in precedence order:\n * `AGENT_LENS_CONFIG` → `<dataDir>/agent-lens.config.json` → repo config → repo example.\n * Returns `null` when none exist (callers fall back to the built-in default source).\n */\nexport function resolveConfigFile(repoRoot: string | null, dataDir: string): string | null {\n const candidates = [\n process.env.AGENT_LENS_CONFIG,\n join(dataDir, \"agent-lens.config.json\"),\n repoRoot ? join(repoRoot, \"agent-lens.config.json\") : null,\n repoRoot ? join(repoRoot, \"agent-lens.config.example.json\") : null,\n ].filter((x): x is string => Boolean(x));\n for (const f of candidates) if (existsSync(f)) return f;\n return null;\n}\n\n/**\n * The built web SPA directory. Tries, in order: the bundled layout (`<pkgDir>/web`, a sibling of the\n * CLI's `dist/`), the dev sibling (`packages/server/dist` → `packages/web/dist`), then the repo path.\n * `fromDir` is the directory of the calling module (`dirname(fileURLToPath(import.meta.url))`).\n */\nexport function resolveWebDist(fromDir: string, repoRoot: string | null = findRepoRoot(fromDir)): string {\n const installed = resolve(fromDir, \"../web\"); // bundled CLI: dist/ and web/ are siblings\n if (existsSync(installed)) return installed;\n const devSibling = resolve(fromDir, \"../../web/dist\"); // packages/server/dist → packages/web/dist\n if (existsSync(devSibling)) return devSibling;\n if (repoRoot) return join(repoRoot, \"packages/web/dist\");\n return installed; // best-effort default; caller guards with existsSync\n}\n","/**\n * Canonical source resolver for Agent Lens (shared by the collector, ingester, server, CLI, and\n * the dev-only scripts/sources.mjs shim).\n *\n * A \"source\" is a labeled agent instance: { label, agent, configDir }. Multiple local accounts\n * (each with its own config folder) are each a source — e.g. \"personal\" → ~/.claude.\n *\n * Resolution order (see resolveConfigFile in paths.ts):\n * 1. $CLAUDE_DIR set → single legacy source { label: $AGENT_LENS_LABEL|\"default\", ~/.claude }\n * 2. $AGENT_LENS_CONFIG → that JSON file\n * 3. <dataDir>/agent-lens.config.json (installed user's config, next to their data)\n * 4. <repo>/agent-lens.config.json → <repo>/agent-lens.config.example.json\n * 5. built-in default → { label: \"default\", agent: \"claude-code\", configDir: \"$HOME/.claude\" }\n *\n * Excluded projects (playgrounds, personal, dummy tests, …) are resolved here too, from the config's\n * top-level `exclude: [<realProjectPath>, …]` plus the `AGENT_LENS_EXCLUDE` env (comma-separated).\n * They are honored at every stage — collect (not mirrored), ingest (not stored / pruned), corpus.\n */\nimport { readFileSync } from \"node:fs\";\nimport { expandPath, findRepoRoot, resolveConfigFile, resolveDataDir } from \"./paths.js\";\n\nexport interface Source {\n label: string;\n agent: string;\n configDir: string;\n}\n\n/** Read + parse the resolved config file, or null when none exists. */\nfunction readConfig(): { sources?: unknown[]; exclude?: unknown[] } | null {\n const repoRoot = findRepoRoot();\n const dataDir = resolveDataDir(repoRoot);\n const file = resolveConfigFile(repoRoot, dataDir);\n return file ? JSON.parse(readFileSync(file, \"utf8\")) : null;\n}\n\nexport function loadSources(): Source[] {\n // 1. legacy single-source override (used by tests and ad-hoc runs)\n if (process.env.CLAUDE_DIR) {\n return [\n {\n label: process.env.AGENT_LENS_LABEL || \"default\",\n agent: \"claude-code\",\n configDir: expandPath(process.env.CLAUDE_DIR),\n },\n ];\n }\n\n const cfg = readConfig() ?? { sources: [{ label: \"default\", agent: \"claude-code\", configDir: \"$HOME/.claude\" }] };\n\n const seen = new Set<string>();\n const out: Source[] = [];\n for (const raw of cfg.sources || []) {\n const s = raw as Partial<Source> | null;\n if (!s || !s.label || !s.configDir) continue;\n if (seen.has(s.label)) throw new Error(`duplicate source label: ${s.label}`);\n seen.add(s.label);\n out.push({ label: s.label, agent: s.agent || \"claude-code\", configDir: expandPath(s.configDir) });\n }\n if (!out.length) throw new Error(\"no valid sources configured\");\n return out;\n}\n\n/**\n * Resolve the list of projects to EXCLUDE everywhere (collect, ingest, corpus). Union of the config's\n * `exclude` array and the `AGENT_LENS_EXCLUDE` env (comma-separated). Returns expanded real paths\n * (no trailing slash). The legacy CLAUDE_DIR mode still honors the env var.\n */\nexport function loadExcludes(): string[] {\n const fromEnv = (process.env.AGENT_LENS_EXCLUDE || \"\").split(\",\");\n let fromCfg: unknown[] = [];\n if (!process.env.CLAUDE_DIR) {\n const cfg = readConfig();\n if (cfg && Array.isArray(cfg.exclude)) fromCfg = cfg.exclude;\n }\n const seen = new Set<string>();\n const out: string[] = [];\n for (const p of [...fromCfg, ...fromEnv]) {\n const e = expandPath(String(p ?? \"\").trim());\n if (e && !seen.has(e)) {\n seen.add(e);\n out.push(e);\n }\n }\n return out;\n}\n","/**\n * Encode a real project path the way Claude Code names its `projects/<encodedDir>` folder: every\n * `/` and `.` becomes `-` (e.g. `/home/u/git/agent-lens` → `-home-u-git-agent-lens`). Trailing\n * separators are dropped so a path with or without a trailing slash encodes identically.\n *\n * Canonical here in core so collect (Stage 1) and ingest/redact (Stage 2 / corpus) agree exactly.\n */\nexport function encodeProjectPath(p: string): string {\n return p.replace(/[/.]/g, \"-\").replace(/-+$/, \"\");\n}\n","/**\n * Agent Lens — Stage 1 collection, in portable Node (ADR-001, ADR-002, ADR-005).\n *\n * Passively copies agent session traces out of each configured source into the local archive before\n * the agent prunes them. Never deletes, never sends data off the machine, never copies secrets. This\n * replaced the former bash+rsync collector with the same semantics, minus the Linux-only dependencies.\n *\n * Per file (mirror of rsync --append-verify + the .versions pre-pass):\n * - no archive copy yet → copy whole\n * - unchanged since last collect (size + mtime) → skip\n * - source ≥ archive AND archive is a byte-prefix→ append the new tail (pure append)\n * - source shorter than archive (compaction) → snapshot the SOURCE to .versions/<ts>/, keep the\n * longer archive (no shrink → retains history)\n * - same-or-longer but prefix differs (divergence) → snapshot the OLD archive, overwrite with source\n * Stage 2 ingests the mirror AND .versions/, deduping events by uuid → maximal history.\n *\n * Secrets (.credentials.json, *.lock) are never copied; a post-pass also removes any stray\n * .credentials.json from the archive. Excluded projects (loadExcludes) are never mirrored.\n */\nimport {\n appendFileSync,\n closeSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n openSync,\n readSync,\n readdirSync,\n statSync,\n unlinkSync,\n utimesSync,\n writeSync,\n chmodSync,\n type Stats,\n} from \"node:fs\";\nimport { basename, dirname, join, relative, sep } from \"node:path\";\nimport { platform } from \"node:os\";\nimport { loadExcludes, loadSources, type Source } from \"./sources.js\";\nimport { findRepoRoot, resolveDataDir } from \"./paths.js\";\nimport { encodeProjectPath } from \"./projects.js\";\n\nconst POSIX = platform() !== \"win32\";\nconst DIR_MODE = 0o700; // archive is as sensitive as the originals (bash used umask 077)\nconst FILE_MODE = 0o600;\nconst CHUNK = 1 << 16;\n\nexport interface CollectOptions {\n /** Archive base dir. Default: `<dataDir>/archive`. */\n archiveBase?: string;\n /** Sources to collect. Default: loadSources(). */\n sources?: Source[];\n /** Real project paths to exclude. Default: loadExcludes(). */\n excludes?: string[];\n /** Shared run timestamp for .versions/<ts>/. Default: now (local time). */\n runTimestamp?: string;\n /** Progress sink. Default: console.log. */\n log?: (msg: string) => void;\n}\n\nexport interface CollectStats {\n sources: number;\n scanned: number;\n copied: number;\n appended: number;\n diverged: number;\n compacted: number;\n snapshots: number;\n}\n\n/** `YYYYMMDDTHHMMSSmmm` in local time (matches `date +%Y%m%dT%H%M%S%3N`). */\nexport function collectTimestamp(d: Date = new Date()): string {\n const p = (n: number, w = 2) => String(n).padStart(w, \"0\");\n return (\n `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}T` +\n `${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}${p(d.getMilliseconds(), 3)}`\n );\n}\n\nfunction ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true, mode: DIR_MODE });\n}\n\n/** Set archive file perms + mtime to mirror the source (like rsync -a / cp -p). */\nfunction syncMeta(dst: string, src: Stats): void {\n if (POSIX) {\n try {\n chmodSync(dst, FILE_MODE);\n } catch {\n /* best effort */\n }\n }\n try {\n utimesSync(dst, src.atime, src.mtime);\n } catch {\n /* best effort */\n }\n}\n\nfunction copyWhole(src: string, dst: string, srcStat: Stats): void {\n ensureDir(dirname(dst));\n copyFileSync(src, dst);\n syncMeta(dst, srcStat);\n}\n\n/** True if the first `len` bytes of `src` byte-equal the whole of `arc` (`arc` is `len` bytes). */\nfunction prefixEquals(src: string, arc: string, len: number): boolean {\n const sf = openSync(src, \"r\");\n const af = openSync(arc, \"r\");\n try {\n const sb = Buffer.allocUnsafe(CHUNK);\n const ab = Buffer.allocUnsafe(CHUNK);\n let remaining = len;\n while (remaining > 0) {\n const want = Math.min(CHUNK, remaining);\n const sr = readSync(sf, sb, 0, want, null);\n const ar = readSync(af, ab, 0, want, null);\n if (sr !== ar || sr === 0) return false;\n if (Buffer.compare(sb.subarray(0, sr), ab.subarray(0, sr)) !== 0) return false;\n remaining -= sr;\n }\n return remaining === 0;\n } finally {\n closeSync(sf);\n closeSync(af);\n }\n}\n\n/** Append `src[from..to)` to the end of `arc`. */\nfunction appendRange(src: string, arc: string, from: number, to: number): void {\n const sf = openSync(src, \"r\");\n const af = openSync(arc, \"a\");\n try {\n const b = Buffer.allocUnsafe(CHUNK);\n let pos = from;\n while (pos < to) {\n const want = Math.min(CHUNK, to - pos);\n const r = readSync(sf, b, 0, want, pos);\n if (r === 0) break;\n writeSync(af, b, 0, r);\n pos += r;\n }\n } finally {\n closeSync(sf);\n closeSync(af);\n }\n}\n\nfunction filesEqual(a: string, b: string): boolean {\n const sa = statSync(a);\n const sb = statSync(b);\n if (sa.size !== sb.size) return false;\n return prefixEquals(a, b, sa.size);\n}\n\n/** Most recent `.versions/<ts>/<rel>` snapshot for `rel`, or null. Timestamps sort chronologically. */\nfunction latestSnapshot(archive: string, relParts: string[]): string | null {\n const versRoot = join(archive, \".versions\");\n if (!existsSync(versRoot)) return null;\n const stamps = readdirSync(versRoot).sort();\n for (let i = stamps.length - 1; i >= 0; i--) {\n const p = join(versRoot, stamps[i]!, ...relParts);\n if (existsSync(p)) return p;\n }\n return null;\n}\n\nfunction snapshot(absPath: string, relParts: string[], versionsDir: string, stats: CollectStats): void {\n const dst = join(versionsDir, ...relParts);\n ensureDir(dirname(dst));\n copyFileSync(absPath, dst);\n syncMeta(dst, statSync(absPath));\n stats.snapshots++;\n}\n\n/**\n * Recursively collect the relevant transcript + sidecar files under `dir`:\n * - `*.jsonl` — session/subagent transcripts and journals (the core traces)\n * - anything under `workflows/` — the Workflow-tool run result sidecars (wf_<id>.json) + scripts\n * - `*.meta.json` — per-subagent metadata (agentType, description, spawnDepth, toolUseId)\n * - anything under `tool-results/` — full un-truncated tool outputs spilled from the transcript\n * Operational/state files (memory, cache, todos, shell-snapshots, …) are intentionally left behind.\n * Secrets (.credentials.json, *.lock) are filtered by the caller. `inWorkflows`/`inToolResults` track\n * whether recursion is inside one of those directories so every file within is captured.\n */\nfunction walkCollectable(dir: string, out: string[], inWorkflows = false, inToolResults = false): void {\n for (const e of readdirSync(dir, { withFileTypes: true })) {\n const p = join(dir, e.name);\n if (e.isDirectory()) {\n walkCollectable(p, out, inWorkflows || e.name === \"workflows\", inToolResults || e.name === \"tool-results\");\n } else if (\n e.isFile() &&\n (e.name.endsWith(\".jsonl\") || inWorkflows || inToolResults || e.name.endsWith(\".meta.json\"))\n ) {\n out.push(p);\n }\n }\n}\n\n/** The single-file sync decision (the heart of the append-verify port). `appendable` is true only for\n * append-only logs (*.jsonl); for rewritten files (JSON/txt sidecars) a same-prefix growth is a\n * rewrite, not an append, so it's handled as a divergence (snapshot old, overwrite) — never appended. */\nfunction syncFile(src: string, dst: string, relParts: string[], archive: string, versionsDir: string, stats: CollectStats, appendable: boolean): void {\n const srcStat = statSync(src);\n if (!existsSync(dst)) {\n copyWhole(src, dst, srcStat);\n stats.copied++;\n return;\n }\n const arcStat = statSync(dst);\n // Unchanged since last collect (we preserve source mtime on write, like rsync -a) → skip, no read.\n if (srcStat.size === arcStat.size && Math.trunc(srcStat.mtimeMs) === Math.trunc(arcStat.mtimeMs)) return;\n\n const ssize = srcStat.size;\n const asize = arcStat.size;\n if (ssize === asize && prefixEquals(src, dst, asize)) {\n // Identical content, mtime moved → just realign metadata (no append, appendable or not).\n syncMeta(dst, srcStat);\n } else if (appendable && ssize > asize && prefixEquals(src, dst, asize)) {\n // Pure append onto an append-only log. Verify the full prefix before appending; never trust size.\n appendRange(src, dst, asize, ssize);\n stats.appended++;\n syncMeta(dst, srcStat);\n } else if (ssize < asize) {\n // Compaction: keep the longer archive; snapshot the compacted source (skip if an identical\n // snapshot already exists, so a stuck-compacted file doesn't re-snapshot every run).\n const last = latestSnapshot(archive, relParts);\n if (!last || !filesEqual(last, src)) snapshot(src, relParts, versionsDir, stats);\n stats.compacted++;\n } else {\n // Divergence: capture the old archive, then overwrite with the source.\n snapshot(dst, relParts, versionsDir, stats);\n copyWhole(src, dst, srcStat);\n stats.diverged++;\n }\n}\n\nfunction removeCredentials(dir: string): void {\n if (!existsSync(dir)) return;\n for (const e of readdirSync(dir, { withFileTypes: true })) {\n const p = join(dir, e.name);\n if (e.isDirectory()) removeCredentials(p);\n else if (e.name === \".credentials.json\") {\n try {\n unlinkSync(p);\n } catch {\n /* ignore */\n }\n }\n }\n}\n\nfunction collectSource(\n source: Source,\n archiveBase: string,\n excludedDirs: string[],\n runTs: string,\n stats: CollectStats,\n log: (m: string) => void,\n): void {\n const { label, configDir } = source;\n const archive = join(archiveBase, label);\n const versionsDir = join(archive, \".versions\", runTs);\n if (!existsSync(configDir)) {\n log(`agent-lens: [${label}] source not found: ${configDir} (skipping)`);\n return;\n }\n ensureDir(join(archive, \"projects\"));\n ensureDir(join(archive, \"settings\"));\n\n const files: string[] = [];\n const projectsDir = join(configDir, \"projects\");\n if (existsSync(projectsDir)) walkCollectable(projectsDir, files);\n const historySrc = join(configDir, \"history.jsonl\");\n if (existsSync(historySrc)) files.push(historySrc);\n\n const before = { scanned: stats.scanned, snapshots: stats.snapshots };\n for (const src of files) {\n const base = basename(src);\n if (base === \".credentials.json\" || base.endsWith(\".lock\")) continue; // never copy secrets\n const relParts = relative(configDir, src).split(sep);\n const relPosix = relParts.join(\"/\");\n if (excludedDirs.some((enc) => relPosix.startsWith(`projects/${enc}/`))) continue; // excluded project\n stats.scanned++;\n syncFile(src, join(archive, ...relParts), relParts, archive, versionsDir, stats, base.endsWith(\".jsonl\"));\n }\n\n // Settings: latest-wins, snapshot old on change.\n for (const f of [\"settings.json\", \"settings.local.json\"]) {\n const s = join(configDir, f);\n if (!existsSync(s)) continue;\n const a = join(archive, \"settings\", f);\n if (existsSync(a) && !filesEqual(s, a)) snapshot(a, [\"settings\", f], versionsDir, stats);\n ensureDir(dirname(a));\n copyFileSync(s, a);\n syncMeta(a, statSync(s));\n }\n\n removeCredentials(archive);\n\n const scanned = stats.scanned - before.scanned;\n const snapshots = stats.snapshots - before.snapshots;\n const line = `${new Date().toISOString()} run=${runTs} source=${label} scanned=${scanned} snapshots=${snapshots} archive=${archive}`;\n log(`agent-lens: ${line}`);\n try {\n appendFileSync(join(archiveBase, \".collect.log\"), line + \"\\n\");\n } catch {\n /* log is best-effort */\n }\n}\n\n/** Collect every configured source into the archive. Returns aggregate counters. */\nexport function collectAll(opts: CollectOptions = {}): CollectStats {\n const log = opts.log ?? ((m: string) => console.log(m));\n const archiveBase = opts.archiveBase ?? join(resolveDataDir(findRepoRoot()), \"archive\");\n const sources = opts.sources ?? loadSources();\n const excludedDirs = (opts.excludes ?? loadExcludes()).map(encodeProjectPath);\n const runTs = opts.runTimestamp ?? collectTimestamp();\n\n ensureDir(archiveBase);\n const stats: CollectStats = { sources: 0, scanned: 0, copied: 0, appended: 0, diverged: 0, compacted: 0, snapshots: 0 };\n if (!sources.length) throw new Error(\"no sources configured\");\n for (const source of sources) {\n stats.sources++;\n collectSource(source, archiveBase, excludedDirs, runTs, stats, log);\n }\n return stats;\n}\n","/**\n * Cross-platform single-instance lock. collect+ingest take this so a too-frequent schedule (or an\n * overlapping watch trigger) can never run two collectors against the same archive or two writers\n * against the same SQLite DB. systemd already serializes its oneshot; this makes launchd/schtasks/\n * watch safe too. Not a hard mutex (a local, single-user tool doesn't need one) — a stale-aware\n * PID file: a run whose holder is dead or older than `staleMs` is reclaimed.\n */\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { hostname } from \"node:os\";\n\nexport interface Lock {\n /** Remove the lock file. Idempotent; also runs automatically on process exit. */\n release(): void;\n}\n\nfunction isAlive(pid: number): boolean {\n if (!pid) return false;\n try {\n process.kill(pid, 0); // signal 0 = existence check, doesn't actually signal\n return true;\n } catch (e) {\n // ESRCH → no such process (dead); EPERM → exists but not ours (alive).\n return (e as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Try to acquire `lockPath`. Returns a Lock on success, or null if another live, non-stale process\n * currently holds it (caller should log \"run in progress\" and exit 0).\n */\nexport function acquireLock(lockPath: string, staleMs = 6 * 60 * 60 * 1000): Lock | null {\n mkdirSync(dirname(lockPath), { recursive: true });\n if (existsSync(lockPath)) {\n try {\n const held = JSON.parse(readFileSync(lockPath, \"utf8\")) as { pid?: number; ts?: number };\n const fresh = typeof held.ts === \"number\" && Date.now() - held.ts < staleMs;\n if (fresh && isAlive(held.pid ?? 0)) return null; // held by a live, recent process\n // else: dead PID or too old → reclaim below\n } catch {\n // corrupt lock file → reclaim\n }\n }\n writeFileSync(lockPath, JSON.stringify({ pid: process.pid, ts: Date.now(), host: hostname() }), { mode: 0o600 });\n let released = false;\n const release = () => {\n if (released) return;\n released = true;\n try {\n // Only remove if it's still ours (avoid deleting a lock a reclaimer took over).\n const cur = JSON.parse(readFileSync(lockPath, \"utf8\")) as { pid?: number };\n if (cur.pid === process.pid) unlinkSync(lockPath);\n } catch {\n /* already gone / unreadable */\n }\n };\n process.once(\"exit\", release);\n return { release };\n}\n","/**\n * Cross-platform OS-service integration for `agent-lens service`. Two targets:\n * - collector → a *periodic* `collect --then-ingest` job (Node can time work via `watch`, but\n * can't survive a reboot alone, so we register it with the OS scheduler)\n * - server → the *long-running* web UI + read-only API daemon (`serve`)\n *\n * Per platform:\n * - Linux → systemd user units (collector: oneshot service + timer; server: Type=simple + Restart) + linger\n * - macOS → launchd LaunchAgents (collector: StartCalendarInterval; server: KeepAlive)\n * - Windows → Task Scheduler (collector: one DAILY task per hour; server: ONLOGON task)\n *\n * The absolute `node` binary (process.execPath) and CLI entry path are baked into every unit/plist/\n * task, because schedulers don't inherit the interactive shell's PATH (the mise problem, generalized).\n * The unit/plist/command text is generated by the pure functions below (unit-tested); runService\n * wraps them with the OS commands.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { findRepoRoot, resolveDataDir } from \"./paths.js\";\n\nexport const DEFAULT_HOURS = [9, 13, 17, 21];\n\nconst SYSTEMD_COLLECTOR_TIMER = \"agent-lens-collect.timer\";\nconst SYSTEMD_COLLECTOR_SERVICE = \"agent-lens-collect.service\";\nconst SYSTEMD_SERVER_SERVICE = \"agent-lens-server.service\";\nconst LAUNCHD_COLLECTOR_LABEL = \"org.agent-lens.collect\";\nconst LAUNCHD_SERVER_LABEL = \"org.agent-lens.server\";\nconst WINDOWS_COLLECTOR_PREFIX = \"AgentLens\\\\Collect\";\nconst WINDOWS_SERVER_TASK = \"AgentLens\\\\Server\";\n\nexport type ServiceTarget = \"collector\" | \"server\";\nexport type ServiceAction = \"install\" | \"uninstall\" | \"status\";\n\nexport interface ServiceParams {\n /** Absolute path to the CLI entry (the bundled agent-lens.js). */\n cliEntry: string;\n /** Which OS integrations to act on. */\n targets: ServiceTarget[];\n /** Hours (0–23) the collector runs at. Default: DEFAULT_HOURS. */\n hours?: number[];\n /** Absolute node binary. Default: process.execPath. */\n node?: string;\n}\n\n/** Parse the CLI target arg: undefined/\"all\" → both; \"collector\"/\"server\" → that one; else throw. */\nexport function parseTargets(spec: string | undefined): ServiceTarget[] {\n if (!spec || spec === \"all\") return [\"collector\", \"server\"];\n if (spec === \"collector\" || spec === \"server\") return [spec];\n throw new Error(`invalid target '${spec}' (expected: collector | server | all)`);\n}\n\nexport function parseHours(spec: string | undefined): number[] {\n if (!spec) return DEFAULT_HOURS;\n const hours = spec\n .split(\",\")\n .map((s) => Number.parseInt(s.trim(), 10))\n .filter((n) => Number.isInteger(n) && n >= 0 && n <= 23);\n if (!hours.length) throw new Error(`invalid hours '${spec}' (expected comma-separated 0-23, e.g. 9,13,17,21)`);\n return [...new Set(hours)].sort((a, b) => a - b);\n}\n\n/** Read AGENT_LENS_PORT / AGENT_LENS_HOST from the current env, if set (baked into the server unit). */\nfunction serverEnv(): Record<string, string> {\n const env: Record<string, string> = {};\n if (process.env.AGENT_LENS_PORT) env.AGENT_LENS_PORT = process.env.AGENT_LENS_PORT;\n if (process.env.AGENT_LENS_HOST) env.AGENT_LENS_HOST = process.env.AGENT_LENS_HOST;\n return env;\n}\n\n// ---- pure generators (unit-tested) --------------------------------------------------------------\n\n/** systemd `OnCalendar` hour list, e.g. [9,13] → \"09,13:00\". */\nexport function onCalendarHours(hours: number[]): string {\n return hours.map((h) => String(h).padStart(2, \"0\")).join(\",\") + \":00\";\n}\n\n/** Collector: oneshot unit that runs one `collect --then-ingest` pass (driven by the timer). */\nexport function systemdCollectorService(node: string, cli: string): string {\n return `[Unit]\nDescription=Agent Lens — collect + ingest Claude Code session traces\n\n[Service]\nType=oneshot\n# Absolute node + CLI paths: systemd user services don't inherit the interactive PATH.\nExecStart=${node} ${cli} collect --then-ingest\nNice=10\nIOSchedulingClass=idle\n`;\n}\n\nexport function systemdCollectorTimer(hours: number[]): string {\n return `[Unit]\nDescription=Agent Lens — periodic Claude Code trace collection\n\n[Timer]\n# A few times a day. Persistent catches runs missed while the machine was off.\nOnCalendar=*-*-* ${onCalendarHours(hours)}\nPersistent=true\nRandomizedDelaySec=300\n\n[Install]\nWantedBy=timers.target\n`;\n}\n\n/** Server: long-running daemon that runs `serve`, restarted on failure. */\nexport function systemdServerService(node: string, cli: string, env: Record<string, string> = {}): string {\n const envLines = Object.entries(env)\n .map(([k, v]) => `Environment=${k}=${v}\\n`)\n .join(\"\");\n return `[Unit]\nDescription=Agent Lens — local web UI + read-only API server\nAfter=network.target\n\n[Service]\nType=simple\n# Absolute node + CLI paths: systemd user services don't inherit the interactive PATH.\nExecStart=${node} ${cli} serve\n${envLines}# Long-running: restart if it crashes.\nRestart=on-failure\nRestartSec=5\nNice=10\n\n[Install]\nWantedBy=default.target\n`;\n}\n\nexport function launchdCollectorPlist(node: string, cli: string, hours: number[], logPath: string): string {\n const cal = hours\n .map((h) => ` <dict><key>Hour</key><integer>${h}</integer><key>Minute</key><integer>0</integer></dict>`)\n .join(\"\\n\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LAUNCHD_COLLECTOR_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${node}</string>\n <string>${cli}</string>\n <string>collect</string>\n <string>--then-ingest</string>\n </array>\n <key>StartCalendarInterval</key>\n <array>\n${cal}\n </array>\n <key>StandardOutPath</key><string>${logPath}</string>\n <key>StandardErrorPath</key><string>${logPath}</string>\n</dict>\n</plist>\n`;\n}\n\n/** Server: launchd agent kept alive (relaunched if it exits) and started at load. */\nexport function launchdServerPlist(node: string, cli: string, logPath: string, env: Record<string, string> = {}): string {\n const envDict = Object.entries(env).length\n ? ` <key>EnvironmentVariables</key>\\n <dict>\\n${Object.entries(env)\n .map(([k, v]) => ` <key>${k}</key><string>${v}</string>`)\n .join(\"\\n\")}\\n </dict>\\n`\n : \"\";\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LAUNCHD_SERVER_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${node}</string>\n <string>${cli}</string>\n <string>serve</string>\n </array>\n${envDict} <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><true/>\n <key>StandardOutPath</key><string>${logPath}</string>\n <key>StandardErrorPath</key><string>${logPath}</string>\n</dict>\n</plist>\n`;\n}\n\n// ---- OS helpers ---------------------------------------------------------------------------------\n\nfunction sh(cmd: string, args: string[], quiet = false): boolean {\n const r = spawnSync(cmd, args, { stdio: quiet ? \"ignore\" : \"inherit\", encoding: \"utf8\" });\n return r.status === 0;\n}\n\nfunction have(cmd: string): boolean {\n const r = spawnSync(cmd, [\"--version\"], { stdio: \"ignore\" });\n return r.status === 0 || r.error === undefined;\n}\n\nfunction systemdUnitDir(): string {\n return join(process.env.XDG_CONFIG_HOME || join(homedir(), \".config\"), \"systemd\", \"user\");\n}\n\n// ---- Linux (systemd user) -----------------------------------------------------------------------\n\nfunction linuxInstall(targets: ServiceTarget[], node: string, cli: string, hours: number[]): void {\n if (!have(\"systemctl\")) {\n console.error(\"agent-lens service: systemctl not found — is this a systemd system?\");\n process.exitCode = 1;\n return;\n }\n const dir = systemdUnitDir();\n mkdirSync(dir, { recursive: true });\n if (targets.includes(\"collector\")) {\n writeFileSync(join(dir, SYSTEMD_COLLECTOR_SERVICE), systemdCollectorService(node, cli));\n writeFileSync(join(dir, SYSTEMD_COLLECTOR_TIMER), systemdCollectorTimer(hours));\n }\n if (targets.includes(\"server\")) {\n writeFileSync(join(dir, SYSTEMD_SERVER_SERVICE), systemdServerService(node, cli, serverEnv()));\n }\n sh(\"systemctl\", [\"--user\", \"daemon-reload\"]);\n if (targets.includes(\"collector\")) {\n sh(\"systemctl\", [\"--user\", \"enable\", \"--now\", SYSTEMD_COLLECTOR_TIMER]);\n console.log(`agent-lens service: installed collector timer (${onCalendarHours(hours)})`);\n sh(\"systemctl\", [\"--user\", \"list-timers\", SYSTEMD_COLLECTOR_TIMER, \"--no-pager\"]);\n }\n if (targets.includes(\"server\")) {\n sh(\"systemctl\", [\"--user\", \"enable\", \"--now\", SYSTEMD_SERVER_SERVICE]);\n console.log(\"agent-lens service: installed server daemon\");\n sh(\"systemctl\", [\"--user\", \"status\", SYSTEMD_SERVER_SERVICE, \"--no-pager\"]);\n }\n // Run while logged out (best-effort) — needed for both the timer and the daemon.\n if (have(\"loginctl\") && !sh(\"loginctl\", [\"enable-linger\", process.env.USER || \"\"], true)) {\n console.log(`agent-lens: could not enable linger automatically; run: sudo loginctl enable-linger ${process.env.USER}`);\n }\n}\n\nfunction linuxUninstall(targets: ServiceTarget[]): void {\n if (!have(\"systemctl\")) return;\n const dir = systemdUnitDir();\n if (targets.includes(\"collector\")) {\n sh(\"systemctl\", [\"--user\", \"disable\", \"--now\", SYSTEMD_COLLECTOR_TIMER], true);\n for (const u of [SYSTEMD_COLLECTOR_TIMER, SYSTEMD_COLLECTOR_SERVICE]) rmSync(join(dir, u), { force: true });\n sh(\"systemctl\", [\"--user\", \"reset-failed\", SYSTEMD_COLLECTOR_SERVICE], true);\n }\n if (targets.includes(\"server\")) {\n sh(\"systemctl\", [\"--user\", \"disable\", \"--now\", SYSTEMD_SERVER_SERVICE], true);\n rmSync(join(dir, SYSTEMD_SERVER_SERVICE), { force: true });\n sh(\"systemctl\", [\"--user\", \"reset-failed\", SYSTEMD_SERVER_SERVICE], true);\n }\n sh(\"systemctl\", [\"--user\", \"daemon-reload\"]);\n console.log(\"agent-lens service: uninstalled (archive untouched)\");\n}\n\nfunction linuxStatus(targets: ServiceTarget[]): void {\n if (!have(\"systemctl\")) return;\n if (targets.includes(\"collector\")) sh(\"systemctl\", [\"--user\", \"list-timers\", SYSTEMD_COLLECTOR_TIMER, \"--no-pager\"]);\n if (targets.includes(\"server\")) sh(\"systemctl\", [\"--user\", \"status\", SYSTEMD_SERVER_SERVICE, \"--no-pager\"]);\n}\n\n// ---- macOS (launchd) ----------------------------------------------------------------------------\n\nfunction launchdPlistPath(label: string): string {\n return join(homedir(), \"Library\", \"LaunchAgents\", `${label}.plist`);\n}\n\nfunction macBootstrap(plist: string): void {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : 0;\n // Prefer the modern bootstrap API; fall back to legacy load -w.\n if (!sh(\"launchctl\", [\"bootstrap\", `gui/${uid}`, plist], true)) sh(\"launchctl\", [\"load\", \"-w\", plist]);\n}\n\nfunction macBootout(label: string, plist: string): void {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : 0;\n if (!sh(\"launchctl\", [\"bootout\", `gui/${uid}/${label}`], true)) sh(\"launchctl\", [\"unload\", \"-w\", plist], true);\n rmSync(plist, { force: true });\n}\n\nfunction macInstall(targets: ServiceTarget[], node: string, cli: string, hours: number[]): void {\n const agents = join(homedir(), \"Library\", \"LaunchAgents\");\n mkdirSync(agents, { recursive: true });\n const dataDir = resolveDataDir(findRepoRoot());\n if (targets.includes(\"collector\")) {\n const plist = launchdPlistPath(LAUNCHD_COLLECTOR_LABEL);\n writeFileSync(plist, launchdCollectorPlist(node, cli, hours, join(dataDir, \"schedule.log\")));\n macBootstrap(plist);\n console.log(`agent-lens service: installed collector agent ${LAUNCHD_COLLECTOR_LABEL} (hours ${hours.join(\",\")})`);\n }\n if (targets.includes(\"server\")) {\n const plist = launchdPlistPath(LAUNCHD_SERVER_LABEL);\n writeFileSync(plist, launchdServerPlist(node, cli, join(dataDir, \"server.log\"), serverEnv()));\n macBootstrap(plist);\n console.log(`agent-lens service: installed server agent ${LAUNCHD_SERVER_LABEL}`);\n }\n}\n\nfunction macUninstall(targets: ServiceTarget[]): void {\n if (targets.includes(\"collector\")) macBootout(LAUNCHD_COLLECTOR_LABEL, launchdPlistPath(LAUNCHD_COLLECTOR_LABEL));\n if (targets.includes(\"server\")) macBootout(LAUNCHD_SERVER_LABEL, launchdPlistPath(LAUNCHD_SERVER_LABEL));\n console.log(\"agent-lens service: uninstalled (archive untouched)\");\n}\n\nfunction macStatus(targets: ServiceTarget[]): void {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : 0;\n if (targets.includes(\"collector\")) sh(\"launchctl\", [\"print\", `gui/${uid}/${LAUNCHD_COLLECTOR_LABEL}`]);\n if (targets.includes(\"server\")) sh(\"launchctl\", [\"print\", `gui/${uid}/${LAUNCHD_SERVER_LABEL}`]);\n}\n\n// ---- Windows (schtasks) -------------------------------------------------------------------------\n\nfunction winCollectorTaskName(hour: number): string {\n return `${WINDOWS_COLLECTOR_PREFIX}_${String(hour).padStart(2, \"0\")}`;\n}\n\nfunction windowsInstall(targets: ServiceTarget[], node: string, cli: string, hours: number[]): void {\n if (targets.includes(\"collector\")) {\n const tr = `\"${node}\" \"${cli}\" collect --then-ingest`;\n for (const h of hours) {\n sh(\"schtasks\", [\"/Create\", \"/F\", \"/TN\", winCollectorTaskName(h), \"/TR\", tr, \"/SC\", \"DAILY\", \"/ST\", `${String(h).padStart(2, \"0\")}:00`]);\n }\n console.log(`agent-lens service: installed ${hours.length} collector task(s) (hours ${hours.join(\",\")})`);\n }\n if (targets.includes(\"server\")) {\n // ONLOGON runs in the user session, so the per-user data dir (%LOCALAPPDATA%) resolves.\n // Note: schtasks has no crash-restart (systemd/launchd do); a crashed server returns on next logon.\n const tr = `\"${node}\" \"${cli}\" serve`;\n sh(\"schtasks\", [\"/Create\", \"/F\", \"/TN\", WINDOWS_SERVER_TASK, \"/TR\", tr, \"/SC\", \"ONLOGON\", \"/RL\", \"LIMITED\"]);\n console.log(\"agent-lens service: installed server task (starts at logon)\");\n }\n}\n\nfunction windowsUninstall(targets: ServiceTarget[]): void {\n if (targets.includes(\"collector\")) {\n // Delete every task under our prefix (we don't need to know the original hours).\n for (let h = 0; h < 24; h++) sh(\"schtasks\", [\"/Delete\", \"/F\", \"/TN\", winCollectorTaskName(h)], true);\n }\n if (targets.includes(\"server\")) sh(\"schtasks\", [\"/Delete\", \"/F\", \"/TN\", WINDOWS_SERVER_TASK], true);\n console.log(\"agent-lens service: uninstalled (archive untouched)\");\n}\n\nfunction windowsStatus(targets: ServiceTarget[]): void {\n if (targets.includes(\"collector\")) {\n sh(\"schtasks\", [\"/Query\", \"/FO\", \"LIST\", \"/TN\", WINDOWS_COLLECTOR_PREFIX], true) ||\n console.log(\"agent-lens service: no collector tasks found under \" + WINDOWS_COLLECTOR_PREFIX);\n }\n if (targets.includes(\"server\")) {\n sh(\"schtasks\", [\"/Query\", \"/FO\", \"LIST\", \"/TN\", WINDOWS_SERVER_TASK], true) ||\n console.log(\"agent-lens service: no server task found (\" + WINDOWS_SERVER_TASK + \")\");\n }\n}\n\n// ---- dispatch -----------------------------------------------------------------------------------\n\nexport function runService(action: string, params: ServiceParams): void {\n const node = params.node ?? process.execPath;\n const cli = params.cliEntry;\n const hours = params.hours ?? DEFAULT_HOURS;\n const targets = params.targets;\n const plat = platform();\n const impl =\n plat === \"linux\"\n ? {\n install: () => linuxInstall(targets, node, cli, hours),\n uninstall: () => linuxUninstall(targets),\n status: () => linuxStatus(targets),\n }\n : plat === \"darwin\"\n ? {\n install: () => macInstall(targets, node, cli, hours),\n uninstall: () => macUninstall(targets),\n status: () => macStatus(targets),\n }\n : plat === \"win32\"\n ? {\n install: () => windowsInstall(targets, node, cli, hours),\n uninstall: () => windowsUninstall(targets),\n status: () => windowsStatus(targets),\n }\n : null;\n if (!impl) {\n console.error(`agent-lens service: unsupported platform '${plat}'`);\n process.exitCode = 1;\n return;\n }\n if (action === \"install\") impl.install();\n else if (action === \"uninstall\") impl.uninstall();\n else if (action === \"status\") impl.status();\n else {\n console.error(`agent-lens service: unknown action '${action}' (use: install | uninstall | status)`);\n process.exitCode = 1;\n }\n}\n","/**\n * Agent Lens — Stage 2 ingest, as importable functions (ADR-001, ADR-003).\n *\n * `runIngest` and `runMetrics` are the library entrypoints so the unified `agent-lens` CLI can\n * bundle and call them in-process; the thin bins in index.ts / metrics-cli.ts just forward argv.\n * The DB-writing engine lives in pipeline.ts (unit-tested in-process).\n */\nimport { readFileSync, statSync, mkdirSync, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport {\n costForUsage,\n findRepoRoot,\n loadExcludes,\n loadSources,\n resolveDataDir,\n SCHEMA_VERSION,\n type SourceAdapter,\n} from \"@agent-lens/core\";\nimport { openDb, openRaw, readSchemaVersion, resetSchema } from \"./db.js\";\nimport { ClaudeCodeAdapter } from \"./adapters/claude-code.js\";\nimport { classify, CLASSIFIER_VERSION } from \"./classify.js\";\nimport { detect, DETECTOR_VERSION } from \"./detect.js\";\nimport { ingestFile, newStats, prepareStatements, pruneExcluded, rebuildDerived } from \"./pipeline.js\";\nimport { ingestWorkflowResults, newWorkflowStats } from \"./workflows.js\";\nimport { ingestSubagentMeta, newMetaStats } from \"./meta.js\";\nimport { ingestToolResults, newToolResultStats } from \"./toolResults.js\";\nimport { parseExcludes, isExcludedArchivePath } from \"./redact.js\";\nimport { sha256, sha256File, streamLines, STREAM_THRESHOLD } from \"./fileread.js\";\n\nfunction parseIngestArgs(argv: string[]) {\n const a = { full: false, db: \"\", archive: \"\" };\n for (let i = 0; i < argv.length; i++) {\n if (argv[i] === \"--full\") a.full = true;\n else if (argv[i] === \"--db\") a.db = argv[++i] ?? \"\";\n else if (argv[i] === \"--archive\") a.archive = argv[++i] ?? \"\";\n }\n return a;\n}\n\n/** Stage 2: (re)build the derived SQLite store from the local archive. Idempotent. */\nexport function runIngest(argv: string[] = process.argv.slice(2)): void {\n const args = parseIngestArgs(argv);\n const dataDir = resolveDataDir(findRepoRoot());\n const archiveRoot = args.archive || process.env.AGENT_LENS_ARCHIVE || join(dataDir, \"archive\");\n const dbPath = args.db || process.env.AGENT_LENS_DB || join(dataDir, \"agent-lens.db\");\n\n if (!existsSync(archiveRoot)) {\n console.error(`agent-lens-ingest: archive not found: ${archiveRoot} (run 'agent-lens collect' first)`);\n process.exit(1);\n }\n mkdirSync(dirname(dbPath), { recursive: true });\n\n // --full: open WITHOUT applying schema (the on-disk schema may be a stale version), then\n // drop+recreate from the archive (source of truth). This is also the migration path — a\n // SCHEMA_VERSION bump's new columns take effect here without a separate migration step.\n const db = args.full ? openRaw(dbPath) : openDb(dbPath);\n if (args.full) {\n resetSchema(db);\n } else {\n // Schema guard: an incremental ingest only runs CREATE IF NOT EXISTS — it adds new tables but\n // cannot migrate altered columns on existing ones, and never advances the stamp (applySchema is\n // stamp-if-absent). So a DB stamped by an older build is stale: refuse rather than silently ingest\n // into a half-migrated schema. `ingest --full` (drop+recreate) is the migration path.\n const dbVer = readSchemaVersion(db);\n if (dbVer != null && dbVer !== SCHEMA_VERSION) {\n console.error(\n `agent-lens-ingest: schema mismatch — DB is v${dbVer}, this build expects v${SCHEMA_VERSION}. ` +\n `Run 'agent-lens ingest --full' to rebuild.`,\n );\n db.close();\n process.exit(1);\n }\n }\n\n // Adapter registry keyed by agent type; configured sources resolved by the shared resolver.\n const adapterList: SourceAdapter[] = [new ClaudeCodeAdapter()];\n const adapterById = new Map(adapterList.map((a) => [a.agentId, a]));\n const sources = loadSources();\n const now = new Date().toISOString();\n\n // Excluded projects (config `exclude` + AGENT_LENS_EXCLUDE): drop any already-ingested ones now\n // (incremental; --full already reset the DB), then filter them out of discovery below.\n const excludePaths = loadExcludes();\n const excludedDirs = parseExcludes(excludePaths.join(\",\"));\n const pruned = pruneExcluded(db, excludePaths);\n\n const stmts = prepareStatements(db);\n const stats = newStats();\n const wfStats = newWorkflowStats();\n const metaStats = newMetaStats();\n const trStats = newToolResultStats();\n // Sessions touched this run; drives the incremental derived rebuild (ADR-010, impacts 2/3).\n const dirty = new Set<string>();\n\n for (const source of sources) {\n const adapter = adapterById.get(source.agent);\n if (!adapter) {\n console.warn(`agent-lens-ingest: no adapter for agent '${source.agent}' (source '${source.label}') — skipping`);\n continue;\n }\n stmts.insAgent.run(adapter.agentId, adapter.agentName);\n stmts.insSource.run({ id: source.label, label: source.label, agent_id: adapter.agentId, config_dir: source.configDir });\n\n let files = adapter.discover(join(archiveRoot, source.label), source.label);\n // Drop excluded projects (matches /projects/<encodedDir>/ so nested subagent files go too).\n if (excludedDirs.length) files = files.filter((f) => !isExcludedArchivePath(f.path, excludedDirs));\n // Mirror before versions so the mirror copy wins canonical fields (ON CONFLICT DO NOTHING).\n files.sort((a, b) => Number(a.isVersion) - Number(b.isVersion));\n\n for (const file of files) {\n stats.files++;\n const st = statSync(file.path);\n const mtimeMs = Math.trunc(st.mtimeMs);\n const prev = args.full\n ? undefined\n : (stmts.getState.get(file.path) as\n | { size: number; mtime_ms: number; sha256: string; events_ingested: number }\n | undefined);\n\n // (a) Stat short-circuit: an unchanged size+mtime means unchanged content — skip without ever\n // reading or hashing the file. Restores true incrementality across the mirror + every\n // .versions snapshot (ADR-010, impact 1).\n if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {\n stats.skipped++;\n continue;\n }\n\n // Size/mtime moved: read + hash to decide. Whole-file for the common case; stream large files.\n const small = st.size <= STREAM_THRESHOLD;\n const buf = small ? readFileSync(file.path) : null;\n const hash = small ? sha256(buf!) : sha256File(file.path);\n\n // Content unchanged though mtime moved (e.g. rsync --append-verify re-stat). Skip ingest, but\n // refresh size/mtime so the next run short-circuits on stat alone.\n if (prev && prev.sha256 === hash) {\n stmts.setState.run({\n file_path: file.path,\n size: st.size,\n mtime_ms: mtimeMs,\n sha256: hash,\n events_ingested: prev.events_ingested,\n ingested_at: now,\n });\n stats.skipped++;\n continue;\n }\n\n const lines = small ? buf!.toString(\"utf8\").split(\"\\n\") : streamLines(file.path);\n ingestFile(db, stmts, adapter, file, lines, { size: st.size, mtimeMs, hash }, now, stats);\n dirty.add(file.sessionId);\n }\n\n // Workflow-tool result sidecars (wf_<id>.json) — the authoritative record of how each run\n // finished; not *.jsonl, so the transcript walk above never sees them. Own table, own skip-state.\n ingestWorkflowResults(db, join(archiveRoot, source.label), source.label, excludedDirs, now, wfStats, args.full);\n\n // Per-subagent metadata (subagents/agent-<id>.meta.json) — the authoritative agentType/description/\n // spawnDepth; joined onto sessions at read time. Own table (session_meta), own skip-state.\n ingestSubagentMeta(db, join(archiveRoot, source.label), source.label, excludedDirs, now, metaStats, args.full);\n\n // Spilled full tool outputs (tool-results/<name>.txt) — the un-truncated result the transcript only\n // summarized; joined onto tool_calls at read time via the summary's marker. Own table, own skip-state.\n ingestToolResults(db, join(archiveRoot, source.label), excludedDirs, now, trStats, args.full);\n }\n\n // Incremental derived rebuild over only the touched sessions (+ their linkage neighborhood); --full\n // rebuilds everything. classify reuses the expanded set rebuildDerived returns.\n const expanded = rebuildDerived(db, args.full ? null : dirty);\n\n // Heuristic classification (ADR-004) over the now-stable derived tables. Deterministic +\n // re-runnable; also exposed standalone as `agent-lens metrics` (see runMetrics).\n const classified = classify(db, args.full ? null : expanded);\n\n // Security findings (ADR-017) over the same derived tool_calls. Deterministic + re-runnable; the\n // detector reuses the expanded dirty set and delete-then-inserts the touched sessions' findings.\n const detected = detect(db, args.full ? null : expanded);\n\n // Report.\n const count = (sql: string) => (db.prepare(sql).get() as any).n as number;\n const sessions = count(\"SELECT COUNT(*) n FROM sessions\");\n const turns = count(\"SELECT COUNT(*) n FROM turns\");\n const events = count(\"SELECT COUNT(*) n FROM events\");\n const tools = count(\"SELECT COUNT(*) n FROM tool_calls\");\n const usageRows = db\n .prepare(\"SELECT model, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens FROM token_usage\")\n .all() as any[];\n let cost = 0;\n let totalTokens = 0;\n for (const u of usageRows) {\n cost += costForUsage(u.model, u);\n totalTokens += u.input_tokens + u.output_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens;\n }\n\n const wfRuns = count(\"SELECT COUNT(*) n FROM workflow_results\");\n const metaRows = count(\"SELECT COUNT(*) n FROM session_meta\");\n const trRows = count(\"SELECT COUNT(*) n FROM tool_results\");\n const sevCounts = db\n .prepare(\"SELECT severity, COUNT(*) n FROM findings GROUP BY severity\")\n .all() as Array<{ severity: string; n: number }>;\n const sevMap = Object.fromEntries(sevCounts.map((r) => [r.severity, r.n]));\n const sevLine = [\"critical\", \"high\", \"medium\", \"low\", \"info\"].map((s) => `${s}=${sevMap[s] ?? 0}`).join(\" \");\n\n db.close();\n console.log(\n `agent-lens-ingest: files=${stats.files} skipped=${stats.skipped} new_events=${stats.newEvents} malformed=${stats.malformed}${pruned ? ` excluded_pruned=${pruned}` : \"\"}\\n` +\n ` sessions=${sessions} turns=${turns} events=${events} tool_calls=${tools} classified=${classified.count}\\n` +\n ` workflow_results=${wfRuns} (sidecar upserted=${wfStats.upserted} skipped=${wfStats.skipped} malformed=${wfStats.malformed})\\n` +\n ` session_meta=${metaRows} (upserted=${metaStats.upserted} skipped=${metaStats.skipped} malformed=${metaStats.malformed})\\n` +\n ` tool_results=${trRows} (upserted=${trStats.upserted} skipped=${trStats.skipped} malformed=${trStats.malformed})\\n` +\n ` findings=${detected.count} (${sevLine})\\n` +\n ` tokens=${totalTokens.toLocaleString()} est_cost=$${cost.toFixed(2)} db=${dbPath}`,\n );\n}\n\n/** Standalone reclassification over an already-ingested DB (no archive re-read). */\nexport function runMetrics(argv: string[] = process.argv.slice(2)): void {\n let dbArg = \"\";\n for (let i = 0; i < argv.length; i++) if (argv[i] === \"--db\") dbArg = argv[++i] ?? \"\";\n const dataDir = resolveDataDir(findRepoRoot());\n const dbPath = dbArg || process.env.AGENT_LENS_DB || join(dataDir, \"agent-lens.db\");\n\n if (!existsSync(dbPath)) {\n console.error(`agent-lens-metrics: db not found: ${dbPath} (run ingest first)`);\n process.exit(1);\n }\n\n const db = openDb(dbPath);\n const r = classify(db);\n const d = detect(db);\n db.close();\n console.log(\n `agent-lens-metrics: classified=${r.count} classifier_version=${CLASSIFIER_VERSION} ` +\n `findings=${d.count} detector_version=${DETECTOR_VERSION} db=${dbPath}`,\n );\n}\n","import Database from \"better-sqlite3\";\nimport { SCHEMA_SQL, SCHEMA_VERSION } from \"@agent-lens/core\";\n\nexport type DB = Database.Database;\n\n/** Open (creating if needed) the DB and set pragmas, WITHOUT applying the schema. */\nexport function openRaw(file: string): DB {\n const db = new Database(file);\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n return db;\n}\n\n/** The schema version stamped in an existing DB, or null if unstamped/brand-new. */\nexport function readSchemaVersion(db: DB): number | null {\n const row = db\n .prepare(\"SELECT value FROM meta WHERE key = 'schema_version'\")\n .get() as { value: string } | undefined;\n if (!row) return null;\n const n = Number(row.value);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * Apply the current schema (idempotent CREATE IF NOT EXISTS) and stamp the version ONLY when absent.\n * Stamping if-absent (not overwrite) is deliberate: an incremental `ingest` runs CREATE IF NOT EXISTS,\n * which adds new *tables* but cannot migrate altered columns on existing ones — so overwriting the stamp\n * would let a stale DB claim the current version and lie. A stale DB keeps its old stamp; the caller\n * (`runIngest`) guards on it. `resetSchema` drops `meta`, so its applySchema re-stamps the current\n * version from scratch — the only path that advances the stamp.\n */\nfunction applySchema(db: DB): void {\n db.exec(SCHEMA_SQL);\n db.prepare(\n \"INSERT INTO meta(key, value) VALUES ('schema_version', ?) ON CONFLICT(key) DO NOTHING\",\n ).run(String(SCHEMA_VERSION));\n}\n\n/**\n * Open (creating if needed) the Agent Lens database and ensure the schema is applied.\n * NOTE: on a stale on-disk schema this only adds missing objects (CREATE IF NOT EXISTS) — it does\n * not migrate changed/added columns. A SCHEMA_VERSION bump is applied via `ingest --full`\n * (openRaw + resetSchema), the canonical rebuild path.\n */\nexport function openDb(file: string): DB {\n const db = openRaw(file);\n applySchema(db);\n return db;\n}\n\n/**\n * Drop and recreate every table from the current SCHEMA_SQL. Used by `ingest --full`: because the\n * archive is the source of truth and the DB is a derived projection, a full rebuild is also the\n * migration path — dropping the tables (vs. DELETE FROM) lets a SCHEMA_VERSION bump's new columns\n * take effect on an existing DB without a separate migration mechanism (ADR-001/009).\n */\nexport function resetSchema(db: DB): void {\n // Disable FK enforcement during teardown: the v3 schema has cross/self references\n // (sessions.parent_turn_id → turns, parent_session_id → sessions) that otherwise make DROP order\n // matter and trip \"FOREIGN KEY constraint failed\". applySchema re-enables FKs (PRAGMA in SCHEMA_SQL).\n db.pragma(\"foreign_keys = OFF\");\n db.exec(`\n DROP TRIGGER IF EXISTS events_au;\n DROP TRIGGER IF EXISTS events_ad;\n DROP TRIGGER IF EXISTS events_ai;\n DROP TABLE IF EXISTS events_fts;\n DROP TABLE IF EXISTS findings;\n DROP TABLE IF EXISTS classifications;\n DROP TABLE IF EXISTS token_usage;\n DROP TABLE IF EXISTS tool_calls;\n DROP TABLE IF EXISTS skills;\n DROP TABLE IF EXISTS events;\n DROP TABLE IF EXISTS turns;\n DROP TABLE IF EXISTS sessions;\n DROP TABLE IF EXISTS projects;\n DROP TABLE IF EXISTS sources;\n DROP TABLE IF EXISTS agents;\n DROP TABLE IF EXISTS workflow_results;\n DROP TABLE IF EXISTS session_meta;\n DROP TABLE IF EXISTS tool_results;\n DROP TABLE IF EXISTS ingest_state;\n DROP TABLE IF EXISTS meta;\n `);\n applySchema(db);\n db.pragma(\"foreign_keys = ON\");\n}\n","import { readdirSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type {\n ParsedLine,\n SourceAdapter,\n SourceFile,\n ToolCallRow,\n ToolResultPatch,\n} from \"@agent-lens/core\";\n\n/** Flatten a message `content` (string or block array) to searchable natural-language text. */\nfunction flattenText(content: unknown): string | null {\n if (content == null) return null;\n if (typeof content === \"string\") return content.trim() || null;\n if (!Array.isArray(content)) return null;\n const parts: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\") parts.push(b.text);\n else if (b.type === \"thinking\" && typeof b.thinking === \"string\") parts.push(b.thinking);\n }\n const text = parts.join(\"\\n\").trim();\n return text || null;\n}\n\n/** Compact projection of a tool result for the transcript view. Preserves line structure — so console\n * output (build logs, git output, …) renders as lines instead of one run-on line — while staying\n * bounded: trailing per-line whitespace and runs of blank lines are trimmed, and the text is capped by\n * both line count and characters so a large result can't be dumped wholesale into the DB. Internal\n * spacing is kept intact so column-aligned output survives. The full untruncated output, when big\n * enough, is spilled by Claude Code to tool-results/<name>.txt and surfaced separately (full_result). */\nconst RESULT_SUMMARY_MAX_LINES = 40;\nconst RESULT_SUMMARY_MAX_CHARS = 2000;\nfunction summarizeResult(content: unknown): string | null {\n let s: string | null = null;\n if (typeof content === \"string\") s = content;\n else if (Array.isArray(content)) {\n const parts: string[] = [];\n for (const block of content) {\n const b = block as Record<string, unknown>;\n if (b && b.type === \"text\" && typeof b.text === \"string\") parts.push(b.text);\n }\n s = parts.join(\"\\n\");\n } else if (content != null) {\n s = JSON.stringify(content);\n }\n if (!s) return null;\n // Normalize newlines, drop trailing per-line whitespace, and collapse 3+ blank lines — but keep\n // internal spacing and single blank lines so the structure reads like the original console output.\n s = s.replace(/\\r\\n/g, \"\\n\").replace(/[ \\t]+\\n/g, \"\\n\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n if (!s) return null;\n let truncated = false;\n const lines = s.split(\"\\n\");\n if (lines.length > RESULT_SUMMARY_MAX_LINES) {\n s = lines.slice(0, RESULT_SUMMARY_MAX_LINES).join(\"\\n\");\n truncated = true;\n }\n if (s.length > RESULT_SUMMARY_MAX_CHARS) {\n s = s.slice(0, RESULT_SUMMARY_MAX_CHARS);\n const nl = s.lastIndexOf(\"\\n\");\n if (nl > RESULT_SUMMARY_MAX_CHARS - 200) s = s.slice(0, nl); // prefer a clean line boundary\n truncated = true;\n }\n s = s.trimEnd();\n return (truncated ? s + \"\\n… (truncated)\" : s) || null;\n}\n\nfunction asString(v: unknown): string | null {\n return typeof v === \"string\" && v.length > 0 ? v : null;\n}\n\nfunction asInt(v: unknown): number {\n return typeof v === \"number\" && Number.isFinite(v) ? Math.trunc(v) : 0;\n}\n\n/**\n * Derive the parent session id for a subagent transcript from its on-disk path. Claude Code nests\n * subagent transcripts under the spawning session's directory:\n * Task/Agent: <...>/projects/<enc>/<PARENT_SESSION_ID>/subagents/agent-<id>.jsonl\n * Workflow: <...>/projects/<enc>/<PARENT_SESSION_ID>/subagents/workflows/wf_<runId>/agent-<id>.jsonl\n * In both layouts the parent session id is the path segment immediately before `subagents/`. This is\n * the deterministic, redaction-surviving link (the Workflow tool emits no toolUseResult.agentId, so\n * the tool_calls.spawned_session_id path never fires for workflow fan-out). Returns null for a main\n * session (no `subagents/` segment).\n */\nexport function parentSessionFromPath(path: string): string | null {\n const parts = path.split(\"/\");\n const i = parts.indexOf(\"subagents\");\n return i > 0 ? parts[i - 1] : null;\n}\n\n/**\n * For a Workflow-tool subagent, the run id from its path. Workflow fan-out nests under\n * `…/subagents/workflows/<runId>/agent-<id>.jsonl`, so the run id is the segment after `workflows/`\n * (e.g. `wf_ab787f1c-ff7`). Returns null for Task/Agent subagents (no `workflows/` segment) and main\n * sessions. The same id appears in the launching Workflow tool_call's result (toolUseResult.runId),\n * which is how a run's agents group and link back to the turn that started them.\n */\nexport function workflowRunFromPath(path: string): string | null {\n const parts = path.split(\"/\");\n const i = parts.indexOf(\"workflows\");\n return i >= 0 && i + 1 < parts.length ? parts[i + 1] : null;\n}\n\nfunction recurseJsonl(dir: string, out: string[]): void {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const p = join(dir, e.name);\n if (e.isDirectory()) recurseJsonl(p, out);\n else if (e.isFile() && e.name.endsWith(\".jsonl\")) out.push(p);\n }\n}\n\nexport class ClaudeCodeAdapter implements SourceAdapter {\n agentId = \"claude-code\";\n agentName = \"Claude Code CLI\";\n\n discover(sourceArchiveDir: string, sourceId: string): SourceFile[] {\n const files: SourceFile[] = [];\n const archiveRoot = sourceArchiveDir;\n\n const addFrom = (projectsDir: string, isVersion: boolean) => {\n const found: string[] = [];\n recurseJsonl(projectsDir, found);\n for (const path of found) {\n // <...>/projects/<encodedDir>/<sessionId>.jsonl\n const parts = path.split(\"/\");\n const sessionId = basename(path, \".jsonl\");\n const encodedDir = parts[parts.length - 2] ?? \"\";\n files.push({\n path,\n sessionId,\n encodedDir,\n isVersion,\n sourceId,\n parentSessionId: parentSessionFromPath(path),\n workflowRunId: workflowRunFromPath(path),\n });\n }\n };\n\n addFrom(join(archiveRoot, \"projects\"), false);\n\n // .versions/<ts>/projects/<encodedDir>/<sessionId>.jsonl\n const versionsRoot = join(archiveRoot, \".versions\");\n try {\n for (const ts of readdirSync(versionsRoot, { withFileTypes: true })) {\n if (ts.isDirectory()) addFrom(join(versionsRoot, ts.name, \"projects\"), true);\n }\n } catch {\n /* no versions yet */\n }\n return files;\n }\n\n parseLine(raw: unknown, file: SourceFile, seq: number): ParsedLine {\n if (!raw || typeof raw !== \"object\") return {};\n const r = raw as Record<string, any>;\n\n // Session-level metadata from the common envelope + pointer lines.\n const meta: ParsedLine[\"meta\"] = {};\n if (asString(r.cwd)) meta.cwd = r.cwd;\n if (asString(r.slug)) meta.slug = r.slug;\n if (asString(r.version)) meta.cli_version = r.version;\n if (asString(r.entrypoint)) meta.entrypoint = r.entrypoint;\n if (asString(r.gitBranch)) meta.git_branch = r.gitBranch;\n if (typeof r.isSidechain === \"boolean\") meta.is_sidechain = r.isSidechain;\n if (r.type === \"ai-title\" && asString(r.aiTitle)) meta.ai_title = r.aiTitle;\n\n // Lines without a uuid (pointer/meta lines) contribute metadata only.\n if (!asString(r.uuid)) return { meta };\n\n const message = (r.message ?? {}) as Record<string, any>;\n const text = flattenText(message.content);\n\n const result: ParsedLine = {\n meta,\n event: {\n uuid: r.uuid,\n session_id: file.sessionId,\n turn_id: null,\n parent_uuid: asString(r.parentUuid),\n seq,\n type: asString(r.type) ?? \"unknown\",\n role: asString(message.role),\n timestamp: asString(r.timestamp),\n model: asString(message.model),\n is_sidechain: r.isSidechain ? 1 : 0,\n is_meta: r.isMeta ? 1 : 0,\n text,\n raw_json: JSON.stringify(raw),\n source_file: file.path,\n },\n };\n\n // Token usage at the assistant-message grain.\n const usage = message.usage as Record<string, any> | undefined;\n if (usage) {\n result.tokenUsage = {\n event_uuid: r.uuid,\n session_id: file.sessionId,\n turn_id: null,\n message_id: asString(message.id),\n model: asString(message.model),\n input_tokens: asInt(usage.input_tokens),\n output_tokens: asInt(usage.output_tokens),\n cache_creation_input_tokens: asInt(usage.cache_creation_input_tokens),\n cache_read_input_tokens: asInt(usage.cache_read_input_tokens),\n service_tier: asString(usage.service_tier),\n };\n }\n\n // Tool calls (tool_use blocks) and tool results (tool_result blocks + toolUseResult).\n const content = message.content;\n if (Array.isArray(content)) {\n const toolCalls: ToolCallRow[] = [];\n const toolResults: ToolResultPatch[] = [];\n const tur = r.toolUseResult as Record<string, any> | undefined;\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, any>;\n\n if (b.type === \"tool_use\" && asString(b.id)) {\n const name = asString(b.name) ?? \"unknown\";\n const input = b.input ?? {};\n toolCalls.push({\n id: b.id,\n event_uuid: r.uuid,\n session_id: file.sessionId,\n turn_id: null,\n tool_name: name,\n caller: asString(b.caller),\n skill_name: name === \"Skill\" ? asString(input.skill) ?? asString(input.command) : null,\n skill_id: null, // content-addressed skill version; linked in rebuildDerived from the injected body\n agent_type:\n name === \"Task\" || name === \"Agent\" ? asString(input.subagent_type) : null,\n spawned_session_id: null, // patched in once the tool_result carries toolUseResult.agentId\n workflow_run_id: null, // patched in from the Workflow tool_result's runId\n workflow_name: name === \"Workflow\" ? asString(input.name) : null, // refined from the result\n resolved_model: null,\n status: null,\n total_duration_ms: null,\n total_tokens: null,\n total_tool_use_count: null,\n input_json: JSON.stringify(input),\n result_summary: null,\n });\n } else if (b.type === \"tool_result\" && asString(b.tool_use_id)) {\n // AskUserQuestion's result is structured (which option the user picked per question, plus any\n // free-text notes) and lives on toolUseResult, not in the block content. Capture it verbatim\n // as JSON so the UI can render the Q&A with the selection marked — the generic 280-char prose\n // summary would truncate multi-question prompts and drop the notes entirely. The questions +\n // options themselves are already in the tool_use input_json, so only answers/annotations are\n // stored here.\n const isAskUser = tur && tur.questions && tur.answers && typeof tur.answers === \"object\";\n toolResults.push({\n tool_use_id: b.tool_use_id,\n result_summary: isAskUser\n ? JSON.stringify({ answers: tur!.answers, annotations: tur!.annotations ?? {} })\n : summarizeResult(b.content),\n ...(tur\n ? {\n status: asString(tur.status),\n agent_type: asString(tur.agentType),\n // Deterministic link: a Task/Agent subagent's transcript is keyed off its agentId\n // ('agent-'||agentId is the session id assigned by discover()'s filename stem).\n spawned_session_id: asString(tur.agentId) ? `agent-${tur.agentId}` : null,\n // Workflow fan-out: the result carries the run id (wf_<id>) + workflow name; this is\n // how a run's subagents (nested under subagents/workflows/<runId>/) link to this turn.\n workflow_run_id: asString(tur.runId),\n workflow_name: asString(tur.workflowName),\n resolved_model: asString(tur.resolvedModel),\n total_duration_ms: typeof tur.totalDurationMs === \"number\" ? tur.totalDurationMs : null,\n total_tokens: typeof tur.totalTokens === \"number\" ? tur.totalTokens : null,\n total_tool_use_count:\n typeof tur.totalToolUseCount === \"number\" ? tur.totalToolUseCount : null,\n }\n : {}),\n });\n }\n }\n if (toolCalls.length) result.toolCalls = toolCalls;\n if (toolResults.length) result.toolResults = toolResults;\n }\n\n return result;\n }\n}\n","/**\n * Agent Lens — heuristic classification (ADR-004). No AI: category + complexity are derived\n * deterministically from signals already in the DB (tool mix, LoC from Edit/Write inputs,\n * turns, tokens, duration, subagent fan-out, prompt keywords). Re-runnable and idempotent —\n * the same DB always yields identical rows. Every input and sub-score is recorded in\n * `signals_json` so a result can be explained and the bands retuned without re-deriving meaning.\n *\n * Scope v1 = sessions (both main and sidechain). The PK (scope, target_id) leaves turn-level\n * for later. `classifier_version` lets a future (e.g. local-LLM) classifier supersede these rows.\n */\nimport type { DB } from \"./db.js\";\n\n// v2 (ADR-004): realistic complexity ceilings so real (long, substantial) main sessions spread\n// across bands instead of pegging in \"large\"; subagent sessions categorized by their spawner role\n// rather than keyword heuristics on a read-only exploration transcript.\nexport const CLASSIFIER_VERSION = 2;\n\nexport type Category = \"feature\" | \"bugfix\" | \"refactor\" | \"docs\" | \"ops\" | \"review\" | \"chore\";\n\n/** Fixed order = deterministic tie-break when scores are equal. */\nconst CATEGORIES: Category[] = [\"bugfix\", \"refactor\", \"docs\", \"ops\", \"review\", \"feature\", \"chore\"];\n\n// Cutoffs tuned to the real main-session score distribution (v2 ceilings) so the five bands\n// partition main work meaningfully (~9/19/32/32/8%). Subagents score low and land \"trivial\".\nconst BANDS: Array<{ max: number; band: string }> = [\n { max: 22, band: \"trivial\" },\n { max: 40, band: \"small\" },\n { max: 55, band: \"medium\" },\n { max: 68, band: \"large\" },\n { max: Infinity, band: \"xl\" },\n];\n\n/**\n * Subagent roles whose work is inherently investigative/planning ⇒ \"review\". A general-purpose (or\n * unknown) subagent does varied work, so fall back to the heuristic on its own transcript.\n */\nconst REVIEW_SUBAGENT_ROLES = new Set([\"Explore\", \"Plan\", \"code-reviewer\", \"claude-code-guide\"]);\nfunction categoryForSubagentRole(role: string | undefined): Category | null {\n if (!role) return null;\n return REVIEW_SUBAGENT_ROLES.has(role) ? \"review\" : null;\n}\n\n/** Number of lines in a string (0 for empty/undefined). Trailing newline doesn't add a line. */\nfunction countLines(s: string | undefined | null): number {\n if (!s) return 0;\n const trimmed = s.endsWith(\"\\n\") ? s.slice(0, -1) : s;\n return trimmed.length === 0 ? 0 : trimmed.split(\"\\n\").length;\n}\n\nconst DOC_EXT = /\\.(md|markdown|rst|txt|adoc)$/i;\nconst OPS_FILE = /(^|\\/)(dockerfile|docker-compose|\\.github\\/|\\.gitlab-ci|makefile)|\\.(ya?ml|tf|sh|toml|ini|cfg|service|timer)$/i;\n\n/** LoC contribution of a single Edit/Write tool call, parsed from its verbatim input_json. */\nexport function locDelta(toolName: string, inputJson: string | null): { added: number; removed: number; file: string | null } {\n if (!inputJson) return { added: 0, removed: 0, file: null };\n let input: any;\n try {\n input = JSON.parse(inputJson);\n } catch {\n return { added: 0, removed: 0, file: null };\n }\n const file = typeof input?.file_path === \"string\" ? input.file_path : null;\n if (toolName === \"Write\") return { added: countLines(input?.content), removed: 0, file };\n if (toolName === \"Edit\") return { added: countLines(input?.new_string), removed: countLines(input?.old_string), file };\n return { added: 0, removed: 0, file };\n}\n\nfunction band(score: number): string {\n for (const b of BANDS) if (score < b.max) return b.band;\n return \"xl\";\n}\n\nconst clamp01 = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x);\n\ninterface Signals {\n toolCounts: Record<string, number>;\n skills: Record<string, number>;\n loc: { added: number; removed: number; net: number; churn: number; files: number };\n turn_count: number;\n event_count: number;\n work_tokens: number; // input + output + cache_creation (cache-read excluded — replay ≠ work)\n cache_read_tokens: number;\n duration_ms: number;\n subagent_count: number;\n is_sidechain: number;\n}\n\n/** Count keyword hits (whole-word-ish) of any phrase in `words` within `text`. */\nfunction hits(text: string, words: string[]): number {\n let n = 0;\n for (const w of words) {\n let i = text.indexOf(w);\n while (i !== -1) {\n n++;\n i = text.indexOf(w, i + w.length);\n }\n }\n return n;\n}\n\nfunction scoreCategories(s: Signals, text: string): Record<Category, number> {\n const t = text.toLowerCase();\n const editN = s.toolCounts[\"Edit\"] ?? 0;\n const writeN = s.toolCounts[\"Write\"] ?? 0;\n const readN = s.toolCounts[\"Read\"] ?? 0;\n const writes = editN + writeN;\n\n const scores: Record<Category, number> = {\n bugfix: 0,\n refactor: 0,\n docs: 0,\n ops: 0,\n review: 0,\n feature: 0,\n chore: 0,\n };\n\n // Keyword evidence (prompt text).\n scores.bugfix += hits(t, [\"bug\", \"fix\", \"broken\", \"crash\", \"regression\", \"traceback\", \"stack trace\", \"failing\", \"error\", \"exception\", \"not working\"]) * 1.0;\n scores.refactor += hits(t, [\"refactor\", \"clean up\", \"cleanup\", \"rename\", \"extract\", \"simplify\", \"dead code\", \"tidy\", \"reorganize\", \"restructure\"]) * 1.0;\n scores.docs += hits(t, [\"readme\", \"document\", \"documentation\", \"changelog\", \"docstring\", \"comment\", \"write docs\", \"usage guide\"]) * 1.0;\n scores.ops += hits(t, [\"deploy\", \"docker\", \"pipeline\", \"ci/cd\", \"ci \", \"systemd\", \"infra\", \"kubernetes\", \"terraform\", \"release\", \"rollback\", \"container\"]) * 1.0;\n scores.review += hits(t, [\"review\", \"audit\", \"inspect\", \"look over\", \"feedback on\"]) * 1.0;\n // \"new \" dropped — too noisy (\"the new X\", \"new file\"); \"new feature\" stays as a strong signal.\n scores.feature += hits(t, [\"add \", \"implement\", \"build \", \"create \", \"feature\", \"support for\", \"new feature\"]) * 0.8;\n\n // Structural evidence (tools + files), scaled so it complements but doesn't drown keywords.\n const docFiles = s.loc.files > 0 ? countMatch(s, DOC_EXT) : 0;\n const opsFiles = s.loc.files > 0 ? countMatch(s, OPS_FILE) : 0;\n if (s.loc.files > 0) {\n scores.docs += 3 * (docFiles / s.loc.files);\n scores.ops += 3 * (opsFiles / s.loc.files);\n }\n // Edit-heavy + low net churn relative to gross churn ⇒ rework, not new code.\n if (writes >= 3 && s.loc.churn > 0 && Math.abs(s.loc.net) / s.loc.churn < 0.34) scores.refactor += 1.5;\n // Edit-dominated (lots of Edits, few/no new Writes) ⇒ reworking existing code, not adding it.\n if (editN >= 4 && editN >= writeN * 2) scores.refactor += 1.0;\n // Read-dominated, little writing ⇒ review/exploration.\n if (readN >= 5 && writes <= 2) scores.review += 1.5;\n // New files written with net-positive LoC ⇒ feature work.\n if (writeN >= 1 && s.loc.net > 20) scores.feature += 1.5;\n\n // No signal at all ⇒ record the default choice so the argmax (in classify) picks it.\n const maxScore = Math.max(...CATEGORIES.map((c) => scores[c]));\n if (maxScore === 0) scores[writes > 0 ? \"feature\" : \"chore\"] += 0.01;\n return scores;\n}\n\n/** Files matching a pattern is recomputed from the stored file list (kept on the signal object). */\nfunction countMatch(s: Signals & { _files?: string[] }, re: RegExp): number {\n const files = s._files ?? [];\n let n = 0;\n for (const f of files) if (re.test(f)) n++;\n return n;\n}\n\nfunction complexity(s: Signals): { score: number; subscores: Record<string, number>; weights: Record<string, number> } {\n // v2 ceilings: tuned to the real p90 of substantial main sessions (churn ~2.6k, files ~29,\n // work-tokens in the tens of millions, multi-hour durations) so a median session sits mid-range\n // instead of pegging every subscore at 1.0. See ADR-004.\n const weights = { loc: 0.25, files: 0.15, turns: 0.2, tokens: 0.2, duration: 0.1, subagents: 0.1 };\n const subscores = {\n loc: clamp01(Math.log1p(s.loc.churn) / Math.log1p(6000)),\n files: clamp01(s.loc.files / 40),\n turns: clamp01(s.turn_count / 40),\n tokens: clamp01(Math.log1p(s.work_tokens) / Math.log1p(40_000_000)),\n duration: clamp01(s.duration_ms / (600 * 60_000)),\n subagents: clamp01(s.subagent_count / 10),\n };\n let acc = 0;\n for (const k of Object.keys(weights) as Array<keyof typeof weights>) acc += weights[k] * subscores[k];\n return { score: Math.round(acc * 1000) / 10, subscores, weights };\n}\n\ninterface SessionAgg {\n id: string;\n is_sidechain: number;\n turn_count: number;\n event_count: number;\n duration_ms: number | null;\n}\n\n/**\n * (Re)classify every session into the `classifications` table. Returns the row count written.\n * One transaction; deterministic; safe to run repeatedly (ON CONFLICT upsert).\n */\nexport function classify(db: DB, dirty?: Set<string> | null): { count: number; version: number } {\n // Incremental: classify only the sessions touched this run (the expanded set rebuildDerived returns).\n // Every signal bulk-load is scoped to the same set so memory and work track the delta, not the whole\n // DB (ADR-010). `null`/undefined → classify all sessions (the `--full` / standalone metrics path).\n const incremental = dirty != null;\n if (incremental) {\n db.exec(\"DROP TABLE IF EXISTS _dirty\");\n db.exec(\"CREATE TEMP TABLE _dirty (id TEXT PRIMARY KEY)\");\n const ins = db.prepare(\"INSERT OR IGNORE INTO _dirty (id) VALUES (?)\");\n db.transaction((ids: Iterable<string>) => {\n for (const id of ids) ins.run(id);\n })(dirty);\n }\n const wSess = incremental ? \" WHERE session_id IN (SELECT id FROM _dirty)\" : \"\";\n const aSess = incremental ? \" AND session_id IN (SELECT id FROM _dirty)\" : \"\";\n\n const sessions = db\n .prepare(`SELECT id, is_sidechain, turn_count, event_count, duration_ms FROM sessions${incremental ? \" WHERE id IN (SELECT id FROM _dirty)\" : \"\"}`)\n .all() as SessionAgg[];\n\n // Bulk-load per-session signals once, then assemble in JS (scales to many sessions).\n const toolMix = new Map<string, Record<string, number>>();\n for (const r of db.prepare(`SELECT session_id, tool_name, COUNT(*) c FROM tool_calls${wSess} GROUP BY session_id, tool_name`).all() as any[]) {\n const m = toolMix.get(r.session_id) ?? {};\n m[r.tool_name] = r.c;\n toolMix.set(r.session_id, m);\n }\n const skillMix = new Map<string, Record<string, number>>();\n for (const r of db.prepare(`SELECT session_id, skill_name, COUNT(*) c FROM tool_calls WHERE skill_name IS NOT NULL${aSess} GROUP BY session_id, skill_name`).all() as any[]) {\n const m = skillMix.get(r.session_id) ?? {};\n m[r.skill_name] = r.c;\n skillMix.set(r.session_id, m);\n }\n const tokens = new Map<string, { work: number; cacheRead: number }>();\n for (const r of db\n .prepare(\n `SELECT session_id, SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage${wSess} GROUP BY session_id`,\n )\n .all() as any[]) {\n tokens.set(r.session_id, { work: (r.i ?? 0) + (r.o ?? 0) + (r.cw ?? 0), cacheRead: r.cr ?? 0 });\n }\n // LoC + files from Edit/Write inputs.\n const loc = new Map<string, { added: number; removed: number; files: Set<string> }>();\n for (const r of db.prepare(`SELECT session_id, tool_name, input_json FROM tool_calls WHERE tool_name IN ('Edit','Write')${aSess}`).all() as any[]) {\n const d = locDelta(r.tool_name, r.input_json);\n const acc = loc.get(r.session_id) ?? { added: 0, removed: 0, files: new Set<string>() };\n acc.added += d.added;\n acc.removed += d.removed;\n if (d.file) acc.files.add(d.file);\n loc.set(r.session_id, acc);\n }\n // Prompt text for keyword signals.\n const promptText = new Map<string, string>();\n for (const r of db.prepare(`SELECT session_id, prompt_preview FROM turns WHERE prompt_preview IS NOT NULL${aSess}`).all() as any[]) {\n promptText.set(r.session_id, (promptText.get(r.session_id) ?? \"\") + \" \" + r.prompt_preview);\n }\n // Subagent role from the spawning Task/Agent tool_call (schema-v3 linkage). Lets us categorize a\n // sidechain session by what it was spawned to do, rather than keyword-matching its transcript.\n const subagentRole = new Map<string, string>();\n for (const r of db\n .prepare(\n `SELECT s.id id, tc.agent_type role FROM sessions s JOIN tool_calls tc ON tc.spawned_session_id = s.id WHERE s.is_sidechain = 1 AND tc.agent_type IS NOT NULL${incremental ? \" AND s.id IN (SELECT id FROM _dirty)\" : \"\"}`,\n )\n .all() as any[]) {\n if (!subagentRole.has(r.id)) subagentRole.set(r.id, r.role);\n }\n\n const upsert = db.prepare(\n `INSERT INTO classifications (scope, target_id, category, complexity_score, complexity_band, signals_json, classifier_version)\n VALUES ('session', @target_id, @category, @complexity_score, @complexity_band, @signals_json, @classifier_version)\n ON CONFLICT(scope, target_id) DO UPDATE SET\n category=excluded.category, complexity_score=excluded.complexity_score,\n complexity_band=excluded.complexity_band, signals_json=excluded.signals_json,\n classifier_version=excluded.classifier_version`,\n );\n\n const tx = db.transaction(() => {\n for (const sess of sessions) {\n const toolCounts = toolMix.get(sess.id) ?? {};\n const skills = skillMix.get(sess.id) ?? {};\n const l = loc.get(sess.id) ?? { added: 0, removed: 0, files: new Set<string>() };\n const tok = tokens.get(sess.id) ?? { work: 0, cacheRead: 0 };\n const filesArr = [...l.files].sort();\n const subagentCount = (toolCounts[\"Agent\"] ?? 0) + (toolCounts[\"Task\"] ?? 0);\n\n const signals: Signals & { _files: string[] } = {\n toolCounts,\n skills,\n loc: { added: l.added, removed: l.removed, net: l.added - l.removed, churn: l.added + l.removed, files: l.files.size },\n turn_count: sess.turn_count,\n event_count: sess.event_count,\n work_tokens: tok.work,\n cache_read_tokens: tok.cacheRead,\n duration_ms: sess.duration_ms ?? 0,\n subagent_count: subagentCount,\n is_sidechain: sess.is_sidechain,\n _files: filesArr,\n };\n\n const catScores = scoreCategories(signals, promptText.get(sess.id) ?? \"\");\n let category: Category = \"chore\";\n let bestScore = -1;\n for (const c of CATEGORIES) {\n if (catScores[c] > bestScore) {\n bestScore = catScores[c];\n category = c;\n }\n }\n // For subagents, prefer the spawner's role (Explore/Plan/… ⇒ review) over keyword heuristics.\n const role = subagentRole.get(sess.id);\n if (sess.is_sidechain) {\n const roleCat = categoryForSubagentRole(role);\n if (roleCat) category = roleCat;\n }\n const cx = complexity(signals);\n\n // Deterministic, explainable signal blob (stable key order; _files dropped, exposed as loc.files).\n const signalsJson = JSON.stringify({\n tool_counts: toolCounts,\n skills,\n loc: signals.loc,\n files: filesArr,\n turn_count: signals.turn_count,\n event_count: signals.event_count,\n work_tokens: signals.work_tokens,\n cache_read_tokens: signals.cache_read_tokens,\n duration_ms: signals.duration_ms,\n subagent_count: signals.subagent_count,\n is_sidechain: signals.is_sidechain,\n subagent_role: role ?? null,\n category_scores: catScores,\n complexity_subscores: cx.subscores,\n complexity_weights: cx.weights,\n classifier_version: CLASSIFIER_VERSION,\n });\n\n upsert.run({\n target_id: sess.id,\n category,\n complexity_score: cx.score,\n complexity_band: band(cx.score),\n signals_json: signalsJson,\n classifier_version: CLASSIFIER_VERSION,\n });\n }\n });\n tx();\n\n if (incremental) db.exec(\"DROP TABLE IF EXISTS _dirty\");\n return { count: sessions.length, version: CLASSIFIER_VERSION };\n}\n","/**\n * Agent Lens — security findings (ADR-017). No AI: risky operations are flagged deterministically\n * by a rule engine over signals already in the DB (each tool call's verbatim `input_json` /\n * `result_summary` / `status`, plus the session's project path). Re-runnable and idempotent — the\n * same DB always yields identical rows. Every matched pattern and context modifier is recorded in\n * `signals_json` so a finding can be explained and rules retuned without re-deriving meaning.\n *\n * agent-lens is a *retrospective* analyzer, so this is forensic awareness — surfacing that an agent\n * *did* something risky after the fact — not runtime prevention. Findings are 0..N per tool call, so\n * an incremental re-run DELETEs the touched sessions' rows then re-INSERTs (delete-then-insert),\n * unlike the 1-per-session `classifications` upsert. `detector_version` lets a future (e.g. local-LLM)\n * engine supersede these rows.\n */\nimport { createHash } from \"node:crypto\";\nimport { bumpSeverity, type Severity, type SecurityCategoryKey } from \"@agent-lens/core\";\nimport type { DB } from \"./db.js\";\n\n// Bump on any rule/severity change so a re-run is attributable to an engine version (mirrors\n// CLASSIFIER_VERSION). Recorded on every row + in signals_json. v2: allowlist agent-owned paths so\n// the agent writing to its own config/work dir (e.g. a plan file under ~/.claude/plans) or a temp dir\n// is not flagged as an out-of-project write. v3: exclude .env.example / config templates from\n// credential-access, require a real content-read verb (ls/file/stat no longer flag), raise `sudo` to\n// high. v4: fix template exclusion when the path is followed by shell text (.env.example | …); score\n// secret reads per pipeline segment so `file/ls … | grep` no longer counts as a content read; derive\n// the agent-owned config roots from the configured sources' `config_dir` (covers ~/.claude-isf and\n// any relocated install) instead of a hardcoded `.claude` pattern. v5: match command-pattern rules\n// against an \"executed view\" of the command (codeOf — comments stripped, echo/printf output\n// neutralized) so a dangerous token that is only printed, commented, quoted (`node -e '… sudo …'`,\n// `grep \"sudo\"`), or inside a heredoc body no longer flags; scope `sudo` to command position (ignores\n// `apt install sudo`); add privilege.exec_generated_script for the write-a-script-then-run-it pattern\n// that makes echoed/heredoc'd text live. v6: exfil.network_upload scores curl/wget uploads by\n// destination scope instead of always-high — external host = high (critical with a file), private/\n// internal host (RFC1918 / link-local / .local / bare service name) = low, loopback = info; a real file\n// (@file / -T / --upload-file) bumps the internal/loopback tiers one step. The host is classified on the\n// verbatim command (commandBare blanks quoted URLs, hiding the host) with -H/--header args stripped so a\n// URL in an Origin/Referer header can't pose as the target; and the upload-flag match is now case-\n// SENSITIVE so curl's -D/-f/-t (dump-header/fail/…) no longer read as -d/-F/-T uploads. v6 also re-tiers\n// several routine ops out of the crowded medium bucket: write_outside_project (non-system) and\n// git_reset_hard / non-protected git_force_push drop to low, and overwrite_critical splits — lockfile\n// churn is low while a CI-config overwrite stays medium (poisoned pipeline is a supply-chain risk);\n// system-path writes and protected-branch force-push keep their high. And curl_pipe_shell no longer\n// flags `curl … | node -e`/`python -c`/`python -m` (the interpreter runs inline code and the piped\n// output is just data, e.g. parsing an API response) — only a shell or a bare node/python that executes\n// the downloaded body still flags. Finding ids are independent of this version, so a bump doesn't\n// invalidate user triage state. v7 cuts three destructive false positives: sql_drop now requires a real\n// database-client invocation (psql/mysql/sqlite3/…) for the Bash path, so a DROP/TRUNCATE keyword that\n// only appears as text — a `grep \"…\\|truncate\"` search pattern, an echoed string — no longer flags (the\n// structured input.query path is unchanged). rm_rf no longer treats `git rm` (a tracked-file removal\n// recoverable from history) as a filesystem `rm -rf`, and matches its recursive/force flags as\n// whitespace-bounded letter clusters so a path token like `reports/…-software-engineer…` is never\n// mistaken for `-fr` flags. And an rm -rf whose targets are all under a temp dir (/tmp, /var/folders)\n// drops from high to low — temp dirs are agent-owned scratch (writes there aren't flagged at all), so a\n// cleanup delete is routine; a home/root/glob target still escalates to critical.\nexport const DETECTOR_VERSION = 7;\n\n// Severity model + category taxonomy are shared from core (packages/core/src/security.ts) so the\n// server and web agree on ordering and keys. Re-exported for tests that drive this module directly.\nexport { bumpSeverity, type Severity };\ntype Category = SecurityCategoryKey;\n\n/** Truncate an evidence snippet so we never dump wholesale secrets/output into the DB. */\nfunction evidence(s: string, max = 200): string {\n const one = s.replace(/\\s+/g, \" \").trim();\n return one.length > max ? one.slice(0, max - 1) + \"…\" : one;\n}\n\n/** Deterministic finding id — stable across runs so re-detection reproduces identical rows. */\nfunction findingId(toolCallId: string, ruleId: string): string {\n return createHash(\"sha1\").update(toolCallId).update(\"\\0\").update(ruleId).digest(\"hex\").slice(0, 16);\n}\n\nexport interface RuleContext {\n toolName: string;\n command: string | null; // Bash command string, verbatim (for evidence + value scanning)\n commandCode: string | null; // executed view, quotes INTACT (comments/heredoc/echo-output neutralized) — for sql_drop\n commandBare: string | null; // commandCode + quoted-string contents blanked — for command-verb rules (sudo, rm, …)\n filePath: string | null; // Read/Write/Edit file_path, if any\n input: any; // parsed input_json (may be null)\n resultSummary: string | null;\n status: string | null; // success | error | ...\n projectPath: string | null; // the session's cwd, for outside-project checks\n ownedConfigDirs: string[]; // agent config roots (from the `sources` table) the agent legitimately owns\n}\n\nexport interface RuleMatch {\n evidence: string; // the offending snippet\n severity?: Severity; // override the rule's baseSeverity (context-driven escalation)\n mods?: Record<string, unknown>; // extra explainability recorded in signals_json\n}\n\ninterface Rule {\n id: string;\n category: Category;\n framework_ref: string;\n title: string;\n tools: string[] | \"*\"; // tool_names this rule applies to\n baseSeverity: Severity;\n test(ctx: RuleContext): RuleMatch | null;\n}\n\n// ---- Shared matchers ------------------------------------------------------\n\n// A path we consider outside the agent's working project (home dotfiles, system dirs, root).\nconst SYSTEM_PATH = /(^|\\s)(\\/etc\\/|\\/usr\\/|\\/bin\\/|\\/sbin\\/|\\/boot\\/|\\/var\\/|\\/root\\/|~\\/\\.ssh\\/|~\\/\\.aws\\/)/;\nconst HOME_OR_ROOT_TARGET = /(\\s|^)(~\\/?|\\/|\\/\\*|\\$HOME\\/?|\\*)(\\s|$)/;\n\n/** Secret-bearing file locations (matched against a file path or a shell command). */\nconst SECRET_FILE =\n /(^|\\/|\\s)(\\.env(\\.[\\w.-]+)?|id_rsa|id_ed25519|id_ecdsa|\\.ssh\\/[\\w.-]+|\\.aws\\/credentials|\\.gcp\\/|gcloud\\/|\\.npmrc|\\.pypirc|\\.netrc|\\.pem|\\.p12|\\.pfx|secrets?\\.(ya?ml|json|txt)|credentials(\\.json)?)\\b/i;\n\n// Non-secret config *templates* (e.g. .env.example, secrets.sample.yaml) — reading these is expected\n// and carries no real secret, so exclude them from credential-access even though they match SECRET_FILE.\n// The trailing (?![\\w-]) matches the end of the \".example\" token whether the token ends the string\n// (a Read filePath) or is followed by more shell text (`… .env.example | head`) — a plain `$` anchor\n// only handled the former and let the Bash-command case slip through.\nconst EXAMPLE_FILE = /\\.(example|sample|template|dist)(?![\\w-])/i;\n\n// Commands that read file *contents* — the actual credential-access signal. Listing/stat commands\n// (ls, file, stat, find, …) are deliberately absent: naming a secret path in an `ls`/`file` doesn't\n// read it, so they must not flag (only a content read does).\nconst CONTENT_READ_CMD = /\\b(cat|less|more|head|tail|bat|xxd|strings|base64|grep|egrep|rg|awk|sed|nl|tac|od|hexdump|cp|scp|sort)\\b/i;\n\n/** Secret-looking values (in a command or a captured result). */\nconst SECRET_VALUE_PATTERNS: Array<{ re: RegExp; label: string; critical?: boolean }> = [\n { re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/, label: \"private-key\", critical: true },\n { re: /\\bAKIA[0-9A-Z]{16}\\b/, label: \"aws-access-key-id\" },\n { re: /\\bASIA[0-9A-Z]{16}\\b/, label: \"aws-temp-key-id\" },\n { re: /\\bgh[pousr]_[A-Za-z0-9]{30,}\\b/, label: \"github-token\" },\n { re: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/, label: \"slack-token\" },\n { re: /\\bsk-[A-Za-z0-9]{20,}\\b/, label: \"api-secret-key\" },\n];\n\nconst isBash = (t: string) => t === \"Bash\";\n\n// SQL keywords that irreversibly drop/empty a database object.\nconst SQL_DESTRUCTIVE = /\\b(DROP\\s+(TABLE|DATABASE|SCHEMA)|TRUNCATE(\\s+TABLE)?)\\b/i;\n// Database-client command words. A DROP/TRUNCATE in a *Bash* command is only executed SQL when one of\n// these runs it; without a client the keyword is inert text (a `grep \"…\\|truncate\"` search pattern, an\n// echoed help string, a log line), so it must not flag. The structured input.query path needs no client.\nconst SQL_CLIENT = /\\b(psql|pgcli|mysql|mysqladmin|mariadb|sqlite3?|sqlcmd|usql|mongo|mongosh|clickhouse-client|cockroach|snowsql|bq)\\b/i;\n\n// ---- Command sanitization ------------------------------------------------\n// Command-pattern rules (\"did the agent run a dangerous command?\") must match the *executed* code,\n// not text that is merely printed or commented. `codeOf()` produces that executed view:\n// • strips shell comments (`# …` never runs), and\n// • neutralizes the arguments of the print builtins echo/printf — their quoted/plain text is data\n// sent to stdout, so `sudo`/`rm -rf`/… inside it is inert. Quoted strings are consumed as whole\n// units, so separators (`;` `|`) inside a printed string never leak out as fake command breaks.\n// NOTE: this intentionally does NOT blank all quoted strings — `psql -c \"DROP …\"`, `sh -c \"…\"` carry\n// executed code inside quotes, which must still be detected. Value-scanning rules (secret_in_data)\n// keep using the raw command, since a secret value is exposed whether or not it was \"executed\".\nconst stripComments = (c: string): string => c.replace(/(^|\\s)#[^\\n]*/g, \"$1\");\nconst neutralizeEcho = (c: string): string =>\n c.replace(/((?:^|[;&|\\n(])\\s*)(?:echo|printf)\\b(?:\"(?:[^\"\\\\]|\\\\.)*\"|'[^']*'|[^;&|\\n])*/gi, \"$1echo\");\n// A heredoc body (`cmd <<EOF … EOF`) is data written to a file/stdin, not commands the shell runs, so a\n// `sudo`/`rm -rf` line inside it is inert (unless the resulting file is then executed — see\n// execGeneratedScript). Replace the whole heredoc, body included, with a placeholder.\nconst blankHeredoc = (c: string): string => c.replace(/<<-?\\s*(['\"]?)(\\w+)\\1[\\s\\S]*?\\n\\s*\\2\\b/g, \"<<HEREDOC\");\n// `codeOf`: quotes INTACT (so `psql -c \"DROP …\"` — executed code inside quotes — still matches), with\n// comments, heredoc bodies, and echo/printf output neutralized.\nconst codeOf = (c: string): string => neutralizeEcho(stripComments(blankHeredoc(c)));\n// Blank the CONTENTS of quoted string literals. A dangerous token inside a quoted *argument*\n// (`node -e '… sudo …'`, `grep \"sudo\"`, `git commit -m \"… rm -rf …\"`) is data passed to a program,\n// not a shell command word — so the command-verb rules must not match it. (Tradeoff: a command truly\n// executed via `sh -c \"sudo …\"` is missed; that inline form is rare and worth the far fewer false\n// positives.) codeOf runs first so in-string separators are already gone from the parts we keep.\nconst blankQuoted = (c: string): string => c.replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"').replace(/'[^']*'/g, \"''\");\nconst bareOf = (c: string): string => blankQuoted(codeOf(c));\n\n// A file is \"executed\" when passed to an interpreter (sh/bash/…), sourced (`source`/`.`), or invoked\n// as a path (`./f`, `/abs/f`). Detects the write-a-script-then-run-it pattern (`echo … > f.sh; sh f.sh`)\n// that turns otherwise-inert printed text into live code. Returns the offending file, or null.\nfunction execGeneratedScript(cmd: string): string | null {\n const targets = new Set<string>();\n for (const m of cmd.matchAll(/(?:>>?|\\btee\\b\\s+(?:-a\\s+)?)\\s*[\"']?([A-Za-z0-9._/~-]+)/g)) {\n if (m[1] && !m[1].startsWith(\"/dev/\")) targets.add(m[1]);\n }\n for (const t of targets) {\n const e = t.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n // Interpreter/source invocation: `sh FILE`, `bash FILE`, `source FILE`, `. FILE` (the interpreter is\n // the command word, so FILE may sit anywhere after it).\n const interp = new RegExp(`\\\\b(?:sh|bash|zsh|ksh|dash|source)\\\\s+[\"']?${e}\\\\b|(?:^|[\\\\n;|&(])\\\\s*\\\\.\\\\s+[\"']?${e}\\\\b`);\n // FILE run directly — must be at COMMAND POSITION (start or after a separator), NOT an argument slot,\n // otherwise the redirect/`tee FILE` target matches its own occurrence (`… | tee /etc/hosts`).\n const hasPath = /^(\\.\\/|\\/|~\\/)/.test(t);\n const path = hasPath\n ? new RegExp(`(?:^|[\\\\n;|&(])\\\\s*${e}(?:\\\\s|$|[;&|<>])`)\n : new RegExp(`(?:^|[\\\\n;|&(])\\\\s*\\\\.\\\\/${e}\\\\b`);\n if (interp.test(cmd) || path.test(cmd)) return t;\n }\n return null;\n}\n\n// Temp dirs (including the per-session scratchpad under /tmp/…) are always agent-owned regardless of\n// which agent/config produced them.\nconst TEMP_PATH = /^(\\/tmp\\/|\\/var\\/folders\\/|\\/private\\/var\\/folders\\/)/;\n\n/**\n * Paths the agent legitimately owns, so writing there is expected, not a finding: its own config/work\n * dir (e.g. plans, memory, todos, projects, settings, skills) and temp dirs. The config roots are the\n * `config_dir`s of the configured sources (the `sources` table, seeded from the project config file),\n * NOT a hardcoded `.claude` pattern — that way side-by-side installs (~/.claude, ~/.claude-isf, …) and\n * any relocated config dir are covered from a single source of truth. Neutralizes the biggest\n * write_outside_project false-positive source.\n */\nexport function isAgentOwnedPath(p: string, configDirs: string[]): boolean {\n if (TEMP_PATH.test(p)) return true;\n if (configDirs.some((d) => p === d || p.startsWith(d + \"/\"))) return true;\n const tmp = process.env.TMPDIR;\n return !!tmp && p.startsWith(tmp.replace(/\\/$/, \"\") + \"/\");\n}\n\n/** Does an absolute file path fall outside the session's project directory? (null-safe.) */\nfunction outsideProject(filePath: string | null, projectPath: string | null, configDirs: string[]): boolean {\n if (!filePath || !filePath.startsWith(\"/\")) return false; // relative paths are project-local\n if (isAgentOwnedPath(filePath, configDirs)) return false; // agent's own config/work dir + temp → expected\n if (!projectPath) return false; // unknown project → don't guess\n return filePath !== projectPath && !filePath.startsWith(projectPath.replace(/\\/$/, \"\") + \"/\");\n}\n\n// Classify the destination host(s) of a curl/wget command for exfil scoring. Runs on the VERBATIM\n// command (not commandBare) so quoted URLs are visible, and strips -H/--header args so a URL sitting\n// in an Origin/Referer header is never mistaken for the request target. Returns the most-severe scope\n// present: external > internal > loopback; \"unknown\" when no host is recognizable (e.g. the target is\n// a shell variable) — treated as external-ish so we don't under-report an unprovable destination.\ntype NetScope = \"external\" | \"internal\" | \"loopback\" | \"unknown\";\nconst SCOPE_RANK: Record<NetScope, number> = { unknown: 0, loopback: 1, internal: 2, external: 3 };\nfunction netTargetScope(rawCommand: string): NetScope {\n const s = rawCommand\n .replace(/(?:-H|--header)\\s+(['\"]).*?\\1/gi, \" \") // quoted header value (Origin/Referer/…)\n .replace(/(?:-H|--header)\\s+[^\\s'\"]+/gi, \" \"); // bare header token\n const authorities: string[] = [];\n // scheme URLs: http(s)://host (host may be a bracketed IPv6 literal)\n for (const m of s.matchAll(/\\bhttps?:\\/\\/(\\[[0-9a-fA-F:]+\\]|[^/\\s\"'<>|]+)/gi)) authorities.push(m[1]);\n // bare host:port targets (curl localhost:4477, curl 10.0.0.5:8080) — require a numeric port so we\n // never mistake a file path or a `key:value` token for a network destination.\n for (const m of s.matchAll(/(?:^|[\\s\"'=(])(\\[[0-9a-fA-F:]+\\]|[A-Za-z0-9.-]+):\\d{2,5}(?=[/\\s\"']|$)/g)) authorities.push(m[1]);\n let scope: NetScope = \"unknown\";\n for (let host of authorities) {\n host = host.replace(/^\\[|\\]$/g, \"\").replace(/^[^@/]*@/, \"\").toLowerCase(); // drop brackets + userinfo\n if (!host.includes(\"::\")) host = host.replace(/:\\d+$/, \"\"); // drop :port (but not the ::1 in IPv6)\n let hs: NetScope;\n if (host === \"localhost\" || /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host) || host === \"0.0.0.0\" || host === \"::1\")\n hs = \"loopback\";\n else if (\n /^10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host) || // RFC1918\n /^192\\.168\\.\\d{1,3}\\.\\d{1,3}$/.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}$/.test(host) ||\n /^169\\.254\\.\\d{1,3}\\.\\d{1,3}$/.test(host) || // link-local\n host.endsWith(\".local\") ||\n host.endsWith(\".internal\") ||\n !host.includes(\".\") // single-label host (a service name, not a public domain)\n )\n hs = \"internal\";\n else hs = \"external\";\n if (SCOPE_RANK[hs] > SCOPE_RANK[scope]) scope = hs;\n }\n return scope;\n}\n\n// ---- The rule set (v1) ----------------------------------------------------\n// Representative, not exhaustive; grouped by framework-anchored category. New rules just append here.\n\nconst RULES: Rule[] = [\n // --- Destructive / data-loss (OWASP ASI02 Tool Misuse) -------------------\n {\n id: \"destructive.rm_rf\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Recursive force delete (rm -rf)\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n if (!ctx.commandBare) return null;\n // `git rm` (even `git rm -rf`) removes *tracked* files and is recoverable from git history — it's\n // not a filesystem `rm -rf`. Neutralize it (glued to `git` so the `rm` word boundary is gone) so it\n // never matches, while a real `rm -rf` elsewhere in the same command still does.\n const c = ctx.commandBare.replace(/\\bgit\\s+rm\\b/gi, \"gitrm\");\n // Isolate the `rm` command's own segment (up to the next |;&\\n) so flags/targets from a later\n // command don't leak in.\n const rmSeg = c.match(/\\brm\\b[^|;&\\n]*/i)?.[0];\n if (!rmSeg) return null;\n // rm needs both a recursive and a force flag. Flag tokens are matched as whitespace-bounded\n // letter-only clusters (`-rf`, `-fr`, `-r -f`, `--recursive --force`) so a path token that merely\n // contains an f before an r — e.g. `reports/…-software-engineer…` — isn't mistaken for `-fr`.\n const combined = /(?:^|\\s)-(?=[a-zA-Z]*[rR])(?=[a-zA-Z]*f)[a-zA-Z]+(?=\\s|$)/.test(rmSeg);\n const recursive = /(?:^|\\s)(?:-[a-zA-Z]*[rR][a-zA-Z]*|--recursive)(?=\\s|$)/.test(rmSeg);\n const force = /(?:^|\\s)(?:-[a-zA-Z]*f[a-zA-Z]*|--force)(?=\\s|$)/.test(rmSeg);\n if (!combined && !(recursive && force)) return null;\n const dangerousTarget = HOME_OR_ROOT_TARGET.test(c) || /\\s\\/(?:\\s|$)/.test(c) || /\\brm\\b[^|;&\\n]*\\*/.test(c);\n // Every non-flag target under a temp dir → routine scratch cleanup, demote to low.\n const targets = rmSeg.split(/\\s+/).slice(1).filter((a) => a && !a.startsWith(\"-\"));\n const tempOnly = targets.length > 0 && targets.every((t) => TEMP_PATH.test(t));\n const severity: Severity = dangerousTarget ? \"critical\" : tempOnly ? \"low\" : \"high\";\n return { evidence: evidence(ctx.command!), severity, mods: { dangerous_target: dangerousTarget, temp_target: tempOnly } };\n },\n },\n {\n id: \"destructive.git_reset_hard\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Discards local changes (git reset --hard)\",\n tools: [\"Bash\"],\n baseSeverity: \"low\", // routine dev op — only uncommitted working-tree changes are lost; commits survive in reflog\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/\\bgit\\s+reset\\s+--hard\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"destructive.git_force_push\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Force-push rewrites remote history\",\n tools: [\"Bash\"],\n baseSeverity: \"low\", // feature-branch force-push is routine; only a protected branch (below) is high\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/\\bgit\\s+push\\b[^|;&\\n]*(--force\\b|--force-with-lease\\b|\\s-f\\b)/i.test(c)) return null;\n const mainBranch = /\\b(main|master|release)\\b/.test(c);\n return { evidence: evidence(ctx.command!), severity: mainBranch ? \"high\" : \"low\", mods: { protected_branch: mainBranch } };\n },\n },\n {\n id: \"destructive.sql_drop\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Drops or truncates a database object\",\n tools: \"*\",\n baseSeverity: \"high\",\n test(ctx) {\n const query = typeof ctx.input?.query === \"string\" ? ctx.input.query : null;\n // A structured DB tool call (input.query) is SQL by definition — flag on the keyword alone.\n if (query && SQL_DESTRUCTIVE.test(query)) return { evidence: evidence(ctx.command ?? query) };\n // A Bash command only executes SQL when a database client runs it; otherwise DROP/TRUNCATE is just\n // text (e.g. a `grep \"…\\|truncate\"` search pattern) and inert, so require both a client and the keyword.\n const code = ctx.commandCode;\n if (code && SQL_CLIENT.test(code) && SQL_DESTRUCTIVE.test(code)) return { evidence: evidence(ctx.command!) };\n return null;\n },\n },\n {\n id: \"destructive.disk_wipe\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Low-level disk write / wipe (dd, mkfs, shred)\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/(\\bdd\\s+if=|\\bmkfs(\\.\\w+)?\\b|\\bshred\\b)/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"destructive.overwrite_critical\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Overwrites a critical project file\",\n tools: [\"Write\", \"Edit\", \"MultiEdit\"],\n baseSeverity: \"low\",\n test(ctx) {\n const f = ctx.filePath;\n if (!f) return null;\n const lock = /(^|\\/)(package-lock\\.json|pnpm-lock\\.ya?ml|yarn\\.lock|Cargo\\.lock|go\\.sum)\\b/.test(f);\n // A CI-pipeline file is more sensitive than a lockfile: modifying it can run arbitrary code or\n // exfiltrate secrets in CI (a supply-chain risk), so it stays medium while lockfile churn is low.\n const ci = /(^|\\/)(\\.github\\/workflows\\/|\\.gitlab-ci\\.ya?ml)\\b/.test(f);\n if (!lock && !ci) return null;\n return { evidence: evidence(f), severity: ci ? \"medium\" : \"low\", mods: { kind: ci ? \"ci-config\" : \"lockfile\" } };\n },\n },\n\n // --- Credential & secret access ------------------------------------------\n {\n id: \"credential.secret_file_access\",\n category: \"credential-access\",\n framework_ref: \"MITRE ATLAS: Credential Access\",\n title: \"Reads a secret/credential file\",\n tools: [\"Read\", \"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n if (isBash(ctx.toolName)) {\n const cmd = ctx.commandBare; // executed view: a secret named only in a comment / echo / quoted arg isn't read\n if (!cmd) return null;\n // Evaluate each pipeline/command segment independently: a secret file is only *read* when a\n // content-read command (cat/grep/…) and the secret path occur in the SAME segment. A bare\n // `ls`/`file`/`stat` that merely names the path lists metadata; piping its OUTPUT into a\n // `grep`/`head` in a later segment (e.g. `file ~/.ssh/id_* | grep -v .pub`) exposes no secret\n // content — the read command never touches the file. Splitting first avoids matching the\n // read command against the wrong segment.\n const segments = cmd.split(/\\|\\||&&|[|;\\n]/);\n const reads = segments.some(\n (seg) => SECRET_FILE.test(seg) && !EXAMPLE_FILE.test(seg) && CONTENT_READ_CMD.test(seg),\n );\n return reads ? { evidence: evidence(ctx.command!) } : null;\n }\n // Read tool: the filePath itself is the target (no command to segment).\n const f = ctx.filePath;\n if (!f || !SECRET_FILE.test(f) || EXAMPLE_FILE.test(f)) return null; // skip .env.example & templates\n return { evidence: evidence(f) };\n },\n },\n {\n id: \"credential.secret_in_data\",\n category: \"credential-access\",\n framework_ref: \"MITRE ATLAS: Credential Access\",\n title: \"Secret-looking value in command or output\",\n tools: \"*\",\n baseSeverity: \"high\",\n test(ctx) {\n const hay = [ctx.command, ctx.resultSummary].filter(Boolean).join(\"\\n\");\n if (!hay) return null;\n for (const p of SECRET_VALUE_PATTERNS) {\n const m = p.re.exec(hay);\n if (m) return { evidence: `${p.label}: ${evidence(m[0], 40)}`, severity: p.critical ? \"critical\" : \"high\", mods: { secret_kind: p.label } };\n }\n return null;\n },\n },\n\n // --- Data exfiltration (MITRE ATLAS AML.T0086) ---------------------------\n {\n id: \"exfil.pipe_to_network\",\n category: \"exfiltration\",\n framework_ref: \"MITRE ATLAS AML.T0086\",\n title: \"Pipes local data into a network command\",\n tools: [\"Bash\"],\n baseSeverity: \"critical\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n // e.g. `cat secrets | curl …`, `tar … | nc host port`, `… | curl -d @-`\n if (!/(\\||<)\\s*(sudo\\s+)?(curl|wget|nc|ncat|netcat|ssh|scp)\\b/i.test(c)) return null;\n if (!/\\b(cat|tar|zip|gzip|base64|env|printenv|cat\\s|<)\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"exfil.network_upload\",\n category: \"exfiltration\",\n framework_ref: \"MITRE ATLAS AML.T0086\",\n title: \"Uploads data to a network host\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n // Upload-shaped curl/wget. NOTE: no /i flag — the short flags here are case-SENSITIVE (curl's -d\n // is data but -D is dump-header, -F is form but -f is --fail, -T is upload but -t is unrelated), so\n // matching case-insensitively turned plain GET smoke tests (`curl -sf`, `curl -D -`) into uploads.\n const upload = /\\b(curl|wget)\\b[^|;&\\n]*(-X\\s*POST|--data\\b|--data-\\w+\\b|\\s-d\\b|-T\\b|--upload-file\\b|-F\\b|--form\\b)/.test(c);\n if (!upload) return null;\n // `@file` / --upload-file / -T means a real file is being sent (matched on commandBare so an `@`\n // inside a quoted arg — e.g. an email in a header value — doesn't count as a file).\n const withFile = /(@[^\\s'\"]+|--upload-file|\\s-T\\b)/.test(c);\n // Score by destination: external host = high (critical with a file); a private/internal host is a\n // lesser concern (low); a loopback call is local IPC / a dev server (info). Sending an actual file\n // bumps the internal/loopback tiers one step. An unrecognizable target (shell var) can't be shown\n // local, so it stays high. Classified on the verbatim command so quoted URLs are visible.\n const scope = netTargetScope(ctx.command!);\n let severity: Severity;\n if (scope === \"external\") severity = withFile ? \"critical\" : \"high\";\n else if (scope === \"unknown\") severity = \"high\";\n else severity = withFile ? bumpSeverity(scope === \"loopback\" ? \"info\" : \"low\", 1) : scope === \"loopback\" ? \"info\" : \"low\";\n return {\n evidence: evidence(ctx.command!),\n severity,\n mods: { target_scope: scope, with_file: withFile },\n };\n },\n },\n {\n id: \"exfil.reverse_shell\",\n category: \"exfiltration\",\n framework_ref: \"MITRE ATLAS AML.T0086\",\n title: \"Netcat / reverse-shell style connection\",\n tools: [\"Bash\"],\n baseSeverity: \"critical\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n if (!/\\b(nc|ncat|netcat)\\b[^|;&\\n]*(-e\\b|-c\\b|\\d{2,5})|\\/dev\\/tcp\\/|bash\\s+-i\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n\n // --- Privilege escalation / guardrail bypass (OWASP LLM06 Excessive Agency)\n {\n id: \"privilege.skip_permissions\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Bypasses the permission guardrail\",\n tools: \"*\",\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/--dangerously-skip-permissions\\b|--yolo\\b|--dangerously-skip\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.curl_pipe_shell\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Executes a remote script (curl | sh)\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n const m = /\\b(?:curl|wget)\\b[^|]*\\|\\s*(?:sudo\\s+)?(sh|bash|zsh|python\\d?|node)\\b([^|;&]*)/i.exec(c);\n if (!m) return null;\n // The danger is the interpreter executing the *downloaded body* piped to its stdin. A shell always\n // does. node/python execute piped stdin as a script only when NOT handed an inline program or\n // module — `curl … | node -e '…'`, `python -c '…'`, `python -m json.tool` just consume the output\n // as DATA (e.g. parsing an API response), which is not remote-script execution and must not flag.\n const interp = m[1].toLowerCase();\n const args = m[2] || \"\";\n if (/^(?:node|python)/.test(interp) && /(?:^|\\s)-(?:e|c|m|p)\\b|(?:^|\\s)--(?:eval|command|module|print)\\b/.test(args))\n return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.sudo\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Runs a command as root (sudo)\",\n tools: [\"Bash\"],\n baseSeverity: \"high\", // root escalation removes a real safety barrier — higher than an in-project op\n test(ctx) {\n const c = ctx.commandBare;\n // sudo must be in *command position* — start of the command or right after a separator (; | && ( ).\n // This ignores `sudo` as an argument (e.g. `apt install sudo`) and, via codeOf, inside echo/comments.\n if (!c || !/(?:^|[\\n;|&(])\\s*sudo\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.exec_generated_script\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Writes a shell script from inline output and executes it\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const raw = ctx.command;\n if (!raw) return null;\n // Echoing a command into a file that is then run/sourced turns otherwise-inert printed text into\n // live code (e.g. `echo \"sudo rm -rf /\" > f.sh; sh f.sh`) — the reason we don't just ignore\n // dangerous tokens inside echo strings. Detect on the comment-stripped raw command.\n const file = execGeneratedScript(stripComments(raw));\n if (!file) return null;\n // Escalate when the generated script itself carries a destructive/privileged payload.\n const critical = /(?:^|[\"'\\s;|&(])sudo\\b|\\brm\\s+[^|;&\\n]*-[a-z]*r[a-z]*f|\\bchmod\\s+0?777\\b|\\bmkfs|\\bdd\\s+if=/i.test(raw);\n return { evidence: evidence(raw), severity: critical ? \"critical\" : \"high\", mods: { script_file: file } };\n },\n },\n {\n id: \"privilege.chmod_777\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Overly permissive permissions (chmod 777)\",\n tools: [\"Bash\"],\n baseSeverity: \"medium\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/\\bchmod\\s+(-[a-zA-Z]+\\s+)*0?777\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.persistence\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Installs a persistence mechanism\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n if (!/\\bcrontab\\s+-|\\bsystemctl\\s+enable\\b|\\blaunchctl\\s+load\\b|>>\\s*~\\/\\.(bashrc|zshrc|profile|bash_profile)\\b|\\/etc\\/(cron|systemd|rc\\.local)/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.write_outside_project\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Writes a file outside the working project\",\n tools: [\"Write\", \"Edit\", \"MultiEdit\"],\n baseSeverity: \"low\", // writing to /tmp, scratch dirs, adjacent repos is routine; only a system path (below) is high\n test(ctx) {\n const f = ctx.filePath;\n if (!outsideProject(f, ctx.projectPath, ctx.ownedConfigDirs)) return null;\n const system = !!f && SYSTEM_PATH.test(f);\n return { evidence: evidence(f!), severity: system ? \"high\" : \"low\", mods: { project: ctx.projectPath, system_path: system } };\n },\n },\n];\n\n/** Rule → tool-name applicability. */\nfunction appliesTo(rule: Rule, toolName: string): boolean {\n return rule.tools === \"*\" || rule.tools.includes(toolName);\n}\n\n/** Parse a tool call's verbatim input_json into a RuleContext (mirrors classify.ts's locDelta parse). */\nfunction buildContext(\n row: { tool_name: string; input_json: string | null; result_summary: string | null; status: string | null },\n projectPath: string | null,\n ownedConfigDirs: string[],\n): RuleContext {\n let input: any = null;\n try {\n input = row.input_json ? JSON.parse(row.input_json) : null;\n } catch {\n input = null;\n }\n const command = typeof input?.command === \"string\" ? input.command : null;\n const commandCode = command != null ? codeOf(command) : null;\n const commandBare = command != null ? bareOf(command) : null;\n const filePath = typeof input?.file_path === \"string\" ? input.file_path : null;\n return { toolName: row.tool_name, command, commandCode, commandBare, filePath, input, resultSummary: row.result_summary, status: row.status, projectPath, ownedConfigDirs };\n}\n\ninterface ToolRow {\n id: string;\n session_id: string;\n event_uuid: string | null;\n turn_id: string | null;\n tool_name: string;\n input_json: string | null;\n result_summary: string | null;\n status: string | null;\n}\n\n/**\n * (Re)scan tool calls into the `findings` table. Returns the total finding count + engine version.\n * Deterministic; safe to run repeatedly. `dirty` (the expanded id set rebuildDerived returns) scopes an\n * incremental run to the touched sessions via delete-then-insert; null/undefined → scan everything.\n */\nexport function detect(db: DB, dirty?: Set<string> | null): { count: number; version: number } {\n const incremental = dirty != null;\n if (incremental) {\n db.exec(\"DROP TABLE IF EXISTS _dirty_sec\");\n db.exec(\"CREATE TEMP TABLE _dirty_sec (id TEXT PRIMARY KEY)\");\n const ins = db.prepare(\"INSERT OR IGNORE INTO _dirty_sec (id) VALUES (?)\");\n db.transaction((ids: Iterable<string>) => {\n for (const id of ids) ins.run(id);\n })(dirty);\n }\n const scope = incremental ? \" WHERE tc.session_id IN (SELECT id FROM _dirty_sec)\" : \"\";\n\n // Agent-owned config roots — the configured sources' `config_dir`s (seeded from the project config\n // file at ingest). Single source of truth for the write_outside_project allowlist, so relocated or\n // side-by-side installs (~/.claude, ~/.claude-isf, …) are covered without a hardcoded path pattern.\n const ownedConfigDirs = (db.prepare(\"SELECT config_dir FROM sources WHERE config_dir IS NOT NULL\").all() as Array<{ config_dir: string }>)\n .map((r) => r.config_dir.replace(/\\/$/, \"\"))\n .filter(Boolean);\n\n // Project path per (scoped) session — for outside-project checks.\n const projPath = new Map<string, string>();\n for (const r of db\n .prepare(\n `SELECT s.id id, p.path path FROM sessions s JOIN projects p ON p.id = s.project_id${\n incremental ? \" WHERE s.id IN (SELECT id FROM _dirty_sec)\" : \"\"\n }`,\n )\n .all() as Array<{ id: string; path: string }>) {\n projPath.set(r.id, r.path);\n }\n\n const rows = db\n .prepare(\n `SELECT tc.id, tc.session_id, tc.event_uuid, tc.turn_id, tc.tool_name, tc.input_json, tc.result_summary, tc.status\n FROM tool_calls tc${scope}`,\n )\n .all() as ToolRow[];\n\n const insert = db.prepare(\n `INSERT INTO findings (id, session_id, tool_call_id, event_uuid, turn_id, rule_id, category, framework_ref, severity, title, evidence, signals_json, detector_version)\n VALUES (@id, @session_id, @tool_call_id, @event_uuid, @turn_id, @rule_id, @category, @framework_ref, @severity, @title, @evidence, @signals_json, @detector_version)\n ON CONFLICT(id) DO NOTHING`,\n );\n const delScope = incremental\n ? \"DELETE FROM findings WHERE session_id IN (SELECT id FROM _dirty_sec)\"\n : \"DELETE FROM findings\";\n\n const tx = db.transaction(() => {\n db.exec(delScope);\n for (const row of rows) {\n const ctx = buildContext(row, projPath.get(row.session_id) ?? null, ownedConfigDirs);\n for (const rule of RULES) {\n if (!appliesTo(rule, ctx.toolName)) continue;\n const m = rule.test(ctx);\n if (!m) continue;\n let severity: Severity = m.severity ?? rule.baseSeverity;\n const mods: Record<string, unknown> = { ...(m.mods ?? {}) };\n // A failed/blocked tool call is an *attempt*, not an accomplished action — de-escalate one band.\n if (row.status === \"error\") {\n severity = bumpSeverity(severity, -1);\n mods.attempted = true;\n }\n const signals = {\n rule: rule.id,\n category: rule.category,\n framework_ref: rule.framework_ref,\n tool_name: ctx.toolName,\n base_severity: rule.baseSeverity,\n severity,\n status: row.status,\n modifiers: mods,\n detector_version: DETECTOR_VERSION,\n };\n insert.run({\n id: findingId(row.id, rule.id),\n session_id: row.session_id,\n tool_call_id: row.id,\n event_uuid: row.event_uuid,\n turn_id: row.turn_id,\n rule_id: rule.id,\n category: rule.category,\n framework_ref: rule.framework_ref,\n severity,\n title: rule.title,\n evidence: m.evidence,\n signals_json: JSON.stringify(signals),\n detector_version: DETECTOR_VERSION,\n });\n }\n }\n });\n tx();\n\n if (incremental) db.exec(\"DROP TABLE IF EXISTS _dirty_sec\");\n const count = (db.prepare(\"SELECT COUNT(*) n FROM findings\").get() as { n: number }).n;\n return { count, version: DETECTOR_VERSION };\n}\n","/**\n * Agent Lens — ingest engine (the DB-writing core of Stage 2), separated from the CLI bootstrap in\n * index.ts so it can be driven in-process by tests against an :memory: DB. index.ts handles argv/env,\n * the disk walk, and the incremental-skip check; everything here operates on an open DB + file content.\n */\nimport { createHash } from \"node:crypto\";\nimport type Database from \"better-sqlite3\";\nimport { packRaw, type SourceAdapter, type SourceFile, type TurnRow } from \"@agent-lens/core\";\nimport { type DB } from \"./db.js\";\n\ntype Stmt = Database.Statement<any[]>;\n\nexport interface IngestStats {\n files: number;\n skipped: number;\n malformed: number;\n newEvents: number;\n}\n\nexport function newStats(): IngestStats {\n return { files: 0, skipped: 0, malformed: 0, newEvents: 0 };\n}\n\nexport function preview(text: string): string {\n return text.replace(/\\s+/g, \" \").trim().slice(0, 140);\n}\n\nconst SKILL_INJECT_PREFIX = \"Base directory for this skill:\";\n\n/** Final name token of a skill id/dir, ignoring path and plugin prefix: \"plugin:foo\" / \".../foo\" → \"foo\". */\nfunction skillNameKey(name: string): string {\n const afterSlash = name.split(\"/\").pop() ?? name;\n const afterColon = afterSlash.split(\":\").pop() ?? afterSlash;\n return afterColon.trim();\n}\n\n/**\n * Parse a skill-body injection event. A Skill firing injects an isMeta user message of the shape\n * `Base directory for this skill: <abs path>/<name>\\n\\n<SKILL.md body>\\n\\nARGUMENTS: <args>`\n * into the transcript — the only place the real skill content appears. We split off the\n * Base-directory line (path differs per install) and the trailing ARGUMENTS block (per-call) so the\n * remaining body is stable across installs/args and can be content-hashed into a version.\n */\nexport function parseSkillInjection(\n text: string,\n): { baseDir: string | null; nameKey: string; body: string; args: string | null } | null {\n if (!text.startsWith(SKILL_INJECT_PREFIX)) return null;\n const firstNl = text.indexOf(\"\\n\");\n const firstLine = (firstNl >= 0 ? text.slice(0, firstNl) : text).slice(SKILL_INJECT_PREFIX.length).trim();\n const baseDir = firstLine || null;\n let body = firstNl >= 0 ? text.slice(firstNl + 1) : \"\";\n // Strip the trailing per-call ARGUMENTS block (use lastIndexOf — the body may mention \"ARGUMENTS\").\n let args: string | null = null;\n const argIdx = body.lastIndexOf(\"\\nARGUMENTS:\");\n if (argIdx >= 0) {\n args = body.slice(argIdx + \"\\nARGUMENTS:\".length).trim() || null;\n body = body.slice(0, argIdx);\n }\n return { baseDir, nameKey: baseDir ? skillNameKey(baseDir) : \"\", body: body.trim(), args };\n}\n\n/** First markdown heading (or first non-empty line) of a body, for list/detail display. */\nfunction skillSummary(body: string): string | null {\n for (const line of body.split(\"\\n\")) {\n const t = line.trim();\n if (!t) continue;\n return (t.startsWith(\"#\") ? t.replace(/^#+\\s*/, \"\").trim() : t).slice(0, 200) || null;\n }\n return null;\n}\n\n/** Content-addressed skill version id: hash of name + normalized body (matches the project's sha1 id convention). */\nfunction skillVersionId(name: string, body: string): string {\n return createHash(\"sha1\").update(name).update(\"\\0\").update(body).digest(\"hex\").slice(0, 16);\n}\n\n/**\n * Remove every session whose project path is in `excludedPaths` — plus its subagent descendants —\n * along with all dependent rows (events → FTS via trigger, token_usage, tool_calls, turns,\n * classifications, findings) and the ingest_state for its files. This is what makes the global exclude list\n * take effect on the NEXT ingest, incremental or full: add a project and its data leaves the DB.\n * Matches projects.path exactly or by path-prefix. Returns the number of sessions pruned.\n */\nexport function pruneExcluded(db: DB, excludedPaths: string[]): number {\n if (!excludedPaths.length) return 0;\n const projects = db.prepare(\"SELECT id, path FROM projects\").all() as Array<{ id: string; path: string }>;\n const projIds = projects.filter((p) => excludedPaths.some((e) => p.path === e || p.path.startsWith(e + \"/\"))).map((p) => p.id);\n if (!projIds.length) return 0;\n\n db.exec(\"DROP TABLE IF EXISTS _prune\");\n db.exec(\"CREATE TEMP TABLE _prune (id TEXT PRIMARY KEY)\");\n const insP = db.prepare(\"INSERT OR IGNORE INTO _prune (id) VALUES (?)\");\n const seed = db.prepare(`SELECT id FROM sessions WHERE project_id IN (${projIds.map(() => \"?\").join(\",\")})`).all(...projIds) as Array<{ id: string }>;\n db.transaction(() => {\n for (const s of seed) insP.run(s.id);\n })();\n // Transitive: subagents whose parent is being pruned (covers cross-project linkage). Fixpoint.\n const expand = db.prepare(\"INSERT OR IGNORE INTO _prune (id) SELECT id FROM sessions WHERE parent_session_id IN (SELECT id FROM _prune)\");\n const cnt = db.prepare(\"SELECT COUNT(*) n FROM _prune\");\n for (let prev = -1; ; ) {\n const n = (cnt.get() as { n: number }).n;\n if (n === prev) break;\n prev = n;\n expand.run();\n }\n const total = (cnt.get() as { n: number }).n;\n if (!total) {\n db.exec(\"DROP TABLE IF EXISTS _prune\");\n return 0;\n }\n const files = (db.prepare(\"SELECT DISTINCT source_file f FROM events WHERE session_id IN (SELECT id FROM _prune) AND source_file IS NOT NULL\").all() as Array<{ f: string }>).map((r) => r.f);\n\n db.pragma(\"foreign_keys = OFF\");\n db.transaction(() => {\n db.exec(\"DELETE FROM findings WHERE session_id IN (SELECT id FROM _prune)\");\n for (const t of [\"token_usage\", \"tool_calls\", \"turns\", \"events\"]) db.exec(`DELETE FROM ${t} WHERE session_id IN (SELECT id FROM _prune)`);\n db.exec(\"DELETE FROM classifications WHERE scope = 'session' AND target_id IN (SELECT id FROM _prune)\");\n db.exec(\"DELETE FROM sessions WHERE id IN (SELECT id FROM _prune)\");\n const delState = db.prepare(\"DELETE FROM ingest_state WHERE file_path = ?\");\n for (const f of files) delState.run(f);\n // Drop now-empty projects (an excluded project leaves no sessions behind).\n db.exec(\"DELETE FROM projects WHERE id NOT IN (SELECT DISTINCT project_id FROM sessions WHERE project_id IS NOT NULL)\");\n })();\n db.pragma(\"foreign_keys = ON\");\n db.exec(\"DROP TABLE IF EXISTS _prune\");\n return total;\n}\n\nfunction durationMs(start: string | null, end: string | null): number | null {\n if (!start || !end) return null;\n const a = Date.parse(start);\n const b = Date.parse(end);\n if (Number.isNaN(a) || Number.isNaN(b)) return null;\n return Math.max(0, b - a);\n}\n\n/** Group a session's events into turns and return the turns + a uuid→turn_id map. */\nexport function buildTurns(\n sessionId: string,\n rows: Array<{ uuid: string; type: string; is_sidechain: number; is_meta: number; timestamp: string | null; model: string | null; text: string | null }>,\n): { turns: TurnRow[]; eventTurn: Map<string, string> } {\n rows.sort((x, y) => (x.timestamp ?? \"\").localeCompare(y.timestamp ?? \"\") || x.uuid.localeCompare(y.uuid));\n const turns: TurnRow[] = [];\n const eventTurn = new Map<string, string>();\n let cur: TurnRow | null = null;\n let seq = 0;\n\n for (const e of rows) {\n // A turn starts at a user prompt with real text. Sidechain (subagent) sessions are entirely\n // sidechain — verified no file mixes main+sidechain events — so their task prompt starts a turn\n // too; we don't filter on is_sidechain here. Tool-result carrier messages have empty text.\n const isPrompt = e.type === \"user\" && e.is_meta === 0 && !!e.text && e.text.trim().length > 0;\n if (isPrompt) {\n cur = {\n id: `${sessionId}:${seq}`,\n session_id: sessionId,\n seq,\n user_event_uuid: e.uuid,\n prompt_preview: preview(e.text!),\n model: null,\n started_at: e.timestamp,\n ended_at: e.timestamp,\n duration_ms: null,\n };\n turns.push(cur);\n seq++;\n }\n if (cur) {\n eventTurn.set(e.uuid, cur.id);\n if (e.model) cur.model = e.model;\n if (e.timestamp && (!cur.ended_at || e.timestamp > cur.ended_at)) cur.ended_at = e.timestamp;\n }\n }\n for (const t of turns) t.duration_ms = durationMs(t.started_at, t.ended_at);\n return { turns, eventTurn };\n}\n\n/**\n * Rebuild derived tables (turns, turn_id linkage, subagent parent linkage, session aggregates) from\n * the idempotent events/tool_calls tables.\n *\n * `dirty` is the set of session ids touched by this ingest run (incremental). When provided, only the\n * affected sessions are rebuilt — but the set is first expanded to its **linkage neighborhood** (the\n * subagent children spawned by a dirty session, and the parent session that spawned a dirty sidechain)\n * via a fixpoint, so cross-session parent/child links stay correct even when parent and child\n * transcripts arrive in different runs (ADR-010). When `dirty` is null/undefined (`--full`), every\n * session is rebuilt — the migration/reset path. Returns the expanded id set (or null for the full\n * path) so `classify()` can reuse it without re-expanding.\n */\nexport function rebuildDerived(db: DB, dirty?: Set<string> | null): Set<string> | null {\n const incremental = dirty != null;\n // Scope fragments: empty for the full rebuild, else restrict to the materialized _dirty temp table.\n const bySession = incremental ? \" WHERE session_id IN (SELECT id FROM _dirty)\" : \"\";\n const byId = incremental ? \" WHERE id IN (SELECT id FROM _dirty)\" : \"\";\n const andId = incremental ? \" AND id IN (SELECT id FROM _dirty)\" : \"\";\n const andSelfId = incremental ? \" AND sessions.id IN (SELECT id FROM _dirty)\" : \"\";\n\n if (incremental) {\n db.exec(\"DROP TABLE IF EXISTS _dirty\");\n db.exec(\"CREATE TEMP TABLE _dirty (id TEXT PRIMARY KEY)\");\n const ins = db.prepare(\"INSERT OR IGNORE INTO _dirty (id) VALUES (?)\");\n db.transaction((ids: Iterable<string>) => {\n for (const id of ids) ins.run(id);\n })(dirty);\n // Fixpoint expansion to the linkage neighborhood: a dirty parent pulls in the children it spawned,\n // and a dirty child pulls in its spawner parent. Loop until the set stops growing (covers nested\n // subagents). Bounded by the session count.\n const expand = db.prepare(`\n INSERT OR IGNORE INTO _dirty (id)\n SELECT spawned_session_id FROM tool_calls\n WHERE session_id IN (SELECT id FROM _dirty) AND spawned_session_id IS NOT NULL\n UNION\n SELECT session_id FROM tool_calls\n WHERE spawned_session_id IN (SELECT id FROM _dirty)\n UNION\n -- structural (path-based) workflow linkage: a dirty parent pulls in its subagent children,\n -- and a dirty child pulls in its structural parent, so the fallback link stays correct when\n -- parent and child transcripts arrive in different runs (ADR-010).\n SELECT id FROM sessions\n WHERE spawn_parent_id IN (SELECT id FROM _dirty)\n UNION\n SELECT spawn_parent_id FROM sessions\n WHERE id IN (SELECT id FROM _dirty) AND spawn_parent_id IS NOT NULL\n `);\n const count = db.prepare(\"SELECT COUNT(*) n FROM _dirty\");\n let prev = -1;\n for (;;) {\n const n = (count.get() as { n: number }).n;\n if (n === prev) break;\n prev = n;\n expand.run();\n }\n }\n\n // Recompute turns from the (idempotent) events table.\n // Null referencing turn_ids BEFORE deleting turns (FK: events/token_usage/tool_calls/sessions -> turns).\n db.exec(`UPDATE events SET turn_id = NULL${bySession}`);\n db.exec(`UPDATE token_usage SET turn_id = NULL${bySession}`);\n db.exec(`UPDATE tool_calls SET turn_id = NULL${bySession}`);\n // Clear skill-version links for the scope so changed/removed bodies don't leave a stale link.\n db.exec(`UPDATE tool_calls SET skill_id = NULL${bySession}`);\n db.exec(`UPDATE sessions SET parent_turn_id = NULL${byId}`);\n db.exec(`DELETE FROM turns${bySession}`);\n\n const sessionIds = db.prepare(`SELECT id FROM sessions${byId}`).all() as Array<{ id: string }>;\n const selEvents = db.prepare(\n \"SELECT uuid, type, is_sidechain, is_meta, timestamp, model, text FROM events WHERE session_id = ?\",\n );\n const insTurn = db.prepare(\n `INSERT INTO turns (id, session_id, seq, user_event_uuid, prompt_preview, model, started_at, ended_at, duration_ms)\n VALUES (@id, @session_id, @seq, @user_event_uuid, @prompt_preview, @model, @started_at, @ended_at, @duration_ms)`,\n );\n const updEventTurn = db.prepare(\"UPDATE events SET turn_id = ? WHERE uuid = ?\");\n\n const tx = db.transaction(() => {\n for (const { id } of sessionIds) {\n const rows = selEvents.all(id) as any[];\n const { turns, eventTurn } = buildTurns(id, rows);\n for (const t of turns) insTurn.run(t);\n for (const [uuid, turnId] of eventTurn) updEventTurn.run(turnId, uuid);\n }\n });\n tx();\n\n // Propagate turn_id to dependent tables.\n db.exec(`UPDATE token_usage SET turn_id = (SELECT turn_id FROM events WHERE events.uuid = token_usage.event_uuid)${bySession}`);\n db.exec(`UPDATE tool_calls SET turn_id = (SELECT turn_id FROM events WHERE events.uuid = tool_calls.event_uuid)${bySession}`);\n\n // Link each Skill tool_call to a content-addressed skill *version*. A firing injects the full\n // SKILL.md body as an isMeta user event right after the launch; we pair each Skill tool_call with\n // the following injection (by skill-name token, ARGUMENTS as tiebreak), normalize + hash the body,\n // UPSERT the version, and stamp tool_calls.skill_id. Firings without a captured body keep skill_id\n // NULL (skill_name stays set). Runs over the same scoped session set as the turn rebuild.\n {\n const selEventsOrdered = db.prepare(\n \"SELECT uuid, type, is_meta, timestamp, text FROM events WHERE session_id = ?\",\n );\n const selSkillCalls = db.prepare(\n \"SELECT id, event_uuid, skill_name, input_json FROM tool_calls WHERE session_id = ? AND tool_name = 'Skill'\",\n );\n const upsertSkill = db.prepare(\n `INSERT INTO skills (id, name, base_dir, body, summary, body_bytes, first_seen, last_seen)\n VALUES (@id, @name, @base_dir, @body, @summary, @body_bytes, @ts, @ts)\n ON CONFLICT(id) DO UPDATE SET\n last_seen = MAX(last_seen, excluded.last_seen),\n first_seen = MIN(first_seen, excluded.first_seen),\n base_dir = excluded.base_dir`,\n );\n const linkCall = db.prepare(\"UPDATE tool_calls SET skill_id = ? WHERE id = ?\");\n\n const linkTx = db.transaction(() => {\n for (const { id: sid } of sessionIds) {\n const calls = selSkillCalls.all(sid) as Array<{ id: string; event_uuid: string | null; skill_name: string | null; input_json: string | null }>;\n if (!calls.length) continue;\n const events = (selEventsOrdered.all(sid) as Array<{ uuid: string; type: string; is_meta: number; timestamp: string | null; text: string | null }>).sort(\n (x, y) => (x.timestamp ?? \"\").localeCompare(y.timestamp ?? \"\") || x.uuid.localeCompare(y.uuid),\n );\n const callByEvent = new Map<string, typeof calls>();\n for (const c of calls) {\n if (!c.event_uuid) continue;\n const arr = callByEvent.get(c.event_uuid) ?? [];\n arr.push(c);\n callByEvent.set(c.event_uuid, arr);\n }\n // Queue of pending Skill calls awaiting their body injection, keyed by skill-name token.\n const pending = new Map<string, Array<{ id: string; args: string | null }>>();\n for (const e of events) {\n const here = callByEvent.get(e.uuid);\n if (here) {\n for (const c of here) {\n if (!c.skill_name) continue;\n const key = skillNameKey(c.skill_name);\n let args: string | null = null;\n try {\n args = c.input_json ? (JSON.parse(c.input_json).args ?? null) : null;\n if (typeof args === \"string\") args = args.trim() || null;\n else if (args != null) args = JSON.stringify(args);\n } catch {\n /* keep null */\n }\n const arr = pending.get(key) ?? [];\n arr.push({ id: c.id, args });\n pending.set(key, arr);\n }\n }\n if (e.is_meta !== 1 || e.type !== \"user\" || !e.text) continue;\n const inj = parseSkillInjection(e.text);\n if (!inj) continue;\n const queue = pending.get(inj.nameKey);\n if (!queue || !queue.length) continue;\n // Prefer a call whose args match the injection's ARGUMENTS; else take the earliest pending.\n let idx = inj.args != null ? queue.findIndex((q) => q.args === inj.args) : -1;\n if (idx < 0) idx = 0;\n const [match] = queue.splice(idx, 1);\n const c = calls.find((x) => x.id === match.id)!;\n const name = c.skill_name!;\n const vid = skillVersionId(name, inj.body);\n upsertSkill.run({\n id: vid,\n name,\n base_dir: inj.baseDir,\n body: inj.body,\n summary: skillSummary(inj.body),\n body_bytes: Buffer.byteLength(inj.body, \"utf8\"),\n ts: e.timestamp,\n });\n linkCall.run(vid, match.id);\n }\n }\n });\n linkTx();\n }\n\n // Link subagent (sidechain) sessions back to the parent turn/session that spawned them. The\n // deterministic key is the spawning Task/Agent tool_call's spawned_session_id (== this session id).\n db.exec(`\n UPDATE sessions SET\n parent_session_id = (SELECT tc.session_id FROM tool_calls tc WHERE tc.spawned_session_id = sessions.id),\n parent_turn_id = (SELECT tc.turn_id FROM tool_calls tc WHERE tc.spawned_session_id = sessions.id)\n WHERE EXISTS (SELECT 1 FROM tool_calls tc WHERE tc.spawned_session_id = sessions.id)${andSelfId}\n `);\n\n // Workflow fan-out carries no toolUseResult.agentId, so the link above never fires for it\n // (finding #1). But the launching Workflow tool_call's result DOES carry a run id (wf_<id>), and the\n // run's subagents nest under …/subagents/workflows/<runId>/ — so sessions.workflow_run_id matches\n // tool_calls.workflow_run_id. That gives workflow agents BOTH their parent session and the exact\n // launching turn. (LIMIT 1: a run id maps to a single Workflow tool_call.)\n db.exec(`\n UPDATE sessions SET\n parent_session_id = COALESCE(parent_session_id, (SELECT tc.session_id FROM tool_calls tc WHERE tc.workflow_run_id = sessions.workflow_run_id LIMIT 1)),\n parent_turn_id = COALESCE(parent_turn_id, (SELECT tc.turn_id FROM tool_calls tc WHERE tc.workflow_run_id = sessions.workflow_run_id LIMIT 1))\n WHERE sessions.workflow_run_id IS NOT NULL\n AND EXISTS (SELECT 1 FROM tool_calls tc WHERE tc.workflow_run_id = sessions.workflow_run_id)${andSelfId}\n `);\n\n // Final fallback for any sidechain still unlinked (e.g. its Workflow tool_call/runId wasn't\n // captured, or a non-workflow nested agent): attribute to the structural parent captured at ingest\n // from the transcript's directory location (<parent>/subagents/…) — the deterministic,\n // redaction-surviving signal. parent_turn_id stays NULL here. Tokens stay siloed → no double-count.\n db.exec(`\n UPDATE sessions SET parent_session_id = spawn_parent_id\n WHERE parent_session_id IS NULL\n AND spawn_parent_id IS NOT NULL\n AND spawn_parent_id IN (SELECT id FROM sessions)${andSelfId}\n `);\n\n // Recompute session aggregates from events/turns.\n db.exec(`\n UPDATE sessions SET\n event_count = (SELECT COUNT(*) FROM events e WHERE e.session_id = sessions.id),\n started_at = (SELECT MIN(timestamp) FROM events e WHERE e.session_id = sessions.id),\n ended_at = (SELECT MAX(timestamp) FROM events e WHERE e.session_id = sessions.id),\n turn_count = (SELECT COUNT(*) FROM turns t WHERE t.session_id = sessions.id),\n is_sidechain = (SELECT CASE WHEN MIN(is_sidechain) = 1 THEN 1 ELSE 0 END FROM events e WHERE e.session_id = sessions.id)${byId}\n `);\n db.exec(`\n UPDATE sessions SET duration_ms =\n CAST((julianday(ended_at) - julianday(started_at)) * 86400000 AS INTEGER)\n WHERE started_at IS NOT NULL AND ended_at IS NOT NULL${andId}\n `);\n\n // Prune phantom sessions: any with zero events is not a real transcript. discover() walks every\n // *.jsonl under projects/, which sweeps up non-transcript files that happen to live there — e.g.\n // a Workflow tool's `journal.jsonl` (lines carry no `uuid`, so they yield no events). insSessionStub\n // still created a row (and they all share the basename → one phantom \"journal\" session). A zero-event\n // session has no events/turns/token_usage/tool_calls referencing it, so the delete is FK-safe; the\n // orphaned-classification sweep keeps the (no-FK) classifications table from accumulating dead rows\n // (kept global — it is one row per session and must catch any now-missing target).\n db.exec(`DELETE FROM sessions WHERE event_count = 0${andId}`);\n db.exec(\"DELETE FROM classifications WHERE scope = 'session' AND target_id NOT IN (SELECT id FROM sessions)\");\n // Security findings for a now-missing session (kept global — detect() runs after this rebuild and\n // only rescans the dirty scope, so a vanished non-dirty session's rows must be swept here).\n db.exec(\"DELETE FROM findings WHERE session_id NOT IN (SELECT id FROM sessions)\");\n // Sweep skill versions no longer referenced by any tool_call. Full path only: an incremental run\n // only re-derives dirty sessions, so a version could still be referenced by a non-dirty session.\n if (!incremental) {\n db.exec(\"DELETE FROM skills WHERE id NOT IN (SELECT skill_id FROM tool_calls WHERE skill_id IS NOT NULL)\");\n }\n\n if (incremental) {\n const expanded = new Set(\n (db.prepare(\"SELECT id FROM _dirty\").all() as Array<{ id: string }>).map((r) => r.id),\n );\n db.exec(\"DROP TABLE IF EXISTS _dirty\");\n return expanded;\n }\n return null;\n}\n\nexport interface IngestStatements {\n insAgent: Stmt;\n insSource: Stmt;\n getState: Stmt;\n setState: Stmt;\n insEvent: Stmt;\n insTokens: Stmt;\n insTool: Stmt;\n patchTool: Stmt;\n upsertProject: Stmt;\n insSessionStub: Stmt;\n upsertSession: Stmt;\n}\n\nexport function prepareStatements(db: DB): IngestStatements {\n return {\n insAgent: db.prepare(\"INSERT OR IGNORE INTO agents (id, name, kind) VALUES (?, ?, 'cli')\"),\n insSource: db.prepare(\n `INSERT INTO sources (id, label, agent_id, config_dir) VALUES (@id, @label, @agent_id, @config_dir)\n ON CONFLICT(id) DO UPDATE SET label=excluded.label, agent_id=excluded.agent_id, config_dir=excluded.config_dir`,\n ),\n getState: db.prepare(\"SELECT size, mtime_ms, sha256, events_ingested FROM ingest_state WHERE file_path = ?\"),\n setState: db.prepare(\n `INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)\n VALUES (@file_path, @size, @mtime_ms, @sha256, @events_ingested, @ingested_at)\n ON CONFLICT(file_path) DO UPDATE SET\n size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256,\n events_ingested=excluded.events_ingested, ingested_at=excluded.ingested_at`,\n ),\n insEvent: db.prepare(\n `INSERT INTO events (uuid, session_id, turn_id, parent_uuid, seq, type, role, timestamp, model, is_sidechain, is_meta, text, raw_json, source_file)\n VALUES (@uuid, @session_id, @turn_id, @parent_uuid, @seq, @type, @role, @timestamp, @model, @is_sidechain, @is_meta, @text, @raw_json, @source_file)\n ON CONFLICT(uuid) DO NOTHING`,\n ),\n insTokens: db.prepare(\n // Bare ON CONFLICT (no target) so the insert is skipped on EITHER the event_uuid PK or the\n // unique (session_id, message_id) index — the latter collapses a response's repeated\n // per-content-block usage lines into a single row.\n `INSERT INTO token_usage (event_uuid, session_id, turn_id, message_id, model, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, service_tier)\n VALUES (@event_uuid, @session_id, @turn_id, @message_id, @model, @input_tokens, @output_tokens, @cache_creation_input_tokens, @cache_read_input_tokens, @service_tier)\n ON CONFLICT DO NOTHING`,\n ),\n insTool: db.prepare(\n `INSERT INTO tool_calls (id, event_uuid, session_id, turn_id, tool_name, caller, skill_name, skill_id, agent_type, spawned_session_id, workflow_run_id, workflow_name, resolved_model, status, total_duration_ms, total_tokens, total_tool_use_count, input_json, result_summary)\n VALUES (@id, @event_uuid, @session_id, @turn_id, @tool_name, @caller, @skill_name, @skill_id, @agent_type, @spawned_session_id, @workflow_run_id, @workflow_name, @resolved_model, @status, @total_duration_ms, @total_tokens, @total_tool_use_count, @input_json, @result_summary)\n ON CONFLICT(id) DO NOTHING`,\n ),\n patchTool: db.prepare(\n `UPDATE tool_calls SET\n status = COALESCE(@status, status),\n agent_type = COALESCE(@agent_type, agent_type),\n spawned_session_id = COALESCE(@spawned_session_id, spawned_session_id),\n workflow_run_id = COALESCE(@workflow_run_id, workflow_run_id),\n workflow_name = COALESCE(@workflow_name, workflow_name),\n resolved_model = COALESCE(@resolved_model, resolved_model),\n total_duration_ms = COALESCE(@total_duration_ms, total_duration_ms),\n total_tokens = COALESCE(@total_tokens, total_tokens),\n total_tool_use_count = COALESCE(@total_tool_use_count, total_tool_use_count),\n result_summary = COALESCE(@result_summary, result_summary)\n WHERE id = @tool_use_id`,\n ),\n upsertProject: db.prepare(\n `INSERT INTO projects (id, agent_id, path, encoded_dir, first_seen, last_seen)\n VALUES (@id, @agent_id, @path, @encoded_dir, @now, @now)\n ON CONFLICT(id) DO UPDATE SET last_seen = @now, encoded_dir = COALESCE(excluded.encoded_dir, encoded_dir)`,\n ),\n insSessionStub: db.prepare(\"INSERT OR IGNORE INTO sessions (id, agent_id, source_id) VALUES (?, ?, ?)\"),\n upsertSession: db.prepare(\n `INSERT INTO sessions (id, agent_id, source_id, project_id, slug, ai_title, cli_version, entrypoint, git_branch, spawn_parent_id, workflow_run_id)\n VALUES (@id, @agent_id, @source_id, @project_id, @slug, @ai_title, @cli_version, @entrypoint, @git_branch, @spawn_parent_id, @workflow_run_id)\n ON CONFLICT(id) DO UPDATE SET\n source_id = COALESCE(excluded.source_id, source_id),\n project_id = COALESCE(excluded.project_id, project_id),\n slug = COALESCE(excluded.slug, slug),\n ai_title = COALESCE(excluded.ai_title, ai_title),\n cli_version = COALESCE(excluded.cli_version, cli_version),\n entrypoint = COALESCE(excluded.entrypoint, entrypoint),\n git_branch = COALESCE(excluded.git_branch, git_branch),\n spawn_parent_id = COALESCE(excluded.spawn_parent_id, spawn_parent_id),\n workflow_run_id = COALESCE(excluded.workflow_run_id, workflow_run_id)`,\n ),\n };\n}\n\n/**\n * Ingest one transcript file's lines in a single transaction: parse each line via the adapter,\n * insert events/token_usage/tool_calls (deduped by their keys), patch tool results, and upsert the\n * session + project metadata. `lines` is an iterable of newline-stripped lines — the caller chooses\n * whole-file read (small files) or a streaming reader (large files), so this engine never holds the\n * full file as one string. `meta` carries the disk stats recorded into ingest_state (tests pass\n * synthetic values). Derived tables (turns, linkage, aggregates) are rebuilt separately afterwards.\n */\nexport function ingestFile(\n db: DB,\n stmts: IngestStatements,\n adapter: SourceAdapter,\n file: SourceFile,\n lines: Iterable<string>,\n meta: { size: number; mtimeMs: number; hash: string },\n now: string,\n stats: IngestStats,\n): void {\n const sessionMeta: Record<string, any> = {};\n let eventsInFile = 0;\n\n const tx = db.transaction(() => {\n // Ensure the session row exists before any event references it (FK).\n stmts.insSessionStub.run(file.sessionId, adapter.agentId, file.sourceId);\n let seq = 0;\n for (const line of lines) {\n if (!line.trim()) continue;\n let raw: unknown;\n try {\n raw = JSON.parse(line);\n } catch {\n stats.malformed++;\n continue;\n }\n const parsed = adapter.parseLine(raw, file, seq++);\n if (parsed.meta) Object.assign(sessionMeta, parsed.meta);\n if (parsed.event) {\n // raw_json is stored gzip-compressed (ADR-011); compress at this single write chokepoint so\n // adapters stay agent-agnostic and emit a plain string.\n const info = stmts.insEvent.run({ ...parsed.event, raw_json: packRaw(parsed.event.raw_json) });\n if (info.changes > 0) {\n stats.newEvents++;\n eventsInFile++;\n }\n }\n if (parsed.tokenUsage) stmts.insTokens.run(parsed.tokenUsage);\n if (parsed.toolCalls) for (const tc of parsed.toolCalls) stmts.insTool.run(tc);\n if (parsed.toolResults)\n for (const tr of parsed.toolResults)\n stmts.patchTool.run({\n tool_use_id: tr.tool_use_id,\n status: tr.status ?? null,\n agent_type: tr.agent_type ?? null,\n spawned_session_id: tr.spawned_session_id ?? null,\n workflow_run_id: tr.workflow_run_id ?? null,\n workflow_name: tr.workflow_name ?? null,\n resolved_model: tr.resolved_model ?? null,\n total_duration_ms: tr.total_duration_ms ?? null,\n total_tokens: tr.total_tokens ?? null,\n total_tool_use_count: tr.total_tool_use_count ?? null,\n result_summary: tr.result_summary ?? null,\n });\n }\n\n // Persist session + project metadata (survives later skip runs).\n let projectId: string | null = null;\n if (sessionMeta.cwd) {\n projectId = createHash(\"sha1\").update(`${adapter.agentId}\\0${sessionMeta.cwd}`).digest(\"hex\").slice(0, 16);\n stmts.upsertProject.run({\n id: projectId,\n agent_id: adapter.agentId,\n path: sessionMeta.cwd,\n encoded_dir: file.encodedDir || null,\n now,\n });\n }\n stmts.upsertSession.run({\n id: file.sessionId,\n agent_id: adapter.agentId,\n source_id: file.sourceId,\n project_id: projectId,\n slug: sessionMeta.slug ?? null,\n ai_title: sessionMeta.ai_title ?? null,\n cli_version: sessionMeta.cli_version ?? null,\n entrypoint: sessionMeta.entrypoint ?? null,\n git_branch: sessionMeta.git_branch ?? null,\n spawn_parent_id: file.parentSessionId ?? null,\n workflow_run_id: file.workflowRunId ?? null,\n });\n\n stmts.setState.run({\n file_path: file.path,\n size: meta.size,\n mtime_ms: Math.trunc(meta.mtimeMs),\n sha256: meta.hash,\n events_ingested: eventsInFile,\n ingested_at: now,\n });\n });\n tx();\n}\n","/**\n * Agent Lens — Stage 2 ingest of Workflow-tool result sidecars.\n *\n * The Workflow tool writes a JSON sidecar next to the launching session when a run finishes:\n * <archive>/<source>/projects/<enc>/<sessionId>/workflows/wf_<id>.json\n * The transcript only carries the \"launched in background\" ack for an async run, so for those runs\n * this file is the ONLY record of how the run finished — status (completed/failed), summary, the\n * returned result payload, the model, phase structure, per-item logs, and roll-up\n * tokens/tool-calls/duration/agent-count. We project those fields into `workflow_results` (verbatim,\n * like tool_calls.input_json — the DB is local; redaction is an export concern). The big `script` and\n * `args` are intentionally NOT re-stored: they already live on the launching Workflow tool_call.\n *\n * Idempotent: keyed by run id, UPSERT on re-ingest; a stat short-circuit (ingest_state) skips\n * unchanged files. Excluded projects are filtered out at discovery.\n */\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { DB } from \"./db.js\";\nimport { isExcludedArchivePath } from \"./redact.js\";\nimport { sha256 } from \"./fileread.js\";\n\nexport interface WorkflowIngestStats {\n files: number;\n upserted: number;\n skipped: number;\n malformed: number;\n}\n\nexport function newWorkflowStats(): WorkflowIngestStats {\n return { files: 0, upserted: 0, skipped: 0, malformed: 0 };\n}\n\n/** Recursively find `wf_*.json` sidecars living directly inside a `workflows/` directory. */\nfunction walkSidecars(dir: string, inWorkflows: boolean, out: string[]): void {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const p = join(dir, e.name);\n if (e.isDirectory()) walkSidecars(p, inWorkflows || e.name === \"workflows\", out);\n // Only the run's own result file (wf_<id>.json); agent-*.meta.json / scripts are handled elsewhere.\n else if (e.isFile() && inWorkflows && /^wf_.+\\.json$/.test(e.name)) out.push(p);\n }\n}\n\n/** All sidecar paths under a source archive (mirror first, then each .versions snapshot). */\nfunction discoverSidecars(sourceArchiveDir: string): string[] {\n const out: string[] = [];\n walkSidecars(join(sourceArchiveDir, \"projects\"), false, out);\n try {\n for (const ts of readdirSync(join(sourceArchiveDir, \".versions\"), { withFileTypes: true }))\n if (ts.isDirectory()) walkSidecars(join(sourceArchiveDir, ts.name, \"projects\"), false, out);\n } catch {\n /* no versions yet */\n }\n return out;\n}\n\n/** Launching session id = the path segment immediately before `workflows/`. */\nfunction sessionFromPath(path: string): string | null {\n const parts = path.split(\"/\");\n const i = parts.indexOf(\"workflows\");\n return i > 0 ? parts[i - 1] : null;\n}\n\n/** Epoch-ms number or ISO/date string → ISO string (null when unparseable/absent). */\nfunction toIso(x: unknown): string | null {\n if (typeof x === \"number\" && Number.isFinite(x)) return new Date(x).toISOString();\n if (typeof x === \"string\" && x) {\n const t = Date.parse(x);\n return Number.isNaN(t) ? null : new Date(t).toISOString();\n }\n return null;\n}\n\nconst jsonOrNull = (v: unknown): string | null => (v == null ? null : JSON.stringify(v));\nconst intOrNull = (v: unknown): number | null => (typeof v === \"number\" && Number.isFinite(v) ? Math.trunc(v) : null);\nconst strOrNull = (v: unknown): string | null => (typeof v === \"string\" && v ? v : null);\n\n/** Strip terminal styling the runner sometimes bakes into defaultModel — both real ANSI escapes\n * (`\\x1b[1m`) and the de-escaped literal form (`claude-opus-4-8[1m]`) — so the stored model is clean. */\nfunction cleanStr(v: unknown): string | null {\n const s = strOrNull(v);\n if (!s) return null;\n return s\n .replace(/\\x1b\\[[0-9;]*[A-Za-z]/g, \"\") // real ANSI escape sequences\n .replace(/\\[[0-9;]+m\\]?/g, \"\") // de-escaped leftovers: \"[1m\", \"[1m]\"\n .trim() || null;\n}\n\n/**\n * Ingest all workflow result sidecars for one source into `workflow_results`. `excludedDirs` are the\n * parsed exclude tokens (same filter the transcript walk uses). Returns nothing; mutates `stats`.\n */\nexport function ingestWorkflowResults(\n db: DB,\n sourceArchiveDir: string,\n sourceId: string,\n excludedDirs: string[],\n now: string,\n stats: WorkflowIngestStats,\n full: boolean,\n): void {\n let paths = discoverSidecars(sourceArchiveDir);\n if (excludedDirs.length) paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));\n // Mirror last so it wins over any older .versions snapshot on UPSERT (a run that went\n // running→completed diverges: the completed copy is in the mirror).\n paths.sort((a, b) => Number(a.includes(\"/.versions/\")) - Number(b.includes(\"/.versions/\")) || (a < b ? -1 : 1));\n\n const getState = db.prepare(\"SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?\");\n const setState = db.prepare(\n `INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)\n VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)\n ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`,\n );\n const upsert = db.prepare(\n `INSERT INTO workflow_results\n (run_id, source_id, session_id, task_id, workflow_name, status, summary, default_model,\n result_json, phases_json, logs_json, progress_json,\n agent_count, total_tokens, total_tool_calls, duration_ms, started_at, ended_at, ingested_at)\n VALUES\n (@run_id, @source_id, @session_id, @task_id, @workflow_name, @status, @summary, @default_model,\n @result_json, @phases_json, @logs_json, @progress_json,\n @agent_count, @total_tokens, @total_tool_calls, @duration_ms, @started_at, @ended_at, @ingested_at)\n ON CONFLICT(run_id) DO UPDATE SET\n source_id=excluded.source_id, session_id=excluded.session_id, task_id=excluded.task_id,\n workflow_name=excluded.workflow_name, status=excluded.status, summary=excluded.summary,\n default_model=excluded.default_model, result_json=excluded.result_json, phases_json=excluded.phases_json,\n logs_json=excluded.logs_json, progress_json=excluded.progress_json, agent_count=excluded.agent_count,\n total_tokens=excluded.total_tokens, total_tool_calls=excluded.total_tool_calls,\n duration_ms=excluded.duration_ms, started_at=excluded.started_at, ended_at=excluded.ended_at,\n ingested_at=excluded.ingested_at`,\n );\n\n for (const path of paths) {\n stats.files++;\n const st = statSync(path);\n const mtimeMs = Math.trunc(st.mtimeMs);\n const prev = full ? undefined : (getState.get(path) as { size: number; mtime_ms: number; sha256: string } | undefined);\n if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {\n stats.skipped++;\n continue;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(path);\n } catch {\n stats.malformed++;\n continue;\n }\n const hash = sha256(buf);\n if (prev && prev.sha256 === hash) {\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.skipped++;\n continue;\n }\n\n let d: any;\n try {\n d = JSON.parse(buf.toString(\"utf8\"));\n } catch {\n stats.malformed++;\n continue;\n }\n if (!d || typeof d !== \"object\") {\n stats.malformed++;\n continue;\n }\n\n const runId = strOrNull(d.runId) ?? basename(path, \".json\");\n const startedAt = toIso(d.startTime) ?? toIso(d.timestamp);\n const durationMs = intOrNull(d.durationMs);\n const endedAt = startedAt && durationMs != null ? new Date(Date.parse(startedAt) + durationMs).toISOString() : null;\n\n upsert.run({\n run_id: runId,\n source_id: sourceId,\n session_id: sessionFromPath(path),\n task_id: strOrNull(d.taskId),\n workflow_name: strOrNull(d.workflowName),\n status: strOrNull(d.status),\n summary: strOrNull(d.summary),\n default_model: cleanStr(d.defaultModel),\n result_json: jsonOrNull(d.result),\n phases_json: Array.isArray(d.phases) ? JSON.stringify(d.phases) : null,\n logs_json: Array.isArray(d.logs) ? JSON.stringify(d.logs) : null,\n progress_json: Array.isArray(d.workflowProgress) ? JSON.stringify(d.workflowProgress) : null,\n agent_count: intOrNull(d.agentCount),\n total_tokens: intOrNull(d.totalTokens),\n total_tool_calls: intOrNull(d.totalToolCalls),\n duration_ms: durationMs,\n started_at: startedAt,\n ended_at: endedAt,\n ingested_at: now,\n });\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.upserted++;\n }\n}\n","/**\n * Agent Lens — metric-preserving transcript redactor (validation Layer 4).\n *\n * Produces a privacy-safe corpus that is realistic enough to validate the whole compute pipeline\n * end-to-end, by an explicit contract:\n *\n * PRESERVE (so metrics are identical to the raw input): uuid, parentUuid, type, timestamp,\n * isSidechain, isMeta, agentId (opaque hex, drives subagent linkage), message.{role,id,model,\n * usage,stop_reason}, toolUseResult numeric/linkage fields, tool_use {id,name}, and — crucially —\n * the *line count* of Edit/Write payloads + the *distinctness & extension* of file paths (these\n * feed the LoC / files complexity sub-scores).\n *\n * REDACT (deny-by-default): all message text/thinking, tool_result content, cwd & file paths\n * (pseudonymized, usernames stripped), slug/aiTitle/gitBranch, and any other free text. Stable\n * irreversible pseudonyms keep joins/counts intact (same input → same pseudonym).\n *\n * Token accounting, cost, durations, counts, subagent linkage, and the complexity *score* are\n * therefore identical between raw and redacted. The classifier *category* is text-derived and is\n * NOT preserved (validated separately by golden fixtures) — callers must exclude it from oracle\n * equality checks.\n *\n * HARD RULES (see memory test-corpus-redaction): never emit data from the agent-lens project\n * itself; scrub project names, paths, users, hosts, secrets. The CLI filters agent-lens sessions\n * and runs a final leak-scan before writing.\n */\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { parseSkillInjection } from \"./pipeline.js\";\n\nconst PLACEHOLDER = \"[redacted]\";\n\n/** Lines in a string, matching classify.ts/countLines exactly (trailing newline doesn't add a line). */\nfunction countLines(s: unknown): number {\n if (typeof s !== \"string\" || s.length === 0) return 0;\n const trimmed = s.endsWith(\"\\n\") ? s.slice(0, -1) : s;\n return trimmed.length === 0 ? 0 : trimmed.split(\"\\n\").length;\n}\n/** A dummy string whose countLines() == n (so LoC sub-scores are preserved). */\nfunction dummyLines(n: number): string {\n return n <= 0 ? \"\" : Array(n).fill(\"x\").join(\"\\n\");\n}\n\nexport class Redactor {\n private cache = new Map<string, string>();\n constructor(private salt: string = randomBytes(8).toString(\"hex\")) {}\n\n private hash(s: string, n = 6): string {\n return createHash(\"sha256\").update(this.salt).update(s).digest(\"hex\").slice(0, n);\n }\n private stable(kind: string, s: string): string {\n const key = `${kind}:${s}`;\n let v = this.cache.get(key);\n if (!v) {\n v = `${kind}-${this.hash(key)}`;\n this.cache.set(key, v);\n }\n return v;\n }\n\n /** Pseudonymize a path: strip the user's home, pseudonymize each segment, keep depth + extension. */\n path(p: unknown): string | null {\n if (typeof p !== \"string\" || !p) return typeof p === \"string\" ? p : null;\n const abs = p.startsWith(\"/\");\n const segs = p.split(\"/\").filter(Boolean);\n const out: string[] = [];\n for (let i = 0; i < segs.length; i++) {\n const seg = segs[i]!;\n // Drop the \"<user>\" right after a home root so usernames never survive.\n if ((segs[i - 1] === \"home\" || segs[i - 1] === \"Users\") && i >= 1) {\n out.push(\"user\");\n continue;\n }\n if (seg === \"home\" || seg === \"Users\") {\n out.push(seg);\n continue;\n }\n const last = i === segs.length - 1;\n const dot = last ? seg.lastIndexOf(\".\") : -1;\n if (dot > 0) out.push(`file-${this.hash(seg)}${seg.slice(dot)}`); // keep extension\n else out.push(this.stable(\"dir\", seg));\n }\n return (abs ? \"/\" : \"\") + out.join(\"/\");\n }\n\n /** Pseudonymize a free-form identifier (slug, label, branch). */\n ident(kind: string, s: unknown): string {\n return typeof s === \"string\" && s ? this.stable(kind, s) : `${kind}-none`;\n }\n\n /** Replace free text with a placeholder, PRESERVING emptiness (turn detection depends on it). */\n private text(s: unknown): string {\n return typeof s === \"string\" && s.length > 0 ? PLACEHOLDER : \"\";\n }\n\n /**\n * Preserve a skill-body injection (\"Base directory for this skill: …\\n\\n<SKILL.md body>\\n\\nARGUMENTS:…\")\n * instead of redacting it — the body is the only real skill content and drives version tracking.\n * We strip the per-call ARGUMENTS block, replace the home-path base-dir line with a non-leaking\n * `/skills/<name>` (keeping the skill-name tail so ingest still links it), keep the body verbatim,\n * and fail closed: if the result trips the leak scan, return null so the caller fully redacts it.\n * Returns null when `s` is not a skill injection.\n */\n private skillText(s: unknown): string | null {\n if (typeof s !== \"string\") return null;\n const inj = parseSkillInjection(s);\n if (!inj) return null;\n const nameTail = (inj.baseDir ?? \"\").split(\"/\").filter(Boolean).pop() ?? \"\";\n const rebuilt = `Base directory for this skill: /skills/${nameTail}\\n\\n${inj.body}\\n`;\n return findLeak(rebuilt) ? null : rebuilt;\n }\n\n private toolInput(name: string, input: any): any {\n if (!input || typeof input !== \"object\") return {};\n const out: any = {};\n // Skill name (skill_name metric); only the Skill tool's command is non-sensitive enough to keep.\n if (typeof input.skill === \"string\") out.skill = input.skill;\n if (name === \"Skill\" && typeof input.command === \"string\") out.command = input.command;\n // Subagent type (agent_type metric).\n if ((name === \"Task\" || name === \"Agent\") && typeof input.subagent_type === \"string\") out.subagent_type = input.subagent_type;\n // LoC-bearing payloads: keep line counts + a pseudonymized path with its extension.\n if (name === \"Write\") {\n out.file_path = this.path(input.file_path);\n out.content = dummyLines(countLines(input.content));\n } else if (name === \"Edit\") {\n out.file_path = this.path(input.file_path);\n out.old_string = dummyLines(countLines(input.old_string));\n out.new_string = dummyLines(countLines(input.new_string));\n }\n return out;\n }\n\n private block(b: any): any {\n if (!b || typeof b !== \"object\") return b;\n switch (b.type) {\n case \"text\":\n return { type: \"text\", text: this.skillText(b.text) ?? this.text(b.text) };\n case \"thinking\":\n return { type: \"thinking\", thinking: this.text(b.thinking) };\n case \"tool_use\":\n return { type: \"tool_use\", id: b.id, name: b.name, input: this.toolInput(String(b.name), b.input), ...(b.caller ? { caller: b.caller } : {}) };\n case \"tool_result\":\n return { type: \"tool_result\", tool_use_id: b.tool_use_id, content: PLACEHOLDER, ...(b.is_error ? { is_error: true } : {}) };\n default:\n return { type: b.type };\n }\n }\n\n private message(m: any): any {\n if (!m || typeof m !== \"object\") return m;\n const out: any = {};\n for (const k of [\"role\", \"id\", \"model\", \"type\", \"usage\", \"stop_reason\", \"stop_sequence\"]) if (k in m) out[k] = m[k];\n if (typeof m.content === \"string\") out.content = this.skillText(m.content) ?? this.text(m.content);\n else if (Array.isArray(m.content)) out.content = m.content.map((b: any) => this.block(b));\n return out;\n }\n\n /** Redact one parsed transcript line (object). */\n line(r: any): any {\n if (!r || typeof r !== \"object\") return r;\n const out: any = {};\n // Structural / metric-bearing scalars kept verbatim.\n for (const k of [\"uuid\", \"parentUuid\", \"type\", \"timestamp\", \"isSidechain\", \"isMeta\", \"agentId\", \"userType\", \"version\"]) if (k in r) out[k] = r[k];\n if (typeof r.cwd === \"string\") out.cwd = this.path(r.cwd);\n if (typeof r.gitBranch === \"string\") out.gitBranch = this.ident(\"branch\", r.gitBranch);\n if (typeof r.slug === \"string\") out.slug = this.ident(\"slug\", r.slug);\n if (r.type === \"ai-title\") out.aiTitle = \"Redacted session\";\n if (r.toolUseResult && typeof r.toolUseResult === \"object\") {\n const t = r.toolUseResult;\n const tu: any = {};\n for (const k of [\"status\", \"agentType\", \"agentId\", \"resolvedModel\", \"totalDurationMs\", \"totalTokens\", \"totalToolUseCount\"]) if (k in t) tu[k] = t[k];\n out.toolUseResult = tu;\n }\n if (\"message\" in r) out.message = this.message(r.message);\n return out;\n }\n\n /** Redact a whole JSONL transcript (string in, string out). Malformed lines are dropped. */\n transcript(content: string): string {\n const lines: string[] = [];\n for (const raw of content.split(\"\\n\")) {\n if (!raw.trim()) continue;\n let obj: any;\n try {\n obj = JSON.parse(raw);\n } catch {\n continue;\n }\n lines.push(JSON.stringify(this.line(obj)));\n }\n return lines.join(\"\\n\") + \"\\n\";\n }\n}\n\n// Canonical Claude Code project-dir encoder now lives in core so collect + ingest agree exactly.\nexport { encodeProjectPath } from \"@agent-lens/core\";\nimport { encodeProjectPath } from \"@agent-lens/core\";\n\n/** Parse the AGENT_LENS_REDACT_EXCLUDE CSV of real project paths into encoded-dir prefixes. */\nexport function parseExcludes(csv: string | undefined | null): string[] {\n return (csv ?? \"\")\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n .map(encodeProjectPath);\n}\n\n/** True if `encodedDir` is (or sits under) any excluded project path — used to keep data out of the corpus. */\nexport function isExcludedDir(encodedDir: string, excluded: string[]): boolean {\n return excluded.some((e) => encodedDir === e || encodedDir.startsWith(e + \"-\"));\n}\n\n/**\n * True if an archive file path belongs to an excluded project. Matches the `/projects/<encodedDir>/`\n * segment, so it catches both the main transcript AND its nested `<UUID>/subagents/agent-*.jsonl`.\n * `excluded` is the encoded-dir list from parseExcludes().\n */\nexport function isExcludedArchivePath(filePath: string, excluded: string[]): boolean {\n return excluded.some((e) => filePath.includes(`/projects/${e}/`));\n}\n\n/** Patterns that must NEVER appear in redacted output (final defense-in-depth leak scan). */\nexport const LEAK_PATTERNS: Array<{ name: string; re: RegExp }> = [\n { name: \"email\", re: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/ },\n { name: \"home-path-with-user\", re: /\\/(?:home|Users)\\/(?!user\\b)[A-Za-z0-9._-]+/ },\n { name: \"ipv4\", re: /\\b\\d{1,3}(?:\\.\\d{1,3}){3}\\b/ },\n { name: \"url\", re: /https?:\\/\\/[^\\s\"']+/ },\n { name: \"aws-key\", re: /AKIA[0-9A-Z]{16}/ },\n { name: \"bearer/token\", re: /\\b(?:sk|pk|ghp|gho|xox[abp])[-_][A-Za-z0-9]{8,}/ },\n];\n\n/** Return the first leak found in `text`, or null. Used to fail closed before writing the corpus. */\nexport function findLeak(text: string): { name: string; sample: string } | null {\n for (const { name, re } of LEAK_PATTERNS) {\n const m = re.exec(text);\n if (m) return { name, sample: m[0].slice(0, 40) };\n }\n return null;\n}\n","/**\n * Agent Lens — transcript file reading (ADR-010, impact 4).\n *\n * Small files are read whole (fast path); files larger than STREAM_THRESHOLD are streamed in fixed\n * chunks so ingest memory stays bounded regardless of transcript size. The streaming SHA-256 is\n * byte-identical to hashing a whole-file `readFileSync` buffer, so the incremental-skip check is\n * unaffected by which path a file takes. Kept separate from the CLI bootstrap so it is unit-testable.\n */\nimport { createHash } from \"node:crypto\";\nimport { openSync, readSync, closeSync } from \"node:fs\";\nimport { StringDecoder } from \"node:string_decoder\";\n\nexport const STREAM_THRESHOLD = 8 * 1024 * 1024;\nconst CHUNK = 64 * 1024;\n\n/** SHA-256 hex of a buffer. */\nexport function sha256(buf: Buffer): string {\n return createHash(\"sha256\").update(buf).digest(\"hex\");\n}\n\n/** SHA-256 hex of a file's bytes, computed by streaming. Equals sha256(readFileSync(path)). */\nexport function sha256File(path: string): string {\n const fd = openSync(path, \"r\");\n const h = createHash(\"sha256\");\n const buf = Buffer.allocUnsafe(CHUNK);\n try {\n let n: number;\n while ((n = readSync(fd, buf, 0, CHUNK, null)) > 0) h.update(buf.subarray(0, n));\n } finally {\n closeSync(fd);\n }\n return h.digest(\"hex\");\n}\n\n/**\n * Lazily yield a file's lines (newline-stripped) by streaming. A StringDecoder handles multibyte\n * characters split across chunk boundaries. Holds at most one line at a time, never the whole file.\n * Yields the same sequence as `readFileSync(path).toString(\"utf8\").split(\"\\n\")` minus the trailing\n * empty element a final newline would produce (ingest skips blank lines anyway).\n */\nexport function* streamLines(path: string): Generator<string> {\n const fd = openSync(path, \"r\");\n const decoder = new StringDecoder(\"utf8\");\n const buf = Buffer.allocUnsafe(CHUNK);\n let leftover = \"\";\n try {\n let n: number;\n while ((n = readSync(fd, buf, 0, CHUNK, null)) > 0) {\n leftover += decoder.write(buf.subarray(0, n));\n let idx: number;\n while ((idx = leftover.indexOf(\"\\n\")) !== -1) {\n yield leftover.slice(0, idx);\n leftover = leftover.slice(idx + 1);\n }\n }\n leftover += decoder.end();\n if (leftover.length > 0) yield leftover;\n } finally {\n closeSync(fd);\n }\n}\n","/**\n * Agent Lens — Stage 2 ingest of per-subagent metadata sidecars.\n *\n * When a subagent (Task/Agent, or a Workflow fan-out agent) runs, Claude Code writes a metadata\n * sidecar next to its transcript:\n * <archive>/<source>/projects/<enc>/<sessionId>/subagents/agent-<id>.meta.json\n * carrying { agentType, description, spawnDepth?, toolUseId }. This is the authoritative source of a\n * subagent's type and human title — Workflow fan-out agents carry no subagent_type on the launching\n * tool_call, so ~700 of them are otherwise untyped. The filename stem `agent-<id>` IS the subagent's\n * session id (the same id `discover()` assigns the transcript), so we key `session_meta` on it and\n * LEFT JOIN at read time (no dependency on the session row existing yet).\n *\n * Idempotent: keyed by session id, UPSERT on re-ingest; a stat short-circuit (ingest_state) skips\n * unchanged files. Excluded projects are filtered out at discovery. Mirrors `workflows.ts`.\n */\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { DB } from \"./db.js\";\nimport { isExcludedArchivePath } from \"./redact.js\";\nimport { sha256 } from \"./fileread.js\";\n\nexport interface MetaIngestStats {\n files: number;\n upserted: number;\n skipped: number;\n malformed: number;\n}\n\nexport function newMetaStats(): MetaIngestStats {\n return { files: 0, upserted: 0, skipped: 0, malformed: 0 };\n}\n\n/** Recursively find `agent-*.meta.json` sidecars living directly inside a `subagents/` directory. */\nfunction walkMeta(dir: string, inSubagents: boolean, out: string[]): void {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const p = join(dir, e.name);\n if (e.isDirectory()) walkMeta(p, inSubagents || e.name === \"subagents\", out);\n else if (e.isFile() && inSubagents && /^agent-.+\\.meta\\.json$/.test(e.name)) out.push(p);\n }\n}\n\n/** All meta paths under a source archive (mirror first, then each .versions snapshot). */\nfunction discoverMeta(sourceArchiveDir: string): string[] {\n const out: string[] = [];\n walkMeta(join(sourceArchiveDir, \"projects\"), false, out);\n try {\n for (const ts of readdirSync(join(sourceArchiveDir, \".versions\"), { withFileTypes: true }))\n if (ts.isDirectory()) walkMeta(join(sourceArchiveDir, ts.name, \"projects\"), false, out);\n } catch {\n /* no versions yet */\n }\n return out;\n}\n\nconst intOrNull = (v: unknown): number | null => (typeof v === \"number\" && Number.isFinite(v) ? Math.trunc(v) : null);\nconst strOrNull = (v: unknown): string | null => (typeof v === \"string\" && v ? v : null);\n\n/**\n * Ingest all subagent meta sidecars for one source into `session_meta`. `excludedDirs` are the parsed\n * exclude tokens (same filter the transcript walk uses). Returns nothing; mutates `stats`.\n */\nexport function ingestSubagentMeta(\n db: DB,\n sourceArchiveDir: string,\n sourceId: string,\n excludedDirs: string[],\n now: string,\n stats: MetaIngestStats,\n full: boolean,\n): void {\n let paths = discoverMeta(sourceArchiveDir);\n if (excludedDirs.length) paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));\n // Mirror last so it wins over any older .versions snapshot on UPSERT.\n paths.sort((a, b) => Number(a.includes(\"/.versions/\")) - Number(b.includes(\"/.versions/\")) || (a < b ? -1 : 1));\n\n const getState = db.prepare(\"SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?\");\n const setState = db.prepare(\n `INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)\n VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)\n ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`,\n );\n const upsert = db.prepare(\n `INSERT INTO session_meta\n (session_id, source_id, agent_type, agent_description, spawn_depth, tool_use_id, ingested_at)\n VALUES\n (@session_id, @source_id, @agent_type, @agent_description, @spawn_depth, @tool_use_id, @ingested_at)\n ON CONFLICT(session_id) DO UPDATE SET\n source_id=excluded.source_id, agent_type=excluded.agent_type,\n agent_description=excluded.agent_description, spawn_depth=excluded.spawn_depth,\n tool_use_id=excluded.tool_use_id, ingested_at=excluded.ingested_at`,\n );\n\n for (const path of paths) {\n stats.files++;\n const st = statSync(path);\n const mtimeMs = Math.trunc(st.mtimeMs);\n const prev = full ? undefined : (getState.get(path) as { size: number; mtime_ms: number; sha256: string } | undefined);\n if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {\n stats.skipped++;\n continue;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(path);\n } catch {\n stats.malformed++;\n continue;\n }\n const hash = sha256(buf);\n if (prev && prev.sha256 === hash) {\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.skipped++;\n continue;\n }\n\n let d: any;\n try {\n d = JSON.parse(buf.toString(\"utf8\"));\n } catch {\n stats.malformed++;\n continue;\n }\n if (!d || typeof d !== \"object\") {\n stats.malformed++;\n continue;\n }\n\n // Filename stem is the subagent's session id: `agent-<id>.meta.json` → `agent-<id>`.\n const sessionId = basename(path, \".meta.json\");\n upsert.run({\n session_id: sessionId,\n source_id: sourceId,\n agent_type: strOrNull(d.agentType),\n agent_description: strOrNull(d.description),\n spawn_depth: intOrNull(d.spawnDepth),\n tool_use_id: strOrNull(d.toolUseId),\n ingested_at: now,\n });\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.upserted++;\n }\n}\n","/**\n * Agent Lens — Stage 2 ingest of spilled full tool-result files.\n *\n * When a tool's output is too large for the transcript, Claude Code writes the full output to disk and\n * keeps only a short summary in the transcript naming the file:\n * <archive>/<source>/projects/<enc>/<sessionId>/tool-results/<name>.txt\n * (the summary reads \"Output too large (…). Full output saved to: …/tool-results/<name>.txt\"). The\n * basename <name> is NOT the tool_use id — it's a short random token — so we key `tool_results` on\n * (session_id, name) and join at read time by parsing that marker out of tool_calls.result_summary.\n *\n * Idempotent: keyed by (session_id, name), UPSERT on re-ingest; a stat short-circuit (ingest_state)\n * skips unchanged files. Excluded projects are filtered out at discovery. Mirrors `workflows.ts`.\n */\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { DB } from \"./db.js\";\nimport { isExcludedArchivePath } from \"./redact.js\";\nimport { sha256 } from \"./fileread.js\";\n\nexport interface ToolResultIngestStats {\n files: number;\n upserted: number;\n skipped: number;\n malformed: number;\n}\n\nexport function newToolResultStats(): ToolResultIngestStats {\n return { files: 0, upserted: 0, skipped: 0, malformed: 0 };\n}\n\n/** Recursively find `*.txt` files living directly inside a `tool-results/` directory. */\nfunction walkResults(dir: string, inResults: boolean, out: string[]): void {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const p = join(dir, e.name);\n if (e.isDirectory()) walkResults(p, inResults || e.name === \"tool-results\", out);\n else if (e.isFile() && inResults && e.name.endsWith(\".txt\")) out.push(p);\n }\n}\n\n/** All tool-result paths under a source archive (mirror first, then each .versions snapshot). */\nfunction discoverResults(sourceArchiveDir: string): string[] {\n const out: string[] = [];\n walkResults(join(sourceArchiveDir, \"projects\"), false, out);\n try {\n for (const ts of readdirSync(join(sourceArchiveDir, \".versions\"), { withFileTypes: true }))\n if (ts.isDirectory()) walkResults(join(sourceArchiveDir, ts.name, \"projects\"), false, out);\n } catch {\n /* no versions yet */\n }\n return out;\n}\n\n/** Session id = the path segment immediately before `tool-results/`. */\nfunction sessionFromPath(path: string): string | null {\n const parts = path.split(\"/\");\n const i = parts.indexOf(\"tool-results\");\n return i > 0 ? parts[i - 1] : null;\n}\n\n/**\n * Ingest all spilled tool-result files for one source into `tool_results`. `excludedDirs` are the\n * parsed exclude tokens (same filter the transcript walk uses). Returns nothing; mutates `stats`.\n */\nexport function ingestToolResults(\n db: DB,\n sourceArchiveDir: string,\n excludedDirs: string[],\n now: string,\n stats: ToolResultIngestStats,\n full: boolean,\n): void {\n let paths = discoverResults(sourceArchiveDir);\n if (excludedDirs.length) paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));\n // Mirror last so it wins over any older .versions snapshot on UPSERT.\n paths.sort((a, b) => Number(a.includes(\"/.versions/\")) - Number(b.includes(\"/.versions/\")) || (a < b ? -1 : 1));\n\n const getState = db.prepare(\"SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?\");\n const setState = db.prepare(\n `INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)\n VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)\n ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`,\n );\n const upsert = db.prepare(\n `INSERT INTO tool_results (session_id, name, path, bytes, text, ingested_at)\n VALUES (@session_id, @name, @path, @bytes, @text, @ingested_at)\n ON CONFLICT(session_id, name) DO UPDATE SET\n path=excluded.path, bytes=excluded.bytes, text=excluded.text, ingested_at=excluded.ingested_at`,\n );\n\n for (const path of paths) {\n const sessionId = sessionFromPath(path);\n if (!sessionId) continue; // outside the expected …/<sessionId>/tool-results/ layout\n stats.files++;\n const st = statSync(path);\n const mtimeMs = Math.trunc(st.mtimeMs);\n const prev = full ? undefined : (getState.get(path) as { size: number; mtime_ms: number; sha256: string } | undefined);\n if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {\n stats.skipped++;\n continue;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(path);\n } catch {\n stats.malformed++;\n continue;\n }\n const hash = sha256(buf);\n if (prev && prev.sha256 === hash) {\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.skipped++;\n continue;\n }\n\n upsert.run({\n session_id: sessionId,\n name: basename(path, \".txt\"),\n path,\n bytes: st.size,\n text: buf.toString(\"utf8\"),\n ingested_at: now,\n });\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.upserted++;\n }\n}\n","/**\n * Agent Lens — Stage 3 local server, as an importable function (ADR-005: 127.0.0.1 only, no egress).\n *\n * `startServer` is the library entrypoint so the unified `agent-lens` CLI can bundle and call it\n * in-process; the thin bin in index.ts just forwards to it. Read-only REST over the SQLite store +\n * Markdown export, and serves the built web SPA. (env: AGENT_LENS_DB, AGENT_LENS_PORT, AGENT_LENS_HOST)\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { findRepoRoot, resolveDataDir, resolveWebDist } from \"@agent-lens/core\";\nimport { openReadonly } from \"./db.js\";\nimport { createApp } from \"./app.js\";\n\nexport async function startServer(): Promise<void> {\n // Resolve relative to this module: in dev it's packages/server/dist; bundled into the CLI it's the\n // published package root (web SPA sits at ../web, data falls back to the per-user dir).\n const here = dirname(fileURLToPath(import.meta.url));\n const repoRoot = findRepoRoot(here);\n const dbPath = process.env.AGENT_LENS_DB || join(resolveDataDir(repoRoot), \"agent-lens.db\");\n const host = process.env.AGENT_LENS_HOST || \"127.0.0.1\"; // loopback only by default\n const port = Number(process.env.AGENT_LENS_PORT || 4477);\n const webDist = resolveWebDist(here, repoRoot);\n\n if (host !== \"127.0.0.1\" && host !== \"localhost\" && !process.env.AGENT_LENS_ALLOW_NONLOCAL) {\n console.error(\n `agent-lens-server: refusing to bind non-loopback host '${host}' (privacy). ` +\n `Set AGENT_LENS_ALLOW_NONLOCAL=1 to override.`,\n );\n process.exit(1);\n }\n if (!existsSync(dbPath)) {\n console.error(`agent-lens-server: db not found: ${dbPath} (run ingest first)`);\n process.exit(1);\n }\n\n // Triage store (ADR-018) sits beside the analytics DB and is never touched by ingest, so user triage\n // survives `ingest --full`. Opened read-write by createApp; the analytics handle stays read-only.\n const triageDbPath = process.env.AGENT_LENS_TRIAGE_DB || join(dirname(dbPath), \"triage.db\");\n\n const db = openReadonly(dbPath);\n const app = await createApp(db, { webDist, triageDbPath });\n\n try {\n await app.listen({ host, port });\n console.log(`agent-lens-server: http://${host}:${port} (db: ${dbPath})`);\n if (!existsSync(webDist)) console.log(\" note: web SPA not built — run `pnpm --filter @agent-lens/web build`\");\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n}\n","import Database from \"better-sqlite3\";\nimport { costForUsage, SCHEMA_VERSION, unpackRaw, severityRank, SECURITY_CATEGORIES } from \"@agent-lens/core\";\n\nexport type DB = Database.Database;\n\nexport function openReadonly(file: string): DB {\n const db = new Database(file, { readonly: true, fileMustExist: true });\n db.pragma(\"query_only = ON\");\n return db;\n}\n\n/**\n * Compare the DB's stamped schema version against the version this server build expects. `stale` means\n * the on-disk DB was written by an older schema (a bump requires `agent-lens ingest --full` to rebuild)\n * — the UI surfaces this so a mismatch doesn't silently show missing/empty columns. `db_version` is null\n * on an unstamped/never-ingested DB (treated as not-stale: nothing to warn about yet).\n */\nexport function schemaStatus(db: DB): { db_version: number | null; expected: number; stale: boolean } {\n const row = db.prepare(\"SELECT value FROM meta WHERE key = 'schema_version'\").get() as\n | { value: string }\n | undefined;\n const dbVersion = row ? Number(row.value) : null;\n const db_version = dbVersion != null && Number.isFinite(dbVersion) ? dbVersion : null;\n return { db_version, expected: SCHEMA_VERSION, stale: db_version != null && db_version !== SCHEMA_VERSION };\n}\n\n/**\n * Wall-clock time of the most recent ingest run that wrote anything, as an ISO8601 string (or null on\n * a never-ingested DB). Every archive file touched by a run gets the same `ingested_at` stamp, so the\n * max across `ingest_state` is exactly \"data last refreshed at\". Powers the header freshness readout.\n */\nexport function lastIngested(db: DB): string | null {\n const row = db.prepare(\"SELECT MAX(ingested_at) AS last FROM ingest_state\").get() as { last: string | null };\n return row?.last ?? null;\n}\n\n/**\n * Split a raw transcript line's message content into natural text vs thinking. Accepts the stored\n * `events.raw_json` value, which is a gzip BLOB (Buffer) post-ADR-011 but may be a legacy plain string;\n * `unpackRaw` normalizes both.\n */\nexport function extractParts(rawJson: string | Buffer): { text: string | null; thinking: string | null } {\n let text = \"\";\n let thinking = \"\";\n try {\n const content = JSON.parse(unpackRaw(rawJson))?.message?.content;\n if (typeof content === \"string\") text = content;\n else if (Array.isArray(content)) {\n for (const b of content) {\n if (b?.type === \"text\" && typeof b.text === \"string\") text += (text ? \"\\n\" : \"\") + b.text;\n else if (b?.type === \"thinking\" && typeof b.thinking === \"string\")\n thinking += (thinking ? \"\\n\" : \"\") + b.thinking;\n }\n }\n } catch {\n /* ignore */\n }\n return { text: text || null, thinking: thinking || null };\n}\n\nexport interface SessionFilters {\n source?: string;\n project?: string;\n model?: string;\n q?: string;\n from?: string;\n to?: string;\n kind?: \"main\" | \"subagent\";\n /** Column to sort the whole filtered list by (before pagination). Defaults to \"started\". */\n sort?: \"started\" | \"title\" | \"turns\" | \"tokens\" | \"cost\" | \"duration\";\n dir?: \"asc\" | \"desc\";\n limit: number;\n offset: number;\n}\n\nexport function listSources(db: DB) {\n return db\n .prepare(\n // Count MAIN sessions only (is_sidechain = 0): the dropdown filters the session list, which\n // defaults to main-only, so a source's count must match what you'd see — not be inflated by the\n // many subagent sidechains each task spawns.\n `SELECT s.id, s.label, s.agent_id, s.config_dir,\n (SELECT COUNT(*) FROM sessions x WHERE x.source_id = s.id AND x.is_sidechain = 0) AS session_count\n FROM sources s ORDER BY s.label`,\n )\n .all();\n}\n\nexport function listProjects(db: DB) {\n return db\n .prepare(\n `SELECT p.id, p.path,\n (SELECT COUNT(*) FROM sessions x WHERE x.project_id = p.id AND x.is_sidechain = 0) AS session_count\n FROM projects p ORDER BY session_count DESC, p.path`,\n )\n .all();\n}\n\nexport function listModels(db: DB) {\n return db\n .prepare(`SELECT DISTINCT model FROM token_usage WHERE model IS NOT NULL ORDER BY model`)\n .all()\n .map((r: any) => r.model);\n}\n\n/**\n * Turn a user's search box input into a safe FTS5 MATCH expression. The raw string is otherwise\n * parsed as FTS5 *query syntax*, so a hyphen, colon (column filter), or bareword operator throws\n * (e.g. \"swe-workflow\" → `SQLITE_ERROR: no such column: workflow`). We treat the input as literal\n * terms: split on whitespace and wrap each token in double quotes (doubling embedded quotes per\n * FTS5 escaping), joined by space (implicit AND). Phrasing also lets the tokenizer split intra-token\n * punctuation, so \"swe-workflow\" matches the adjacent tokens \"swe workflow\". Returns \"\" for input\n * with no usable tokens (caller guards on a non-empty trimmed query). */\nfunction toFtsQuery(raw: string): string {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`)\n .join(\" \");\n}\n\nexport function listSessions(db: DB, f: SessionFilters) {\n const where: string[] = [];\n const params: any[] = [];\n if (f.source) (where.push(\"s.source_id = ?\"), params.push(f.source));\n if (f.project) (where.push(\"s.project_id = ?\"), params.push(f.project));\n // Date-inclusive on both ends (see findingWhere): a picked `to` day must include that day's sessions.\n if (f.from) (where.push(\"date(s.started_at) >= date(?)\"), params.push(f.from));\n if (f.to) (where.push(\"date(s.started_at) <= date(?)\"), params.push(f.to));\n if (f.kind === \"main\") where.push(\"s.is_sidechain = 0\");\n if (f.kind === \"subagent\") where.push(\"s.is_sidechain = 1\");\n if (f.model) {\n where.push(\"EXISTS (SELECT 1 FROM token_usage t WHERE t.session_id = s.id AND t.model = ?)\");\n params.push(f.model);\n }\n if (f.q && f.q.trim()) {\n // Match transcript text (FTS) OR the session's own name (slug/ai_title) OR its project path, so a\n // query like \"fix-youtube-embed-csp-policy\" finds the session by slug even when that string never\n // appears verbatim in an event. A project-path *subquery* (not a join on p.path) keeps the\n // un-joined COUNT(*) query above valid. SQLite LIKE is case-insensitive for ASCII.\n const like = `%${f.q.trim()}%`;\n where.push(\n `(s.id IN (SELECT DISTINCT e.session_id FROM events e JOIN events_fts f ON f.rowid = e.rowid WHERE events_fts MATCH ?)\n OR s.slug LIKE ? OR s.ai_title LIKE ?\n OR s.project_id IN (SELECT id FROM projects WHERE path LIKE ?))`,\n );\n params.push(toFtsQuery(f.q), like, like, like);\n }\n const whereSql = where.length ? `WHERE ${where.join(\" AND \")}` : \"\";\n\n const total = (db.prepare(`SELECT COUNT(*) n FROM sessions s ${whereSql}`).get(...params) as any).n;\n\n const baseSelect = `SELECT s.id, s.ai_title, s.slug, s.source_id, s.is_sidechain, s.started_at, s.ended_at,\n s.duration_ms, s.event_count, s.turn_count, p.path AS project_path,\n (SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models\n FROM sessions s LEFT JOIN projects p ON p.id = s.project_id\n ${whereSql}`;\n\n // Columns sortable directly in SQL (stable-paginated with an id tiebreak). tokens/cost aren't stored\n // — they're derived from token_usage with per-model pricing — so those sort over the whole matching\n // set in JS below.\n const NATIVE_ORDER: Record<string, string> = {\n started: \"s.started_at\",\n title: \"COALESCE(s.ai_title, s.slug)\",\n turns: \"s.turn_count\",\n duration: \"s.duration_ms\",\n };\n const sortKey = f.sort ?? \"started\";\n const sqlDir = f.dir === \"asc\" ? \"ASC\" : \"DESC\";\n\n let rows: any[];\n if (sortKey === \"tokens\" || sortKey === \"cost\") {\n // Whole-list sort by a derived metric: materialize every matching session, cost them all, sort,\n // then take the page. Heavier than the SQL path but the only way to page a JS-computed column\n // consistently. Fine at this tool's scale (personal analytics).\n const all = db.prepare(baseSelect).all(...params) as any[];\n attachSessionCost(db, all);\n const sign = f.dir === \"asc\" ? 1 : -1;\n all.sort((a, b) => {\n const c = (sortKey === \"tokens\" ? a.tokens - b.tokens : a.cost - b.cost) * sign;\n return c !== 0 ? c : String(a.id).localeCompare(String(b.id));\n });\n rows = all.slice(f.offset, f.offset + f.limit);\n } else {\n const orderCol = NATIVE_ORDER[sortKey] ?? NATIVE_ORDER.started;\n rows = db\n .prepare(`${baseSelect} ORDER BY ${orderCol} ${sqlDir}, s.id ASC LIMIT ? OFFSET ?`)\n .all(...params, f.limit, f.offset) as any[];\n attachSessionCost(db, rows);\n }\n\n return { total, sessions: rows };\n}\n\n/** Attach per-session `tokens`, `token_split`, `cost` (USD, cache-aware) and `title` in place. Costs\n * are grouped by model so per-model rates apply. IDs are chunked so the whole-list sort path (which can\n * pass thousands of sessions) stays under SQLite's bound-parameter limit. */\nfunction attachSessionCost(db: DB, rows: any[]) {\n type Acc = { tokens: number; cost: number; split: { input: number; output: number; cache_creation: number; cache_read: number } };\n const costBySession = new Map<string, Acc>();\n const ids = rows.map((r) => r.id);\n const CHUNK = 800;\n for (let i = 0; i < ids.length; i += CHUNK) {\n const batch = ids.slice(i, i + CHUNK);\n const ph = batch.map(() => \"?\").join(\",\");\n const usage = db\n .prepare(\n `SELECT session_id, model,\n SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage WHERE session_id IN (${ph}) GROUP BY session_id, model`,\n )\n .all(...batch) as any[];\n for (const u of usage) {\n const acc = costBySession.get(u.session_id) ?? { tokens: 0, cost: 0, split: { input: 0, output: 0, cache_creation: 0, cache_read: 0 } };\n acc.tokens += u.i + u.o + u.cw + u.cr;\n acc.split.input += u.i;\n acc.split.output += u.o;\n acc.split.cache_creation += u.cw;\n acc.split.cache_read += u.cr;\n acc.cost += costForUsage(u.model, {\n input_tokens: u.i,\n output_tokens: u.o,\n cache_creation_input_tokens: u.cw,\n cache_read_input_tokens: u.cr,\n });\n costBySession.set(u.session_id, acc);\n }\n }\n for (const r of rows) {\n const c = costBySession.get(r.id);\n r.tokens = c?.tokens ?? 0;\n r.token_split = c?.split ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };\n r.cost = c ? Number(c.cost.toFixed(4)) : 0;\n r.title = r.ai_title || r.slug || null;\n }\n}\n\nexport function getSession(db: DB, id: string) {\n const session = db\n .prepare(\n `SELECT s.*, p.path AS project_path FROM sessions s\n LEFT JOIN projects p ON p.id = s.project_id WHERE s.id = ?`,\n )\n .get(id) as any;\n if (!session) return null;\n\n const turns = db.prepare(\"SELECT * FROM turns WHERE session_id = ? ORDER BY seq\").all(id);\n const eventRows = db\n .prepare(\n `SELECT uuid, type, role, timestamp, model, is_sidechain, turn_id, raw_json\n FROM events WHERE session_id = ? ORDER BY timestamp, seq`,\n )\n .all(id) as any[];\n const toolRows = db\n .prepare(\n `SELECT id, event_uuid, tool_name, skill_name, skill_id, agent_type, spawned_session_id,\n workflow_run_id, workflow_name, status,\n total_duration_ms, total_tokens, input_json, result_summary,\n (SELECT COUNT(*) FROM sessions s WHERE s.workflow_run_id = tool_calls.workflow_run_id) AS workflow_agent_count\n FROM tool_calls WHERE session_id = ?`,\n )\n .all(id) as any[];\n\n // Spilled full tool outputs: when a result_summary is the \"Full output saved to: …/tool-results/\n // <name>.txt\" marker (the transcript's 280-char stand-in), attach the un-truncated text from\n // tool_results so the UI can expand it. Keyed by (session_id, name); guarded for a pre-ingest DB.\n if (tableExists(db, \"tool_results\")) {\n const getFull = db.prepare(\"SELECT text, bytes FROM tool_results WHERE session_id = ? AND name = ?\");\n for (const t of toolRows) {\n const m = typeof t.result_summary === \"string\" ? t.result_summary.match(/tool-results\\/([A-Za-z0-9_-]+)\\.txt/) : null;\n if (!m) continue;\n const full = getFull.get(id, m[1]) as { text: string; bytes: number } | undefined;\n if (full) t.full_result = { text: full.text, bytes: full.bytes };\n }\n }\n\n // Security findings (ADR-017): attach each tool call's findings inline (for the transcript severity\n // badge + \"why\" panel) and collect a session-level list for the header summary. Guarded for a\n // read-only pre-ingest DB whose schema predates the findings table.\n const sessionFindings: any[] = [];\n if (tableExists(db, \"findings\")) {\n const findingRows = db\n .prepare(\n `SELECT id, tool_call_id, event_uuid, turn_id, rule_id, category, framework_ref, severity, title, evidence, signals_json\n FROM findings WHERE session_id = ?`,\n )\n .all(id) as any[];\n const byToolCall = new Map<string, any[]>();\n for (const fr of findingRows) {\n const f = {\n id: fr.id,\n tool_call_id: fr.tool_call_id,\n event_uuid: fr.event_uuid,\n turn_id: fr.turn_id,\n rule_id: fr.rule_id,\n category: fr.category,\n framework_ref: fr.framework_ref,\n severity: fr.severity,\n title: fr.title,\n evidence: fr.evidence,\n signals: safeJson(fr.signals_json),\n };\n sessionFindings.push(f);\n if (fr.tool_call_id) (byToolCall.get(fr.tool_call_id) ?? byToolCall.set(fr.tool_call_id, []).get(fr.tool_call_id))!.push(f);\n }\n for (const t of toolRows) t.findings = byToolCall.get(t.id) ?? [];\n // Most-severe first so a session banner can lead with the worst.\n sessionFindings.sort((a, b) => severityRank(b.severity) - severityRank(a.severity));\n }\n\n const toolsByEvent = new Map<string, any[]>();\n for (const t of toolRows) {\n if (!t.event_uuid) continue;\n (toolsByEvent.get(t.event_uuid) ?? toolsByEvent.set(t.event_uuid, []).get(t.event_uuid))!.push(t);\n }\n\n const events = eventRows.map((e) => {\n const { text, thinking } = extractParts(e.raw_json);\n return {\n uuid: e.uuid,\n type: e.type,\n role: e.role,\n timestamp: e.timestamp,\n model: e.model,\n is_sidechain: e.is_sidechain,\n turn_id: e.turn_id,\n text,\n thinking,\n toolCalls: toolsByEvent.get(e.uuid) ?? [],\n };\n });\n\n const usage = db\n .prepare(\n `SELECT model, SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage WHERE session_id = ? GROUP BY model`,\n )\n .all(id) as any[];\n // Keep the token categories split so the UI can show input/output/cache-write/cache-read\n // separately. Cache stays IN the total and IS cost-attributed (at its discounted rate) — it is\n // differentiated, never dropped.\n const split = { input: 0, output: 0, cache_creation: 0, cache_read: 0 };\n let cost = 0;\n for (const u of usage) {\n split.input += u.i;\n split.output += u.o;\n split.cache_creation += u.cw;\n split.cache_read += u.cr;\n cost += costForUsage(u.model, {\n input_tokens: u.i,\n output_tokens: u.o,\n cache_creation_input_tokens: u.cw,\n cache_read_input_tokens: u.cr,\n });\n }\n session.tokens = split.input + split.output + split.cache_creation + split.cache_read;\n session.token_split = split;\n session.cost = Number(cost.toFixed(4));\n session.title = session.ai_title || session.slug || null;\n\n // Heuristic classification (Phase 4): category + complexity + the signals that produced them\n // (tool/skill mix, LoC, files, subagent count) — already captured in signals_json by the classifier.\n const cls = db\n .prepare(\n `SELECT category, complexity_score, complexity_band, signals_json, classifier_version\n FROM classifications WHERE scope = 'session' AND target_id = ?`,\n )\n .get(id) as any;\n let classification: any = null;\n if (cls) {\n let signals: any = null;\n try {\n signals = cls.signals_json ? JSON.parse(cls.signals_json) : null;\n } catch {\n /* ignore malformed */\n }\n classification = {\n category: cls.category,\n complexity_score: cls.complexity_score,\n complexity_band: cls.complexity_band,\n classifier_version: cls.classifier_version,\n signals,\n };\n }\n\n // Subagent linkage (schema v3): if this is a sidechain session, resolve the parent turn/session\n // that spawned it so the UI can offer a \"spawned by\" crumb back to the originating turn.\n let parent: { id: string; title: string | null; turn_seq: number | null } | null = null;\n if (session.parent_session_id) {\n const p = db\n .prepare(\n `SELECT ps.id, ps.ai_title, ps.slug, t.seq AS turn_seq\n FROM sessions ps LEFT JOIN turns t ON t.id = ?\n WHERE ps.id = ?`,\n )\n .get(session.parent_turn_id, session.parent_session_id) as any;\n if (p) parent = { id: p.id, title: p.ai_title || p.slug || null, turn_seq: p.turn_seq ?? null };\n }\n\n // Spawned subagents (schema v3): sessions whose parent_session_id points back here. Nesting them\n // under the parent is why the flat session list defaults to main-only — a task with N subagents is\n // one row with N children, not N+1 sibling rows sharing the same slug.\n // Subagent metadata sidecars (session_meta) supply the authoritative agentType/description/spawnDepth\n // — LEFT JOIN so a subagent still lists when its meta hasn't been ingested (or the table is absent on\n // a pre-ingest read-only DB).\n const hasMeta = tableExists(db, \"session_meta\");\n const children = db\n .prepare(\n `SELECT s.id, s.ai_title, s.slug, s.turn_count, s.started_at, s.workflow_run_id,\n (SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models\n ${hasMeta ? \", sm.agent_type, sm.agent_description, sm.spawn_depth\" : \", NULL AS agent_type, NULL AS agent_description, NULL AS spawn_depth\"}\n FROM sessions s ${hasMeta ? \"LEFT JOIN session_meta sm ON sm.session_id = s.id\" : \"\"}\n WHERE s.parent_session_id = ? ORDER BY s.started_at`,\n )\n .all(id) as any[];\n for (const ch of children) {\n ch.title = ch.ai_title || ch.slug || null;\n const u = db\n .prepare(\n `SELECT model, SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage WHERE session_id = ? GROUP BY model`,\n )\n .all(ch.id) as any[];\n ch.tokens = u.reduce((a, r) => a + r.i + r.o + r.cw + r.cr, 0);\n ch.cost = Number(\n u.reduce((a, r) => a + costForUsage(r.model, { input_tokens: r.i, output_tokens: r.o, cache_creation_input_tokens: r.cw, cache_read_input_tokens: r.cr }), 0).toFixed(4),\n );\n }\n\n // Workflow runs launched from THIS session: each Workflow tool_call carries a run id + name and sits\n // on a turn. This lets the UI group the spawned subagents by run and link each group back to the\n // exact launching turn (turn_seq) — instead of one flat, unattributed fan-out list.\n // Run status rides along from the result sidecar (workflow_results) when ingested, so the session's\n // workflow-run groups can flag a failed/completed run at a glance. NULL when no sidecar (async, still\n // pending) or the table isn't present yet (read-only pre-ingest DB).\n const hasWR = tableExists(db, \"workflow_results\");\n const workflow_runs = db\n .prepare(\n `SELECT tc.workflow_run_id AS run_id, tc.workflow_name AS name, t.seq AS turn_seq,\n (SELECT COUNT(*) FROM sessions s WHERE s.workflow_run_id = tc.workflow_run_id) AS agent_count\n ${hasWR ? \", (SELECT status FROM workflow_results wr WHERE wr.run_id = tc.workflow_run_id) AS status\" : \", NULL AS status\"}\n FROM tool_calls tc LEFT JOIN turns t ON t.id = tc.turn_id\n WHERE tc.session_id = ? AND tc.workflow_run_id IS NOT NULL\n ORDER BY t.seq`,\n )\n .all(id) as any[];\n\n return { session, turns, events, classification, parent, children, workflow_runs, findings: sessionFindings };\n}\n\n/**\n * A Workflow-tool run's detail: the launching tool_call (name, status, returned result) + the turn\n * and session it was launched from, every subagent session fanned out under its `workflow_run_id`,\n * and roll-up stats (agent count, tokens, cost, wall-clock span). Powers the /workflow/:run_id page.\n * Returns null when no Workflow tool_call carries this run id (→ 404).\n */\n/** Pull the inner text of a single `<tag>…</tag>` from a flattened message (non-greedy). */\nfunction xmlTag(text: string, tag: string): string | null {\n return text.match(new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`))?.[1]?.trim() ?? null;\n}\n\n/** Whether a table exists — the server opens the DB read-only, so a not-yet-ingested schema (no\n * workflow_results table) must degrade gracefully rather than throw. */\nfunction tableExists(db: DB, name: string): boolean {\n return !!db.prepare(\"SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?\").get(name);\n}\n\n/** Parse a stored JSON column back to a value, or null when absent/malformed. */\nfunction safeJson(s: string | null): unknown {\n if (!s) return null;\n try {\n return JSON.parse(s);\n } catch {\n return null;\n }\n}\n\nexport function getWorkflow(db: DB, runId: string) {\n const wf = db\n .prepare(\n `SELECT tc.id AS tool_use_id, tc.workflow_run_id AS run_id, tc.workflow_name AS name, tc.status, tc.result_summary,\n tc.input_json AS input_json,\n tc.session_id AS parent_session_id, t.seq AS turn_seq,\n ps.ai_title AS parent_ai_title, ps.slug AS parent_slug\n FROM tool_calls tc\n LEFT JOIN turns t ON t.id = tc.turn_id\n LEFT JOIN sessions ps ON ps.id = tc.session_id\n WHERE tc.workflow_run_id = ? AND tc.tool_name = 'Workflow'\n ORDER BY t.seq\n LIMIT 1`,\n )\n .get(runId) as any;\n if (!wf) return null;\n\n // Subagent sessions in this run (same projection as getSession's children, plus the wall-clock\n // fields so the page can show each agent's span and a run-level min/max). LEFT JOIN session_meta for\n // the authoritative type/description/depth — these workflow agents carry none in-transcript.\n const hasMeta = tableExists(db, \"session_meta\");\n const agents = db\n .prepare(\n `SELECT s.id, s.ai_title, s.slug, s.turn_count, s.started_at, s.ended_at, s.duration_ms,\n (SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models\n ${hasMeta ? \", sm.agent_type, sm.agent_description, sm.spawn_depth\" : \", NULL AS agent_type, NULL AS agent_description, NULL AS spawn_depth\"}\n FROM sessions s ${hasMeta ? \"LEFT JOIN session_meta sm ON sm.session_id = s.id\" : \"\"}\n WHERE s.workflow_run_id = ? ORDER BY s.started_at`,\n )\n .all(runId) as any[];\n\n let total_tokens = 0;\n let total_cost = 0;\n let started_at: string | null = null;\n let ended_at: string | null = null;\n for (const a of agents) {\n a.title = a.ai_title || a.slug || null;\n const u = db\n .prepare(\n `SELECT model, SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage WHERE session_id = ? GROUP BY model`,\n )\n .all(a.id) as any[];\n a.tokens = u.reduce((acc, r) => acc + r.i + r.o + r.cw + r.cr, 0);\n a.cost = Number(\n u.reduce((acc, r) => acc + costForUsage(r.model, { input_tokens: r.i, output_tokens: r.o, cache_creation_input_tokens: r.cw, cache_read_input_tokens: r.cr }), 0).toFixed(4),\n );\n total_tokens += a.tokens;\n total_cost += a.cost;\n if (a.started_at && (!started_at || a.started_at < started_at)) started_at = a.started_at;\n if (a.ended_at && (!ended_at || a.ended_at > ended_at)) ended_at = a.ended_at;\n }\n const duration_ms = started_at && ended_at ? new Date(ended_at).getTime() - new Date(started_at).getTime() : null;\n\n // The workflow's ACTUAL result arrives later as a `<task-notification>` user message in the\n // launching session — tc.result_summary is only the \"launched in background\" ack. Find that\n // message by the Workflow tool-use id and surface its completion status, summary, result, and\n // failures. (The same task can notify more than once; take the most recent.)\n let completion: { status: string | null; summary: string | null; result: string | null; failures: string | null } | null = null;\n if (wf.tool_use_id && wf.parent_session_id) {\n const ev = db\n .prepare(\n `SELECT raw_json FROM events\n WHERE session_id = ? AND text LIKE '%<task-notification>%' AND text LIKE ?\n ORDER BY timestamp DESC LIMIT 1`,\n )\n .get(wf.parent_session_id, `%${wf.tool_use_id}%`) as any;\n if (ev) {\n const { text } = extractParts(ev.raw_json);\n const t = text ?? \"\";\n completion = { status: xmlTag(t, \"status\"), summary: xmlTag(t, \"summary\"), result: xmlTag(t, \"result\"), failures: xmlTag(t, \"failures\") };\n }\n }\n\n // The runner's own result sidecar (workflow_results), ingested from wf_<id>.json. It's the\n // authoritative record of how the run finished — especially for async runs that never posted a\n // task-notification (completion above stays null) or that failed before fanning out any agents.\n // Sidecar-preferred: it supplies status + the returned result and fills the completion when the\n // transcript has none. Its self-reported roll-up (model, tokens, tool calls, phases, per-item logs,\n // duration, agent count) rides along as `run` so the page can show it even with zero ingested agents.\n const wr = tableExists(db, \"workflow_results\")\n ? (db\n .prepare(\n `SELECT status, summary, default_model, result_json, phases_json, logs_json, progress_json,\n agent_count, total_tokens, total_tool_calls, duration_ms, started_at, ended_at\n FROM workflow_results WHERE run_id = ?`,\n )\n .get(wf.run_id) as any)\n : null;\n\n let run: {\n status: string | null;\n summary: string | null;\n default_model: string | null;\n agent_count: number | null;\n total_tokens: number | null;\n total_tool_calls: number | null;\n duration_ms: number | null;\n started_at: string | null;\n ended_at: string | null;\n phases: unknown;\n logs: unknown;\n progress: unknown;\n } | null = null;\n if (wr) {\n run = {\n status: wr.status ?? null,\n summary: wr.summary ?? null,\n default_model: wr.default_model ?? null,\n agent_count: wr.agent_count ?? null,\n total_tokens: wr.total_tokens ?? null,\n total_tool_calls: wr.total_tool_calls ?? null,\n duration_ms: wr.duration_ms ?? null,\n started_at: wr.started_at ?? null,\n ended_at: wr.ended_at ?? null,\n phases: safeJson(wr.phases_json),\n logs: safeJson(wr.logs_json),\n // The per-phase/per-agent event timeline (workflowProgress) — lets the page render a phase graph\n // with a per-phase descriptor (agent count, models). Absent on older/failed runs → UI falls back.\n progress: safeJson(wr.progress_json),\n };\n if (wr.result_json != null || wr.status) {\n completion = {\n status: wr.status ?? completion?.status ?? null,\n summary: wr.summary ?? completion?.summary ?? null,\n result: wr.result_json ?? completion?.result ?? null,\n failures: completion?.failures ?? null,\n };\n }\n }\n\n return {\n run_id: wf.run_id,\n name: wf.name ?? null,\n status: wr?.status ?? wf.status ?? null,\n result_summary: wf.result_summary ?? null,\n input_json: wf.input_json ?? null,\n completion,\n run,\n parent: { id: wf.parent_session_id, title: wf.parent_ai_title || wf.parent_slug || null, turn_seq: wf.turn_seq ?? null },\n agents,\n stats: { agent_count: agents.length, total_tokens, total_cost: Number(total_cost.toFixed(4)), started_at, ended_at, duration_ms },\n };\n}\n\nexport interface SkillFilters {\n q?: string;\n source?: string;\n project?: string;\n}\n\n/**\n * One row per fired skill (grouped by name), for the Skills list. `version_count` is the number of\n * distinct captured content versions (COUNT(DISTINCT skill_id) ignores firings with no body), while\n * `call_count` counts every firing — so a skill whose body was never captured still appears. Filters\n * (name search, source, project) mirror the sessions list and apply to the firing session.\n */\nexport function listSkills(db: DB, f: SkillFilters = {}) {\n const where = [\"tc.tool_name = 'Skill'\", \"tc.skill_name IS NOT NULL\"];\n const params: any[] = [];\n // Search matches the skill name OR any captured version body, so a query for a phrase that only\n // appears inside a SKILL.md still surfaces the skill. (skills.body is the normalized body; joined by\n // name rather than skill_id so firings with no captured body still match on name.)\n if (f.q && f.q.trim()) {\n const like = `%${f.q.trim()}%`;\n where.push(\"(tc.skill_name LIKE ? OR EXISTS (SELECT 1 FROM skills sk WHERE sk.name = tc.skill_name AND sk.body LIKE ?))\");\n params.push(like, like);\n }\n if (f.source) (where.push(\"s.source_id = ?\"), params.push(f.source));\n if (f.project) (where.push(\"s.project_id = ?\"), params.push(f.project));\n const rows = db\n .prepare(\n `SELECT tc.skill_name AS name,\n COUNT(*) AS call_count,\n COUNT(DISTINCT tc.skill_id) AS version_count,\n MAX(e.timestamp) AS last_fired,\n GROUP_CONCAT(DISTINCT s.source_id) AS sources\n FROM tool_calls tc\n JOIN sessions s ON s.id = tc.session_id\n LEFT JOIN events e ON e.uuid = tc.event_uuid\n WHERE ${where.join(\" AND \")}\n GROUP BY tc.skill_name\n ORDER BY call_count DESC, name`,\n )\n .all(...params) as any[];\n for (const r of rows) r.sources = r.sources ? String(r.sources).split(\",\").filter(Boolean) : [];\n return rows;\n}\n\n/**\n * All data for one skill (by name): its content versions (most-recent first, each with body +\n * firing count) and the sessions that fired it, tagged with which version (`version_id`) so the UI\n * can map the session list to the selected version. Returns null (→ 404) when the name never fired.\n */\nexport function getSkill(db: DB, name: string) {\n const versions = db\n .prepare(\n `SELECT sk.id, sk.base_dir, sk.summary, sk.body, sk.body_bytes, sk.first_seen, sk.last_seen,\n (SELECT COUNT(*) FROM tool_calls tc WHERE tc.skill_id = sk.id) AS call_count\n FROM skills sk WHERE sk.name = ? ORDER BY sk.last_seen DESC, sk.id`,\n )\n .all(name) as any[];\n\n const sessions = db\n .prepare(\n `SELECT s.id, s.ai_title, s.slug, s.source_id, s.started_at, p.path AS project_path,\n tc.skill_id AS version_id, MIN(e.timestamp) AS fired_at, COUNT(*) AS fire_count\n FROM tool_calls tc\n JOIN sessions s ON s.id = tc.session_id\n LEFT JOIN projects p ON p.id = s.project_id\n LEFT JOIN events e ON e.uuid = tc.event_uuid\n WHERE tc.tool_name = 'Skill' AND tc.skill_name = ?\n GROUP BY s.id, tc.skill_id\n ORDER BY fired_at DESC`,\n )\n .all(name) as any[];\n\n if (!versions.length && !sessions.length) return null;\n for (const s of sessions) s.title = s.ai_title || s.slug || null;\n const call_count = sessions.reduce((a, s) => a + (s.fire_count as number), 0);\n return { name, versions, sessions, call_count };\n}\n\n// ---- Security findings (ADR-017) + triage (ADR-018) ----------------------\n\nexport interface FindingFilters {\n severity?: string;\n category?: string;\n rule?: string;\n session?: string;\n source?: string;\n project?: string;\n from?: string;\n to?: string;\n /** open (default; excludes dismissed + muted) | dismissed | muted | all. */\n status?: \"open\" | \"dismissed\" | \"muted\" | \"all\";\n /** Sort the whole filtered list before pagination. Defaults to \"severity\" (most-severe first). */\n sort?: \"severity\" | \"session\" | \"rule\" | \"category\" | \"time\";\n dir?: \"asc\" | \"desc\";\n limit: number;\n offset: number;\n}\n\n// Rank severities inside SQL so the list can be severity-ordered and paginated without materializing\n// everything (mirrors SEVERITY_ORDER in core: info=1 … critical=5).\nconst SEVERITY_RANK_SQL =\n \"CASE f.severity WHEN 'critical' THEN 5 WHEN 'high' THEN 4 WHEN 'medium' THEN 3 WHEN 'low' THEN 2 WHEN 'info' THEN 1 ELSE 0 END\";\n\n// A finding is muted when a muted_rules row matches its rule at global/project/source scope. Only valid\n// when the triage DB is ATTACHed (createApp does this); guarded by triageAttached() everywhere it's used.\nconst MUTED_SQL = `EXISTS (SELECT 1 FROM triage.muted_rules m WHERE m.rule_id = f.rule_id\n AND (m.scope='global' OR (m.scope='project' AND m.scope_id = s.project_id) OR (m.scope='source' AND m.scope_id = s.source_id)))`;\n\n/** Whether the writable triage store is ATTACHed to this (read) connection — gates all triage SQL. */\nexport function triageAttached(db: DB): boolean {\n return (db.prepare(\"PRAGMA database_list\").all() as Array<{ name: string }>).some((r) => r.name === \"triage\");\n}\n\n/** WHERE fragments shared by list + summary: user filters (no status) over findings f / sessions s. */\nfunction findingWhere(f: FindingFilters): { sql: string[]; params: any[] } {\n const sql: string[] = [];\n const params: any[] = [];\n if (f.severity) (sql.push(\"f.severity = ?\"), params.push(f.severity));\n if (f.category) (sql.push(\"f.category = ?\"), params.push(f.category));\n if (f.rule) (sql.push(\"f.rule_id = ?\"), params.push(f.rule));\n if (f.session) (sql.push(\"f.session_id = ?\"), params.push(f.session));\n if (f.source) (sql.push(\"s.source_id = ?\"), params.push(f.source));\n if (f.project) (sql.push(\"s.project_id = ?\"), params.push(f.project));\n // Compare on the DATE part so a picked day is inclusive on both ends — `to = 2026-07-14` must include\n // 2026-07-14 events (a plain `started_at <= '2026-07-14'` would exclude that whole day's timestamps).\n if (f.from) (sql.push(\"date(s.started_at) >= date(?)\"), params.push(f.from));\n if (f.to) (sql.push(\"date(s.started_at) <= date(?)\"), params.push(f.to));\n return { sql, params };\n}\n\n/** Status → WHERE fragment over the dismissed LEFT JOIN (`d`) + MUTED_SQL. Empty when triage absent. */\nfunction statusWhere(status: FindingFilters[\"status\"], attached: boolean): string {\n if (!attached) return status === \"dismissed\" || status === \"muted\" ? \"1 = 0\" : \"\"; // no triage → nothing dismissed/muted\n switch (status) {\n case \"dismissed\":\n return \"d.finding_id IS NOT NULL\";\n case \"muted\":\n return `d.finding_id IS NULL AND ${MUTED_SQL}`;\n case \"all\":\n return \"\";\n default: // open\n return `d.finding_id IS NULL AND NOT ${MUTED_SQL}`;\n }\n}\n\n/**\n * The browsable findings list (GET /api/security/findings), filtered/sorted/paged. Each row carries\n * enough session context (title, project, source, when) to render and link without a second fetch, plus\n * its triage state (dismissed flag/note/when) when the triage store is attached. Default status hides\n * dismissed + muted so real, un-triaged findings surface. Guarded for a pre-ingest read-only DB.\n */\nexport function listFindings(db: DB, f: FindingFilters) {\n if (!tableExists(db, \"findings\")) return { total: 0, findings: [] };\n const attached = triageAttached(db);\n\n const w = findingWhere(f);\n const st = statusWhere(f.status ?? \"open\", attached);\n const where = [...w.sql, ...(st ? [st] : [])];\n const whereSql = where.length ? `WHERE ${where.join(\" AND \")}` : \"\";\n const dismissJoin = attached ? \"LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id\" : \"\";\n const triageCols = attached\n ? `, (d.finding_id IS NOT NULL) AS dismissed, d.note AS dismiss_note, d.dismissed_at, ${MUTED_SQL} AS muted`\n : `, 0 AS dismissed, NULL AS dismiss_note, NULL AS dismissed_at, 0 AS muted`;\n\n const total = (\n db\n .prepare(`SELECT COUNT(*) n FROM findings f JOIN sessions s ON s.id = f.session_id ${dismissJoin} ${whereSql}`)\n .get(...w.params) as any\n ).n;\n\n const ORDER: Record<string, string> = {\n severity: SEVERITY_RANK_SQL,\n session: \"f.session_id\",\n rule: \"f.rule_id\",\n category: \"f.category\",\n time: \"s.started_at\",\n };\n const orderCol = ORDER[f.sort ?? \"severity\"] ?? SEVERITY_RANK_SQL;\n const dir = f.dir === \"asc\" ? \"ASC\" : \"DESC\";\n\n const findings = db\n .prepare(\n `SELECT f.id, f.session_id, f.tool_call_id, f.event_uuid, f.turn_id, f.rule_id, f.category,\n f.framework_ref, f.severity, f.title, f.evidence, tc.tool_name,\n COALESCE(s.ai_title, s.slug) AS session_title, s.source_id, s.is_sidechain,\n s.started_at, p.path AS project_path, s.project_id ${triageCols}\n FROM findings f\n JOIN sessions s ON s.id = f.session_id\n LEFT JOIN projects p ON p.id = s.project_id\n LEFT JOIN tool_calls tc ON tc.id = f.tool_call_id\n ${dismissJoin}\n ${whereSql}\n ORDER BY ${orderCol} ${dir}, ${SEVERITY_RANK_SQL} DESC, f.id ASC\n LIMIT ? OFFSET ?`,\n )\n .all(...w.params, f.limit, f.offset);\n\n return { total, findings };\n}\n\n/**\n * Roll-up for the /security page header + the Dashboard KPI. Counts (by severity/category/rule,\n * sessions flagged) are computed over **open** findings — dismissed + muted excluded — so the KPIs show\n * what still needs review; `dismissed` and `muted` totals ride along for the triage affordances. The\n * framework reference content (core) powers the \"what & why\" explainers. Guarded for a pre-ingest DB.\n */\nexport function securitySummary(db: DB) {\n const categories = SECURITY_CATEGORIES;\n if (!tableExists(db, \"findings\")) {\n return { total: 0, sessions_flagged: 0, dismissed: 0, muted: 0, by_severity: [], by_category: [], by_rule: [], categories };\n }\n const attached = triageAttached(db);\n const dismissJoin = attached ? \"LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id\" : \"\";\n const base = `FROM findings f JOIN sessions s ON s.id = f.session_id ${dismissJoin}`;\n const openWhere = attached ? `WHERE d.finding_id IS NULL AND NOT ${MUTED_SQL}` : \"\";\n\n const scalar = (sql: string) => (db.prepare(sql).get() as any).n as number;\n const total = scalar(`SELECT COUNT(*) n ${base} ${openWhere}`);\n const sessions_flagged = scalar(`SELECT COUNT(DISTINCT f.session_id) n ${base} ${openWhere}`);\n const dismissed = attached ? scalar(`SELECT COUNT(*) n FROM findings f LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id WHERE d.finding_id IS NOT NULL`) : 0;\n const muted = attached ? scalar(`SELECT COUNT(*) n ${base} WHERE d.finding_id IS NULL AND ${MUTED_SQL}`) : 0;\n\n const by_severity = db.prepare(`SELECT f.severity AS severity, COUNT(*) n ${base} ${openWhere} GROUP BY f.severity ORDER BY ${SEVERITY_RANK_SQL} DESC`).all();\n const by_category = db.prepare(`SELECT f.category AS category, COUNT(*) n ${base} ${openWhere} GROUP BY f.category ORDER BY n DESC`).all();\n const by_rule = db\n .prepare(\n `SELECT f.rule_id AS rule_id, f.category AS category, f.title AS title, COUNT(*) n, MAX(${SEVERITY_RANK_SQL}) AS rank\n ${base} ${openWhere} GROUP BY f.rule_id ORDER BY rank DESC, n DESC`,\n )\n .all();\n return { total, sessions_flagged, dismissed, muted, by_severity, by_category, by_rule, categories };\n}\n","/**\n * Agent Lens — Fastify app factory (ADR-005: read-only over the SQLite store).\n *\n * `createApp(db, opts)` builds the route tree against an already-open DB handle so the same app can\n * be driven by the CLI entry (index.ts, with .listen) and by tests (app.inject, no socket).\n */\nimport { existsSync } from \"node:fs\";\nimport Fastify, { type FastifyInstance } from \"fastify\";\nimport fastifyStatic from \"@fastify/static\";\nimport { sessionToMarkdown, type MarkdownEvent } from \"@agent-lens/core\";\nimport { type DB, lastIngested, schemaStatus, listSources, listProjects, listModels, listSessions, getSession, getWorkflow, listSkills, getSkill, listFindings, securitySummary } from \"./db.js\";\nimport { dashboardOverview, dashboardTimeseries, dashboardBreakdowns, type DashFilters } from \"./dashboard.js\";\nimport { originAllowed, runRefresh } from \"./refresh.js\";\nimport { openTriage, dismiss, reopen, muteRule, unmute, listMutes, type TriageDB, type MuteScope } from \"./triage.js\";\n\nexport interface CreateAppOpts {\n /** Absolute path to the built web SPA; when present it is served with a history fallback. */\n webDist?: string | null;\n /** Path to the writable security-triage store (ADR-018). When set, it is opened read-write for\n * triage writes and ATTACHed to the read handle so the findings list can JOIN triage state. */\n triageDbPath?: string | null;\n}\n\nexport async function createApp(db: DB, opts: CreateAppOpts = {}): Promise<FastifyInstance> {\n const app = Fastify({ logger: false });\n\n // Security triage store (ADR-018): a separate writable SQLite, ATTACHed to the read handle so the\n // findings queries can JOIN dismissed/muted state, while writes go through `triageDb`. The analytics\n // handle stays read-only. Absent → the security endpoints degrade to the un-triaged (all-open) view.\n let triageDb: TriageDB | null = null;\n if (opts.triageDbPath) {\n triageDb = openTriage(opts.triageDbPath); // creates the file + schema before we ATTACH it\n db.exec(`ATTACH DATABASE '${opts.triageDbPath.replace(/'/g, \"''\")}' AS triage`);\n app.addHook(\"onClose\", async () => triageDb?.close());\n }\n\n // Shared CSRF + availability guard for the triage write routes (same posture as /api/refresh).\n const guardWrite = (req: any, reply: any): boolean => {\n if (!originAllowed(req.headers.origin)) {\n reply.code(403).send({ error: { code: \"FORBIDDEN_ORIGIN\", message: \"cross-origin write blocked\" } });\n return false;\n }\n if (!triageDb) {\n reply.code(503).send({ error: { code: \"TRIAGE_UNAVAILABLE\", message: \"triage store not configured\" } });\n return false;\n }\n return true;\n };\n\n app.get(\"/api/health\", async () => {\n const s = schemaStatus(db);\n return { ok: true, last_ingested: lastIngested(db), schema_version: s.db_version, schema_stale: s.stale };\n });\n\n // The one write-action on this read-only server: run a collect + ingest pass on the host so the UI\n // can pull in new transcripts on demand (ADR-015). Guarded against cross-site CSRF (Origin) and\n // concurrent runs (single-instance lock → 409). See refresh.ts.\n app.post(\"/api/refresh\", async (req, reply) => {\n if (!originAllowed(req.headers.origin)) {\n return reply.code(403).send({ error: { code: \"FORBIDDEN_ORIGIN\", message: \"cross-origin refresh blocked\" } });\n }\n let result;\n try {\n result = runRefresh();\n } catch (e: any) {\n return reply.code(500).send({ error: { code: \"REFRESH_FAILED\", message: String(e?.message ?? e) } });\n }\n if (!result) {\n return reply.code(409).send({ error: { code: \"REFRESH_IN_PROGRESS\", message: \"a collect/ingest run is already in progress\" } });\n }\n return { ok: true, collected: result.collected, last_ingested: lastIngested(db) };\n });\n app.get(\"/api/sources\", async () => listSources(db));\n app.get(\"/api/projects\", async () => listProjects(db));\n app.get(\"/api/models\", async () => listModels(db));\n\n // Dashboard aggregates (Phase 4). All read-only; filters: source, from, to.\n const dashFilters = (req: any): DashFilters => {\n const q = req.query as Record<string, string>;\n return { source: q.source, from: q.from, to: q.to };\n };\n app.get(\"/api/dashboard/overview\", async (req) => dashboardOverview(db, dashFilters(req)));\n app.get(\"/api/dashboard/timeseries\", async (req) => {\n const q = req.query as Record<string, string>;\n return dashboardTimeseries(db, dashFilters(req), q.bucket);\n });\n app.get(\"/api/dashboard/breakdowns\", async (req) => dashboardBreakdowns(db, dashFilters(req)));\n\n app.get(\"/api/sessions\", async (req) => {\n const q = req.query as Record<string, string>;\n return listSessions(db, {\n source: q.source,\n project: q.project,\n model: q.model,\n q: q.q,\n from: q.from,\n to: q.to,\n kind: q.kind === \"main\" || q.kind === \"subagent\" ? q.kind : undefined,\n sort: ([\"started\", \"title\", \"turns\", \"tokens\", \"cost\", \"duration\"] as const).find((s) => s === q.sort),\n dir: q.dir === \"asc\" ? \"asc\" : q.dir === \"desc\" ? \"desc\" : undefined,\n limit: Math.min(Number(q.limit) || 50, 200),\n offset: Number(q.offset) || 0,\n });\n });\n\n app.get(\"/api/sessions/:id\", async (req, reply) => {\n const { id } = req.params as { id: string };\n const result = getSession(db, id);\n if (!result) return reply.code(404).send({ error: \"not found\" });\n return result;\n });\n\n app.get(\"/api/workflows/:run_id\", async (req, reply) => {\n const { run_id } = req.params as { run_id: string };\n const result = getWorkflow(db, run_id);\n if (!result) return reply.code(404).send({ error: \"not found\" });\n return result;\n });\n\n // Security findings (ADR-017). Read-only; browsable list + roll-up summary for the /security page,\n // the transcript inline badges (served via /api/sessions/:id), and the Dashboard KPI.\n app.get(\"/api/security/summary\", async () => securitySummary(db));\n const findingFilters = (q: Record<string, string>) => ({\n severity: q.severity,\n category: q.category,\n rule: q.rule,\n session: q.session,\n source: q.source,\n project: q.project,\n from: q.from,\n to: q.to,\n status: ([\"open\", \"dismissed\", \"muted\", \"all\"] as const).find((s) => s === q.status),\n });\n app.get(\"/api/security/findings\", async (req) => {\n const q = req.query as Record<string, string>;\n return listFindings(db, {\n ...findingFilters(q),\n sort: ([\"severity\", \"session\", \"rule\", \"category\", \"time\"] as const).find((s) => s === q.sort),\n dir: q.dir === \"asc\" ? \"asc\" : q.dir === \"desc\" ? \"desc\" : undefined,\n limit: Math.min(Number(q.limit) || 50, 5000),\n offset: Number(q.offset) || 0,\n });\n });\n\n // Triage writes (ADR-018): CSRF-guarded, loopback-only, same posture as /api/refresh.\n app.get(\"/api/security/mutes\", async () => (triageDb ? listMutes(triageDb) : []));\n app.post(\"/api/security/dismiss\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { ids?: string[]; note?: string };\n const n = dismiss(triageDb!, Array.isArray(b.ids) ? b.ids : [], b.note ?? null);\n return { ok: true, dismissed: n };\n });\n app.post(\"/api/security/reopen\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { ids?: string[] };\n const n = reopen(triageDb!, Array.isArray(b.ids) ? b.ids : []);\n return { ok: true, reopened: n };\n });\n // Dismiss every OPEN finding matching a filter (bulk cleanup) — collects ids server-side, then marks.\n app.post(\"/api/security/dismiss-matching\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { filter?: Record<string, string>; note?: string };\n const page = listFindings(db, { ...findingFilters(b.filter ?? {}), status: \"open\", limit: 100000, offset: 0 });\n const ids = (page.findings as Array<{ id: string }>).map((r) => r.id);\n const n = dismiss(triageDb!, ids, b.note ?? null);\n return { ok: true, dismissed: n };\n });\n app.post(\"/api/security/mute\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { rule_id?: string; scope?: string; scope_id?: string; note?: string };\n if (!b.rule_id) return reply.code(400).send({ error: { code: \"BAD_REQUEST\", message: \"rule_id required\" } });\n const scope = ([\"global\", \"project\", \"source\"] as const).find((s) => s === b.scope) ?? \"global\";\n muteRule(triageDb!, b.rule_id, scope as MuteScope, b.scope_id ?? \"\", b.note ?? null);\n return { ok: true };\n });\n app.post(\"/api/security/unmute\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { rule_id?: string; scope?: string; scope_id?: string };\n if (!b.rule_id) return reply.code(400).send({ error: { code: \"BAD_REQUEST\", message: \"rule_id required\" } });\n const scope = ([\"global\", \"project\", \"source\"] as const).find((s) => s === b.scope) ?? \"global\";\n const n = unmute(triageDb!, b.rule_id, scope as MuteScope, b.scope_id ?? \"\");\n return { ok: true, unmuted: n };\n });\n\n app.get(\"/api/skills\", async (req) => {\n const q = req.query as Record<string, string>;\n return listSkills(db, { q: q.q, source: q.source, project: q.project });\n });\n\n app.get(\"/api/skills/:name\", async (req, reply) => {\n const { name } = req.params as { name: string };\n const result = getSkill(db, decodeURIComponent(name));\n if (!result) return reply.code(404).send({ error: \"not found\" });\n return result;\n });\n\n app.get(\"/api/sessions/:id/export.md\", async (req, reply) => {\n const { id } = req.params as { id: string };\n const result = getSession(db, id);\n if (!result) return reply.code(404).send({ error: \"not found\" });\n const s = result.session;\n const events: MarkdownEvent[] = result.events.map((e) => ({\n type: e.type,\n role: e.role,\n timestamp: e.timestamp,\n text: e.text,\n thinking: e.thinking,\n toolCalls: e.toolCalls.map((t: any) => ({\n tool_name: t.tool_name,\n skill_name: t.skill_name,\n agent_type: t.agent_type,\n input_json: t.input_json,\n status: t.status,\n })),\n }));\n const md = sessionToMarkdown(\n {\n id: s.id,\n title: s.title,\n source: s.source_id,\n project: s.project_path,\n model: null,\n started_at: s.started_at,\n ended_at: s.ended_at,\n },\n events,\n );\n reply\n .header(\"content-type\", \"text/markdown; charset=utf-8\")\n .header(\"content-disposition\", `attachment; filename=\"session-${id.slice(0, 8)}.md\"`)\n .send(md);\n });\n\n // Serve the built SPA (if present) with a history fallback for client routes.\n if (opts.webDist && existsSync(opts.webDist)) {\n await app.register(fastifyStatic, { root: opts.webDist });\n app.setNotFoundHandler((req, reply) => {\n if (req.url.startsWith(\"/api/\")) return reply.code(404).send({ error: \"not found\" });\n return reply.sendFile(\"index.html\");\n });\n }\n\n return app;\n}\n","/**\n * Agent Lens — dashboard aggregation queries (Phase 4). Read-only; everything is computed as\n * server-side aggregates (GROUP BY over indexed columns) so the charts plot bounded series, not\n * raw rows — this is what lets the UI scale to years of data. Token series are always kept split\n * (input / output / cache-creation / cache-read); cache-read is never folded into a single\n * \"tokens\" number because it dominates and misleads. Cost is derived via the shared pricing table.\n */\nimport { costForUsage, rateForModel } from \"@agent-lens/core\";\nimport type { DB } from \"./db.js\";\n\nexport interface DashFilters {\n source?: string;\n from?: string;\n to?: string;\n}\n\n/** WHERE clause + params over the `sessions` alias `s` (started_at / source_id). */\nfunction sessionWhere(f: DashFilters): { sql: string; params: any[] } {\n const where: string[] = [];\n const params: any[] = [];\n if (f.source) (where.push(\"s.source_id = ?\"), params.push(f.source));\n // Date-inclusive on both ends (compare the DATE part) so a picked `to` day includes that day's\n // events — a plain `started_at <= '2026-07-14'` would drop everything after 2026-07-14T00:00.\n if (f.from) (where.push(\"date(s.started_at) >= date(?)\"), params.push(f.from));\n if (f.to) (where.push(\"date(s.started_at) <= date(?)\"), params.push(f.to));\n return { sql: where.length ? `WHERE ${where.join(\" AND \")}` : \"\", params };\n}\n\ninterface Split {\n input: number;\n output: number;\n cache_creation: number;\n cache_read: number;\n}\nconst zeroSplit = (): Split => ({ input: 0, output: 0, cache_creation: 0, cache_read: 0 });\n\n/** Sum a grouped usage row into a split accumulator. */\nfunction addUsage(acc: Split, r: any) {\n acc.input += r.i ?? 0;\n acc.output += r.o ?? 0;\n acc.cache_creation += r.cw ?? 0;\n acc.cache_read += r.cr ?? 0;\n}\n\nfunction pct(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const rank = Math.ceil((p / 100) * sorted.length) - 1;\n return sorted[Math.min(Math.max(rank, 0), sorted.length - 1)]!;\n}\n\nexport function dashboardOverview(db: DB, f: DashFilters) {\n const w = sessionWhere(f);\n\n const counts = db\n .prepare(\n `SELECT\n COUNT(*) sessions,\n SUM(CASE WHEN is_sidechain=0 THEN 1 ELSE 0 END) main,\n SUM(CASE WHEN is_sidechain=1 THEN 1 ELSE 0 END) subagent,\n SUM(turn_count) turns,\n COUNT(DISTINCT project_id) projects\n FROM sessions s ${w.sql}`,\n )\n .get(...w.params) as any;\n\n const toolCount = (\n db\n .prepare(`SELECT COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id ${w.sql}`)\n .get(...w.params) as any\n ).n;\n\n // Per-model usage → token split + cache-aware cost; track unpriced models honestly.\n const usage = db\n .prepare(\n `SELECT t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,\n SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr\n FROM token_usage t JOIN sessions s ON s.id = t.session_id ${w.sql}\n GROUP BY t.model`,\n )\n .all(...w.params) as any[];\n const tokens = zeroSplit();\n let cost = 0;\n const unpriced: string[] = [];\n for (const u of usage) {\n addUsage(tokens, u);\n cost += costForUsage(u.model, { input_tokens: u.i, output_tokens: u.o, cache_creation_input_tokens: u.cw, cache_read_input_tokens: u.cr });\n if (u.model && !rateForModel(u.model) && (u.i || u.o || u.cw || u.cr)) unpriced.push(u.model);\n }\n const totalTokens = tokens.input + tokens.output + tokens.cache_creation + tokens.cache_read;\n\n // Turn-duration percentiles (work cadence), excluding null durations.\n const durs = (\n db\n .prepare(\n `SELECT tn.duration_ms d FROM turns tn JOIN sessions s ON s.id = tn.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tn.duration_ms IS NOT NULL ORDER BY tn.duration_ms`,\n )\n .all(...w.params) as any[]\n ).map((r) => r.d as number);\n\n return {\n range: { from: f.from ?? null, to: f.to ?? null, source: f.source ?? null },\n sessions: counts.sessions ?? 0,\n sessions_main: counts.main ?? 0,\n sessions_subagent: counts.subagent ?? 0,\n turns: counts.turns ?? 0,\n projects: counts.projects ?? 0,\n tool_calls: toolCount ?? 0,\n tokens, // {input, output, cache_creation, cache_read}\n total_tokens: totalTokens,\n cache_read_ratio: totalTokens ? tokens.cache_read / totalTokens : 0,\n cost: Number(cost.toFixed(4)),\n unpriced_models: [...new Set(unpriced)].sort(),\n turn_duration_ms: { p50: pct(durs, 50), p95: pct(durs, 95), count: durs.length },\n };\n}\n\ntype Bucket = \"day\" | \"week\" | \"month\";\n\n/** Pick a bucket so the series stays small regardless of how wide the range is. */\nfunction chooseBucket(db: DB, f: DashFilters): Bucket {\n const w = sessionWhere(f);\n const r = db\n .prepare(`SELECT MIN(started_at) mn, MAX(started_at) mx FROM sessions s ${w.sql}`)\n .get(...w.params) as any;\n if (!r?.mn || !r?.mx) return \"day\";\n const spanDays = (Date.parse(r.mx) - Date.parse(r.mn)) / 86_400_000;\n if (spanDays <= 92) return \"day\";\n if (spanDays <= 730) return \"week\";\n return \"month\";\n}\n\nconst BUCKET_EXPR: Record<Bucket, string> = {\n day: \"strftime('%Y-%m-%d', s.started_at)\",\n week: \"strftime('%Y-W%W', s.started_at)\",\n month: \"strftime('%Y-%m', s.started_at)\",\n};\n\nexport function dashboardTimeseries(db: DB, f: DashFilters, bucketParam?: string) {\n const bucket: Bucket = bucketParam === \"day\" || bucketParam === \"week\" || bucketParam === \"month\" ? bucketParam : chooseBucket(db, f);\n const w = sessionWhere(f);\n const expr = BUCKET_EXPR[bucket];\n\n type Row = { bucket: string } & Split & { cost: number; sessions: number; turns: number };\n const byBucket = new Map<string, Row>();\n const get = (b: string): Row => {\n let r = byBucket.get(b);\n if (!r) {\n r = { bucket: b, ...zeroSplit(), cost: 0, sessions: 0, turns: 0 };\n byBucket.set(b, r);\n }\n return r;\n };\n\n for (const u of db\n .prepare(\n `SELECT ${expr} b, t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,\n SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr\n FROM token_usage t JOIN sessions s ON s.id = t.session_id\n ${w.sql} GROUP BY b, t.model`,\n )\n .all(...w.params) as any[]) {\n if (!u.b) continue;\n const r = get(u.b);\n addUsage(r, u);\n r.cost += costForUsage(u.model, { input_tokens: u.i, output_tokens: u.o, cache_creation_input_tokens: u.cw, cache_read_input_tokens: u.cr });\n }\n for (const s of db.prepare(`SELECT ${expr} b, COUNT(*) n FROM sessions s ${w.sql} GROUP BY b`).all(...w.params) as any[]) {\n if (s.b) get(s.b).sessions = s.n;\n }\n for (const t of db\n .prepare(`SELECT ${expr} b, COUNT(*) n FROM turns tn JOIN sessions s ON s.id = tn.session_id ${w.sql} GROUP BY b`)\n .all(...w.params) as any[]) {\n if (t.b) get(t.b).turns = t.n;\n }\n\n const series = [...byBucket.values()].sort((a, b) => a.bucket.localeCompare(b.bucket));\n for (const r of series) r.cost = Number(r.cost.toFixed(4));\n return { bucket, series };\n}\n\nexport function dashboardBreakdowns(db: DB, f: DashFilters) {\n const w = sessionWhere(f);\n\n const modelRows = db\n .prepare(\n `SELECT t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,\n SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr,\n COUNT(DISTINCT t.session_id) sessions\n FROM token_usage t JOIN sessions s ON s.id = t.session_id ${w.sql}\n GROUP BY t.model ORDER BY (SUM(t.input_tokens)+SUM(t.output_tokens)+SUM(t.cache_creation_input_tokens)+SUM(t.cache_read_input_tokens)) DESC`,\n )\n .all(...w.params) as any[];\n const byModel = modelRows.map((u) => ({\n model: u.model,\n tokens: { input: u.i ?? 0, output: u.o ?? 0, cache_creation: u.cw ?? 0, cache_read: u.cr ?? 0 },\n total_tokens: (u.i ?? 0) + (u.o ?? 0) + (u.cw ?? 0) + (u.cr ?? 0),\n cost: Number(costForUsage(u.model, { input_tokens: u.i, output_tokens: u.o, cache_creation_input_tokens: u.cw, cache_read_input_tokens: u.cr }).toFixed(4)),\n sessions: u.sessions ?? 0,\n priced: !!rateForModel(u.model),\n }));\n\n const bySource = db\n .prepare(\n `SELECT COALESCE(s.source_id, '(none)') source, COUNT(*) sessions, SUM(s.turn_count) turns\n FROM sessions s ${w.sql} GROUP BY s.source_id ORDER BY sessions DESC`,\n )\n .all(...w.params);\n\n // Category / complexity over MAIN sessions only (subagents inherit the parent's task).\n const mainW = { sql: w.sql ? w.sql + \" AND s.is_sidechain = 0\" : \"WHERE s.is_sidechain = 0\", params: w.params };\n const byCategory = db\n .prepare(\n `SELECT c.category, COUNT(*) n FROM classifications c JOIN sessions s ON s.id = c.target_id\n ${mainW.sql} GROUP BY c.category ORDER BY n DESC`,\n )\n .all(...mainW.params);\n const byComplexity = db\n .prepare(\n `SELECT c.complexity_band band, COUNT(*) n FROM classifications c JOIN sessions s ON s.id = c.target_id\n ${mainW.sql} GROUP BY c.complexity_band ORDER BY n DESC`,\n )\n .all(...mainW.params);\n\n const tools = db\n .prepare(\n `SELECT tc.tool_name name, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql} GROUP BY tc.tool_name ORDER BY n DESC LIMIT 20`,\n )\n .all(...w.params);\n\n const skills = db\n .prepare(\n `SELECT tc.skill_name name, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tc.skill_name IS NOT NULL GROUP BY tc.skill_name ORDER BY n DESC LIMIT 20`,\n )\n .all(...w.params);\n\n // Per-version breakdown for the grouped skill bar chart: each captured version's firing count,\n // with the version id + its last-seen time so the chart can stack/hover and deep-link to\n // /skill/:name?v=<id>. Same source/from/to filter as the by-name `skills` query above.\n const skillVersions = db\n .prepare(\n `SELECT tc.skill_name name, sk.id version_id, sk.summary, sk.last_seen, COUNT(*) n\n FROM tool_calls tc\n JOIN skills sk ON sk.id = tc.skill_id\n JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tc.skill_id IS NOT NULL\n GROUP BY tc.skill_name, sk.id ORDER BY name, n DESC`,\n )\n .all(...w.params);\n\n // Subagent fan-out: spawns by type, plus a per-(main-)session histogram of subagent calls.\n const subagentByType = db\n .prepare(\n `SELECT tc.agent_type type, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tc.agent_type IS NOT NULL GROUP BY tc.agent_type ORDER BY n DESC`,\n )\n .all(...w.params);\n const perSession = db\n .prepare(\n `SELECT COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} s.is_sidechain = 0 AND tc.tool_name IN ('Agent','Task')\n GROUP BY tc.session_id`,\n )\n .all(...w.params) as any[];\n const counts = perSession.map((r) => r.n as number);\n const subagentFanout = {\n by_type: subagentByType,\n sessions_with_subagents: counts.length,\n total_spawns: counts.reduce((a, b) => a + b, 0),\n max_per_session: counts.length ? Math.max(...counts) : 0,\n avg_per_session: counts.length ? Number((counts.reduce((a, b) => a + b, 0) / counts.length).toFixed(2)) : 0,\n };\n\n return { by_model: byModel, by_source: bySource, by_category: byCategory, by_complexity: byComplexity, tools, skills, skill_versions: skillVersions, subagent_fanout: subagentFanout };\n}\n","/**\n * `POST /api/refresh` support — the one write-action on an otherwise read-only server (ADR-005).\n * Runs a single collect + ingest pass on the host so the UI can pull in new transcripts on demand,\n * without waiting for the scheduled/`watch` collector. See ADR-015 for the scoped exception.\n *\n * Two guards keep this safe on an unauthenticated loopback server:\n * - `originAllowed` blocks cross-site CSRF (a web page you visit POSTing to 127.0.0.1).\n * - the shared single-instance lock stops it racing a scheduled/`watch`/CLI run.\n * The server's own DB handle stays read-only; ingest opens its own short-lived write connection\n * (WAL permits one writer alongside readers), so the read-only invariant of THIS handle holds.\n */\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { acquireLock, collectAll, findRepoRoot, resolveDataDir, type CollectStats } from \"@agent-lens/core\";\nimport { runIngest } from \"@agent-lens/ingest\";\n\nexport interface RefreshResult {\n ok: true;\n collected: CollectStats;\n}\n\n/**\n * CSRF guard for the loopback server. Allow requests with no `Origin` (non-browser callers like curl\n * or the CLI — not a CSRF vector) and same-machine origins; reject anything cross-site. The server\n * already binds loopback only (ADR-005); this stops a page you visit from driving localhost.\n */\nexport function originAllowed(origin: string | undefined): boolean {\n if (!origin) return true;\n try {\n const h = new URL(origin).hostname;\n return h === \"127.0.0.1\" || h === \"localhost\" || h === \"::1\";\n } catch {\n return false;\n }\n}\n\n/**\n * Run one collect + ingest pass under the shared single-instance lock. Returns `null` if a run is\n * already in progress (caller → 409). Throws on collect/ingest failure (caller → 500). Synchronous\n * and blocking: on a large archive this briefly stalls the server, acceptable for a local\n * single-user tool (the clicking user is waiting on it anyway).\n */\nexport function runRefresh(): RefreshResult | null {\n const dataDir = resolveDataDir(findRepoRoot());\n const lock = acquireLock(join(dataDir, \".agent-lens.lock\"));\n if (!lock) return null;\n try {\n const collected = collectAll();\n // runIngest process.exit(1)s if the archive dir is missing — which would kill the server. Ensure\n // it exists first (collectAll creates it when there's anything to copy; this covers the empty case).\n const archiveRoot = process.env.AGENT_LENS_ARCHIVE || join(dataDir, \"archive\");\n if (!existsSync(archiveRoot)) mkdirSync(archiveRoot, { recursive: true });\n runIngest([]);\n return { ok: true, collected };\n } finally {\n lock.release();\n }\n}\n","/**\n * Agent Lens — security-finding triage store (ADR-018). User-authored triage state (which findings are\n * dismissed as safe, which rules are muted) is NOT derivable from the archive, so it can't live in the\n * rebuildable analytics DB — `detect()` wipes and re-inserts `findings` on every ingest, and\n * `ingest --full` drops every analytics table. Instead it lives in a **separate, writable `triage.db`**\n * that ingest never touches, keyed by the *stable* finding id (`sha1(tool_call_id, rule_id)`), so triage\n * survives re-detection and full rebuilds automatically.\n *\n * The analytics server handle stays read-only (ADR-005): it only ATTACHes this file to JOIN triage\n * state into the findings list. Writes go through this dedicated handle, guarded by the same CSRF check\n * as `POST /api/refresh`. WAL lets the writer run alongside the read handle's queries.\n */\nimport Database from \"better-sqlite3\";\n\nexport type TriageDB = Database.Database;\n\nexport const TRIAGE_SCHEMA_SQL = /* sql */ `\nPRAGMA journal_mode = WAL;\n\n-- Findings the user reviewed and marked safe. Keyed by the stable finding id so it survives re-ingest.\nCREATE TABLE IF NOT EXISTS dismissed_findings (\n finding_id TEXT PRIMARY KEY, -- findings.id = sha1(tool_call_id, rule_id)\n tool_call_id TEXT, -- reference only (may be null)\n rule_id TEXT, -- reference only (may be null)\n note TEXT,\n dismissed_at TEXT NOT NULL\n);\n\n-- Rules the user muted (suppressed as noise), optionally scoped to a project or source. A muted rule\n-- hides its current AND future findings from the open view — the scalable lever for a noisy rule.\nCREATE TABLE IF NOT EXISTS muted_rules (\n rule_id TEXT NOT NULL,\n scope TEXT NOT NULL DEFAULT 'global', -- 'global' | 'project' | 'source'\n scope_id TEXT NOT NULL DEFAULT '', -- project_id / source_id; '' for global (NOT NULL keeps it in the PK)\n note TEXT,\n muted_at TEXT NOT NULL,\n PRIMARY KEY (rule_id, scope, scope_id)\n);\n`;\n\n/** Open (creating if needed) the writable triage DB and ensure its schema. */\nexport function openTriage(file: string): TriageDB {\n const db = new Database(file);\n db.pragma(\"journal_mode = WAL\");\n db.exec(TRIAGE_SCHEMA_SQL);\n return db;\n}\n\nconst now = () => new Date().toISOString();\n\n/** Mark findings safe (idempotent upsert). Returns the number of ids processed. */\nexport function dismiss(db: TriageDB, ids: string[], note?: string | null): number {\n const stmt = db.prepare(\n `INSERT INTO dismissed_findings (finding_id, note, dismissed_at) VALUES (?, ?, ?)\n ON CONFLICT(finding_id) DO UPDATE SET note = excluded.note, dismissed_at = excluded.dismissed_at`,\n );\n const ts = now();\n const tx = db.transaction((list: string[]) => {\n for (const id of list) stmt.run(id, note ?? null, ts);\n });\n tx(ids);\n return ids.length;\n}\n\n/** Reopen (un-dismiss) findings. Returns the number of rows removed. */\nexport function reopen(db: TriageDB, ids: string[]): number {\n const stmt = db.prepare(\"DELETE FROM dismissed_findings WHERE finding_id = ?\");\n let n = 0;\n const tx = db.transaction((list: string[]) => {\n for (const id of list) n += stmt.run(id).changes;\n });\n tx(ids);\n return n;\n}\n\nexport type MuteScope = \"global\" | \"project\" | \"source\";\n\n/** Mute a rule (optionally scoped). Idempotent. */\nexport function muteRule(db: TriageDB, ruleId: string, scope: MuteScope = \"global\", scopeId = \"\", note?: string | null): void {\n db.prepare(\n `INSERT INTO muted_rules (rule_id, scope, scope_id, note, muted_at) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(rule_id, scope, scope_id) DO UPDATE SET note = excluded.note, muted_at = excluded.muted_at`,\n ).run(ruleId, scope, scope === \"global\" ? \"\" : scopeId, note ?? null, now());\n}\n\n/** Unmute a rule. Returns rows removed (0 if it wasn't muted at that scope). */\nexport function unmute(db: TriageDB, ruleId: string, scope: MuteScope = \"global\", scopeId = \"\"): number {\n return db\n .prepare(\"DELETE FROM muted_rules WHERE rule_id = ? AND scope = ? AND scope_id = ?\")\n .run(ruleId, scope, scope === \"global\" ? \"\" : scopeId).changes;\n}\n\nexport interface MuteRow {\n rule_id: string;\n scope: string;\n scope_id: string;\n note: string | null;\n muted_at: string;\n}\n\n/** All muted rules, newest first — for the management panel. */\nexport function listMutes(db: TriageDB): MuteRow[] {\n return db.prepare(\"SELECT rule_id, scope, scope_id, note, muted_at FROM muted_rules ORDER BY muted_at DESC\").all() as MuteRow[];\n}\n","/**\n * `agent-lens watch` — a resident process that collects + ingests whenever a source's transcripts\n * change (debounced), and optionally on a fixed interval. This is the Node-native periodic option;\n * `agent-lens service install collector` layers OS-level persistence (reboot survival) on top.\n *\n * Overlap safety: an in-process guard collapses a burst of file events into one cycle and never runs\n * two cycles at once; the shared single-instance lock additionally guards against a scheduled run\n * firing concurrently.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport chokidar from \"chokidar\";\nimport { acquireLock, collectAll, findRepoRoot, loadSources, resolveDataDir, type Source } from \"@agent-lens/core\";\nimport { runIngest } from \"@agent-lens/ingest\";\n\nexport interface WatchOptions {\n intervalSec?: number;\n poll?: boolean;\n}\n\nexport async function runWatch(opts: WatchOptions = {}): Promise<void> {\n const sources: Source[] = loadSources();\n const lockPath = join(resolveDataDir(findRepoRoot()), \".agent-lens.lock\");\n\n let running = false;\n let pending = false;\n const cycle = () => {\n if (running) {\n pending = true; // a change arrived mid-cycle — run once more when this finishes\n return;\n }\n running = true;\n const lock = acquireLock(lockPath);\n if (!lock) {\n running = false;\n console.error(\"agent-lens watch: another collect/ingest run is in progress — skipping this cycle\");\n return;\n }\n try {\n const stats = collectAll({ sources });\n console.log(\n `agent-lens watch: collected ${stats.scanned} files (${stats.appended} appended, ${stats.snapshots} snapshots) — ingesting`,\n );\n runIngest([]);\n } catch (e) {\n console.error(\"agent-lens watch: cycle failed:\", e);\n } finally {\n lock.release();\n running = false;\n if (pending) {\n pending = false;\n setTimeout(cycle, 0);\n }\n }\n };\n\n cycle(); // initial catch-up\n\n const watchPaths = sources.map((s) => join(s.configDir, \"projects\")).filter((p) => existsSync(p));\n if (watchPaths.length === 0) {\n console.error(\"agent-lens watch: no source 'projects' directories found — nothing to watch\");\n }\n const watcher = chokidar.watch(watchPaths, {\n ignoreInitial: true,\n usePolling: !!opts.poll,\n awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },\n });\n let debounce: NodeJS.Timeout | null = null;\n const trigger = () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(cycle, 3000); // coalesce bursts of writes into one cycle\n };\n watcher.on(\"add\", trigger).on(\"change\", trigger);\n\n if (opts.intervalSec && opts.intervalSec > 0) {\n setInterval(cycle, opts.intervalSec * 1000);\n }\n\n console.log(\n `agent-lens watch: watching ${watchPaths.length} source dir(s)` +\n (opts.intervalSec ? ` + every ${opts.intervalSec}s` : \"\") +\n \" — Ctrl-C to stop\",\n );\n // Keep the process alive indefinitely (chokidar + timers hold it open, but be explicit).\n return new Promise<void>(() => {});\n}\n"],"mappings":"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","names":["readFileSync","dirname","join","fileURLToPath","cli","version","readFileSync","readFileSync","existsSync","dirname","join","platform","platform","dirname","join","existsSync","existsSync","mkdirSync","readFileSync","unlinkSync","dirname","mkdirSync","writeFileSync","homedir","platform","join","cli","join","homedir","mkdirSync","writeFileSync","platform","readFileSync","statSync","mkdirSync","existsSync","dirname","join","readdirSync","basename","join","createHash","createHash","c","now","readdirSync","readFileSync","statSync","basename","join","createHash","createHash","openSync","readSync","closeSync","CHUNK","readdirSync","join","now","statSync","readFileSync","basename","durationMs","readdirSync","readFileSync","statSync","basename","join","readdirSync","join","intOrNull","strOrNull","now","statSync","readFileSync","basename","readdirSync","readFileSync","statSync","basename","join","readdirSync","join","sessionFromPath","now","statSync","readFileSync","basename","join","existsSync","mkdirSync","dirname","now","statSync","readFileSync","existsSync","dirname","join","fileURLToPath","Database","Database","CHUNK","existsSync","existsSync","mkdirSync","join","join","existsSync","mkdirSync","Database","existsSync","dirname","fileURLToPath","join","existsSync","existsSync","join","join","existsSync","readFileSync","join","dirname","fileURLToPath"]}
1
+ {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/cac@6.7.14/node_modules/cac/dist/index.mjs","../../core/src/schema.ts","../../core/src/security.ts","../../core/src/errors.ts","../../core/src/pricing.ts","../../core/src/markdown.ts","../../core/src/rawjson.ts","../../core/src/paths.ts","../../core/src/sources.ts","../../core/src/projects.ts","../../core/src/collect.ts","../../core/src/lock.ts","../../core/src/service.ts","../../ingest/src/run.ts","../../ingest/src/db.ts","../../ingest/src/adapters/claude-code.ts","../../ingest/src/classify.ts","../../ingest/src/detect.ts","../../ingest/src/errors.ts","../../ingest/src/pipeline.ts","../../ingest/src/workflows.ts","../../ingest/src/redact.ts","../../ingest/src/fileread.ts","../../ingest/src/meta.ts","../../ingest/src/toolResults.ts","../../server/src/run.ts","../../server/src/db.ts","../../server/src/app.ts","../../server/src/dashboard.ts","../../server/src/refresh.ts","../../server/src/triage.ts","../src/watch.ts"],"sourcesContent":["/**\n * `agent-lens` — the unified CLI. One binary dispatching the pipeline: collect → ingest → serve,\n * plus watch (resident collect+ingest) and service (install collect+ingest and/or serve as OS\n * services). Bundled into a single file by tsup so it installs as one npm package (ADR-010).\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { cac } from \"cac\";\nimport { acquireLock, collectAll, findRepoRoot, parseHours, parseTargets, resolveDataDir, runService } from \"@agent-lens/core\";\nimport { runIngest, runMetrics } from \"@agent-lens/ingest\";\nimport { startServer } from \"@agent-lens/server\";\nimport { runWatch } from \"./watch.js\";\n\nfunction version(): string {\n try {\n const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), \"../package.json\"), \"utf8\"));\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/** Run `fn` under the single-instance lock so collect/ingest never overlap another run. */\nfunction withLock(fn: () => void): void {\n const lockPath = join(resolveDataDir(findRepoRoot()), \".agent-lens.lock\");\n const lock = acquireLock(lockPath);\n if (!lock) {\n console.error(\"agent-lens: another collect/ingest run is in progress — skipping\");\n return;\n }\n try {\n fn();\n } finally {\n lock.release();\n }\n}\n\nconst cli = cac(\"agent-lens\");\n\ncli\n .command(\"collect\", \"Copy new session traces into the local archive\")\n .option(\"--then-ingest\", \"Run ingest immediately after collecting\")\n .action((opts: { thenIngest?: boolean }) => {\n withLock(() => {\n const s = collectAll();\n console.log(\n `agent-lens: collect done — ${s.sources} source(s), ${s.scanned} scanned, ${s.copied} copied, ` +\n `${s.appended} appended, ${s.diverged} diverged, ${s.compacted} compacted, ${s.snapshots} snapshots`,\n );\n if (opts.thenIngest) runIngest([]);\n });\n });\n\ncli\n .command(\"ingest\", \"Build/update the SQLite projection from the archive\")\n .option(\"--full\", \"Ignore incremental state and re-read every file\")\n .option(\"--db <path>\", \"SQLite DB path\")\n .option(\"--archive <path>\", \"Archive directory\")\n .action((opts: { full?: boolean; db?: string; archive?: string }) => {\n const argv: string[] = [];\n if (opts.full) argv.push(\"--full\");\n if (opts.db) argv.push(\"--db\", opts.db);\n if (opts.archive) argv.push(\"--archive\", opts.archive);\n withLock(() => runIngest(argv));\n });\n\ncli\n .command(\"serve\", \"Serve the web UI + read-only API on 127.0.0.1\")\n .action(async () => {\n await startServer();\n });\n\ncli\n .command(\"watch\", \"Watch sources and collect + ingest on change (resident process)\")\n .option(\"--interval <sec>\", \"Also run on a fixed interval (seconds)\")\n .option(\"--poll\", \"Use polling — for network filesystems where fs events don't fire\")\n .action(async (opts: { interval?: string; poll?: boolean }) => {\n await runWatch({ intervalSec: opts.interval ? Number(opts.interval) : undefined, poll: !!opts.poll });\n });\n\ncli\n .command(\"metrics\", \"Re-run classification over an already-ingested DB\")\n .option(\"--db <path>\", \"SQLite DB path\")\n .action((opts: { db?: string }) => {\n runMetrics(opts.db ? [\"--db\", opts.db] : []);\n });\n\ncli\n .command(\n \"service <action> [target]\",\n \"Install/uninstall/status OS services — action: install|uninstall|status; target: collector|server|all (default all)\",\n )\n .option(\"--times <hours>\", \"Collector cadence: comma-separated hours 0-23 (default: 9,13,17,21)\")\n .action((action: string, target: string | undefined, opts: { times?: string }) => {\n const targets = parseTargets(target);\n const hours = action === \"install\" ? parseHours(opts.times) : undefined;\n // The bundled CLI file is this module; bake its absolute path + node into every unit/plist/task.\n runService(action, { cliEntry: fileURLToPath(import.meta.url), hours, targets });\n });\n\ncli.help();\ncli.version(version());\ncli.parse();\n","import { EventEmitter } from 'events';\n\nfunction toArr(any) {\n\treturn any == null ? [] : Array.isArray(any) ? any : [any];\n}\n\nfunction toVal(out, key, val, opts) {\n\tvar x, old=out[key], nxt=(\n\t\t!!~opts.string.indexOf(key) ? (val == null || val === true ? '' : String(val))\n\t\t: typeof val === 'boolean' ? val\n\t\t: !!~opts.boolean.indexOf(key) ? (val === 'false' ? false : val === 'true' || (out._.push((x = +val,x * 0 === 0) ? x : val),!!val))\n\t\t: (x = +val,x * 0 === 0) ? x : val\n\t);\n\tout[key] = old == null ? nxt : (Array.isArray(old) ? old.concat(nxt) : [old, nxt]);\n}\n\nfunction mri2 (args, opts) {\n\targs = args || [];\n\topts = opts || {};\n\n\tvar k, arr, arg, name, val, out={ _:[] };\n\tvar i=0, j=0, idx=0, len=args.length;\n\n\tconst alibi = opts.alias !== void 0;\n\tconst strict = opts.unknown !== void 0;\n\tconst defaults = opts.default !== void 0;\n\n\topts.alias = opts.alias || {};\n\topts.string = toArr(opts.string);\n\topts.boolean = toArr(opts.boolean);\n\n\tif (alibi) {\n\t\tfor (k in opts.alias) {\n\t\t\tarr = opts.alias[k] = toArr(opts.alias[k]);\n\t\t\tfor (i=0; i < arr.length; i++) {\n\t\t\t\t(opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (i=opts.boolean.length; i-- > 0;) {\n\t\tarr = opts.alias[opts.boolean[i]] || [];\n\t\tfor (j=arr.length; j-- > 0;) opts.boolean.push(arr[j]);\n\t}\n\n\tfor (i=opts.string.length; i-- > 0;) {\n\t\tarr = opts.alias[opts.string[i]] || [];\n\t\tfor (j=arr.length; j-- > 0;) opts.string.push(arr[j]);\n\t}\n\n\tif (defaults) {\n\t\tfor (k in opts.default) {\n\t\t\tname = typeof opts.default[k];\n\t\t\tarr = opts.alias[k] = opts.alias[k] || [];\n\t\t\tif (opts[name] !== void 0) {\n\t\t\t\topts[name].push(k);\n\t\t\t\tfor (i=0; i < arr.length; i++) {\n\t\t\t\t\topts[name].push(arr[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst keys = strict ? Object.keys(opts.alias) : [];\n\n\tfor (i=0; i < len; i++) {\n\t\targ = args[i];\n\n\t\tif (arg === '--') {\n\t\t\tout._ = out._.concat(args.slice(++i));\n\t\t\tbreak;\n\t\t}\n\n\t\tfor (j=0; j < arg.length; j++) {\n\t\t\tif (arg.charCodeAt(j) !== 45) break; // \"-\"\n\t\t}\n\n\t\tif (j === 0) {\n\t\t\tout._.push(arg);\n\t\t} else if (arg.substring(j, j + 3) === 'no-') {\n\t\t\tname = arg.substring(j + 3);\n\t\t\tif (strict && !~keys.indexOf(name)) {\n\t\t\t\treturn opts.unknown(arg);\n\t\t\t}\n\t\t\tout[name] = false;\n\t\t} else {\n\t\t\tfor (idx=j+1; idx < arg.length; idx++) {\n\t\t\t\tif (arg.charCodeAt(idx) === 61) break; // \"=\"\n\t\t\t}\n\n\t\t\tname = arg.substring(j, idx);\n\t\t\tval = arg.substring(++idx) || (i+1 === len || (''+args[i+1]).charCodeAt(0) === 45 || args[++i]);\n\t\t\tarr = (j === 2 ? [name] : name);\n\n\t\t\tfor (idx=0; idx < arr.length; idx++) {\n\t\t\t\tname = arr[idx];\n\t\t\t\tif (strict && !~keys.indexOf(name)) return opts.unknown('-'.repeat(j) + name);\n\t\t\t\ttoVal(out, name, (idx + 1 < arr.length) || val, opts);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (defaults) {\n\t\tfor (k in opts.default) {\n\t\t\tif (out[k] === void 0) {\n\t\t\t\tout[k] = opts.default[k];\n\t\t\t}\n\t\t}\n\t}\n\n\tif (alibi) {\n\t\tfor (k in out) {\n\t\t\tarr = opts.alias[k] || [];\n\t\t\twhile (arr.length > 0) {\n\t\t\t\tout[arr.shift()] = out[k];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out;\n}\n\nconst removeBrackets = (v) => v.replace(/[<[].+/, \"\").trim();\nconst findAllBrackets = (v) => {\n const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;\n const SQUARE_BRACKET_RE_GLOBAL = /\\[([^\\]]+)\\]/g;\n const res = [];\n const parse = (match) => {\n let variadic = false;\n let value = match[1];\n if (value.startsWith(\"...\")) {\n value = value.slice(3);\n variadic = true;\n }\n return {\n required: match[0].startsWith(\"<\"),\n value,\n variadic\n };\n };\n let angledMatch;\n while (angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v)) {\n res.push(parse(angledMatch));\n }\n let squareMatch;\n while (squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v)) {\n res.push(parse(squareMatch));\n }\n return res;\n};\nconst getMriOptions = (options) => {\n const result = {alias: {}, boolean: []};\n for (const [index, option] of options.entries()) {\n if (option.names.length > 1) {\n result.alias[option.names[0]] = option.names.slice(1);\n }\n if (option.isBoolean) {\n if (option.negated) {\n const hasStringTypeOption = options.some((o, i) => {\n return i !== index && o.names.some((name) => option.names.includes(name)) && typeof o.required === \"boolean\";\n });\n if (!hasStringTypeOption) {\n result.boolean.push(option.names[0]);\n }\n } else {\n result.boolean.push(option.names[0]);\n }\n }\n }\n return result;\n};\nconst findLongest = (arr) => {\n return arr.sort((a, b) => {\n return a.length > b.length ? -1 : 1;\n })[0];\n};\nconst padRight = (str, length) => {\n return str.length >= length ? str : `${str}${\" \".repeat(length - str.length)}`;\n};\nconst camelcase = (input) => {\n return input.replace(/([a-z])-([a-z])/g, (_, p1, p2) => {\n return p1 + p2.toUpperCase();\n });\n};\nconst setDotProp = (obj, keys, val) => {\n let i = 0;\n let length = keys.length;\n let t = obj;\n let x;\n for (; i < length; ++i) {\n x = t[keys[i]];\n t = t[keys[i]] = i === length - 1 ? val : x != null ? x : !!~keys[i + 1].indexOf(\".\") || !(+keys[i + 1] > -1) ? {} : [];\n }\n};\nconst setByType = (obj, transforms) => {\n for (const key of Object.keys(transforms)) {\n const transform = transforms[key];\n if (transform.shouldTransform) {\n obj[key] = Array.prototype.concat.call([], obj[key]);\n if (typeof transform.transformFunction === \"function\") {\n obj[key] = obj[key].map(transform.transformFunction);\n }\n }\n }\n};\nconst getFileName = (input) => {\n const m = /([^\\\\\\/]+)$/.exec(input);\n return m ? m[1] : \"\";\n};\nconst camelcaseOptionName = (name) => {\n return name.split(\".\").map((v, i) => {\n return i === 0 ? camelcase(v) : v;\n }).join(\".\");\n};\nclass CACError extends Error {\n constructor(message) {\n super(message);\n this.name = this.constructor.name;\n if (typeof Error.captureStackTrace === \"function\") {\n Error.captureStackTrace(this, this.constructor);\n } else {\n this.stack = new Error(message).stack;\n }\n }\n}\n\nclass Option {\n constructor(rawName, description, config) {\n this.rawName = rawName;\n this.description = description;\n this.config = Object.assign({}, config);\n rawName = rawName.replace(/\\.\\*/g, \"\");\n this.negated = false;\n this.names = removeBrackets(rawName).split(\",\").map((v) => {\n let name = v.trim().replace(/^-{1,2}/, \"\");\n if (name.startsWith(\"no-\")) {\n this.negated = true;\n name = name.replace(/^no-/, \"\");\n }\n return camelcaseOptionName(name);\n }).sort((a, b) => a.length > b.length ? 1 : -1);\n this.name = this.names[this.names.length - 1];\n if (this.negated && this.config.default == null) {\n this.config.default = true;\n }\n if (rawName.includes(\"<\")) {\n this.required = true;\n } else if (rawName.includes(\"[\")) {\n this.required = false;\n } else {\n this.isBoolean = true;\n }\n }\n}\n\nconst processArgs = process.argv;\nconst platformInfo = `${process.platform}-${process.arch} node-${process.version}`;\n\nclass Command {\n constructor(rawName, description, config = {}, cli) {\n this.rawName = rawName;\n this.description = description;\n this.config = config;\n this.cli = cli;\n this.options = [];\n this.aliasNames = [];\n this.name = removeBrackets(rawName);\n this.args = findAllBrackets(rawName);\n this.examples = [];\n }\n usage(text) {\n this.usageText = text;\n return this;\n }\n allowUnknownOptions() {\n this.config.allowUnknownOptions = true;\n return this;\n }\n ignoreOptionDefaultValue() {\n this.config.ignoreOptionDefaultValue = true;\n return this;\n }\n version(version, customFlags = \"-v, --version\") {\n this.versionNumber = version;\n this.option(customFlags, \"Display version number\");\n return this;\n }\n example(example) {\n this.examples.push(example);\n return this;\n }\n option(rawName, description, config) {\n const option = new Option(rawName, description, config);\n this.options.push(option);\n return this;\n }\n alias(name) {\n this.aliasNames.push(name);\n return this;\n }\n action(callback) {\n this.commandAction = callback;\n return this;\n }\n isMatched(name) {\n return this.name === name || this.aliasNames.includes(name);\n }\n get isDefaultCommand() {\n return this.name === \"\" || this.aliasNames.includes(\"!\");\n }\n get isGlobalCommand() {\n return this instanceof GlobalCommand;\n }\n hasOption(name) {\n name = name.split(\".\")[0];\n return this.options.find((option) => {\n return option.names.includes(name);\n });\n }\n outputHelp() {\n const {name, commands} = this.cli;\n const {\n versionNumber,\n options: globalOptions,\n helpCallback\n } = this.cli.globalCommand;\n let sections = [\n {\n body: `${name}${versionNumber ? `/${versionNumber}` : \"\"}`\n }\n ];\n sections.push({\n title: \"Usage\",\n body: ` $ ${name} ${this.usageText || this.rawName}`\n });\n const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;\n if (showCommands) {\n const longestCommandName = findLongest(commands.map((command) => command.rawName));\n sections.push({\n title: \"Commands\",\n body: commands.map((command) => {\n return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;\n }).join(\"\\n\")\n });\n sections.push({\n title: `For more info, run any command with the \\`--help\\` flag`,\n body: commands.map((command) => ` $ ${name}${command.name === \"\" ? \"\" : ` ${command.name}`} --help`).join(\"\\n\")\n });\n }\n let options = this.isGlobalCommand ? globalOptions : [...this.options, ...globalOptions || []];\n if (!this.isGlobalCommand && !this.isDefaultCommand) {\n options = options.filter((option) => option.name !== \"version\");\n }\n if (options.length > 0) {\n const longestOptionName = findLongest(options.map((option) => option.rawName));\n sections.push({\n title: \"Options\",\n body: options.map((option) => {\n return ` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${option.config.default === void 0 ? \"\" : `(default: ${option.config.default})`}`;\n }).join(\"\\n\")\n });\n }\n if (this.examples.length > 0) {\n sections.push({\n title: \"Examples\",\n body: this.examples.map((example) => {\n if (typeof example === \"function\") {\n return example(name);\n }\n return example;\n }).join(\"\\n\")\n });\n }\n if (helpCallback) {\n sections = helpCallback(sections) || sections;\n }\n console.log(sections.map((section) => {\n return section.title ? `${section.title}:\n${section.body}` : section.body;\n }).join(\"\\n\\n\"));\n }\n outputVersion() {\n const {name} = this.cli;\n const {versionNumber} = this.cli.globalCommand;\n if (versionNumber) {\n console.log(`${name}/${versionNumber} ${platformInfo}`);\n }\n }\n checkRequiredArgs() {\n const minimalArgsCount = this.args.filter((arg) => arg.required).length;\n if (this.cli.args.length < minimalArgsCount) {\n throw new CACError(`missing required args for command \\`${this.rawName}\\``);\n }\n }\n checkUnknownOptions() {\n const {options, globalCommand} = this.cli;\n if (!this.config.allowUnknownOptions) {\n for (const name of Object.keys(options)) {\n if (name !== \"--\" && !this.hasOption(name) && !globalCommand.hasOption(name)) {\n throw new CACError(`Unknown option \\`${name.length > 1 ? `--${name}` : `-${name}`}\\``);\n }\n }\n }\n }\n checkOptionValue() {\n const {options: parsedOptions, globalCommand} = this.cli;\n const options = [...globalCommand.options, ...this.options];\n for (const option of options) {\n const value = parsedOptions[option.name.split(\".\")[0]];\n if (option.required) {\n const hasNegated = options.some((o) => o.negated && o.names.includes(option.name));\n if (value === true || value === false && !hasNegated) {\n throw new CACError(`option \\`${option.rawName}\\` value is missing`);\n }\n }\n }\n }\n}\nclass GlobalCommand extends Command {\n constructor(cli) {\n super(\"@@global@@\", \"\", {}, cli);\n }\n}\n\nvar __assign = Object.assign;\nclass CAC extends EventEmitter {\n constructor(name = \"\") {\n super();\n this.name = name;\n this.commands = [];\n this.rawArgs = [];\n this.args = [];\n this.options = {};\n this.globalCommand = new GlobalCommand(this);\n this.globalCommand.usage(\"<command> [options]\");\n }\n usage(text) {\n this.globalCommand.usage(text);\n return this;\n }\n command(rawName, description, config) {\n const command = new Command(rawName, description || \"\", config, this);\n command.globalCommand = this.globalCommand;\n this.commands.push(command);\n return command;\n }\n option(rawName, description, config) {\n this.globalCommand.option(rawName, description, config);\n return this;\n }\n help(callback) {\n this.globalCommand.option(\"-h, --help\", \"Display this message\");\n this.globalCommand.helpCallback = callback;\n this.showHelpOnExit = true;\n return this;\n }\n version(version, customFlags = \"-v, --version\") {\n this.globalCommand.version(version, customFlags);\n this.showVersionOnExit = true;\n return this;\n }\n example(example) {\n this.globalCommand.example(example);\n return this;\n }\n outputHelp() {\n if (this.matchedCommand) {\n this.matchedCommand.outputHelp();\n } else {\n this.globalCommand.outputHelp();\n }\n }\n outputVersion() {\n this.globalCommand.outputVersion();\n }\n setParsedInfo({args, options}, matchedCommand, matchedCommandName) {\n this.args = args;\n this.options = options;\n if (matchedCommand) {\n this.matchedCommand = matchedCommand;\n }\n if (matchedCommandName) {\n this.matchedCommandName = matchedCommandName;\n }\n return this;\n }\n unsetMatchedCommand() {\n this.matchedCommand = void 0;\n this.matchedCommandName = void 0;\n }\n parse(argv = processArgs, {\n run = true\n } = {}) {\n this.rawArgs = argv;\n if (!this.name) {\n this.name = argv[1] ? getFileName(argv[1]) : \"cli\";\n }\n let shouldParse = true;\n for (const command of this.commands) {\n const parsed = this.mri(argv.slice(2), command);\n const commandName = parsed.args[0];\n if (command.isMatched(commandName)) {\n shouldParse = false;\n const parsedInfo = __assign(__assign({}, parsed), {\n args: parsed.args.slice(1)\n });\n this.setParsedInfo(parsedInfo, command, commandName);\n this.emit(`command:${commandName}`, command);\n }\n }\n if (shouldParse) {\n for (const command of this.commands) {\n if (command.name === \"\") {\n shouldParse = false;\n const parsed = this.mri(argv.slice(2), command);\n this.setParsedInfo(parsed, command);\n this.emit(`command:!`, command);\n }\n }\n }\n if (shouldParse) {\n const parsed = this.mri(argv.slice(2));\n this.setParsedInfo(parsed);\n }\n if (this.options.help && this.showHelpOnExit) {\n this.outputHelp();\n run = false;\n this.unsetMatchedCommand();\n }\n if (this.options.version && this.showVersionOnExit && this.matchedCommandName == null) {\n this.outputVersion();\n run = false;\n this.unsetMatchedCommand();\n }\n const parsedArgv = {args: this.args, options: this.options};\n if (run) {\n this.runMatchedCommand();\n }\n if (!this.matchedCommand && this.args[0]) {\n this.emit(\"command:*\");\n }\n return parsedArgv;\n }\n mri(argv, command) {\n const cliOptions = [\n ...this.globalCommand.options,\n ...command ? command.options : []\n ];\n const mriOptions = getMriOptions(cliOptions);\n let argsAfterDoubleDashes = [];\n const doubleDashesIndex = argv.indexOf(\"--\");\n if (doubleDashesIndex > -1) {\n argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);\n argv = argv.slice(0, doubleDashesIndex);\n }\n let parsed = mri2(argv, mriOptions);\n parsed = Object.keys(parsed).reduce((res, name) => {\n return __assign(__assign({}, res), {\n [camelcaseOptionName(name)]: parsed[name]\n });\n }, {_: []});\n const args = parsed._;\n const options = {\n \"--\": argsAfterDoubleDashes\n };\n const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;\n let transforms = Object.create(null);\n for (const cliOption of cliOptions) {\n if (!ignoreDefault && cliOption.config.default !== void 0) {\n for (const name of cliOption.names) {\n options[name] = cliOption.config.default;\n }\n }\n if (Array.isArray(cliOption.config.type)) {\n if (transforms[cliOption.name] === void 0) {\n transforms[cliOption.name] = Object.create(null);\n transforms[cliOption.name][\"shouldTransform\"] = true;\n transforms[cliOption.name][\"transformFunction\"] = cliOption.config.type[0];\n }\n }\n }\n for (const key of Object.keys(parsed)) {\n if (key !== \"_\") {\n const keys = key.split(\".\");\n setDotProp(options, keys, parsed[key]);\n setByType(options, transforms);\n }\n }\n return {\n args,\n options\n };\n }\n runMatchedCommand() {\n const {args, options, matchedCommand: command} = this;\n if (!command || !command.commandAction)\n return;\n command.checkUnknownOptions();\n command.checkOptionValue();\n command.checkRequiredArgs();\n const actionArgs = [];\n command.args.forEach((arg, index) => {\n if (arg.variadic) {\n actionArgs.push(args.slice(index));\n } else {\n actionArgs.push(args[index]);\n }\n });\n actionArgs.push(options);\n return command.commandAction.apply(this, actionArgs);\n }\n}\n\nconst cac = (name = \"\") => new CAC(name);\n\nexport default cac;\nexport { CAC, Command, cac };\n","/**\n * Agent Lens — normalized, agent-agnostic data model (SQLite DDL).\n *\n * Design notes\n * - Agent-agnostic: Claude Code is one *source adapter*. Adding another agent later means a new\n * adapter that emits rows for these same tables — no schema change (see ADR-003).\n * - Event-grained source of truth: every transcript line becomes a row in `events`, keyed by its\n * stable `uuid`. Re-ingesting the same event is an UPSERT, so ingesting the mirror plus all\n * `.versions/` divergence backups deduplicates to the *maximal* history and recovers\n * compaction-dropped events (see ADR-001 / ADR-002).\n * - Hierarchy: session › turn › event. A \"turn\" = one user prompt → assistant completion.\n * - `raw_json` keeps the original line verbatim for lossless re-derivation; structured columns are\n * projections for querying/dashboards. It is stored gzip-compressed as a BLOB (ADR-011) — write via\n * `packRaw`, read via `unpackRaw` (both in `rawjson.ts`).\n *\n * This module only declares the schema. Connection handling and migrations live in `db.ts`\n * (Phase 2). Bump SCHEMA_VERSION on any DDL change.\n */\n\nexport const SCHEMA_VERSION = 13;\n\nexport const SCHEMA_SQL = /* sql */ `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\n-- Schema/version bookkeeping ------------------------------------------------\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- An agent *type* (e.g. 'claude-code'). Extensible to other agents. ---------\nCREATE TABLE IF NOT EXISTS agents (\n id TEXT PRIMARY KEY, -- 'claude-code'\n name TEXT NOT NULL, -- 'Claude Code CLI'\n kind TEXT NOT NULL DEFAULT 'cli'\n);\n\n-- A configured source = a labeled agent *instance* (e.g. account 'personal' --\n-- of agent 'claude-code' rooted at ~/.claude). Sessions belong to a source. --\nCREATE TABLE IF NOT EXISTS sources (\n id TEXT PRIMARY KEY, -- the (unique) label, e.g. 'personal'\n label TEXT NOT NULL,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n config_dir TEXT\n);\n\n-- A project = a working directory a session ran in. ------------------------\nCREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY, -- stable hash of (agent_id, path)\n agent_id TEXT NOT NULL REFERENCES agents(id),\n path TEXT NOT NULL, -- decoded absolute cwd\n encoded_dir TEXT, -- original '-home-...' archive dir name\n first_seen TEXT, -- ISO8601\n last_seen TEXT,\n UNIQUE (agent_id, path)\n);\n\n-- A session = one transcript file (sessionId). -----------------------------\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY, -- sessionId (UUID)\n agent_id TEXT NOT NULL REFERENCES agents(id),\n source_id TEXT REFERENCES sources(id),\n project_id TEXT REFERENCES projects(id),\n slug TEXT,\n ai_title TEXT,\n cli_version TEXT,\n entrypoint TEXT,\n git_branch TEXT,\n is_sidechain INTEGER NOT NULL DEFAULT 0, -- 1 = subagent thread\n spawn_parent_id TEXT, -- structural parent from path (<parent>/subagents/…); raw link hint, set at ingest\n workflow_run_id TEXT, -- for a Workflow-tool subagent: its run id (wf_<id>) from the path; groups fan-out by run\n parent_session_id TEXT REFERENCES sessions(id), -- spawning session (subagents only); derived in rebuildDerived\n parent_turn_id TEXT REFERENCES turns(id), -- the turn that spawned this subagent\n started_at TEXT, -- ISO8601 of first event\n ended_at TEXT, -- ISO8601 of last event\n duration_ms INTEGER,\n event_count INTEGER NOT NULL DEFAULT 0,\n turn_count INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_source ON sessions(source_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at);\nCREATE INDEX IF NOT EXISTS idx_sessions_parent_session ON sessions(parent_session_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_workflow_run ON sessions(workflow_run_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_parent_turn ON sessions(parent_turn_id);\n\n-- A turn = user prompt -> assistant completion within a session. ------------\nCREATE TABLE IF NOT EXISTS turns (\n id TEXT PRIMARY KEY, -- hash(session_id, seq)\n session_id TEXT NOT NULL REFERENCES sessions(id),\n seq INTEGER NOT NULL, -- 0-based order within session\n user_event_uuid TEXT, -- the originating user event\n prompt_preview TEXT, -- short, for lists (full text in events)\n model TEXT, -- model that answered this turn\n started_at TEXT,\n ended_at TEXT,\n duration_ms INTEGER,\n UNIQUE (session_id, seq)\n);\nCREATE INDEX IF NOT EXISTS idx_turns_session ON turns(session_id);\n\n-- Every transcript line. uuid is the dedup key across archive versions. -----\nCREATE TABLE IF NOT EXISTS events (\n uuid TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n turn_id TEXT REFERENCES turns(id),\n parent_uuid TEXT,\n seq INTEGER, -- line order as first observed\n type TEXT NOT NULL, -- user | assistant | system | attachment | ...\n role TEXT, -- user | assistant (message.role)\n timestamp TEXT, -- ISO8601\n model TEXT,\n is_sidechain INTEGER NOT NULL DEFAULT 0,\n is_meta INTEGER NOT NULL DEFAULT 0,\n text TEXT, -- flattened text/thinking for search + preview\n raw_json BLOB NOT NULL, -- original line, verbatim, gzip-compressed (ADR-011); read via unpackRaw()\n source_file TEXT -- archive path the canonical copy came from\n);\nCREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_turn ON events(turn_id);\nCREATE INDEX IF NOT EXISTS idx_events_type ON events(type);\nCREATE INDEX IF NOT EXISTS idx_events_ts ON events(timestamp);\n\n-- Tool invocations (incl. subagents / Task calls). -------------------------\nCREATE TABLE IF NOT EXISTS tool_calls (\n id TEXT PRIMARY KEY, -- tool_use id, or hash\n event_uuid TEXT REFERENCES events(uuid),\n session_id TEXT NOT NULL REFERENCES sessions(id),\n turn_id TEXT REFERENCES turns(id),\n tool_name TEXT NOT NULL, -- Edit, Write, Bash, Skill, Task, ...\n caller TEXT,\n skill_name TEXT, -- when tool_name = Skill\n skill_id TEXT REFERENCES skills(id), -- the specific skill *version* this call fired (content hash); set in rebuildDerived\n agent_type TEXT, -- subagent type (toolUseResult.agentType)\n spawned_session_id TEXT, -- for Task/Agent: the subagent session id ('agent-'||agentId)\n workflow_run_id TEXT, -- for Workflow: the run id (toolUseResult.runId, e.g. wf_<id>) — ties the run to its subagents\n workflow_name TEXT, -- for Workflow: the workflow name (toolUseResult.workflowName)\n resolved_model TEXT,\n status TEXT, -- success | error | ... ('error' when the tool_result had is_error)\n error_type TEXT, -- heuristic bucket for status='error' rows (see errors.ts); NULL otherwise\n total_duration_ms INTEGER,\n total_tokens INTEGER,\n total_tool_use_count INTEGER,\n input_json TEXT, -- tool input, verbatim\n result_summary TEXT -- short result projection (no secrets dumped wholesale)\n);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_tool ON tool_calls(tool_name);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_skill ON tool_calls(skill_name);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_skill_id ON tool_calls(skill_id);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_spawned ON tool_calls(spawned_session_id);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_workflow_run ON tool_calls(workflow_run_id);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_error_type ON tool_calls(error_type);\n\n-- Skill *versions* (content-addressed). A skill firing injects the full SKILL.md body into the\n-- transcript as an isMeta user event (begins \"Base directory for this skill: …\", then the body,\n-- then a trailing \"ARGUMENTS: …\" block). That body is the only real skill content available (the\n-- trace carries no description/created_at). We identify a version by hashing the *normalized* body\n-- (Base-directory line + per-call ARGUMENTS block stripped, trailing whitespace trimmed) so the same\n-- content from either install / any args is one version, and a content change shows as a new row.\n-- Linked from tool_calls.skill_id in rebuildDerived. See plan: content-addressed skill versioning.\nCREATE TABLE IF NOT EXISTS skills (\n id TEXT PRIMARY KEY, -- sha1 of name + NUL + normalized_body\n name TEXT NOT NULL,\n base_dir TEXT, -- last-seen base directory (informational; NOT part of the hash)\n body TEXT NOT NULL, -- normalized SKILL.md body (Base-dir line + ARGUMENTS block stripped)\n summary TEXT, -- first heading/intro line, for list + detail display\n body_bytes INTEGER,\n first_seen TEXT, -- earliest firing timestamp (MIN across firings)\n last_seen TEXT -- latest firing timestamp (MAX across firings)\n);\nCREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);\n\n-- Token usage at the assistant-response grain. Cost derived later from model. --\n-- One API response is logged across multiple JSONL lines (one per content block: text + each\n-- tool_use), and Claude Code stamps the SAME usage object on every line. We dedup at the response\n-- grain via the unique (session_id, message_id) index so a response's tokens are counted ONCE, not\n-- once per content block (which over-counted tokens ~2.3x and cost ~3x). event_uuid stays the PK\n-- (the row is attached to whichever content-block event won the insert). --\nCREATE TABLE IF NOT EXISTS token_usage (\n event_uuid TEXT PRIMARY KEY REFERENCES events(uuid),\n session_id TEXT NOT NULL REFERENCES sessions(id),\n turn_id TEXT REFERENCES turns(id),\n message_id TEXT, -- Anthropic response id (msg_…); dedup key\n model TEXT,\n input_tokens INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_input_tokens INTEGER NOT NULL DEFAULT 0,\n cache_read_input_tokens INTEGER NOT NULL DEFAULT 0,\n service_tier TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_token_usage_session ON token_usage(session_id);\n-- Partial so rare usage rows lacking a message_id (e.g. <synthetic>) don't collide on NULL. --\nCREATE UNIQUE INDEX IF NOT EXISTS idx_token_usage_msg\n ON token_usage(session_id, message_id) WHERE message_id IS NOT NULL;\n\n-- Heuristic classification (category + complexity), re-runnable. -----------\n-- scope: 'session' | 'turn'; target_id references that entity. -------------\nCREATE TABLE IF NOT EXISTS classifications (\n scope TEXT NOT NULL, -- 'session' | 'turn'\n target_id TEXT NOT NULL,\n category TEXT, -- feature | bugfix | refactor | docs | ops | review | ...\n complexity_score REAL,\n complexity_band TEXT, -- trivial | small | medium | large | xl\n signals_json TEXT, -- inputs used, for transparency/repro\n classifier_version INTEGER NOT NULL DEFAULT 1,\n PRIMARY KEY (scope, target_id)\n);\n\n-- Security findings (ADR-017), re-runnable. A deterministic rule engine (detect.ts) scans each\n-- tool call's verbatim input/result for risky operations — destructive/data-loss, credential access,\n-- data exfiltration, privilege/guardrail bypass — and records one row per (tool_call, rule) match.\n-- Findings are 0..N per tool call (unlike classifications, which are 1-per-session), so an incremental\n-- re-run DELETEs the touched sessions' rows and re-INSERTs (delete-then-insert) rather than upserting.\n-- Every input/modifier is captured in signals_json so a finding is fully explainable; framework_ref\n-- anchors it to OWASP Agentic / MITRE ATLAS. detector_version lets a future engine supersede rows.\nCREATE TABLE IF NOT EXISTS findings (\n id TEXT PRIMARY KEY, -- deterministic hash(tool_call_id, rule_id)\n session_id TEXT NOT NULL REFERENCES sessions(id),\n tool_call_id TEXT REFERENCES tool_calls(id),\n event_uuid TEXT, -- the tool call's event, for jump-to-transcript\n turn_id TEXT,\n rule_id TEXT NOT NULL, -- 'destructive.rm_rf'\n category TEXT NOT NULL, -- destructive | credential-access | exfiltration | privilege-bypass\n framework_ref TEXT, -- 'OWASP ASI02' | 'MITRE ATLAS AML.T0086' | ...\n severity TEXT NOT NULL, -- info | low | medium | high | critical\n title TEXT,\n evidence TEXT, -- matched snippet, truncated (no wholesale secret dump)\n signals_json TEXT, -- why: rule, matched pattern, context modifiers\n detector_version INTEGER NOT NULL DEFAULT 1\n);\nCREATE INDEX IF NOT EXISTS idx_findings_session ON findings(session_id);\nCREATE INDEX IF NOT EXISTS idx_findings_severity ON findings(severity);\nCREATE INDEX IF NOT EXISTS idx_findings_category ON findings(category);\nCREATE INDEX IF NOT EXISTS idx_findings_rule ON findings(rule_id);\nCREATE INDEX IF NOT EXISTS idx_findings_tool_call ON findings(tool_call_id);\n\n-- Workflow-tool run results, ingested from the sidecar JSON the runner writes next to the launching\n-- session (…/projects/<enc>/<sessionId>/workflows/wf_<id>.json). The transcript only carries the\n-- \"launched in background\" ack for an async run; this sidecar is the authoritative record of how the\n-- run finished — status (completed/failed), summary, the returned result payload, and roll-up\n-- tokens/tool-calls/duration/agent-count. One row per run id; UPSERT on re-ingest. Stored verbatim\n-- (like tool_calls.input_json) — the DB is local; redaction is an export-time concern. --------------\nCREATE TABLE IF NOT EXISTS workflow_results (\n run_id TEXT PRIMARY KEY, -- wf_<id> (matches tool_calls.workflow_run_id / sessions.workflow_run_id)\n source_id TEXT, -- which configured source (account) it came from\n session_id TEXT, -- launching session (path segment before /workflows/)\n task_id TEXT, -- async task id (the \"Task ID: …\" in the launch ack)\n workflow_name TEXT,\n status TEXT, -- completed | failed | running | ...\n summary TEXT,\n default_model TEXT, -- model the run executed on (e.g. claude-fable-5)\n result_json TEXT, -- the returned result object, verbatim JSON (null when none)\n phases_json TEXT, -- [{title}] the run's phase structure (e.g. Generate → Judge)\n logs_json TEXT, -- concise per-item outcome lines the runner emitted\n progress_json TEXT, -- workflowProgress: the phase/agent event timeline\n agent_count INTEGER, -- agents the run reports having fanned out to\n total_tokens INTEGER,\n total_tool_calls INTEGER,\n duration_ms INTEGER,\n started_at TEXT, -- ISO, from the sidecar startTime/timestamp\n ended_at TEXT, -- ISO, started_at + duration\n ingested_at TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_workflow_results_session ON workflow_results(session_id);\n\n-- Per-subagent metadata sidecar (subagents/agent-<id>.meta.json). One row per subagent session,\n-- joined at read time onto sessions (LEFT JOIN, may precede the session row's arrival). Supplies the\n-- authoritative agentType/description/spawnDepth — Workflow fan-out subagents carry none in-transcript.\nCREATE TABLE IF NOT EXISTS session_meta (\n session_id TEXT PRIMARY KEY, -- 'agent-<id>' = the subagent session id (meta filename stem)\n source_id TEXT,\n agent_type TEXT, -- meta.agentType (e.g. 'Explore', 'claude-code-guide')\n agent_description TEXT, -- meta.description (a real human title)\n spawn_depth INTEGER, -- meta.spawnDepth (optional; absent ⇒ top-level)\n tool_use_id TEXT, -- meta.toolUseId (the spawning tool_call)\n ingested_at TEXT\n);\n\n-- Full un-truncated tool outputs spilled to disk (tool-results/<name>.txt) when a result was too large\n-- for the transcript, which keeps only a 280-char summary naming the file. Joined at read time by\n-- parsing that summary's '…/tool-results/<name>.txt' marker back to (session_id, name).\nCREATE TABLE IF NOT EXISTS tool_results (\n session_id TEXT, -- session the tool ran in (from the archive path)\n name TEXT, -- file basename without .txt (matches the marker in tool_calls.result_summary)\n path TEXT, -- archive path (provenance)\n bytes INTEGER, -- original size, for the UI\n text TEXT, -- full un-truncated output, stored inline (self-contained DB)\n ingested_at TEXT,\n PRIMARY KEY (session_id, name)\n);\n\n-- Idempotent ingest bookkeeping per archive file. --------------------------\nCREATE TABLE IF NOT EXISTS ingest_state (\n file_path TEXT PRIMARY KEY,\n size INTEGER,\n mtime_ms INTEGER,\n sha256 TEXT,\n events_ingested INTEGER NOT NULL DEFAULT 0,\n ingested_at TEXT\n);\n\n-- Full-text search over event text (contentless external-content FTS). ------\nCREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(\n text,\n content='events',\n content_rowid='rowid'\n);\n\n-- Keep FTS in sync with events. --------------------------------------------\nCREATE TRIGGER IF NOT EXISTS events_ai AFTER INSERT ON events BEGIN\n INSERT INTO events_fts(rowid, text) VALUES (new.rowid, new.text);\nEND;\nCREATE TRIGGER IF NOT EXISTS events_ad AFTER DELETE ON events BEGIN\n INSERT INTO events_fts(events_fts, rowid, text) VALUES ('delete', old.rowid, old.text);\nEND;\nCREATE TRIGGER IF NOT EXISTS events_au AFTER UPDATE ON events\nWHEN old.text IS NOT new.text BEGIN\n INSERT INTO events_fts(events_fts, rowid, text) VALUES ('delete', old.rowid, old.text);\n INSERT INTO events_fts(rowid, text) VALUES (new.rowid, new.text);\nEND;\n`;\n","/**\n * Agent Lens — security findings: shared severity model + framework-anchored reference content\n * (ADR-017). This is the low-level, dependency-free source of truth so the ingest detector\n * (packages/ingest/src/detect.ts), the server queries, and the web UI all agree on the severity\n * ordering and the category taxonomy. The reference content also powers the \"what & why\" explainer\n * blocks on the /security page.\n */\n\nexport type Severity = \"info\" | \"low\" | \"medium\" | \"high\" | \"critical\";\n\n/** Ascending order of concern; index doubles as the numeric rank (info=0 … critical=4). */\nexport const SEVERITY_ORDER: Severity[] = [\"info\", \"low\", \"medium\", \"high\", \"critical\"];\n\n/** Numeric rank of a severity (info=0 … critical=4); -1 for an unknown value. */\nexport function severityRank(s: string): number {\n return SEVERITY_ORDER.indexOf(s as Severity);\n}\n\n/** Shift a severity by `delta` bands, clamped to the [info, critical] range. */\nexport function bumpSeverity(s: Severity, delta: number): Severity {\n const i = SEVERITY_ORDER.indexOf(s);\n const j = Math.max(0, Math.min(SEVERITY_ORDER.length - 1, i + delta));\n return SEVERITY_ORDER[j];\n}\n\n/** The four v1 risk families a finding is categorized into. */\nexport type SecurityCategoryKey = \"destructive\" | \"credential-access\" | \"exfiltration\" | \"privilege-bypass\";\n\nexport interface SecurityCategoryRef {\n key: SecurityCategoryKey;\n title: string;\n /** Short framework anchor shown as a chip (matches findings.framework_ref). */\n framework_ref: string;\n framework_url: string;\n /** What the detector looks for. */\n what: string;\n /** Why it matters for a host running an autonomous agent. */\n why: string;\n /** What to do when you see one. */\n remediation: string;\n}\n\n/**\n * Reference content for each category — the browsable \"security page with info\" the feature was scoped\n * around. Anchored to OWASP Top 10 for Agentic Apps 2026 and MITRE ATLAS. Kept deliberately concise;\n * the point is orientation, not a full framework reproduction.\n */\nexport const SECURITY_CATEGORIES: SecurityCategoryRef[] = [\n {\n key: \"destructive\",\n title: \"Destructive & data-loss operations\",\n framework_ref: \"OWASP ASI02\",\n framework_url: \"https://genai.owasp.org/\",\n what: \"Commands or edits that irreversibly delete, overwrite, or truncate data — rm -rf, git reset --hard, force-pushes, DROP/TRUNCATE, disk writes (dd/mkfs/shred), and overwrites of critical project files (lockfiles, CI config).\",\n why: \"An agent acting on a bad plan or a poisoned instruction can wipe work, history, or a database with a single tool call. Because agent-lens sees sessions after the fact, this is how you notice the damage — and the blast radius — even when nobody was watching live.\",\n remediation: \"Open the flagged session, confirm the target was intended, and check the tool result for whether it succeeded. Restore from git/backups if needed, and consider a PreToolUse hook to block the pattern going forward.\",\n },\n {\n key: \"credential-access\",\n title: \"Secret & credential access\",\n framework_ref: \"MITRE ATLAS: Credential Access\",\n framework_url: \"https://atlas.mitre.org/\",\n what: \"Reads of secret-bearing files (.env, ~/.ssh keys, ~/.aws/credentials, .npmrc, .pem) or secret-looking values (private keys, AWS keys, GitHub/Slack tokens) appearing in a command or a captured tool result.\",\n why: \"Credentials the agent touched may have been exposed to the model context, logs, or a downstream tool — and could be leaked or reused. Retrospective visibility tells you which secrets to rotate.\",\n remediation: \"Treat any accessed secret as potentially exposed: rotate it. Verify the access was purposeful, and keep secrets out of the working tree the agent operates on.\",\n },\n {\n key: \"exfiltration\",\n title: \"Data exfiltration\",\n framework_ref: \"MITRE ATLAS AML.T0086\",\n framework_url: \"https://atlas.mitre.org/techniques/AML.T0086\",\n what: \"Local data flowing outbound: curl/wget uploads to external hosts, piping files into a network command, netcat/reverse-shell patterns, and other ways an agent's write-capable tools become an exfiltration channel.\",\n why: \"Exfiltration via the agent's own tool calls (AML.T0086) is a primary agentic attack path — a prompt-injected agent can POST your files or secrets to an attacker with one command. These are the highest-signal findings to review.\",\n remediation: \"Inspect the destination host and the data referenced in the command. If it was not you, treat it as an incident: rotate anything referenced, review what left the host, and audit the session for the injection source.\",\n },\n {\n key: \"privilege-bypass\",\n title: \"Privilege escalation & guardrail bypass\",\n framework_ref: \"OWASP LLM06\",\n framework_url: \"https://genai.owasp.org/\",\n what: \"Actions that widen the agent's authority or sidestep safety: sudo, chmod 777, curl | sh, --dangerously-skip-permissions, writes outside the working project, and persistence mechanisms (cron, systemd, shell rc files).\",\n why: \"Excessive agency (LLM06) is the root cause behind most agent incidents — every disabled guardrail or out-of-scope write removes a barrier the design relied on. Persistence in particular outlives the session that created it.\",\n remediation: \"Confirm the escalation was necessary and scoped. Remove any persistence the agent added, re-tighten permissions, and avoid running agents with skipped permission prompts on a trusted host.\",\n },\n];\n\n/** Quick lookup of a category's reference content by key. */\nexport function securityCategory(key: string): SecurityCategoryRef | undefined {\n return SECURITY_CATEGORIES.find((c) => c.key === key);\n}\n","/**\n * Agent Lens — tool-error classification. No AI: an errored tool call is bucketed deterministically\n * by pattern-matching its verbatim `result_summary`. Re-runnable and idempotent — the same text always\n * yields the same bucket. `ERROR_CLASSIFIER_VERSION` lets a future engine supersede prior labels.\n *\n * ── The authority boundary (why this is careful) ────────────────────────────────────────────────\n * The Messages API's tool_result carries a single boolean `is_error` and **no field for *why*** a\n * result is an error — the API does not distinguish a tool that genuinely failed from one the user or\n * a permission/guardrail system rejected (see platform.claude.com tool-use docs). Claude Code sets\n * `is_error: true` for *both* real failures and permission denials / guardrail blocks; the only signal\n * of which is the human-readable result text (see code.claude.com/docs/en/errors + the auto-mode post).\n *\n * Therefore: the raw `is_error` / `tool_calls.status='error'` **count** is the authoritative, documented\n * signal. The `type` bucket and the failure-vs-rejection `kind` below are a **heuristic over Claude\n * Code's result wording** — best-effort, and liable to drift if that wording changes. Surface them as\n * such (not as an API-blessed fact), and bump ERROR_CLASSIFIER_VERSION when the patterns change.\n */\n\n// Bump on any pattern/bucket change so a reclassification is attributable to an engine version\n// (mirrors CLASSIFIER_VERSION / DETECTOR_VERSION).\nexport const ERROR_CLASSIFIER_VERSION = 1;\n\n/** The error bucket. `other` = errored but unmatched by the patterns below. */\nexport type ToolErrorType =\n | \"string-not-found\" // Edit: the old_string wasn't found in the file\n | \"command-failed\" // Bash: non-zero exit code\n | \"file-state\" // Read/Write/Edit: file missing / not-read-first / changed-since-read / is-a-directory\n | \"token-limit\" // Read: file content exceeds the tool's token cap\n | \"user-rejected\" // the human declined / interrupted / cancelled the tool use\n | \"guardrail-blocked\" // Claude Code's own guardrail blocked the command\n | \"other\";\n\n/**\n * Whether the error reflects the agent's tool genuinely failing (`failure`) or a human/harness\n * declining to run it (`rejection`). Rejections are NOT agent failures — separating them keeps the\n * \"how often did the agent's tools fail\" signal honest.\n */\nexport type ToolErrorKind = \"failure\" | \"rejection\";\n\nexport interface ToolErrorClass {\n type: ToolErrorType;\n kind: ToolErrorKind;\n}\n\n/** Error types that represent a human/harness declining the tool use — NOT the agent's tool failing. */\nexport const REJECTION_TYPES: ReadonlySet<ToolErrorType> = new Set<ToolErrorType>([\"user-rejected\", \"guardrail-blocked\"]);\n\n/** The kind (failure vs rejection) implied by an error type — the single mapping used everywhere. */\nexport function errorKind(type: ToolErrorType): ToolErrorKind {\n return REJECTION_TYPES.has(type) ? \"rejection\" : \"failure\";\n}\n\n// Ordered most-specific → fallback; first match wins. Rejection patterns are checked first because a\n// rejection message can also mention a command that would otherwise look like a real failure.\nconst PATTERNS: Array<{ re: RegExp; type: ToolErrorType; kind: ToolErrorKind }> = [\n // — Rejections / blocks: is_error, but the agent's tool didn't fail — a human or guardrail stopped it.\n { re: /interrupted by user|tool use was rejected|does(?:n['’]| not)t want to proceed|\\bCancelled:/i, type: \"user-rejected\", kind: \"rejection\" },\n { re: /\\bBlocked:/i, type: \"guardrail-blocked\", kind: \"rejection\" },\n // — Real tool failures.\n { re: /String to replace not found/i, type: \"string-not-found\", kind: \"failure\" },\n { re: /exceeds maximum allowed tokens/i, type: \"token-limit\", kind: \"failure\" },\n // A non-zero Bash exit code is a command failure regardless of what its stderr text mentions — check\n // it before the file-state fs-error patterns, since ENOENT/EISDIR also appear inside command stderr\n // (e.g. `Exit code 2 npm error ENOENT`). The file-state patterns then catch the Read/Write/Edit\n // tool_use_errors, which carry no exit code.\n { re: /\\bExit code\\b/i, type: \"command-failed\", kind: \"failure\" },\n { re: /has not been read yet|modified since (?:you |it was )?read|File (?:content )?does not exist|\\bEISDIR\\b|\\bENOENT\\b/i, type: \"file-state\", kind: \"failure\" },\n];\n\n/**\n * Classify an errored tool call from its `result_summary` text. Only call this on tool calls already\n * known to be errors (`status='error'` / `is_error: true`) — it always returns a class, defaulting to\n * `{ type: \"other\", kind: \"failure\" }` for an errored result whose text matches no pattern.\n */\nexport function classifyToolError(resultSummary: string | null | undefined): ToolErrorClass {\n const s = resultSummary ?? \"\";\n for (const p of PATTERNS) {\n if (p.re.test(s)) return { type: p.type, kind: p.kind };\n }\n return { type: \"other\", kind: \"failure\" };\n}\n","/**\n * Model pricing for deriving cost from token counts (ADR-003: no cost is stored in traces).\n * USD per 1M tokens. Cache-read is the discounted rate; cache-write the premium rate.\n *\n * Rates are matched by prefix so dated/variant model ids resolve (e.g. 'claude-opus-4-8[1m]').\n * Update as pricing changes; unknown models cost 0 and are reported separately.\n */\n\nexport interface Rate {\n input: number;\n output: number;\n cacheWrite: number;\n cacheRead: number;\n}\n\nexport interface UsageTokens {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n}\n\n/**\n * Prefix → rate. Longest matching prefix wins, so dated/variant ids resolve to the most specific\n * entry (e.g. 'claude-opus-4-8' beats the legacy 'claude-opus-4' fallback).\n * USD per 1M tokens. Convention: cacheWrite (5-minute) = 1.25× input, cacheRead = 0.1× input.\n */\nexport const PRICE_TABLE: Record<string, Rate> = {\n // Opus 4.0 / 4.1 launched at $15/$75; Opus 4.5 cut the Opus tier to $5/$25 and 4.6/4.7/4.8 kept it.\n // The bare 'claude-opus-4' fallback stays at the legacy rate for 4.0/4.1; 4.5+ override below.\n \"claude-opus-4\": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },\n \"claude-opus-4-5\": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },\n \"claude-opus-4-6\": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },\n \"claude-opus-4-7\": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },\n \"claude-opus-4-8\": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },\n \"claude-fable-5\": { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1.0 },\n \"claude-sonnet-4\": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },\n \"claude-haiku-4\": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },\n \"claude-3-5-haiku\": { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },\n \"claude-3-5-sonnet\": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },\n};\n\nexport function rateForModel(model: string | null | undefined): Rate | null {\n if (!model) return null;\n let best: { len: number; rate: Rate } | null = null;\n for (const [prefix, rate] of Object.entries(PRICE_TABLE)) {\n if (model.startsWith(prefix) && (!best || prefix.length > best.len)) {\n best = { len: prefix.length, rate };\n }\n }\n return best?.rate ?? null;\n}\n\n/** Cost in USD for one usage record. Returns 0 for unknown models. */\nexport function costForUsage(model: string | null | undefined, u: UsageTokens): number {\n const r = rateForModel(model);\n if (!r) return 0;\n const M = 1_000_000;\n return (\n (u.input_tokens * r.input +\n u.output_tokens * r.output +\n u.cache_creation_input_tokens * r.cacheWrite +\n u.cache_read_input_tokens * r.cacheRead) /\n M\n );\n}\n","/** Render a session to Markdown — shared by the export endpoint (ADR: data exportable as markdown). */\n\nexport interface MarkdownSession {\n id: string;\n title: string | null;\n source: string | null;\n project: string | null;\n model: string | null;\n started_at: string | null;\n ended_at: string | null;\n}\n\nexport interface MarkdownToolCall {\n tool_name: string;\n skill_name: string | null;\n agent_type: string | null;\n input_json: string | null;\n status: string | null;\n}\n\nexport interface MarkdownEvent {\n type: string;\n role: string | null;\n timestamp: string | null;\n text: string | null;\n thinking: string | null;\n toolCalls: MarkdownToolCall[];\n}\n\nfunction fence(s: string): string {\n return s.length > 2000 ? s.slice(0, 2000) + \"\\n… (truncated)\" : s;\n}\n\nexport function sessionToMarkdown(s: MarkdownSession, events: MarkdownEvent[]): string {\n const out: string[] = [];\n out.push(`# ${s.title || \"Session \" + s.id.slice(0, 8)}`);\n out.push(\"\");\n const meta = [\n [\"Session\", s.id],\n [\"Source\", s.source],\n [\"Project\", s.project],\n [\"Model\", s.model],\n [\"Started\", s.started_at],\n [\"Ended\", s.ended_at],\n ].filter(([, v]) => v);\n for (const [k, v] of meta) out.push(`- **${k}:** ${v}`);\n out.push(\"\");\n out.push(\"---\");\n out.push(\"\");\n\n for (const e of events) {\n const who = e.role || e.type;\n const ts = e.timestamp ? ` _(${e.timestamp})_` : \"\";\n if (e.thinking) {\n out.push(`### 🧠 thinking${ts}`);\n out.push(\"\");\n out.push(fence(e.thinking));\n out.push(\"\");\n }\n if (e.text) {\n const icon = who === \"user\" ? \"👤\" : who === \"assistant\" ? \"🤖\" : \"⚙️\";\n out.push(`### ${icon} ${who}${ts}`);\n out.push(\"\");\n out.push(fence(e.text));\n out.push(\"\");\n }\n for (const tc of e.toolCalls) {\n const label = tc.skill_name\n ? `Skill: ${tc.skill_name}`\n : tc.agent_type\n ? `${tc.tool_name} → ${tc.agent_type}`\n : tc.tool_name;\n const status = tc.status ? ` _(${tc.status})_` : \"\";\n out.push(`> 🔧 **${label}**${status}`);\n if (tc.input_json && tc.input_json !== \"{}\") {\n out.push(\"```json\");\n out.push(fence(tc.input_json));\n out.push(\"```\");\n }\n out.push(\"\");\n }\n }\n return out.join(\"\\n\");\n}\n","/**\n * Agent Lens — `events.raw_json` storage codec (ADR-011).\n *\n * Every transcript line is kept verbatim in `events.raw_json` for lossless re-derivation. That column\n * dominates DB growth, so it is stored gzip-compressed as a BLOB. This module is the single chokepoint:\n * the ingest writer compresses with `packRaw`, the server reader decompresses with `unpackRaw`.\n *\n * `unpackRaw` sniffs the gzip magic bytes (0x1f 0x8b) so it transparently handles a mix of compressed\n * BLOBs and legacy plain-text rows (a DB written before the migration, or read before `--full` runs).\n * JSON always begins with `{`/`[`/`\"`/whitespace, never 0x1f, so the sniff is unambiguous.\n */\nimport { gzipSync, gunzipSync } from \"node:zlib\";\n\n/** Compress a raw transcript line for storage in `events.raw_json` (BLOB). */\nexport function packRaw(json: string): Buffer {\n return gzipSync(Buffer.from(json, \"utf8\"));\n}\n\n/** Decompress a stored `raw_json` value. Accepts gzip BLOBs and legacy plain text/strings. */\nexport function unpackRaw(value: string | Buffer): string {\n if (Buffer.isBuffer(value)) {\n if (value.length >= 2 && value[0] === 0x1f && value[1] === 0x8b) {\n return gunzipSync(value).toString(\"utf8\");\n }\n return value.toString(\"utf8\");\n }\n return value;\n}\n","/**\n * Path resolution shared by ingest, server, and the CLI. The goal is that agent-lens works both\n * from the dev monorepo (data under <repo>/data, config at the repo root) AND when installed as a\n * standalone package under node_modules (per-user OS data dir, no repo layout to assume).\n *\n * Nothing here reads process state beyond env vars + the filesystem, so it is safe to call from any\n * entrypoint. `import.meta.url`-relative resolution replaces the old hardcoded \"../../..\" repo anchor.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { homedir, platform } from \"node:os\";\n\n/** Expand a leading `~` or `$HOME` and resolve to an absolute path. Empty input passes through. */\nexport function expandPath(p: string): string {\n if (!p) return p;\n let out = p;\n if (out === \"~\") out = homedir();\n else if (out.startsWith(\"~/\")) out = join(homedir(), out.slice(2));\n out = out.replace(/\\$HOME/g, homedir());\n return resolve(out);\n}\n\nconst moduleDir = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Find the agent-lens monorepo root by walking up from `from` (default: this module's location).\n * A directory qualifies only if it has BOTH a `pnpm-workspace.yaml` AND a `package.json` named\n * \"agent-lens\" — so an unrelated pnpm workspace never matches, and the published/bundled package\n * (which ships neither) resolves to `null`, sending data to the per-user dir instead.\n */\nexport function findRepoRoot(from: string = moduleDir): string | null {\n let dir = resolve(from);\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, \"package.json\"), \"utf8\"));\n if (pkg?.name === \"agent-lens\") return dir;\n } catch {\n /* not the file we want — keep walking */\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/** Per-user data directory used when not running from the repo (installed CLI). */\nexport function userDataDir(): string {\n const home = homedir();\n if (platform() === \"win32\") {\n return join(process.env.LOCALAPPDATA || join(home, \"AppData\", \"Local\"), \"agent-lens\");\n }\n if (platform() === \"darwin\") {\n return join(home, \"Library\", \"Application Support\", \"agent-lens\");\n }\n return join(process.env.XDG_DATA_HOME || join(home, \".local\", \"share\"), \"agent-lens\");\n}\n\n/** The data dir: `AGENT_LENS_DATA` → `<repo>/data` (dev) → per-user OS dir (installed). */\nexport function resolveDataDir(repoRoot: string | null = findRepoRoot()): string {\n if (process.env.AGENT_LENS_DATA) return resolve(process.env.AGENT_LENS_DATA);\n if (repoRoot) return join(repoRoot, \"data\");\n return userDataDir();\n}\n\n/**\n * First existing config file, in precedence order:\n * `AGENT_LENS_CONFIG` → `<dataDir>/agent-lens.config.json` → repo config → repo example.\n * Returns `null` when none exist (callers fall back to the built-in default source).\n */\nexport function resolveConfigFile(repoRoot: string | null, dataDir: string): string | null {\n const candidates = [\n process.env.AGENT_LENS_CONFIG,\n join(dataDir, \"agent-lens.config.json\"),\n repoRoot ? join(repoRoot, \"agent-lens.config.json\") : null,\n repoRoot ? join(repoRoot, \"agent-lens.config.example.json\") : null,\n ].filter((x): x is string => Boolean(x));\n for (const f of candidates) if (existsSync(f)) return f;\n return null;\n}\n\n/**\n * The built web SPA directory. Tries, in order: the bundled layout (`<pkgDir>/web`, a sibling of the\n * CLI's `dist/`), the dev sibling (`packages/server/dist` → `packages/web/dist`), then the repo path.\n * `fromDir` is the directory of the calling module (`dirname(fileURLToPath(import.meta.url))`).\n */\nexport function resolveWebDist(fromDir: string, repoRoot: string | null = findRepoRoot(fromDir)): string {\n const installed = resolve(fromDir, \"../web\"); // bundled CLI: dist/ and web/ are siblings\n if (existsSync(installed)) return installed;\n const devSibling = resolve(fromDir, \"../../web/dist\"); // packages/server/dist → packages/web/dist\n if (existsSync(devSibling)) return devSibling;\n if (repoRoot) return join(repoRoot, \"packages/web/dist\");\n return installed; // best-effort default; caller guards with existsSync\n}\n","/**\n * Canonical source resolver for Agent Lens (shared by the collector, ingester, server, CLI, and\n * the dev-only scripts/sources.mjs shim).\n *\n * A \"source\" is a labeled agent instance: { label, agent, configDir }. Multiple local accounts\n * (each with its own config folder) are each a source — e.g. \"personal\" → ~/.claude.\n *\n * Resolution order (see resolveConfigFile in paths.ts):\n * 1. $CLAUDE_DIR set → single legacy source { label: $AGENT_LENS_LABEL|\"default\", ~/.claude }\n * 2. $AGENT_LENS_CONFIG → that JSON file\n * 3. <dataDir>/agent-lens.config.json (installed user's config, next to their data)\n * 4. <repo>/agent-lens.config.json → <repo>/agent-lens.config.example.json\n * 5. built-in default → { label: \"default\", agent: \"claude-code\", configDir: \"$HOME/.claude\" }\n *\n * Excluded projects (playgrounds, personal, dummy tests, …) are resolved here too, from the config's\n * top-level `exclude: [<realProjectPath>, …]` plus the `AGENT_LENS_EXCLUDE` env (comma-separated).\n * They are honored at every stage — collect (not mirrored), ingest (not stored / pruned), corpus.\n */\nimport { readFileSync } from \"node:fs\";\nimport { expandPath, findRepoRoot, resolveConfigFile, resolveDataDir } from \"./paths.js\";\n\nexport interface Source {\n label: string;\n agent: string;\n configDir: string;\n}\n\n/** Read + parse the resolved config file, or null when none exists. */\nfunction readConfig(): { sources?: unknown[]; exclude?: unknown[] } | null {\n const repoRoot = findRepoRoot();\n const dataDir = resolveDataDir(repoRoot);\n const file = resolveConfigFile(repoRoot, dataDir);\n return file ? JSON.parse(readFileSync(file, \"utf8\")) : null;\n}\n\nexport function loadSources(): Source[] {\n // 1. legacy single-source override (used by tests and ad-hoc runs)\n if (process.env.CLAUDE_DIR) {\n return [\n {\n label: process.env.AGENT_LENS_LABEL || \"default\",\n agent: \"claude-code\",\n configDir: expandPath(process.env.CLAUDE_DIR),\n },\n ];\n }\n\n const cfg = readConfig() ?? { sources: [{ label: \"default\", agent: \"claude-code\", configDir: \"$HOME/.claude\" }] };\n\n const seen = new Set<string>();\n const out: Source[] = [];\n for (const raw of cfg.sources || []) {\n const s = raw as Partial<Source> | null;\n if (!s || !s.label || !s.configDir) continue;\n if (seen.has(s.label)) throw new Error(`duplicate source label: ${s.label}`);\n seen.add(s.label);\n out.push({ label: s.label, agent: s.agent || \"claude-code\", configDir: expandPath(s.configDir) });\n }\n if (!out.length) throw new Error(\"no valid sources configured\");\n return out;\n}\n\n/**\n * Resolve the list of projects to EXCLUDE everywhere (collect, ingest, corpus). Union of the config's\n * `exclude` array and the `AGENT_LENS_EXCLUDE` env (comma-separated). Returns expanded real paths\n * (no trailing slash). The legacy CLAUDE_DIR mode still honors the env var.\n */\nexport function loadExcludes(): string[] {\n const fromEnv = (process.env.AGENT_LENS_EXCLUDE || \"\").split(\",\");\n let fromCfg: unknown[] = [];\n if (!process.env.CLAUDE_DIR) {\n const cfg = readConfig();\n if (cfg && Array.isArray(cfg.exclude)) fromCfg = cfg.exclude;\n }\n const seen = new Set<string>();\n const out: string[] = [];\n for (const p of [...fromCfg, ...fromEnv]) {\n const e = expandPath(String(p ?? \"\").trim());\n if (e && !seen.has(e)) {\n seen.add(e);\n out.push(e);\n }\n }\n return out;\n}\n","/**\n * Encode a real project path the way Claude Code names its `projects/<encodedDir>` folder: every\n * `/` and `.` becomes `-` (e.g. `/home/u/git/agent-lens` → `-home-u-git-agent-lens`). Trailing\n * separators are dropped so a path with or without a trailing slash encodes identically.\n *\n * Canonical here in core so collect (Stage 1) and ingest/redact (Stage 2 / corpus) agree exactly.\n */\nexport function encodeProjectPath(p: string): string {\n return p.replace(/[/.]/g, \"-\").replace(/-+$/, \"\");\n}\n","/**\n * Agent Lens — Stage 1 collection, in portable Node (ADR-001, ADR-002, ADR-005).\n *\n * Passively copies agent session traces out of each configured source into the local archive before\n * the agent prunes them. Never deletes, never sends data off the machine, never copies secrets. This\n * replaced the former bash+rsync collector with the same semantics, minus the Linux-only dependencies.\n *\n * Per file (mirror of rsync --append-verify + the .versions pre-pass):\n * - no archive copy yet → copy whole\n * - unchanged since last collect (size + mtime) → skip\n * - source ≥ archive AND archive is a byte-prefix→ append the new tail (pure append)\n * - source shorter than archive (compaction) → snapshot the SOURCE to .versions/<ts>/, keep the\n * longer archive (no shrink → retains history)\n * - same-or-longer but prefix differs (divergence) → snapshot the OLD archive, overwrite with source\n * Stage 2 ingests the mirror AND .versions/, deduping events by uuid → maximal history.\n *\n * Secrets (.credentials.json, *.lock) are never copied; a post-pass also removes any stray\n * .credentials.json from the archive. Excluded projects (loadExcludes) are never mirrored.\n */\nimport {\n appendFileSync,\n closeSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n openSync,\n readSync,\n readdirSync,\n statSync,\n unlinkSync,\n utimesSync,\n writeSync,\n chmodSync,\n type Stats,\n} from \"node:fs\";\nimport { basename, dirname, join, relative, sep } from \"node:path\";\nimport { platform } from \"node:os\";\nimport { loadExcludes, loadSources, type Source } from \"./sources.js\";\nimport { findRepoRoot, resolveDataDir } from \"./paths.js\";\nimport { encodeProjectPath } from \"./projects.js\";\n\nconst POSIX = platform() !== \"win32\";\nconst DIR_MODE = 0o700; // archive is as sensitive as the originals (bash used umask 077)\nconst FILE_MODE = 0o600;\nconst CHUNK = 1 << 16;\n\nexport interface CollectOptions {\n /** Archive base dir. Default: `<dataDir>/archive`. */\n archiveBase?: string;\n /** Sources to collect. Default: loadSources(). */\n sources?: Source[];\n /** Real project paths to exclude. Default: loadExcludes(). */\n excludes?: string[];\n /** Shared run timestamp for .versions/<ts>/. Default: now (local time). */\n runTimestamp?: string;\n /** Progress sink. Default: console.log. */\n log?: (msg: string) => void;\n}\n\nexport interface CollectStats {\n sources: number;\n scanned: number;\n copied: number;\n appended: number;\n diverged: number;\n compacted: number;\n snapshots: number;\n}\n\n/** `YYYYMMDDTHHMMSSmmm` in local time (matches `date +%Y%m%dT%H%M%S%3N`). */\nexport function collectTimestamp(d: Date = new Date()): string {\n const p = (n: number, w = 2) => String(n).padStart(w, \"0\");\n return (\n `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}T` +\n `${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}${p(d.getMilliseconds(), 3)}`\n );\n}\n\nfunction ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true, mode: DIR_MODE });\n}\n\n/** Set archive file perms + mtime to mirror the source (like rsync -a / cp -p). */\nfunction syncMeta(dst: string, src: Stats): void {\n if (POSIX) {\n try {\n chmodSync(dst, FILE_MODE);\n } catch {\n /* best effort */\n }\n }\n try {\n utimesSync(dst, src.atime, src.mtime);\n } catch {\n /* best effort */\n }\n}\n\nfunction copyWhole(src: string, dst: string, srcStat: Stats): void {\n ensureDir(dirname(dst));\n copyFileSync(src, dst);\n syncMeta(dst, srcStat);\n}\n\n/** True if the first `len` bytes of `src` byte-equal the whole of `arc` (`arc` is `len` bytes). */\nfunction prefixEquals(src: string, arc: string, len: number): boolean {\n const sf = openSync(src, \"r\");\n const af = openSync(arc, \"r\");\n try {\n const sb = Buffer.allocUnsafe(CHUNK);\n const ab = Buffer.allocUnsafe(CHUNK);\n let remaining = len;\n while (remaining > 0) {\n const want = Math.min(CHUNK, remaining);\n const sr = readSync(sf, sb, 0, want, null);\n const ar = readSync(af, ab, 0, want, null);\n if (sr !== ar || sr === 0) return false;\n if (Buffer.compare(sb.subarray(0, sr), ab.subarray(0, sr)) !== 0) return false;\n remaining -= sr;\n }\n return remaining === 0;\n } finally {\n closeSync(sf);\n closeSync(af);\n }\n}\n\n/** Append `src[from..to)` to the end of `arc`. */\nfunction appendRange(src: string, arc: string, from: number, to: number): void {\n const sf = openSync(src, \"r\");\n const af = openSync(arc, \"a\");\n try {\n const b = Buffer.allocUnsafe(CHUNK);\n let pos = from;\n while (pos < to) {\n const want = Math.min(CHUNK, to - pos);\n const r = readSync(sf, b, 0, want, pos);\n if (r === 0) break;\n writeSync(af, b, 0, r);\n pos += r;\n }\n } finally {\n closeSync(sf);\n closeSync(af);\n }\n}\n\nfunction filesEqual(a: string, b: string): boolean {\n const sa = statSync(a);\n const sb = statSync(b);\n if (sa.size !== sb.size) return false;\n return prefixEquals(a, b, sa.size);\n}\n\n/** Most recent `.versions/<ts>/<rel>` snapshot for `rel`, or null. Timestamps sort chronologically. */\nfunction latestSnapshot(archive: string, relParts: string[]): string | null {\n const versRoot = join(archive, \".versions\");\n if (!existsSync(versRoot)) return null;\n const stamps = readdirSync(versRoot).sort();\n for (let i = stamps.length - 1; i >= 0; i--) {\n const p = join(versRoot, stamps[i]!, ...relParts);\n if (existsSync(p)) return p;\n }\n return null;\n}\n\nfunction snapshot(absPath: string, relParts: string[], versionsDir: string, stats: CollectStats): void {\n const dst = join(versionsDir, ...relParts);\n ensureDir(dirname(dst));\n copyFileSync(absPath, dst);\n syncMeta(dst, statSync(absPath));\n stats.snapshots++;\n}\n\n/**\n * Recursively collect the relevant transcript + sidecar files under `dir`:\n * - `*.jsonl` — session/subagent transcripts and journals (the core traces)\n * - anything under `workflows/` — the Workflow-tool run result sidecars (wf_<id>.json) + scripts\n * - `*.meta.json` — per-subagent metadata (agentType, description, spawnDepth, toolUseId)\n * - anything under `tool-results/` — full un-truncated tool outputs spilled from the transcript\n * Operational/state files (memory, cache, todos, shell-snapshots, …) are intentionally left behind.\n * Secrets (.credentials.json, *.lock) are filtered by the caller. `inWorkflows`/`inToolResults` track\n * whether recursion is inside one of those directories so every file within is captured.\n */\nfunction walkCollectable(dir: string, out: string[], inWorkflows = false, inToolResults = false): void {\n for (const e of readdirSync(dir, { withFileTypes: true })) {\n const p = join(dir, e.name);\n if (e.isDirectory()) {\n walkCollectable(p, out, inWorkflows || e.name === \"workflows\", inToolResults || e.name === \"tool-results\");\n } else if (\n e.isFile() &&\n (e.name.endsWith(\".jsonl\") || inWorkflows || inToolResults || e.name.endsWith(\".meta.json\"))\n ) {\n out.push(p);\n }\n }\n}\n\n/** The single-file sync decision (the heart of the append-verify port). `appendable` is true only for\n * append-only logs (*.jsonl); for rewritten files (JSON/txt sidecars) a same-prefix growth is a\n * rewrite, not an append, so it's handled as a divergence (snapshot old, overwrite) — never appended. */\nfunction syncFile(src: string, dst: string, relParts: string[], archive: string, versionsDir: string, stats: CollectStats, appendable: boolean): void {\n const srcStat = statSync(src);\n if (!existsSync(dst)) {\n copyWhole(src, dst, srcStat);\n stats.copied++;\n return;\n }\n const arcStat = statSync(dst);\n // Unchanged since last collect (we preserve source mtime on write, like rsync -a) → skip, no read.\n if (srcStat.size === arcStat.size && Math.trunc(srcStat.mtimeMs) === Math.trunc(arcStat.mtimeMs)) return;\n\n const ssize = srcStat.size;\n const asize = arcStat.size;\n if (ssize === asize && prefixEquals(src, dst, asize)) {\n // Identical content, mtime moved → just realign metadata (no append, appendable or not).\n syncMeta(dst, srcStat);\n } else if (appendable && ssize > asize && prefixEquals(src, dst, asize)) {\n // Pure append onto an append-only log. Verify the full prefix before appending; never trust size.\n appendRange(src, dst, asize, ssize);\n stats.appended++;\n syncMeta(dst, srcStat);\n } else if (ssize < asize) {\n // Compaction: keep the longer archive; snapshot the compacted source (skip if an identical\n // snapshot already exists, so a stuck-compacted file doesn't re-snapshot every run).\n const last = latestSnapshot(archive, relParts);\n if (!last || !filesEqual(last, src)) snapshot(src, relParts, versionsDir, stats);\n stats.compacted++;\n } else {\n // Divergence: capture the old archive, then overwrite with the source.\n snapshot(dst, relParts, versionsDir, stats);\n copyWhole(src, dst, srcStat);\n stats.diverged++;\n }\n}\n\nfunction removeCredentials(dir: string): void {\n if (!existsSync(dir)) return;\n for (const e of readdirSync(dir, { withFileTypes: true })) {\n const p = join(dir, e.name);\n if (e.isDirectory()) removeCredentials(p);\n else if (e.name === \".credentials.json\") {\n try {\n unlinkSync(p);\n } catch {\n /* ignore */\n }\n }\n }\n}\n\nfunction collectSource(\n source: Source,\n archiveBase: string,\n excludedDirs: string[],\n runTs: string,\n stats: CollectStats,\n log: (m: string) => void,\n): void {\n const { label, configDir } = source;\n const archive = join(archiveBase, label);\n const versionsDir = join(archive, \".versions\", runTs);\n if (!existsSync(configDir)) {\n log(`agent-lens: [${label}] source not found: ${configDir} (skipping)`);\n return;\n }\n ensureDir(join(archive, \"projects\"));\n ensureDir(join(archive, \"settings\"));\n\n const files: string[] = [];\n const projectsDir = join(configDir, \"projects\");\n if (existsSync(projectsDir)) walkCollectable(projectsDir, files);\n const historySrc = join(configDir, \"history.jsonl\");\n if (existsSync(historySrc)) files.push(historySrc);\n\n const before = { scanned: stats.scanned, snapshots: stats.snapshots };\n for (const src of files) {\n const base = basename(src);\n if (base === \".credentials.json\" || base.endsWith(\".lock\")) continue; // never copy secrets\n const relParts = relative(configDir, src).split(sep);\n const relPosix = relParts.join(\"/\");\n if (excludedDirs.some((enc) => relPosix.startsWith(`projects/${enc}/`))) continue; // excluded project\n stats.scanned++;\n syncFile(src, join(archive, ...relParts), relParts, archive, versionsDir, stats, base.endsWith(\".jsonl\"));\n }\n\n // Settings: latest-wins, snapshot old on change.\n for (const f of [\"settings.json\", \"settings.local.json\"]) {\n const s = join(configDir, f);\n if (!existsSync(s)) continue;\n const a = join(archive, \"settings\", f);\n if (existsSync(a) && !filesEqual(s, a)) snapshot(a, [\"settings\", f], versionsDir, stats);\n ensureDir(dirname(a));\n copyFileSync(s, a);\n syncMeta(a, statSync(s));\n }\n\n removeCredentials(archive);\n\n const scanned = stats.scanned - before.scanned;\n const snapshots = stats.snapshots - before.snapshots;\n const line = `${new Date().toISOString()} run=${runTs} source=${label} scanned=${scanned} snapshots=${snapshots} archive=${archive}`;\n log(`agent-lens: ${line}`);\n try {\n appendFileSync(join(archiveBase, \".collect.log\"), line + \"\\n\");\n } catch {\n /* log is best-effort */\n }\n}\n\n/** Collect every configured source into the archive. Returns aggregate counters. */\nexport function collectAll(opts: CollectOptions = {}): CollectStats {\n const log = opts.log ?? ((m: string) => console.log(m));\n const archiveBase = opts.archiveBase ?? join(resolveDataDir(findRepoRoot()), \"archive\");\n const sources = opts.sources ?? loadSources();\n const excludedDirs = (opts.excludes ?? loadExcludes()).map(encodeProjectPath);\n const runTs = opts.runTimestamp ?? collectTimestamp();\n\n ensureDir(archiveBase);\n const stats: CollectStats = { sources: 0, scanned: 0, copied: 0, appended: 0, diverged: 0, compacted: 0, snapshots: 0 };\n if (!sources.length) throw new Error(\"no sources configured\");\n for (const source of sources) {\n stats.sources++;\n collectSource(source, archiveBase, excludedDirs, runTs, stats, log);\n }\n return stats;\n}\n","/**\n * Cross-platform single-instance lock. collect+ingest take this so a too-frequent schedule (or an\n * overlapping watch trigger) can never run two collectors against the same archive or two writers\n * against the same SQLite DB. systemd already serializes its oneshot; this makes launchd/schtasks/\n * watch safe too. Not a hard mutex (a local, single-user tool doesn't need one) — a stale-aware\n * PID file: a run whose holder is dead or older than `staleMs` is reclaimed.\n */\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { hostname } from \"node:os\";\n\nexport interface Lock {\n /** Remove the lock file. Idempotent; also runs automatically on process exit. */\n release(): void;\n}\n\nfunction isAlive(pid: number): boolean {\n if (!pid) return false;\n try {\n process.kill(pid, 0); // signal 0 = existence check, doesn't actually signal\n return true;\n } catch (e) {\n // ESRCH → no such process (dead); EPERM → exists but not ours (alive).\n return (e as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Try to acquire `lockPath`. Returns a Lock on success, or null if another live, non-stale process\n * currently holds it (caller should log \"run in progress\" and exit 0).\n */\nexport function acquireLock(lockPath: string, staleMs = 6 * 60 * 60 * 1000): Lock | null {\n mkdirSync(dirname(lockPath), { recursive: true });\n if (existsSync(lockPath)) {\n try {\n const held = JSON.parse(readFileSync(lockPath, \"utf8\")) as { pid?: number; ts?: number };\n const fresh = typeof held.ts === \"number\" && Date.now() - held.ts < staleMs;\n if (fresh && isAlive(held.pid ?? 0)) return null; // held by a live, recent process\n // else: dead PID or too old → reclaim below\n } catch {\n // corrupt lock file → reclaim\n }\n }\n writeFileSync(lockPath, JSON.stringify({ pid: process.pid, ts: Date.now(), host: hostname() }), { mode: 0o600 });\n let released = false;\n const release = () => {\n if (released) return;\n released = true;\n try {\n // Only remove if it's still ours (avoid deleting a lock a reclaimer took over).\n const cur = JSON.parse(readFileSync(lockPath, \"utf8\")) as { pid?: number };\n if (cur.pid === process.pid) unlinkSync(lockPath);\n } catch {\n /* already gone / unreadable */\n }\n };\n process.once(\"exit\", release);\n return { release };\n}\n","/**\n * Cross-platform OS-service integration for `agent-lens service`. Two targets:\n * - collector → a *periodic* `collect --then-ingest` job (Node can time work via `watch`, but\n * can't survive a reboot alone, so we register it with the OS scheduler)\n * - server → the *long-running* web UI + read-only API daemon (`serve`)\n *\n * Per platform:\n * - Linux → systemd user units (collector: oneshot service + timer; server: Type=simple + Restart) + linger\n * - macOS → launchd LaunchAgents (collector: StartCalendarInterval; server: KeepAlive)\n * - Windows → Task Scheduler (collector: one DAILY task per hour; server: ONLOGON task)\n *\n * The absolute `node` binary (process.execPath) and CLI entry path are baked into every unit/plist/\n * task, because schedulers don't inherit the interactive shell's PATH (the mise problem, generalized).\n * The unit/plist/command text is generated by the pure functions below (unit-tested); runService\n * wraps them with the OS commands.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { findRepoRoot, resolveDataDir } from \"./paths.js\";\n\nexport const DEFAULT_HOURS = [9, 13, 17, 21];\n\nconst SYSTEMD_COLLECTOR_TIMER = \"agent-lens-collect.timer\";\nconst SYSTEMD_COLLECTOR_SERVICE = \"agent-lens-collect.service\";\nconst SYSTEMD_SERVER_SERVICE = \"agent-lens-server.service\";\nconst LAUNCHD_COLLECTOR_LABEL = \"org.agent-lens.collect\";\nconst LAUNCHD_SERVER_LABEL = \"org.agent-lens.server\";\nconst WINDOWS_COLLECTOR_PREFIX = \"AgentLens\\\\Collect\";\nconst WINDOWS_SERVER_TASK = \"AgentLens\\\\Server\";\n\nexport type ServiceTarget = \"collector\" | \"server\";\nexport type ServiceAction = \"install\" | \"uninstall\" | \"status\";\n\nexport interface ServiceParams {\n /** Absolute path to the CLI entry (the bundled agent-lens.js). */\n cliEntry: string;\n /** Which OS integrations to act on. */\n targets: ServiceTarget[];\n /** Hours (0–23) the collector runs at. Default: DEFAULT_HOURS. */\n hours?: number[];\n /** Absolute node binary. Default: process.execPath. */\n node?: string;\n}\n\n/** Parse the CLI target arg: undefined/\"all\" → both; \"collector\"/\"server\" → that one; else throw. */\nexport function parseTargets(spec: string | undefined): ServiceTarget[] {\n if (!spec || spec === \"all\") return [\"collector\", \"server\"];\n if (spec === \"collector\" || spec === \"server\") return [spec];\n throw new Error(`invalid target '${spec}' (expected: collector | server | all)`);\n}\n\nexport function parseHours(spec: string | undefined): number[] {\n if (!spec) return DEFAULT_HOURS;\n const hours = spec\n .split(\",\")\n .map((s) => Number.parseInt(s.trim(), 10))\n .filter((n) => Number.isInteger(n) && n >= 0 && n <= 23);\n if (!hours.length) throw new Error(`invalid hours '${spec}' (expected comma-separated 0-23, e.g. 9,13,17,21)`);\n return [...new Set(hours)].sort((a, b) => a - b);\n}\n\n/** Read AGENT_LENS_PORT / AGENT_LENS_HOST from the current env, if set (baked into the server unit). */\nfunction serverEnv(): Record<string, string> {\n const env: Record<string, string> = {};\n if (process.env.AGENT_LENS_PORT) env.AGENT_LENS_PORT = process.env.AGENT_LENS_PORT;\n if (process.env.AGENT_LENS_HOST) env.AGENT_LENS_HOST = process.env.AGENT_LENS_HOST;\n return env;\n}\n\n// ---- pure generators (unit-tested) --------------------------------------------------------------\n\n/** systemd `OnCalendar` hour list, e.g. [9,13] → \"09,13:00\". */\nexport function onCalendarHours(hours: number[]): string {\n return hours.map((h) => String(h).padStart(2, \"0\")).join(\",\") + \":00\";\n}\n\n/** Collector: oneshot unit that runs one `collect --then-ingest` pass (driven by the timer). */\nexport function systemdCollectorService(node: string, cli: string): string {\n return `[Unit]\nDescription=Agent Lens — collect + ingest Claude Code session traces\n\n[Service]\nType=oneshot\n# Absolute node + CLI paths: systemd user services don't inherit the interactive PATH.\nExecStart=${node} ${cli} collect --then-ingest\nNice=10\nIOSchedulingClass=idle\n`;\n}\n\nexport function systemdCollectorTimer(hours: number[]): string {\n return `[Unit]\nDescription=Agent Lens — periodic Claude Code trace collection\n\n[Timer]\n# A few times a day. Persistent catches runs missed while the machine was off.\nOnCalendar=*-*-* ${onCalendarHours(hours)}\nPersistent=true\nRandomizedDelaySec=300\n\n[Install]\nWantedBy=timers.target\n`;\n}\n\n/** Server: long-running daemon that runs `serve`, restarted on failure. */\nexport function systemdServerService(node: string, cli: string, env: Record<string, string> = {}): string {\n const envLines = Object.entries(env)\n .map(([k, v]) => `Environment=${k}=${v}\\n`)\n .join(\"\");\n return `[Unit]\nDescription=Agent Lens — local web UI + read-only API server\nAfter=network.target\n\n[Service]\nType=simple\n# Absolute node + CLI paths: systemd user services don't inherit the interactive PATH.\nExecStart=${node} ${cli} serve\n${envLines}# Long-running: restart if it crashes.\nRestart=on-failure\nRestartSec=5\nNice=10\n\n[Install]\nWantedBy=default.target\n`;\n}\n\nexport function launchdCollectorPlist(node: string, cli: string, hours: number[], logPath: string): string {\n const cal = hours\n .map((h) => ` <dict><key>Hour</key><integer>${h}</integer><key>Minute</key><integer>0</integer></dict>`)\n .join(\"\\n\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LAUNCHD_COLLECTOR_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${node}</string>\n <string>${cli}</string>\n <string>collect</string>\n <string>--then-ingest</string>\n </array>\n <key>StartCalendarInterval</key>\n <array>\n${cal}\n </array>\n <key>StandardOutPath</key><string>${logPath}</string>\n <key>StandardErrorPath</key><string>${logPath}</string>\n</dict>\n</plist>\n`;\n}\n\n/** Server: launchd agent kept alive (relaunched if it exits) and started at load. */\nexport function launchdServerPlist(node: string, cli: string, logPath: string, env: Record<string, string> = {}): string {\n const envDict = Object.entries(env).length\n ? ` <key>EnvironmentVariables</key>\\n <dict>\\n${Object.entries(env)\n .map(([k, v]) => ` <key>${k}</key><string>${v}</string>`)\n .join(\"\\n\")}\\n </dict>\\n`\n : \"\";\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LAUNCHD_SERVER_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${node}</string>\n <string>${cli}</string>\n <string>serve</string>\n </array>\n${envDict} <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><true/>\n <key>StandardOutPath</key><string>${logPath}</string>\n <key>StandardErrorPath</key><string>${logPath}</string>\n</dict>\n</plist>\n`;\n}\n\n// ---- OS helpers ---------------------------------------------------------------------------------\n\nfunction sh(cmd: string, args: string[], quiet = false): boolean {\n const r = spawnSync(cmd, args, { stdio: quiet ? \"ignore\" : \"inherit\", encoding: \"utf8\" });\n return r.status === 0;\n}\n\nfunction have(cmd: string): boolean {\n const r = spawnSync(cmd, [\"--version\"], { stdio: \"ignore\" });\n return r.status === 0 || r.error === undefined;\n}\n\nfunction systemdUnitDir(): string {\n return join(process.env.XDG_CONFIG_HOME || join(homedir(), \".config\"), \"systemd\", \"user\");\n}\n\n// ---- Linux (systemd user) -----------------------------------------------------------------------\n\nfunction linuxInstall(targets: ServiceTarget[], node: string, cli: string, hours: number[]): void {\n if (!have(\"systemctl\")) {\n console.error(\"agent-lens service: systemctl not found — is this a systemd system?\");\n process.exitCode = 1;\n return;\n }\n const dir = systemdUnitDir();\n mkdirSync(dir, { recursive: true });\n if (targets.includes(\"collector\")) {\n writeFileSync(join(dir, SYSTEMD_COLLECTOR_SERVICE), systemdCollectorService(node, cli));\n writeFileSync(join(dir, SYSTEMD_COLLECTOR_TIMER), systemdCollectorTimer(hours));\n }\n if (targets.includes(\"server\")) {\n writeFileSync(join(dir, SYSTEMD_SERVER_SERVICE), systemdServerService(node, cli, serverEnv()));\n }\n sh(\"systemctl\", [\"--user\", \"daemon-reload\"]);\n if (targets.includes(\"collector\")) {\n sh(\"systemctl\", [\"--user\", \"enable\", \"--now\", SYSTEMD_COLLECTOR_TIMER]);\n console.log(`agent-lens service: installed collector timer (${onCalendarHours(hours)})`);\n sh(\"systemctl\", [\"--user\", \"list-timers\", SYSTEMD_COLLECTOR_TIMER, \"--no-pager\"]);\n }\n if (targets.includes(\"server\")) {\n sh(\"systemctl\", [\"--user\", \"enable\", \"--now\", SYSTEMD_SERVER_SERVICE]);\n console.log(\"agent-lens service: installed server daemon\");\n sh(\"systemctl\", [\"--user\", \"status\", SYSTEMD_SERVER_SERVICE, \"--no-pager\"]);\n }\n // Run while logged out (best-effort) — needed for both the timer and the daemon.\n if (have(\"loginctl\") && !sh(\"loginctl\", [\"enable-linger\", process.env.USER || \"\"], true)) {\n console.log(`agent-lens: could not enable linger automatically; run: sudo loginctl enable-linger ${process.env.USER}`);\n }\n}\n\nfunction linuxUninstall(targets: ServiceTarget[]): void {\n if (!have(\"systemctl\")) return;\n const dir = systemdUnitDir();\n if (targets.includes(\"collector\")) {\n sh(\"systemctl\", [\"--user\", \"disable\", \"--now\", SYSTEMD_COLLECTOR_TIMER], true);\n for (const u of [SYSTEMD_COLLECTOR_TIMER, SYSTEMD_COLLECTOR_SERVICE]) rmSync(join(dir, u), { force: true });\n sh(\"systemctl\", [\"--user\", \"reset-failed\", SYSTEMD_COLLECTOR_SERVICE], true);\n }\n if (targets.includes(\"server\")) {\n sh(\"systemctl\", [\"--user\", \"disable\", \"--now\", SYSTEMD_SERVER_SERVICE], true);\n rmSync(join(dir, SYSTEMD_SERVER_SERVICE), { force: true });\n sh(\"systemctl\", [\"--user\", \"reset-failed\", SYSTEMD_SERVER_SERVICE], true);\n }\n sh(\"systemctl\", [\"--user\", \"daemon-reload\"]);\n console.log(\"agent-lens service: uninstalled (archive untouched)\");\n}\n\nfunction linuxStatus(targets: ServiceTarget[]): void {\n if (!have(\"systemctl\")) return;\n if (targets.includes(\"collector\")) sh(\"systemctl\", [\"--user\", \"list-timers\", SYSTEMD_COLLECTOR_TIMER, \"--no-pager\"]);\n if (targets.includes(\"server\")) sh(\"systemctl\", [\"--user\", \"status\", SYSTEMD_SERVER_SERVICE, \"--no-pager\"]);\n}\n\n// ---- macOS (launchd) ----------------------------------------------------------------------------\n\nfunction launchdPlistPath(label: string): string {\n return join(homedir(), \"Library\", \"LaunchAgents\", `${label}.plist`);\n}\n\nfunction macBootstrap(plist: string): void {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : 0;\n // Prefer the modern bootstrap API; fall back to legacy load -w.\n if (!sh(\"launchctl\", [\"bootstrap\", `gui/${uid}`, plist], true)) sh(\"launchctl\", [\"load\", \"-w\", plist]);\n}\n\nfunction macBootout(label: string, plist: string): void {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : 0;\n if (!sh(\"launchctl\", [\"bootout\", `gui/${uid}/${label}`], true)) sh(\"launchctl\", [\"unload\", \"-w\", plist], true);\n rmSync(plist, { force: true });\n}\n\nfunction macInstall(targets: ServiceTarget[], node: string, cli: string, hours: number[]): void {\n const agents = join(homedir(), \"Library\", \"LaunchAgents\");\n mkdirSync(agents, { recursive: true });\n const dataDir = resolveDataDir(findRepoRoot());\n if (targets.includes(\"collector\")) {\n const plist = launchdPlistPath(LAUNCHD_COLLECTOR_LABEL);\n writeFileSync(plist, launchdCollectorPlist(node, cli, hours, join(dataDir, \"schedule.log\")));\n macBootstrap(plist);\n console.log(`agent-lens service: installed collector agent ${LAUNCHD_COLLECTOR_LABEL} (hours ${hours.join(\",\")})`);\n }\n if (targets.includes(\"server\")) {\n const plist = launchdPlistPath(LAUNCHD_SERVER_LABEL);\n writeFileSync(plist, launchdServerPlist(node, cli, join(dataDir, \"server.log\"), serverEnv()));\n macBootstrap(plist);\n console.log(`agent-lens service: installed server agent ${LAUNCHD_SERVER_LABEL}`);\n }\n}\n\nfunction macUninstall(targets: ServiceTarget[]): void {\n if (targets.includes(\"collector\")) macBootout(LAUNCHD_COLLECTOR_LABEL, launchdPlistPath(LAUNCHD_COLLECTOR_LABEL));\n if (targets.includes(\"server\")) macBootout(LAUNCHD_SERVER_LABEL, launchdPlistPath(LAUNCHD_SERVER_LABEL));\n console.log(\"agent-lens service: uninstalled (archive untouched)\");\n}\n\nfunction macStatus(targets: ServiceTarget[]): void {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : 0;\n if (targets.includes(\"collector\")) sh(\"launchctl\", [\"print\", `gui/${uid}/${LAUNCHD_COLLECTOR_LABEL}`]);\n if (targets.includes(\"server\")) sh(\"launchctl\", [\"print\", `gui/${uid}/${LAUNCHD_SERVER_LABEL}`]);\n}\n\n// ---- Windows (schtasks) -------------------------------------------------------------------------\n\nfunction winCollectorTaskName(hour: number): string {\n return `${WINDOWS_COLLECTOR_PREFIX}_${String(hour).padStart(2, \"0\")}`;\n}\n\nfunction windowsInstall(targets: ServiceTarget[], node: string, cli: string, hours: number[]): void {\n if (targets.includes(\"collector\")) {\n const tr = `\"${node}\" \"${cli}\" collect --then-ingest`;\n for (const h of hours) {\n sh(\"schtasks\", [\"/Create\", \"/F\", \"/TN\", winCollectorTaskName(h), \"/TR\", tr, \"/SC\", \"DAILY\", \"/ST\", `${String(h).padStart(2, \"0\")}:00`]);\n }\n console.log(`agent-lens service: installed ${hours.length} collector task(s) (hours ${hours.join(\",\")})`);\n }\n if (targets.includes(\"server\")) {\n // ONLOGON runs in the user session, so the per-user data dir (%LOCALAPPDATA%) resolves.\n // Note: schtasks has no crash-restart (systemd/launchd do); a crashed server returns on next logon.\n const tr = `\"${node}\" \"${cli}\" serve`;\n sh(\"schtasks\", [\"/Create\", \"/F\", \"/TN\", WINDOWS_SERVER_TASK, \"/TR\", tr, \"/SC\", \"ONLOGON\", \"/RL\", \"LIMITED\"]);\n console.log(\"agent-lens service: installed server task (starts at logon)\");\n }\n}\n\nfunction windowsUninstall(targets: ServiceTarget[]): void {\n if (targets.includes(\"collector\")) {\n // Delete every task under our prefix (we don't need to know the original hours).\n for (let h = 0; h < 24; h++) sh(\"schtasks\", [\"/Delete\", \"/F\", \"/TN\", winCollectorTaskName(h)], true);\n }\n if (targets.includes(\"server\")) sh(\"schtasks\", [\"/Delete\", \"/F\", \"/TN\", WINDOWS_SERVER_TASK], true);\n console.log(\"agent-lens service: uninstalled (archive untouched)\");\n}\n\nfunction windowsStatus(targets: ServiceTarget[]): void {\n if (targets.includes(\"collector\")) {\n sh(\"schtasks\", [\"/Query\", \"/FO\", \"LIST\", \"/TN\", WINDOWS_COLLECTOR_PREFIX], true) ||\n console.log(\"agent-lens service: no collector tasks found under \" + WINDOWS_COLLECTOR_PREFIX);\n }\n if (targets.includes(\"server\")) {\n sh(\"schtasks\", [\"/Query\", \"/FO\", \"LIST\", \"/TN\", WINDOWS_SERVER_TASK], true) ||\n console.log(\"agent-lens service: no server task found (\" + WINDOWS_SERVER_TASK + \")\");\n }\n}\n\n// ---- dispatch -----------------------------------------------------------------------------------\n\nexport function runService(action: string, params: ServiceParams): void {\n const node = params.node ?? process.execPath;\n const cli = params.cliEntry;\n const hours = params.hours ?? DEFAULT_HOURS;\n const targets = params.targets;\n const plat = platform();\n const impl =\n plat === \"linux\"\n ? {\n install: () => linuxInstall(targets, node, cli, hours),\n uninstall: () => linuxUninstall(targets),\n status: () => linuxStatus(targets),\n }\n : plat === \"darwin\"\n ? {\n install: () => macInstall(targets, node, cli, hours),\n uninstall: () => macUninstall(targets),\n status: () => macStatus(targets),\n }\n : plat === \"win32\"\n ? {\n install: () => windowsInstall(targets, node, cli, hours),\n uninstall: () => windowsUninstall(targets),\n status: () => windowsStatus(targets),\n }\n : null;\n if (!impl) {\n console.error(`agent-lens service: unsupported platform '${plat}'`);\n process.exitCode = 1;\n return;\n }\n if (action === \"install\") impl.install();\n else if (action === \"uninstall\") impl.uninstall();\n else if (action === \"status\") impl.status();\n else {\n console.error(`agent-lens service: unknown action '${action}' (use: install | uninstall | status)`);\n process.exitCode = 1;\n }\n}\n","/**\n * Agent Lens — Stage 2 ingest, as importable functions (ADR-001, ADR-003).\n *\n * `runIngest` and `runMetrics` are the library entrypoints so the unified `agent-lens` CLI can\n * bundle and call them in-process; the thin bins in index.ts / metrics-cli.ts just forward argv.\n * The DB-writing engine lives in pipeline.ts (unit-tested in-process).\n */\nimport { readFileSync, statSync, mkdirSync, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport {\n costForUsage,\n findRepoRoot,\n loadExcludes,\n loadSources,\n resolveDataDir,\n SCHEMA_VERSION,\n type SourceAdapter,\n} from \"@agent-lens/core\";\nimport { openDb, openRaw, readSchemaVersion, resetSchema } from \"./db.js\";\nimport { ClaudeCodeAdapter } from \"./adapters/claude-code.js\";\nimport { classify, CLASSIFIER_VERSION } from \"./classify.js\";\nimport { detect, DETECTOR_VERSION } from \"./detect.js\";\nimport { classifyErrors } from \"./errors.js\";\nimport { ingestFile, newStats, prepareStatements, pruneExcluded, rebuildDerived } from \"./pipeline.js\";\nimport { ingestWorkflowResults, newWorkflowStats } from \"./workflows.js\";\nimport { ingestSubagentMeta, newMetaStats } from \"./meta.js\";\nimport { ingestToolResults, newToolResultStats } from \"./toolResults.js\";\nimport { parseExcludes, isExcludedArchivePath } from \"./redact.js\";\nimport { sha256, sha256File, streamLines, STREAM_THRESHOLD } from \"./fileread.js\";\n\nfunction parseIngestArgs(argv: string[]) {\n const a = { full: false, db: \"\", archive: \"\" };\n for (let i = 0; i < argv.length; i++) {\n if (argv[i] === \"--full\") a.full = true;\n else if (argv[i] === \"--db\") a.db = argv[++i] ?? \"\";\n else if (argv[i] === \"--archive\") a.archive = argv[++i] ?? \"\";\n }\n return a;\n}\n\n/** Stage 2: (re)build the derived SQLite store from the local archive. Idempotent. */\nexport function runIngest(argv: string[] = process.argv.slice(2)): void {\n const args = parseIngestArgs(argv);\n const dataDir = resolveDataDir(findRepoRoot());\n const archiveRoot = args.archive || process.env.AGENT_LENS_ARCHIVE || join(dataDir, \"archive\");\n const dbPath = args.db || process.env.AGENT_LENS_DB || join(dataDir, \"agent-lens.db\");\n\n if (!existsSync(archiveRoot)) {\n console.error(`agent-lens-ingest: archive not found: ${archiveRoot} (run 'agent-lens collect' first)`);\n process.exit(1);\n }\n mkdirSync(dirname(dbPath), { recursive: true });\n\n // --full: open WITHOUT applying schema (the on-disk schema may be a stale version), then\n // drop+recreate from the archive (source of truth). This is also the migration path — a\n // SCHEMA_VERSION bump's new columns take effect here without a separate migration step.\n const db = args.full ? openRaw(dbPath) : openDb(dbPath);\n if (args.full) {\n resetSchema(db);\n } else {\n // Schema guard: an incremental ingest only runs CREATE IF NOT EXISTS — it adds new tables but\n // cannot migrate altered columns on existing ones, and never advances the stamp (applySchema is\n // stamp-if-absent). So a DB stamped by an older build is stale: refuse rather than silently ingest\n // into a half-migrated schema. `ingest --full` (drop+recreate) is the migration path.\n const dbVer = readSchemaVersion(db);\n if (dbVer != null && dbVer !== SCHEMA_VERSION) {\n console.error(\n `agent-lens-ingest: schema mismatch — DB is v${dbVer}, this build expects v${SCHEMA_VERSION}. ` +\n `Run 'agent-lens ingest --full' to rebuild.`,\n );\n db.close();\n process.exit(1);\n }\n }\n\n // Adapter registry keyed by agent type; configured sources resolved by the shared resolver.\n const adapterList: SourceAdapter[] = [new ClaudeCodeAdapter()];\n const adapterById = new Map(adapterList.map((a) => [a.agentId, a]));\n const sources = loadSources();\n const now = new Date().toISOString();\n\n // Excluded projects (config `exclude` + AGENT_LENS_EXCLUDE): drop any already-ingested ones now\n // (incremental; --full already reset the DB), then filter them out of discovery below.\n const excludePaths = loadExcludes();\n const excludedDirs = parseExcludes(excludePaths.join(\",\"));\n const pruned = pruneExcluded(db, excludePaths);\n\n const stmts = prepareStatements(db);\n const stats = newStats();\n const wfStats = newWorkflowStats();\n const metaStats = newMetaStats();\n const trStats = newToolResultStats();\n // Sessions touched this run; drives the incremental derived rebuild (ADR-010, impacts 2/3).\n const dirty = new Set<string>();\n\n for (const source of sources) {\n const adapter = adapterById.get(source.agent);\n if (!adapter) {\n console.warn(`agent-lens-ingest: no adapter for agent '${source.agent}' (source '${source.label}') — skipping`);\n continue;\n }\n stmts.insAgent.run(adapter.agentId, adapter.agentName);\n stmts.insSource.run({ id: source.label, label: source.label, agent_id: adapter.agentId, config_dir: source.configDir });\n\n let files = adapter.discover(join(archiveRoot, source.label), source.label);\n // Drop excluded projects (matches /projects/<encodedDir>/ so nested subagent files go too).\n if (excludedDirs.length) files = files.filter((f) => !isExcludedArchivePath(f.path, excludedDirs));\n // Mirror before versions so the mirror copy wins canonical fields (ON CONFLICT DO NOTHING).\n files.sort((a, b) => Number(a.isVersion) - Number(b.isVersion));\n\n for (const file of files) {\n stats.files++;\n const st = statSync(file.path);\n const mtimeMs = Math.trunc(st.mtimeMs);\n const prev = args.full\n ? undefined\n : (stmts.getState.get(file.path) as\n | { size: number; mtime_ms: number; sha256: string; events_ingested: number }\n | undefined);\n\n // (a) Stat short-circuit: an unchanged size+mtime means unchanged content — skip without ever\n // reading or hashing the file. Restores true incrementality across the mirror + every\n // .versions snapshot (ADR-010, impact 1).\n if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {\n stats.skipped++;\n continue;\n }\n\n // Size/mtime moved: read + hash to decide. Whole-file for the common case; stream large files.\n const small = st.size <= STREAM_THRESHOLD;\n const buf = small ? readFileSync(file.path) : null;\n const hash = small ? sha256(buf!) : sha256File(file.path);\n\n // Content unchanged though mtime moved (e.g. rsync --append-verify re-stat). Skip ingest, but\n // refresh size/mtime so the next run short-circuits on stat alone.\n if (prev && prev.sha256 === hash) {\n stmts.setState.run({\n file_path: file.path,\n size: st.size,\n mtime_ms: mtimeMs,\n sha256: hash,\n events_ingested: prev.events_ingested,\n ingested_at: now,\n });\n stats.skipped++;\n continue;\n }\n\n const lines = small ? buf!.toString(\"utf8\").split(\"\\n\") : streamLines(file.path);\n ingestFile(db, stmts, adapter, file, lines, { size: st.size, mtimeMs, hash }, now, stats);\n dirty.add(file.sessionId);\n }\n\n // Workflow-tool result sidecars (wf_<id>.json) — the authoritative record of how each run\n // finished; not *.jsonl, so the transcript walk above never sees them. Own table, own skip-state.\n ingestWorkflowResults(db, join(archiveRoot, source.label), source.label, excludedDirs, now, wfStats, args.full);\n\n // Per-subagent metadata (subagents/agent-<id>.meta.json) — the authoritative agentType/description/\n // spawnDepth; joined onto sessions at read time. Own table (session_meta), own skip-state.\n ingestSubagentMeta(db, join(archiveRoot, source.label), source.label, excludedDirs, now, metaStats, args.full);\n\n // Spilled full tool outputs (tool-results/<name>.txt) — the un-truncated result the transcript only\n // summarized; joined onto tool_calls at read time via the summary's marker. Own table, own skip-state.\n ingestToolResults(db, join(archiveRoot, source.label), excludedDirs, now, trStats, args.full);\n }\n\n // Incremental derived rebuild over only the touched sessions (+ their linkage neighborhood); --full\n // rebuilds everything. classify reuses the expanded set rebuildDerived returns.\n const expanded = rebuildDerived(db, args.full ? null : dirty);\n\n // Heuristic classification (ADR-004) over the now-stable derived tables. Deterministic +\n // re-runnable; also exposed standalone as `agent-lens metrics` (see runMetrics).\n const classified = classify(db, args.full ? null : expanded);\n\n // Security findings (ADR-017) over the same derived tool_calls. Deterministic + re-runnable; the\n // detector reuses the expanded dirty set and delete-then-inserts the touched sessions' findings.\n const detected = detect(db, args.full ? null : expanded);\n\n // Tool-error classification (error_type on errored tool_calls). Deterministic + re-runnable; reuses\n // the same expanded dirty set. Powers the sessions error-type filter + dashboard/detail breakdowns.\n classifyErrors(db, args.full ? null : expanded);\n\n // Report.\n const count = (sql: string) => (db.prepare(sql).get() as any).n as number;\n const sessions = count(\"SELECT COUNT(*) n FROM sessions\");\n const turns = count(\"SELECT COUNT(*) n FROM turns\");\n const events = count(\"SELECT COUNT(*) n FROM events\");\n const tools = count(\"SELECT COUNT(*) n FROM tool_calls\");\n const usageRows = db\n .prepare(\"SELECT model, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens FROM token_usage\")\n .all() as any[];\n let cost = 0;\n let totalTokens = 0;\n for (const u of usageRows) {\n cost += costForUsage(u.model, u);\n totalTokens += u.input_tokens + u.output_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens;\n }\n\n const wfRuns = count(\"SELECT COUNT(*) n FROM workflow_results\");\n const metaRows = count(\"SELECT COUNT(*) n FROM session_meta\");\n const trRows = count(\"SELECT COUNT(*) n FROM tool_results\");\n const sevCounts = db\n .prepare(\"SELECT severity, COUNT(*) n FROM findings GROUP BY severity\")\n .all() as Array<{ severity: string; n: number }>;\n const sevMap = Object.fromEntries(sevCounts.map((r) => [r.severity, r.n]));\n const sevLine = [\"critical\", \"high\", \"medium\", \"low\", \"info\"].map((s) => `${s}=${sevMap[s] ?? 0}`).join(\" \");\n\n db.close();\n console.log(\n `agent-lens-ingest: files=${stats.files} skipped=${stats.skipped} new_events=${stats.newEvents} malformed=${stats.malformed}${pruned ? ` excluded_pruned=${pruned}` : \"\"}\\n` +\n ` sessions=${sessions} turns=${turns} events=${events} tool_calls=${tools} classified=${classified.count}\\n` +\n ` workflow_results=${wfRuns} (sidecar upserted=${wfStats.upserted} skipped=${wfStats.skipped} malformed=${wfStats.malformed})\\n` +\n ` session_meta=${metaRows} (upserted=${metaStats.upserted} skipped=${metaStats.skipped} malformed=${metaStats.malformed})\\n` +\n ` tool_results=${trRows} (upserted=${trStats.upserted} skipped=${trStats.skipped} malformed=${trStats.malformed})\\n` +\n ` findings=${detected.count} (${sevLine})\\n` +\n ` tokens=${totalTokens.toLocaleString()} est_cost=$${cost.toFixed(2)} db=${dbPath}`,\n );\n}\n\n/** Standalone reclassification over an already-ingested DB (no archive re-read). */\nexport function runMetrics(argv: string[] = process.argv.slice(2)): void {\n let dbArg = \"\";\n for (let i = 0; i < argv.length; i++) if (argv[i] === \"--db\") dbArg = argv[++i] ?? \"\";\n const dataDir = resolveDataDir(findRepoRoot());\n const dbPath = dbArg || process.env.AGENT_LENS_DB || join(dataDir, \"agent-lens.db\");\n\n if (!existsSync(dbPath)) {\n console.error(`agent-lens-metrics: db not found: ${dbPath} (run ingest first)`);\n process.exit(1);\n }\n\n const db = openDb(dbPath);\n const r = classify(db);\n const d = detect(db);\n const e = classifyErrors(db);\n db.close();\n console.log(\n `agent-lens-metrics: classified=${r.count} classifier_version=${CLASSIFIER_VERSION} ` +\n `findings=${d.count} detector_version=${DETECTOR_VERSION} errors_classified=${e.count} error_classifier_version=${e.version} db=${dbPath}`,\n );\n}\n","import Database from \"better-sqlite3\";\nimport { SCHEMA_SQL, SCHEMA_VERSION } from \"@agent-lens/core\";\n\nexport type DB = Database.Database;\n\n/** Open (creating if needed) the DB and set pragmas, WITHOUT applying the schema. */\nexport function openRaw(file: string): DB {\n const db = new Database(file);\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n return db;\n}\n\n/** The schema version stamped in an existing DB, or null if unstamped/brand-new. */\nexport function readSchemaVersion(db: DB): number | null {\n const row = db\n .prepare(\"SELECT value FROM meta WHERE key = 'schema_version'\")\n .get() as { value: string } | undefined;\n if (!row) return null;\n const n = Number(row.value);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * Apply the current schema (idempotent CREATE IF NOT EXISTS) and stamp the version ONLY when absent.\n * Stamping if-absent (not overwrite) is deliberate: an incremental `ingest` runs CREATE IF NOT EXISTS,\n * which adds new *tables* but cannot migrate altered columns on existing ones — so overwriting the stamp\n * would let a stale DB claim the current version and lie. A stale DB keeps its old stamp; the caller\n * (`runIngest`) guards on it. `resetSchema` drops `meta`, so its applySchema re-stamps the current\n * version from scratch — the only path that advances the stamp.\n */\nfunction applySchema(db: DB): void {\n db.exec(SCHEMA_SQL);\n db.prepare(\n \"INSERT INTO meta(key, value) VALUES ('schema_version', ?) ON CONFLICT(key) DO NOTHING\",\n ).run(String(SCHEMA_VERSION));\n}\n\n/**\n * Open (creating if needed) the Agent Lens database and ensure the schema is applied.\n * NOTE: on a stale on-disk schema this only adds missing objects (CREATE IF NOT EXISTS) — it does\n * not migrate changed/added columns. A SCHEMA_VERSION bump is applied via `ingest --full`\n * (openRaw + resetSchema), the canonical rebuild path.\n */\nexport function openDb(file: string): DB {\n const db = openRaw(file);\n applySchema(db);\n return db;\n}\n\n/**\n * Drop and recreate every table from the current SCHEMA_SQL. Used by `ingest --full`: because the\n * archive is the source of truth and the DB is a derived projection, a full rebuild is also the\n * migration path — dropping the tables (vs. DELETE FROM) lets a SCHEMA_VERSION bump's new columns\n * take effect on an existing DB without a separate migration mechanism (ADR-001/009).\n */\nexport function resetSchema(db: DB): void {\n // Disable FK enforcement during teardown: the v3 schema has cross/self references\n // (sessions.parent_turn_id → turns, parent_session_id → sessions) that otherwise make DROP order\n // matter and trip \"FOREIGN KEY constraint failed\". applySchema re-enables FKs (PRAGMA in SCHEMA_SQL).\n db.pragma(\"foreign_keys = OFF\");\n db.exec(`\n DROP TRIGGER IF EXISTS events_au;\n DROP TRIGGER IF EXISTS events_ad;\n DROP TRIGGER IF EXISTS events_ai;\n DROP TABLE IF EXISTS events_fts;\n DROP TABLE IF EXISTS findings;\n DROP TABLE IF EXISTS classifications;\n DROP TABLE IF EXISTS token_usage;\n DROP TABLE IF EXISTS tool_calls;\n DROP TABLE IF EXISTS skills;\n DROP TABLE IF EXISTS events;\n DROP TABLE IF EXISTS turns;\n DROP TABLE IF EXISTS sessions;\n DROP TABLE IF EXISTS projects;\n DROP TABLE IF EXISTS sources;\n DROP TABLE IF EXISTS agents;\n DROP TABLE IF EXISTS workflow_results;\n DROP TABLE IF EXISTS session_meta;\n DROP TABLE IF EXISTS tool_results;\n DROP TABLE IF EXISTS ingest_state;\n DROP TABLE IF EXISTS meta;\n `);\n applySchema(db);\n db.pragma(\"foreign_keys = ON\");\n}\n","import { readdirSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type {\n ParsedLine,\n SourceAdapter,\n SourceFile,\n ToolCallRow,\n ToolResultPatch,\n} from \"@agent-lens/core\";\n\n/** Flatten a message `content` (string or block array) to searchable natural-language text. */\nfunction flattenText(content: unknown): string | null {\n if (content == null) return null;\n if (typeof content === \"string\") return content.trim() || null;\n if (!Array.isArray(content)) return null;\n const parts: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\") parts.push(b.text);\n else if (b.type === \"thinking\" && typeof b.thinking === \"string\") parts.push(b.thinking);\n }\n const text = parts.join(\"\\n\").trim();\n return text || null;\n}\n\n/** Compact projection of a tool result for the transcript view. Preserves line structure — so console\n * output (build logs, git output, …) renders as lines instead of one run-on line — while staying\n * bounded: trailing per-line whitespace and runs of blank lines are trimmed, and the text is capped by\n * both line count and characters so a large result can't be dumped wholesale into the DB. Internal\n * spacing is kept intact so column-aligned output survives. The full untruncated output, when big\n * enough, is spilled by Claude Code to tool-results/<name>.txt and surfaced separately (full_result). */\nconst RESULT_SUMMARY_MAX_LINES = 40;\nconst RESULT_SUMMARY_MAX_CHARS = 2000;\nfunction summarizeResult(content: unknown): string | null {\n let s: string | null = null;\n if (typeof content === \"string\") s = content;\n else if (Array.isArray(content)) {\n const parts: string[] = [];\n for (const block of content) {\n const b = block as Record<string, unknown>;\n if (b && b.type === \"text\" && typeof b.text === \"string\") parts.push(b.text);\n }\n s = parts.join(\"\\n\");\n } else if (content != null) {\n s = JSON.stringify(content);\n }\n if (!s) return null;\n // Normalize newlines, drop trailing per-line whitespace, and collapse 3+ blank lines — but keep\n // internal spacing and single blank lines so the structure reads like the original console output.\n s = s.replace(/\\r\\n/g, \"\\n\").replace(/[ \\t]+\\n/g, \"\\n\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n if (!s) return null;\n let truncated = false;\n const lines = s.split(\"\\n\");\n if (lines.length > RESULT_SUMMARY_MAX_LINES) {\n s = lines.slice(0, RESULT_SUMMARY_MAX_LINES).join(\"\\n\");\n truncated = true;\n }\n if (s.length > RESULT_SUMMARY_MAX_CHARS) {\n s = s.slice(0, RESULT_SUMMARY_MAX_CHARS);\n const nl = s.lastIndexOf(\"\\n\");\n if (nl > RESULT_SUMMARY_MAX_CHARS - 200) s = s.slice(0, nl); // prefer a clean line boundary\n truncated = true;\n }\n s = s.trimEnd();\n return (truncated ? s + \"\\n… (truncated)\" : s) || null;\n}\n\nfunction asString(v: unknown): string | null {\n return typeof v === \"string\" && v.length > 0 ? v : null;\n}\n\nfunction asInt(v: unknown): number {\n return typeof v === \"number\" && Number.isFinite(v) ? Math.trunc(v) : 0;\n}\n\n/**\n * Derive the parent session id for a subagent transcript from its on-disk path. Claude Code nests\n * subagent transcripts under the spawning session's directory:\n * Task/Agent: <...>/projects/<enc>/<PARENT_SESSION_ID>/subagents/agent-<id>.jsonl\n * Workflow: <...>/projects/<enc>/<PARENT_SESSION_ID>/subagents/workflows/wf_<runId>/agent-<id>.jsonl\n * In both layouts the parent session id is the path segment immediately before `subagents/`. This is\n * the deterministic, redaction-surviving link (the Workflow tool emits no toolUseResult.agentId, so\n * the tool_calls.spawned_session_id path never fires for workflow fan-out). Returns null for a main\n * session (no `subagents/` segment).\n */\nexport function parentSessionFromPath(path: string): string | null {\n const parts = path.split(\"/\");\n const i = parts.indexOf(\"subagents\");\n return i > 0 ? parts[i - 1] : null;\n}\n\n/**\n * For a Workflow-tool subagent, the run id from its path. Workflow fan-out nests under\n * `…/subagents/workflows/<runId>/agent-<id>.jsonl`, so the run id is the segment after `workflows/`\n * (e.g. `wf_ab787f1c-ff7`). Returns null for Task/Agent subagents (no `workflows/` segment) and main\n * sessions. The same id appears in the launching Workflow tool_call's result (toolUseResult.runId),\n * which is how a run's agents group and link back to the turn that started them.\n */\nexport function workflowRunFromPath(path: string): string | null {\n const parts = path.split(\"/\");\n const i = parts.indexOf(\"workflows\");\n return i >= 0 && i + 1 < parts.length ? parts[i + 1] : null;\n}\n\nfunction recurseJsonl(dir: string, out: string[]): void {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const p = join(dir, e.name);\n if (e.isDirectory()) recurseJsonl(p, out);\n else if (e.isFile() && e.name.endsWith(\".jsonl\")) out.push(p);\n }\n}\n\nexport class ClaudeCodeAdapter implements SourceAdapter {\n agentId = \"claude-code\";\n agentName = \"Claude Code CLI\";\n\n discover(sourceArchiveDir: string, sourceId: string): SourceFile[] {\n const files: SourceFile[] = [];\n const archiveRoot = sourceArchiveDir;\n\n const addFrom = (projectsDir: string, isVersion: boolean) => {\n const found: string[] = [];\n recurseJsonl(projectsDir, found);\n for (const path of found) {\n // <...>/projects/<encodedDir>/<sessionId>.jsonl\n const parts = path.split(\"/\");\n const sessionId = basename(path, \".jsonl\");\n const encodedDir = parts[parts.length - 2] ?? \"\";\n files.push({\n path,\n sessionId,\n encodedDir,\n isVersion,\n sourceId,\n parentSessionId: parentSessionFromPath(path),\n workflowRunId: workflowRunFromPath(path),\n });\n }\n };\n\n addFrom(join(archiveRoot, \"projects\"), false);\n\n // .versions/<ts>/projects/<encodedDir>/<sessionId>.jsonl\n const versionsRoot = join(archiveRoot, \".versions\");\n try {\n for (const ts of readdirSync(versionsRoot, { withFileTypes: true })) {\n if (ts.isDirectory()) addFrom(join(versionsRoot, ts.name, \"projects\"), true);\n }\n } catch {\n /* no versions yet */\n }\n return files;\n }\n\n parseLine(raw: unknown, file: SourceFile, seq: number): ParsedLine {\n if (!raw || typeof raw !== \"object\") return {};\n const r = raw as Record<string, any>;\n\n // Session-level metadata from the common envelope + pointer lines.\n const meta: ParsedLine[\"meta\"] = {};\n if (asString(r.cwd)) meta.cwd = r.cwd;\n if (asString(r.slug)) meta.slug = r.slug;\n if (asString(r.version)) meta.cli_version = r.version;\n if (asString(r.entrypoint)) meta.entrypoint = r.entrypoint;\n if (asString(r.gitBranch)) meta.git_branch = r.gitBranch;\n if (typeof r.isSidechain === \"boolean\") meta.is_sidechain = r.isSidechain;\n if (r.type === \"ai-title\" && asString(r.aiTitle)) meta.ai_title = r.aiTitle;\n\n // Lines without a uuid (pointer/meta lines) contribute metadata only.\n if (!asString(r.uuid)) return { meta };\n\n const message = (r.message ?? {}) as Record<string, any>;\n const text = flattenText(message.content);\n\n const result: ParsedLine = {\n meta,\n event: {\n uuid: r.uuid,\n session_id: file.sessionId,\n turn_id: null,\n parent_uuid: asString(r.parentUuid),\n seq,\n type: asString(r.type) ?? \"unknown\",\n role: asString(message.role),\n timestamp: asString(r.timestamp),\n model: asString(message.model),\n is_sidechain: r.isSidechain ? 1 : 0,\n is_meta: r.isMeta ? 1 : 0,\n text,\n raw_json: JSON.stringify(raw),\n source_file: file.path,\n },\n };\n\n // Token usage at the assistant-message grain.\n const usage = message.usage as Record<string, any> | undefined;\n if (usage) {\n result.tokenUsage = {\n event_uuid: r.uuid,\n session_id: file.sessionId,\n turn_id: null,\n message_id: asString(message.id),\n model: asString(message.model),\n input_tokens: asInt(usage.input_tokens),\n output_tokens: asInt(usage.output_tokens),\n cache_creation_input_tokens: asInt(usage.cache_creation_input_tokens),\n cache_read_input_tokens: asInt(usage.cache_read_input_tokens),\n service_tier: asString(usage.service_tier),\n };\n }\n\n // Tool calls (tool_use blocks) and tool results (tool_result blocks + toolUseResult).\n const content = message.content;\n if (Array.isArray(content)) {\n const toolCalls: ToolCallRow[] = [];\n const toolResults: ToolResultPatch[] = [];\n const tur = r.toolUseResult as Record<string, any> | undefined;\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, any>;\n\n if (b.type === \"tool_use\" && asString(b.id)) {\n const name = asString(b.name) ?? \"unknown\";\n const input = b.input ?? {};\n toolCalls.push({\n id: b.id,\n event_uuid: r.uuid,\n session_id: file.sessionId,\n turn_id: null,\n tool_name: name,\n caller: asString(b.caller),\n skill_name: name === \"Skill\" ? asString(input.skill) ?? asString(input.command) : null,\n skill_id: null, // content-addressed skill version; linked in rebuildDerived from the injected body\n agent_type:\n name === \"Task\" || name === \"Agent\" ? asString(input.subagent_type) : null,\n spawned_session_id: null, // patched in once the tool_result carries toolUseResult.agentId\n workflow_run_id: null, // patched in from the Workflow tool_result's runId\n workflow_name: name === \"Workflow\" ? asString(input.name) : null, // refined from the result\n resolved_model: null,\n status: null,\n total_duration_ms: null,\n total_tokens: null,\n total_tool_use_count: null,\n input_json: JSON.stringify(input),\n result_summary: null,\n });\n } else if (b.type === \"tool_result\" && asString(b.tool_use_id)) {\n // AskUserQuestion's result is structured (which option the user picked per question, plus any\n // free-text notes) and lives on toolUseResult, not in the block content. Capture it verbatim\n // as JSON so the UI can render the Q&A with the selection marked — the generic 280-char prose\n // summary would truncate multi-question prompts and drop the notes entirely. The questions +\n // options themselves are already in the tool_use input_json, so only answers/annotations are\n // stored here.\n const isAskUser = tur && tur.questions && tur.answers && typeof tur.answers === \"object\";\n toolResults.push({\n tool_use_id: b.tool_use_id,\n result_summary: isAskUser\n ? JSON.stringify({ answers: tur!.answers, annotations: tur!.annotations ?? {} })\n : summarizeResult(b.content),\n // Tool failure is signalled by `is_error` on the tool_result block itself — NOT by\n // toolUseResult.status (whose values are task/lifecycle states like completed/pending/\n // async_launched and never \"error\"). Map is_error → status=\"error\" so the error roll-ups,\n // the transcript `tool-err` styling, and detect.ts's failed-attempt de-escalation all fire.\n // Non-error results fall back to the toolUseResult lifecycle status when present.\n status: b.is_error === true ? \"error\" : tur ? asString(tur.status) : null,\n ...(tur\n ? {\n agent_type: asString(tur.agentType),\n // Deterministic link: a Task/Agent subagent's transcript is keyed off its agentId\n // ('agent-'||agentId is the session id assigned by discover()'s filename stem).\n spawned_session_id: asString(tur.agentId) ? `agent-${tur.agentId}` : null,\n // Workflow fan-out: the result carries the run id (wf_<id>) + workflow name; this is\n // how a run's subagents (nested under subagents/workflows/<runId>/) link to this turn.\n workflow_run_id: asString(tur.runId),\n workflow_name: asString(tur.workflowName),\n resolved_model: asString(tur.resolvedModel),\n total_duration_ms: typeof tur.totalDurationMs === \"number\" ? tur.totalDurationMs : null,\n total_tokens: typeof tur.totalTokens === \"number\" ? tur.totalTokens : null,\n total_tool_use_count:\n typeof tur.totalToolUseCount === \"number\" ? tur.totalToolUseCount : null,\n }\n : {}),\n });\n }\n }\n if (toolCalls.length) result.toolCalls = toolCalls;\n if (toolResults.length) result.toolResults = toolResults;\n }\n\n return result;\n }\n}\n","/**\n * Agent Lens — heuristic classification (ADR-004). No AI: category + complexity are derived\n * deterministically from signals already in the DB (tool mix, LoC from Edit/Write inputs,\n * turns, tokens, duration, subagent fan-out, prompt keywords). Re-runnable and idempotent —\n * the same DB always yields identical rows. Every input and sub-score is recorded in\n * `signals_json` so a result can be explained and the bands retuned without re-deriving meaning.\n *\n * Scope v1 = sessions (both main and sidechain). The PK (scope, target_id) leaves turn-level\n * for later. `classifier_version` lets a future (e.g. local-LLM) classifier supersede these rows.\n */\nimport type { DB } from \"./db.js\";\n\n// v2 (ADR-004): realistic complexity ceilings so real (long, substantial) main sessions spread\n// across bands instead of pegging in \"large\"; subagent sessions categorized by their spawner role\n// rather than keyword heuristics on a read-only exploration transcript.\nexport const CLASSIFIER_VERSION = 2;\n\nexport type Category = \"feature\" | \"bugfix\" | \"refactor\" | \"docs\" | \"ops\" | \"review\" | \"chore\";\n\n/** Fixed order = deterministic tie-break when scores are equal. */\nconst CATEGORIES: Category[] = [\"bugfix\", \"refactor\", \"docs\", \"ops\", \"review\", \"feature\", \"chore\"];\n\n// Cutoffs tuned to the real main-session score distribution (v2 ceilings) so the five bands\n// partition main work meaningfully (~9/19/32/32/8%). Subagents score low and land \"trivial\".\nconst BANDS: Array<{ max: number; band: string }> = [\n { max: 22, band: \"trivial\" },\n { max: 40, band: \"small\" },\n { max: 55, band: \"medium\" },\n { max: 68, band: \"large\" },\n { max: Infinity, band: \"xl\" },\n];\n\n/**\n * Subagent roles whose work is inherently investigative/planning ⇒ \"review\". A general-purpose (or\n * unknown) subagent does varied work, so fall back to the heuristic on its own transcript.\n */\nconst REVIEW_SUBAGENT_ROLES = new Set([\"Explore\", \"Plan\", \"code-reviewer\", \"claude-code-guide\"]);\nfunction categoryForSubagentRole(role: string | undefined): Category | null {\n if (!role) return null;\n return REVIEW_SUBAGENT_ROLES.has(role) ? \"review\" : null;\n}\n\n/** Number of lines in a string (0 for empty/undefined). Trailing newline doesn't add a line. */\nfunction countLines(s: string | undefined | null): number {\n if (!s) return 0;\n const trimmed = s.endsWith(\"\\n\") ? s.slice(0, -1) : s;\n return trimmed.length === 0 ? 0 : trimmed.split(\"\\n\").length;\n}\n\nconst DOC_EXT = /\\.(md|markdown|rst|txt|adoc)$/i;\nconst OPS_FILE = /(^|\\/)(dockerfile|docker-compose|\\.github\\/|\\.gitlab-ci|makefile)|\\.(ya?ml|tf|sh|toml|ini|cfg|service|timer)$/i;\n\n/** LoC contribution of a single Edit/Write tool call, parsed from its verbatim input_json. */\nexport function locDelta(toolName: string, inputJson: string | null): { added: number; removed: number; file: string | null } {\n if (!inputJson) return { added: 0, removed: 0, file: null };\n let input: any;\n try {\n input = JSON.parse(inputJson);\n } catch {\n return { added: 0, removed: 0, file: null };\n }\n const file = typeof input?.file_path === \"string\" ? input.file_path : null;\n if (toolName === \"Write\") return { added: countLines(input?.content), removed: 0, file };\n if (toolName === \"Edit\") return { added: countLines(input?.new_string), removed: countLines(input?.old_string), file };\n return { added: 0, removed: 0, file };\n}\n\nfunction band(score: number): string {\n for (const b of BANDS) if (score < b.max) return b.band;\n return \"xl\";\n}\n\nconst clamp01 = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x);\n\ninterface Signals {\n toolCounts: Record<string, number>;\n skills: Record<string, number>;\n loc: { added: number; removed: number; net: number; churn: number; files: number };\n turn_count: number;\n event_count: number;\n work_tokens: number; // input + output + cache_creation (cache-read excluded — replay ≠ work)\n cache_read_tokens: number;\n duration_ms: number;\n subagent_count: number;\n is_sidechain: number;\n}\n\n/** Count keyword hits (whole-word-ish) of any phrase in `words` within `text`. */\nfunction hits(text: string, words: string[]): number {\n let n = 0;\n for (const w of words) {\n let i = text.indexOf(w);\n while (i !== -1) {\n n++;\n i = text.indexOf(w, i + w.length);\n }\n }\n return n;\n}\n\nfunction scoreCategories(s: Signals, text: string): Record<Category, number> {\n const t = text.toLowerCase();\n const editN = s.toolCounts[\"Edit\"] ?? 0;\n const writeN = s.toolCounts[\"Write\"] ?? 0;\n const readN = s.toolCounts[\"Read\"] ?? 0;\n const writes = editN + writeN;\n\n const scores: Record<Category, number> = {\n bugfix: 0,\n refactor: 0,\n docs: 0,\n ops: 0,\n review: 0,\n feature: 0,\n chore: 0,\n };\n\n // Keyword evidence (prompt text).\n scores.bugfix += hits(t, [\"bug\", \"fix\", \"broken\", \"crash\", \"regression\", \"traceback\", \"stack trace\", \"failing\", \"error\", \"exception\", \"not working\"]) * 1.0;\n scores.refactor += hits(t, [\"refactor\", \"clean up\", \"cleanup\", \"rename\", \"extract\", \"simplify\", \"dead code\", \"tidy\", \"reorganize\", \"restructure\"]) * 1.0;\n scores.docs += hits(t, [\"readme\", \"document\", \"documentation\", \"changelog\", \"docstring\", \"comment\", \"write docs\", \"usage guide\"]) * 1.0;\n scores.ops += hits(t, [\"deploy\", \"docker\", \"pipeline\", \"ci/cd\", \"ci \", \"systemd\", \"infra\", \"kubernetes\", \"terraform\", \"release\", \"rollback\", \"container\"]) * 1.0;\n scores.review += hits(t, [\"review\", \"audit\", \"inspect\", \"look over\", \"feedback on\"]) * 1.0;\n // \"new \" dropped — too noisy (\"the new X\", \"new file\"); \"new feature\" stays as a strong signal.\n scores.feature += hits(t, [\"add \", \"implement\", \"build \", \"create \", \"feature\", \"support for\", \"new feature\"]) * 0.8;\n\n // Structural evidence (tools + files), scaled so it complements but doesn't drown keywords.\n const docFiles = s.loc.files > 0 ? countMatch(s, DOC_EXT) : 0;\n const opsFiles = s.loc.files > 0 ? countMatch(s, OPS_FILE) : 0;\n if (s.loc.files > 0) {\n scores.docs += 3 * (docFiles / s.loc.files);\n scores.ops += 3 * (opsFiles / s.loc.files);\n }\n // Edit-heavy + low net churn relative to gross churn ⇒ rework, not new code.\n if (writes >= 3 && s.loc.churn > 0 && Math.abs(s.loc.net) / s.loc.churn < 0.34) scores.refactor += 1.5;\n // Edit-dominated (lots of Edits, few/no new Writes) ⇒ reworking existing code, not adding it.\n if (editN >= 4 && editN >= writeN * 2) scores.refactor += 1.0;\n // Read-dominated, little writing ⇒ review/exploration.\n if (readN >= 5 && writes <= 2) scores.review += 1.5;\n // New files written with net-positive LoC ⇒ feature work.\n if (writeN >= 1 && s.loc.net > 20) scores.feature += 1.5;\n\n // No signal at all ⇒ record the default choice so the argmax (in classify) picks it.\n const maxScore = Math.max(...CATEGORIES.map((c) => scores[c]));\n if (maxScore === 0) scores[writes > 0 ? \"feature\" : \"chore\"] += 0.01;\n return scores;\n}\n\n/** Files matching a pattern is recomputed from the stored file list (kept on the signal object). */\nfunction countMatch(s: Signals & { _files?: string[] }, re: RegExp): number {\n const files = s._files ?? [];\n let n = 0;\n for (const f of files) if (re.test(f)) n++;\n return n;\n}\n\nfunction complexity(s: Signals): { score: number; subscores: Record<string, number>; weights: Record<string, number> } {\n // v2 ceilings: tuned to the real p90 of substantial main sessions (churn ~2.6k, files ~29,\n // work-tokens in the tens of millions, multi-hour durations) so a median session sits mid-range\n // instead of pegging every subscore at 1.0. See ADR-004.\n const weights = { loc: 0.25, files: 0.15, turns: 0.2, tokens: 0.2, duration: 0.1, subagents: 0.1 };\n const subscores = {\n loc: clamp01(Math.log1p(s.loc.churn) / Math.log1p(6000)),\n files: clamp01(s.loc.files / 40),\n turns: clamp01(s.turn_count / 40),\n tokens: clamp01(Math.log1p(s.work_tokens) / Math.log1p(40_000_000)),\n duration: clamp01(s.duration_ms / (600 * 60_000)),\n subagents: clamp01(s.subagent_count / 10),\n };\n let acc = 0;\n for (const k of Object.keys(weights) as Array<keyof typeof weights>) acc += weights[k] * subscores[k];\n return { score: Math.round(acc * 1000) / 10, subscores, weights };\n}\n\ninterface SessionAgg {\n id: string;\n is_sidechain: number;\n turn_count: number;\n event_count: number;\n duration_ms: number | null;\n}\n\n/**\n * (Re)classify every session into the `classifications` table. Returns the row count written.\n * One transaction; deterministic; safe to run repeatedly (ON CONFLICT upsert).\n */\nexport function classify(db: DB, dirty?: Set<string> | null): { count: number; version: number } {\n // Incremental: classify only the sessions touched this run (the expanded set rebuildDerived returns).\n // Every signal bulk-load is scoped to the same set so memory and work track the delta, not the whole\n // DB (ADR-010). `null`/undefined → classify all sessions (the `--full` / standalone metrics path).\n const incremental = dirty != null;\n if (incremental) {\n db.exec(\"DROP TABLE IF EXISTS _dirty\");\n db.exec(\"CREATE TEMP TABLE _dirty (id TEXT PRIMARY KEY)\");\n const ins = db.prepare(\"INSERT OR IGNORE INTO _dirty (id) VALUES (?)\");\n db.transaction((ids: Iterable<string>) => {\n for (const id of ids) ins.run(id);\n })(dirty);\n }\n const wSess = incremental ? \" WHERE session_id IN (SELECT id FROM _dirty)\" : \"\";\n const aSess = incremental ? \" AND session_id IN (SELECT id FROM _dirty)\" : \"\";\n\n const sessions = db\n .prepare(`SELECT id, is_sidechain, turn_count, event_count, duration_ms FROM sessions${incremental ? \" WHERE id IN (SELECT id FROM _dirty)\" : \"\"}`)\n .all() as SessionAgg[];\n\n // Bulk-load per-session signals once, then assemble in JS (scales to many sessions).\n const toolMix = new Map<string, Record<string, number>>();\n for (const r of db.prepare(`SELECT session_id, tool_name, COUNT(*) c FROM tool_calls${wSess} GROUP BY session_id, tool_name`).all() as any[]) {\n const m = toolMix.get(r.session_id) ?? {};\n m[r.tool_name] = r.c;\n toolMix.set(r.session_id, m);\n }\n const skillMix = new Map<string, Record<string, number>>();\n for (const r of db.prepare(`SELECT session_id, skill_name, COUNT(*) c FROM tool_calls WHERE skill_name IS NOT NULL${aSess} GROUP BY session_id, skill_name`).all() as any[]) {\n const m = skillMix.get(r.session_id) ?? {};\n m[r.skill_name] = r.c;\n skillMix.set(r.session_id, m);\n }\n const tokens = new Map<string, { work: number; cacheRead: number }>();\n for (const r of db\n .prepare(\n `SELECT session_id, SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage${wSess} GROUP BY session_id`,\n )\n .all() as any[]) {\n tokens.set(r.session_id, { work: (r.i ?? 0) + (r.o ?? 0) + (r.cw ?? 0), cacheRead: r.cr ?? 0 });\n }\n // LoC + files from Edit/Write inputs.\n const loc = new Map<string, { added: number; removed: number; files: Set<string> }>();\n for (const r of db.prepare(`SELECT session_id, tool_name, input_json FROM tool_calls WHERE tool_name IN ('Edit','Write')${aSess}`).all() as any[]) {\n const d = locDelta(r.tool_name, r.input_json);\n const acc = loc.get(r.session_id) ?? { added: 0, removed: 0, files: new Set<string>() };\n acc.added += d.added;\n acc.removed += d.removed;\n if (d.file) acc.files.add(d.file);\n loc.set(r.session_id, acc);\n }\n // Prompt text for keyword signals.\n const promptText = new Map<string, string>();\n for (const r of db.prepare(`SELECT session_id, prompt_preview FROM turns WHERE prompt_preview IS NOT NULL${aSess}`).all() as any[]) {\n promptText.set(r.session_id, (promptText.get(r.session_id) ?? \"\") + \" \" + r.prompt_preview);\n }\n // Subagent role from the spawning Task/Agent tool_call (schema-v3 linkage). Lets us categorize a\n // sidechain session by what it was spawned to do, rather than keyword-matching its transcript.\n const subagentRole = new Map<string, string>();\n for (const r of db\n .prepare(\n `SELECT s.id id, tc.agent_type role FROM sessions s JOIN tool_calls tc ON tc.spawned_session_id = s.id WHERE s.is_sidechain = 1 AND tc.agent_type IS NOT NULL${incremental ? \" AND s.id IN (SELECT id FROM _dirty)\" : \"\"}`,\n )\n .all() as any[]) {\n if (!subagentRole.has(r.id)) subagentRole.set(r.id, r.role);\n }\n\n const upsert = db.prepare(\n `INSERT INTO classifications (scope, target_id, category, complexity_score, complexity_band, signals_json, classifier_version)\n VALUES ('session', @target_id, @category, @complexity_score, @complexity_band, @signals_json, @classifier_version)\n ON CONFLICT(scope, target_id) DO UPDATE SET\n category=excluded.category, complexity_score=excluded.complexity_score,\n complexity_band=excluded.complexity_band, signals_json=excluded.signals_json,\n classifier_version=excluded.classifier_version`,\n );\n\n const tx = db.transaction(() => {\n for (const sess of sessions) {\n const toolCounts = toolMix.get(sess.id) ?? {};\n const skills = skillMix.get(sess.id) ?? {};\n const l = loc.get(sess.id) ?? { added: 0, removed: 0, files: new Set<string>() };\n const tok = tokens.get(sess.id) ?? { work: 0, cacheRead: 0 };\n const filesArr = [...l.files].sort();\n const subagentCount = (toolCounts[\"Agent\"] ?? 0) + (toolCounts[\"Task\"] ?? 0);\n\n const signals: Signals & { _files: string[] } = {\n toolCounts,\n skills,\n loc: { added: l.added, removed: l.removed, net: l.added - l.removed, churn: l.added + l.removed, files: l.files.size },\n turn_count: sess.turn_count,\n event_count: sess.event_count,\n work_tokens: tok.work,\n cache_read_tokens: tok.cacheRead,\n duration_ms: sess.duration_ms ?? 0,\n subagent_count: subagentCount,\n is_sidechain: sess.is_sidechain,\n _files: filesArr,\n };\n\n const catScores = scoreCategories(signals, promptText.get(sess.id) ?? \"\");\n let category: Category = \"chore\";\n let bestScore = -1;\n for (const c of CATEGORIES) {\n if (catScores[c] > bestScore) {\n bestScore = catScores[c];\n category = c;\n }\n }\n // For subagents, prefer the spawner's role (Explore/Plan/… ⇒ review) over keyword heuristics.\n const role = subagentRole.get(sess.id);\n if (sess.is_sidechain) {\n const roleCat = categoryForSubagentRole(role);\n if (roleCat) category = roleCat;\n }\n const cx = complexity(signals);\n\n // Deterministic, explainable signal blob (stable key order; _files dropped, exposed as loc.files).\n const signalsJson = JSON.stringify({\n tool_counts: toolCounts,\n skills,\n loc: signals.loc,\n files: filesArr,\n turn_count: signals.turn_count,\n event_count: signals.event_count,\n work_tokens: signals.work_tokens,\n cache_read_tokens: signals.cache_read_tokens,\n duration_ms: signals.duration_ms,\n subagent_count: signals.subagent_count,\n is_sidechain: signals.is_sidechain,\n subagent_role: role ?? null,\n category_scores: catScores,\n complexity_subscores: cx.subscores,\n complexity_weights: cx.weights,\n classifier_version: CLASSIFIER_VERSION,\n });\n\n upsert.run({\n target_id: sess.id,\n category,\n complexity_score: cx.score,\n complexity_band: band(cx.score),\n signals_json: signalsJson,\n classifier_version: CLASSIFIER_VERSION,\n });\n }\n });\n tx();\n\n if (incremental) db.exec(\"DROP TABLE IF EXISTS _dirty\");\n return { count: sessions.length, version: CLASSIFIER_VERSION };\n}\n","/**\n * Agent Lens — security findings (ADR-017). No AI: risky operations are flagged deterministically\n * by a rule engine over signals already in the DB (each tool call's verbatim `input_json` /\n * `result_summary` / `status`, plus the session's project path). Re-runnable and idempotent — the\n * same DB always yields identical rows. Every matched pattern and context modifier is recorded in\n * `signals_json` so a finding can be explained and rules retuned without re-deriving meaning.\n *\n * agent-lens is a *retrospective* analyzer, so this is forensic awareness — surfacing that an agent\n * *did* something risky after the fact — not runtime prevention. Findings are 0..N per tool call, so\n * an incremental re-run DELETEs the touched sessions' rows then re-INSERTs (delete-then-insert),\n * unlike the 1-per-session `classifications` upsert. `detector_version` lets a future (e.g. local-LLM)\n * engine supersede these rows.\n */\nimport { createHash } from \"node:crypto\";\nimport { bumpSeverity, type Severity, type SecurityCategoryKey } from \"@agent-lens/core\";\nimport type { DB } from \"./db.js\";\n\n// Bump on any rule/severity change so a re-run is attributable to an engine version (mirrors\n// CLASSIFIER_VERSION). Recorded on every row + in signals_json. v2: allowlist agent-owned paths so\n// the agent writing to its own config/work dir (e.g. a plan file under ~/.claude/plans) or a temp dir\n// is not flagged as an out-of-project write. v3: exclude .env.example / config templates from\n// credential-access, require a real content-read verb (ls/file/stat no longer flag), raise `sudo` to\n// high. v4: fix template exclusion when the path is followed by shell text (.env.example | …); score\n// secret reads per pipeline segment so `file/ls … | grep` no longer counts as a content read; derive\n// the agent-owned config roots from the configured sources' `config_dir` (covers ~/.claude-isf and\n// any relocated install) instead of a hardcoded `.claude` pattern. v5: match command-pattern rules\n// against an \"executed view\" of the command (codeOf — comments stripped, echo/printf output\n// neutralized) so a dangerous token that is only printed, commented, quoted (`node -e '… sudo …'`,\n// `grep \"sudo\"`), or inside a heredoc body no longer flags; scope `sudo` to command position (ignores\n// `apt install sudo`); add privilege.exec_generated_script for the write-a-script-then-run-it pattern\n// that makes echoed/heredoc'd text live. v6: exfil.network_upload scores curl/wget uploads by\n// destination scope instead of always-high — external host = high (critical with a file), private/\n// internal host (RFC1918 / link-local / .local / bare service name) = low, loopback = info; a real file\n// (@file / -T / --upload-file) bumps the internal/loopback tiers one step. The host is classified on the\n// verbatim command (commandBare blanks quoted URLs, hiding the host) with -H/--header args stripped so a\n// URL in an Origin/Referer header can't pose as the target; and the upload-flag match is now case-\n// SENSITIVE so curl's -D/-f/-t (dump-header/fail/…) no longer read as -d/-F/-T uploads. v6 also re-tiers\n// several routine ops out of the crowded medium bucket: write_outside_project (non-system) and\n// git_reset_hard / non-protected git_force_push drop to low, and overwrite_critical splits — lockfile\n// churn is low while a CI-config overwrite stays medium (poisoned pipeline is a supply-chain risk);\n// system-path writes and protected-branch force-push keep their high. And curl_pipe_shell no longer\n// flags `curl … | node -e`/`python -c`/`python -m` (the interpreter runs inline code and the piped\n// output is just data, e.g. parsing an API response) — only a shell or a bare node/python that executes\n// the downloaded body still flags. Finding ids are independent of this version, so a bump doesn't\n// invalidate user triage state. v7 cuts three destructive false positives: sql_drop now requires a real\n// database-client invocation (psql/mysql/sqlite3/…) for the Bash path, so a DROP/TRUNCATE keyword that\n// only appears as text — a `grep \"…\\|truncate\"` search pattern, an echoed string — no longer flags (the\n// structured input.query path is unchanged). rm_rf no longer treats `git rm` (a tracked-file removal\n// recoverable from history) as a filesystem `rm -rf`, and matches its recursive/force flags as\n// whitespace-bounded letter clusters so a path token like `reports/…-software-engineer…` is never\n// mistaken for `-fr` flags. And an rm -rf whose targets are all under a temp dir (/tmp, /var/folders)\n// drops from high to low — temp dirs are agent-owned scratch (writes there aren't flagged at all), so a\n// cleanup delete is routine; a home/root/glob target still escalates to critical.\nexport const DETECTOR_VERSION = 7;\n\n// Severity model + category taxonomy are shared from core (packages/core/src/security.ts) so the\n// server and web agree on ordering and keys. Re-exported for tests that drive this module directly.\nexport { bumpSeverity, type Severity };\ntype Category = SecurityCategoryKey;\n\n/** Truncate an evidence snippet so we never dump wholesale secrets/output into the DB. */\nfunction evidence(s: string, max = 200): string {\n const one = s.replace(/\\s+/g, \" \").trim();\n return one.length > max ? one.slice(0, max - 1) + \"…\" : one;\n}\n\n/** Deterministic finding id — stable across runs so re-detection reproduces identical rows. */\nfunction findingId(toolCallId: string, ruleId: string): string {\n return createHash(\"sha1\").update(toolCallId).update(\"\\0\").update(ruleId).digest(\"hex\").slice(0, 16);\n}\n\nexport interface RuleContext {\n toolName: string;\n command: string | null; // Bash command string, verbatim (for evidence + value scanning)\n commandCode: string | null; // executed view, quotes INTACT (comments/heredoc/echo-output neutralized) — for sql_drop\n commandBare: string | null; // commandCode + quoted-string contents blanked — for command-verb rules (sudo, rm, …)\n filePath: string | null; // Read/Write/Edit file_path, if any\n input: any; // parsed input_json (may be null)\n resultSummary: string | null;\n status: string | null; // success | error | ...\n projectPath: string | null; // the session's cwd, for outside-project checks\n ownedConfigDirs: string[]; // agent config roots (from the `sources` table) the agent legitimately owns\n}\n\nexport interface RuleMatch {\n evidence: string; // the offending snippet\n severity?: Severity; // override the rule's baseSeverity (context-driven escalation)\n mods?: Record<string, unknown>; // extra explainability recorded in signals_json\n}\n\ninterface Rule {\n id: string;\n category: Category;\n framework_ref: string;\n title: string;\n tools: string[] | \"*\"; // tool_names this rule applies to\n baseSeverity: Severity;\n test(ctx: RuleContext): RuleMatch | null;\n}\n\n// ---- Shared matchers ------------------------------------------------------\n\n// A path we consider outside the agent's working project (home dotfiles, system dirs, root).\nconst SYSTEM_PATH = /(^|\\s)(\\/etc\\/|\\/usr\\/|\\/bin\\/|\\/sbin\\/|\\/boot\\/|\\/var\\/|\\/root\\/|~\\/\\.ssh\\/|~\\/\\.aws\\/)/;\nconst HOME_OR_ROOT_TARGET = /(\\s|^)(~\\/?|\\/|\\/\\*|\\$HOME\\/?|\\*)(\\s|$)/;\n\n/** Secret-bearing file locations (matched against a file path or a shell command). */\nconst SECRET_FILE =\n /(^|\\/|\\s)(\\.env(\\.[\\w.-]+)?|id_rsa|id_ed25519|id_ecdsa|\\.ssh\\/[\\w.-]+|\\.aws\\/credentials|\\.gcp\\/|gcloud\\/|\\.npmrc|\\.pypirc|\\.netrc|\\.pem|\\.p12|\\.pfx|secrets?\\.(ya?ml|json|txt)|credentials(\\.json)?)\\b/i;\n\n// Non-secret config *templates* (e.g. .env.example, secrets.sample.yaml) — reading these is expected\n// and carries no real secret, so exclude them from credential-access even though they match SECRET_FILE.\n// The trailing (?![\\w-]) matches the end of the \".example\" token whether the token ends the string\n// (a Read filePath) or is followed by more shell text (`… .env.example | head`) — a plain `$` anchor\n// only handled the former and let the Bash-command case slip through.\nconst EXAMPLE_FILE = /\\.(example|sample|template|dist)(?![\\w-])/i;\n\n// Commands that read file *contents* — the actual credential-access signal. Listing/stat commands\n// (ls, file, stat, find, …) are deliberately absent: naming a secret path in an `ls`/`file` doesn't\n// read it, so they must not flag (only a content read does).\nconst CONTENT_READ_CMD = /\\b(cat|less|more|head|tail|bat|xxd|strings|base64|grep|egrep|rg|awk|sed|nl|tac|od|hexdump|cp|scp|sort)\\b/i;\n\n/** Secret-looking values (in a command or a captured result). */\nconst SECRET_VALUE_PATTERNS: Array<{ re: RegExp; label: string; critical?: boolean }> = [\n { re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/, label: \"private-key\", critical: true },\n { re: /\\bAKIA[0-9A-Z]{16}\\b/, label: \"aws-access-key-id\" },\n { re: /\\bASIA[0-9A-Z]{16}\\b/, label: \"aws-temp-key-id\" },\n { re: /\\bgh[pousr]_[A-Za-z0-9]{30,}\\b/, label: \"github-token\" },\n { re: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/, label: \"slack-token\" },\n { re: /\\bsk-[A-Za-z0-9]{20,}\\b/, label: \"api-secret-key\" },\n];\n\nconst isBash = (t: string) => t === \"Bash\";\n\n// SQL keywords that irreversibly drop/empty a database object.\nconst SQL_DESTRUCTIVE = /\\b(DROP\\s+(TABLE|DATABASE|SCHEMA)|TRUNCATE(\\s+TABLE)?)\\b/i;\n// Database-client command words. A DROP/TRUNCATE in a *Bash* command is only executed SQL when one of\n// these runs it; without a client the keyword is inert text (a `grep \"…\\|truncate\"` search pattern, an\n// echoed help string, a log line), so it must not flag. The structured input.query path needs no client.\nconst SQL_CLIENT = /\\b(psql|pgcli|mysql|mysqladmin|mariadb|sqlite3?|sqlcmd|usql|mongo|mongosh|clickhouse-client|cockroach|snowsql|bq)\\b/i;\n\n// ---- Command sanitization ------------------------------------------------\n// Command-pattern rules (\"did the agent run a dangerous command?\") must match the *executed* code,\n// not text that is merely printed or commented. `codeOf()` produces that executed view:\n// • strips shell comments (`# …` never runs), and\n// • neutralizes the arguments of the print builtins echo/printf — their quoted/plain text is data\n// sent to stdout, so `sudo`/`rm -rf`/… inside it is inert. Quoted strings are consumed as whole\n// units, so separators (`;` `|`) inside a printed string never leak out as fake command breaks.\n// NOTE: this intentionally does NOT blank all quoted strings — `psql -c \"DROP …\"`, `sh -c \"…\"` carry\n// executed code inside quotes, which must still be detected. Value-scanning rules (secret_in_data)\n// keep using the raw command, since a secret value is exposed whether or not it was \"executed\".\nconst stripComments = (c: string): string => c.replace(/(^|\\s)#[^\\n]*/g, \"$1\");\nconst neutralizeEcho = (c: string): string =>\n c.replace(/((?:^|[;&|\\n(])\\s*)(?:echo|printf)\\b(?:\"(?:[^\"\\\\]|\\\\.)*\"|'[^']*'|[^;&|\\n])*/gi, \"$1echo\");\n// A heredoc body (`cmd <<EOF … EOF`) is data written to a file/stdin, not commands the shell runs, so a\n// `sudo`/`rm -rf` line inside it is inert (unless the resulting file is then executed — see\n// execGeneratedScript). Replace the whole heredoc, body included, with a placeholder.\nconst blankHeredoc = (c: string): string => c.replace(/<<-?\\s*(['\"]?)(\\w+)\\1[\\s\\S]*?\\n\\s*\\2\\b/g, \"<<HEREDOC\");\n// `codeOf`: quotes INTACT (so `psql -c \"DROP …\"` — executed code inside quotes — still matches), with\n// comments, heredoc bodies, and echo/printf output neutralized.\nconst codeOf = (c: string): string => neutralizeEcho(stripComments(blankHeredoc(c)));\n// Blank the CONTENTS of quoted string literals. A dangerous token inside a quoted *argument*\n// (`node -e '… sudo …'`, `grep \"sudo\"`, `git commit -m \"… rm -rf …\"`) is data passed to a program,\n// not a shell command word — so the command-verb rules must not match it. (Tradeoff: a command truly\n// executed via `sh -c \"sudo …\"` is missed; that inline form is rare and worth the far fewer false\n// positives.) codeOf runs first so in-string separators are already gone from the parts we keep.\nconst blankQuoted = (c: string): string => c.replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"').replace(/'[^']*'/g, \"''\");\nconst bareOf = (c: string): string => blankQuoted(codeOf(c));\n\n// A file is \"executed\" when passed to an interpreter (sh/bash/…), sourced (`source`/`.`), or invoked\n// as a path (`./f`, `/abs/f`). Detects the write-a-script-then-run-it pattern (`echo … > f.sh; sh f.sh`)\n// that turns otherwise-inert printed text into live code. Returns the offending file, or null.\nfunction execGeneratedScript(cmd: string): string | null {\n const targets = new Set<string>();\n for (const m of cmd.matchAll(/(?:>>?|\\btee\\b\\s+(?:-a\\s+)?)\\s*[\"']?([A-Za-z0-9._/~-]+)/g)) {\n if (m[1] && !m[1].startsWith(\"/dev/\")) targets.add(m[1]);\n }\n for (const t of targets) {\n const e = t.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n // Interpreter/source invocation: `sh FILE`, `bash FILE`, `source FILE`, `. FILE` (the interpreter is\n // the command word, so FILE may sit anywhere after it).\n const interp = new RegExp(`\\\\b(?:sh|bash|zsh|ksh|dash|source)\\\\s+[\"']?${e}\\\\b|(?:^|[\\\\n;|&(])\\\\s*\\\\.\\\\s+[\"']?${e}\\\\b`);\n // FILE run directly — must be at COMMAND POSITION (start or after a separator), NOT an argument slot,\n // otherwise the redirect/`tee FILE` target matches its own occurrence (`… | tee /etc/hosts`).\n const hasPath = /^(\\.\\/|\\/|~\\/)/.test(t);\n const path = hasPath\n ? new RegExp(`(?:^|[\\\\n;|&(])\\\\s*${e}(?:\\\\s|$|[;&|<>])`)\n : new RegExp(`(?:^|[\\\\n;|&(])\\\\s*\\\\.\\\\/${e}\\\\b`);\n if (interp.test(cmd) || path.test(cmd)) return t;\n }\n return null;\n}\n\n// Temp dirs (including the per-session scratchpad under /tmp/…) are always agent-owned regardless of\n// which agent/config produced them.\nconst TEMP_PATH = /^(\\/tmp\\/|\\/var\\/folders\\/|\\/private\\/var\\/folders\\/)/;\n\n/**\n * Paths the agent legitimately owns, so writing there is expected, not a finding: its own config/work\n * dir (e.g. plans, memory, todos, projects, settings, skills) and temp dirs. The config roots are the\n * `config_dir`s of the configured sources (the `sources` table, seeded from the project config file),\n * NOT a hardcoded `.claude` pattern — that way side-by-side installs (~/.claude, ~/.claude-isf, …) and\n * any relocated config dir are covered from a single source of truth. Neutralizes the biggest\n * write_outside_project false-positive source.\n */\nexport function isAgentOwnedPath(p: string, configDirs: string[]): boolean {\n if (TEMP_PATH.test(p)) return true;\n if (configDirs.some((d) => p === d || p.startsWith(d + \"/\"))) return true;\n const tmp = process.env.TMPDIR;\n return !!tmp && p.startsWith(tmp.replace(/\\/$/, \"\") + \"/\");\n}\n\n/** Does an absolute file path fall outside the session's project directory? (null-safe.) */\nfunction outsideProject(filePath: string | null, projectPath: string | null, configDirs: string[]): boolean {\n if (!filePath || !filePath.startsWith(\"/\")) return false; // relative paths are project-local\n if (isAgentOwnedPath(filePath, configDirs)) return false; // agent's own config/work dir + temp → expected\n if (!projectPath) return false; // unknown project → don't guess\n return filePath !== projectPath && !filePath.startsWith(projectPath.replace(/\\/$/, \"\") + \"/\");\n}\n\n// Classify the destination host(s) of a curl/wget command for exfil scoring. Runs on the VERBATIM\n// command (not commandBare) so quoted URLs are visible, and strips -H/--header args so a URL sitting\n// in an Origin/Referer header is never mistaken for the request target. Returns the most-severe scope\n// present: external > internal > loopback; \"unknown\" when no host is recognizable (e.g. the target is\n// a shell variable) — treated as external-ish so we don't under-report an unprovable destination.\ntype NetScope = \"external\" | \"internal\" | \"loopback\" | \"unknown\";\nconst SCOPE_RANK: Record<NetScope, number> = { unknown: 0, loopback: 1, internal: 2, external: 3 };\nfunction netTargetScope(rawCommand: string): NetScope {\n const s = rawCommand\n .replace(/(?:-H|--header)\\s+(['\"]).*?\\1/gi, \" \") // quoted header value (Origin/Referer/…)\n .replace(/(?:-H|--header)\\s+[^\\s'\"]+/gi, \" \"); // bare header token\n const authorities: string[] = [];\n // scheme URLs: http(s)://host (host may be a bracketed IPv6 literal)\n for (const m of s.matchAll(/\\bhttps?:\\/\\/(\\[[0-9a-fA-F:]+\\]|[^/\\s\"'<>|]+)/gi)) authorities.push(m[1]);\n // bare host:port targets (curl localhost:4477, curl 10.0.0.5:8080) — require a numeric port so we\n // never mistake a file path or a `key:value` token for a network destination.\n for (const m of s.matchAll(/(?:^|[\\s\"'=(])(\\[[0-9a-fA-F:]+\\]|[A-Za-z0-9.-]+):\\d{2,5}(?=[/\\s\"']|$)/g)) authorities.push(m[1]);\n let scope: NetScope = \"unknown\";\n for (let host of authorities) {\n host = host.replace(/^\\[|\\]$/g, \"\").replace(/^[^@/]*@/, \"\").toLowerCase(); // drop brackets + userinfo\n if (!host.includes(\"::\")) host = host.replace(/:\\d+$/, \"\"); // drop :port (but not the ::1 in IPv6)\n let hs: NetScope;\n if (host === \"localhost\" || /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host) || host === \"0.0.0.0\" || host === \"::1\")\n hs = \"loopback\";\n else if (\n /^10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host) || // RFC1918\n /^192\\.168\\.\\d{1,3}\\.\\d{1,3}$/.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}$/.test(host) ||\n /^169\\.254\\.\\d{1,3}\\.\\d{1,3}$/.test(host) || // link-local\n host.endsWith(\".local\") ||\n host.endsWith(\".internal\") ||\n !host.includes(\".\") // single-label host (a service name, not a public domain)\n )\n hs = \"internal\";\n else hs = \"external\";\n if (SCOPE_RANK[hs] > SCOPE_RANK[scope]) scope = hs;\n }\n return scope;\n}\n\n// ---- The rule set (v1) ----------------------------------------------------\n// Representative, not exhaustive; grouped by framework-anchored category. New rules just append here.\n\nconst RULES: Rule[] = [\n // --- Destructive / data-loss (OWASP ASI02 Tool Misuse) -------------------\n {\n id: \"destructive.rm_rf\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Recursive force delete (rm -rf)\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n if (!ctx.commandBare) return null;\n // `git rm` (even `git rm -rf`) removes *tracked* files and is recoverable from git history — it's\n // not a filesystem `rm -rf`. Neutralize it (glued to `git` so the `rm` word boundary is gone) so it\n // never matches, while a real `rm -rf` elsewhere in the same command still does.\n const c = ctx.commandBare.replace(/\\bgit\\s+rm\\b/gi, \"gitrm\");\n // Isolate the `rm` command's own segment (up to the next |;&\\n) so flags/targets from a later\n // command don't leak in.\n const rmSeg = c.match(/\\brm\\b[^|;&\\n]*/i)?.[0];\n if (!rmSeg) return null;\n // rm needs both a recursive and a force flag. Flag tokens are matched as whitespace-bounded\n // letter-only clusters (`-rf`, `-fr`, `-r -f`, `--recursive --force`) so a path token that merely\n // contains an f before an r — e.g. `reports/…-software-engineer…` — isn't mistaken for `-fr`.\n const combined = /(?:^|\\s)-(?=[a-zA-Z]*[rR])(?=[a-zA-Z]*f)[a-zA-Z]+(?=\\s|$)/.test(rmSeg);\n const recursive = /(?:^|\\s)(?:-[a-zA-Z]*[rR][a-zA-Z]*|--recursive)(?=\\s|$)/.test(rmSeg);\n const force = /(?:^|\\s)(?:-[a-zA-Z]*f[a-zA-Z]*|--force)(?=\\s|$)/.test(rmSeg);\n if (!combined && !(recursive && force)) return null;\n const dangerousTarget = HOME_OR_ROOT_TARGET.test(c) || /\\s\\/(?:\\s|$)/.test(c) || /\\brm\\b[^|;&\\n]*\\*/.test(c);\n // Every non-flag target under a temp dir → routine scratch cleanup, demote to low.\n const targets = rmSeg.split(/\\s+/).slice(1).filter((a) => a && !a.startsWith(\"-\"));\n const tempOnly = targets.length > 0 && targets.every((t) => TEMP_PATH.test(t));\n const severity: Severity = dangerousTarget ? \"critical\" : tempOnly ? \"low\" : \"high\";\n return { evidence: evidence(ctx.command!), severity, mods: { dangerous_target: dangerousTarget, temp_target: tempOnly } };\n },\n },\n {\n id: \"destructive.git_reset_hard\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Discards local changes (git reset --hard)\",\n tools: [\"Bash\"],\n baseSeverity: \"low\", // routine dev op — only uncommitted working-tree changes are lost; commits survive in reflog\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/\\bgit\\s+reset\\s+--hard\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"destructive.git_force_push\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Force-push rewrites remote history\",\n tools: [\"Bash\"],\n baseSeverity: \"low\", // feature-branch force-push is routine; only a protected branch (below) is high\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/\\bgit\\s+push\\b[^|;&\\n]*(--force\\b|--force-with-lease\\b|\\s-f\\b)/i.test(c)) return null;\n const mainBranch = /\\b(main|master|release)\\b/.test(c);\n return { evidence: evidence(ctx.command!), severity: mainBranch ? \"high\" : \"low\", mods: { protected_branch: mainBranch } };\n },\n },\n {\n id: \"destructive.sql_drop\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Drops or truncates a database object\",\n tools: \"*\",\n baseSeverity: \"high\",\n test(ctx) {\n const query = typeof ctx.input?.query === \"string\" ? ctx.input.query : null;\n // A structured DB tool call (input.query) is SQL by definition — flag on the keyword alone.\n if (query && SQL_DESTRUCTIVE.test(query)) return { evidence: evidence(ctx.command ?? query) };\n // A Bash command only executes SQL when a database client runs it; otherwise DROP/TRUNCATE is just\n // text (e.g. a `grep \"…\\|truncate\"` search pattern) and inert, so require both a client and the keyword.\n const code = ctx.commandCode;\n if (code && SQL_CLIENT.test(code) && SQL_DESTRUCTIVE.test(code)) return { evidence: evidence(ctx.command!) };\n return null;\n },\n },\n {\n id: \"destructive.disk_wipe\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Low-level disk write / wipe (dd, mkfs, shred)\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/(\\bdd\\s+if=|\\bmkfs(\\.\\w+)?\\b|\\bshred\\b)/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"destructive.overwrite_critical\",\n category: \"destructive\",\n framework_ref: \"OWASP ASI02\",\n title: \"Overwrites a critical project file\",\n tools: [\"Write\", \"Edit\", \"MultiEdit\"],\n baseSeverity: \"low\",\n test(ctx) {\n const f = ctx.filePath;\n if (!f) return null;\n const lock = /(^|\\/)(package-lock\\.json|pnpm-lock\\.ya?ml|yarn\\.lock|Cargo\\.lock|go\\.sum)\\b/.test(f);\n // A CI-pipeline file is more sensitive than a lockfile: modifying it can run arbitrary code or\n // exfiltrate secrets in CI (a supply-chain risk), so it stays medium while lockfile churn is low.\n const ci = /(^|\\/)(\\.github\\/workflows\\/|\\.gitlab-ci\\.ya?ml)\\b/.test(f);\n if (!lock && !ci) return null;\n return { evidence: evidence(f), severity: ci ? \"medium\" : \"low\", mods: { kind: ci ? \"ci-config\" : \"lockfile\" } };\n },\n },\n\n // --- Credential & secret access ------------------------------------------\n {\n id: \"credential.secret_file_access\",\n category: \"credential-access\",\n framework_ref: \"MITRE ATLAS: Credential Access\",\n title: \"Reads a secret/credential file\",\n tools: [\"Read\", \"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n if (isBash(ctx.toolName)) {\n const cmd = ctx.commandBare; // executed view: a secret named only in a comment / echo / quoted arg isn't read\n if (!cmd) return null;\n // Evaluate each pipeline/command segment independently: a secret file is only *read* when a\n // content-read command (cat/grep/…) and the secret path occur in the SAME segment. A bare\n // `ls`/`file`/`stat` that merely names the path lists metadata; piping its OUTPUT into a\n // `grep`/`head` in a later segment (e.g. `file ~/.ssh/id_* | grep -v .pub`) exposes no secret\n // content — the read command never touches the file. Splitting first avoids matching the\n // read command against the wrong segment.\n const segments = cmd.split(/\\|\\||&&|[|;\\n]/);\n const reads = segments.some(\n (seg) => SECRET_FILE.test(seg) && !EXAMPLE_FILE.test(seg) && CONTENT_READ_CMD.test(seg),\n );\n return reads ? { evidence: evidence(ctx.command!) } : null;\n }\n // Read tool: the filePath itself is the target (no command to segment).\n const f = ctx.filePath;\n if (!f || !SECRET_FILE.test(f) || EXAMPLE_FILE.test(f)) return null; // skip .env.example & templates\n return { evidence: evidence(f) };\n },\n },\n {\n id: \"credential.secret_in_data\",\n category: \"credential-access\",\n framework_ref: \"MITRE ATLAS: Credential Access\",\n title: \"Secret-looking value in command or output\",\n tools: \"*\",\n baseSeverity: \"high\",\n test(ctx) {\n const hay = [ctx.command, ctx.resultSummary].filter(Boolean).join(\"\\n\");\n if (!hay) return null;\n for (const p of SECRET_VALUE_PATTERNS) {\n const m = p.re.exec(hay);\n if (m) return { evidence: `${p.label}: ${evidence(m[0], 40)}`, severity: p.critical ? \"critical\" : \"high\", mods: { secret_kind: p.label } };\n }\n return null;\n },\n },\n\n // --- Data exfiltration (MITRE ATLAS AML.T0086) ---------------------------\n {\n id: \"exfil.pipe_to_network\",\n category: \"exfiltration\",\n framework_ref: \"MITRE ATLAS AML.T0086\",\n title: \"Pipes local data into a network command\",\n tools: [\"Bash\"],\n baseSeverity: \"critical\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n // e.g. `cat secrets | curl …`, `tar … | nc host port`, `… | curl -d @-`\n if (!/(\\||<)\\s*(sudo\\s+)?(curl|wget|nc|ncat|netcat|ssh|scp)\\b/i.test(c)) return null;\n if (!/\\b(cat|tar|zip|gzip|base64|env|printenv|cat\\s|<)\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"exfil.network_upload\",\n category: \"exfiltration\",\n framework_ref: \"MITRE ATLAS AML.T0086\",\n title: \"Uploads data to a network host\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n // Upload-shaped curl/wget. NOTE: no /i flag — the short flags here are case-SENSITIVE (curl's -d\n // is data but -D is dump-header, -F is form but -f is --fail, -T is upload but -t is unrelated), so\n // matching case-insensitively turned plain GET smoke tests (`curl -sf`, `curl -D -`) into uploads.\n const upload = /\\b(curl|wget)\\b[^|;&\\n]*(-X\\s*POST|--data\\b|--data-\\w+\\b|\\s-d\\b|-T\\b|--upload-file\\b|-F\\b|--form\\b)/.test(c);\n if (!upload) return null;\n // `@file` / --upload-file / -T means a real file is being sent (matched on commandBare so an `@`\n // inside a quoted arg — e.g. an email in a header value — doesn't count as a file).\n const withFile = /(@[^\\s'\"]+|--upload-file|\\s-T\\b)/.test(c);\n // Score by destination: external host = high (critical with a file); a private/internal host is a\n // lesser concern (low); a loopback call is local IPC / a dev server (info). Sending an actual file\n // bumps the internal/loopback tiers one step. An unrecognizable target (shell var) can't be shown\n // local, so it stays high. Classified on the verbatim command so quoted URLs are visible.\n const scope = netTargetScope(ctx.command!);\n let severity: Severity;\n if (scope === \"external\") severity = withFile ? \"critical\" : \"high\";\n else if (scope === \"unknown\") severity = \"high\";\n else severity = withFile ? bumpSeverity(scope === \"loopback\" ? \"info\" : \"low\", 1) : scope === \"loopback\" ? \"info\" : \"low\";\n return {\n evidence: evidence(ctx.command!),\n severity,\n mods: { target_scope: scope, with_file: withFile },\n };\n },\n },\n {\n id: \"exfil.reverse_shell\",\n category: \"exfiltration\",\n framework_ref: \"MITRE ATLAS AML.T0086\",\n title: \"Netcat / reverse-shell style connection\",\n tools: [\"Bash\"],\n baseSeverity: \"critical\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n if (!/\\b(nc|ncat|netcat)\\b[^|;&\\n]*(-e\\b|-c\\b|\\d{2,5})|\\/dev\\/tcp\\/|bash\\s+-i\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n\n // --- Privilege escalation / guardrail bypass (OWASP LLM06 Excessive Agency)\n {\n id: \"privilege.skip_permissions\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Bypasses the permission guardrail\",\n tools: \"*\",\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/--dangerously-skip-permissions\\b|--yolo\\b|--dangerously-skip\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.curl_pipe_shell\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Executes a remote script (curl | sh)\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n const m = /\\b(?:curl|wget)\\b[^|]*\\|\\s*(?:sudo\\s+)?(sh|bash|zsh|python\\d?|node)\\b([^|;&]*)/i.exec(c);\n if (!m) return null;\n // The danger is the interpreter executing the *downloaded body* piped to its stdin. A shell always\n // does. node/python execute piped stdin as a script only when NOT handed an inline program or\n // module — `curl … | node -e '…'`, `python -c '…'`, `python -m json.tool` just consume the output\n // as DATA (e.g. parsing an API response), which is not remote-script execution and must not flag.\n const interp = m[1].toLowerCase();\n const args = m[2] || \"\";\n if (/^(?:node|python)/.test(interp) && /(?:^|\\s)-(?:e|c|m|p)\\b|(?:^|\\s)--(?:eval|command|module|print)\\b/.test(args))\n return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.sudo\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Runs a command as root (sudo)\",\n tools: [\"Bash\"],\n baseSeverity: \"high\", // root escalation removes a real safety barrier — higher than an in-project op\n test(ctx) {\n const c = ctx.commandBare;\n // sudo must be in *command position* — start of the command or right after a separator (; | && ( ).\n // This ignores `sudo` as an argument (e.g. `apt install sudo`) and, via codeOf, inside echo/comments.\n if (!c || !/(?:^|[\\n;|&(])\\s*sudo\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.exec_generated_script\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Writes a shell script from inline output and executes it\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const raw = ctx.command;\n if (!raw) return null;\n // Echoing a command into a file that is then run/sourced turns otherwise-inert printed text into\n // live code (e.g. `echo \"sudo rm -rf /\" > f.sh; sh f.sh`) — the reason we don't just ignore\n // dangerous tokens inside echo strings. Detect on the comment-stripped raw command.\n const file = execGeneratedScript(stripComments(raw));\n if (!file) return null;\n // Escalate when the generated script itself carries a destructive/privileged payload.\n const critical = /(?:^|[\"'\\s;|&(])sudo\\b|\\brm\\s+[^|;&\\n]*-[a-z]*r[a-z]*f|\\bchmod\\s+0?777\\b|\\bmkfs|\\bdd\\s+if=/i.test(raw);\n return { evidence: evidence(raw), severity: critical ? \"critical\" : \"high\", mods: { script_file: file } };\n },\n },\n {\n id: \"privilege.chmod_777\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Overly permissive permissions (chmod 777)\",\n tools: [\"Bash\"],\n baseSeverity: \"medium\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c || !/\\bchmod\\s+(-[a-zA-Z]+\\s+)*0?777\\b/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.persistence\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Installs a persistence mechanism\",\n tools: [\"Bash\"],\n baseSeverity: \"high\",\n test(ctx) {\n const c = ctx.commandBare;\n if (!c) return null;\n if (!/\\bcrontab\\s+-|\\bsystemctl\\s+enable\\b|\\blaunchctl\\s+load\\b|>>\\s*~\\/\\.(bashrc|zshrc|profile|bash_profile)\\b|\\/etc\\/(cron|systemd|rc\\.local)/i.test(c)) return null;\n return { evidence: evidence(ctx.command!) };\n },\n },\n {\n id: \"privilege.write_outside_project\",\n category: \"privilege-bypass\",\n framework_ref: \"OWASP LLM06\",\n title: \"Writes a file outside the working project\",\n tools: [\"Write\", \"Edit\", \"MultiEdit\"],\n baseSeverity: \"low\", // writing to /tmp, scratch dirs, adjacent repos is routine; only a system path (below) is high\n test(ctx) {\n const f = ctx.filePath;\n if (!outsideProject(f, ctx.projectPath, ctx.ownedConfigDirs)) return null;\n const system = !!f && SYSTEM_PATH.test(f);\n return { evidence: evidence(f!), severity: system ? \"high\" : \"low\", mods: { project: ctx.projectPath, system_path: system } };\n },\n },\n];\n\n/** Rule → tool-name applicability. */\nfunction appliesTo(rule: Rule, toolName: string): boolean {\n return rule.tools === \"*\" || rule.tools.includes(toolName);\n}\n\n/** Parse a tool call's verbatim input_json into a RuleContext (mirrors classify.ts's locDelta parse). */\nfunction buildContext(\n row: { tool_name: string; input_json: string | null; result_summary: string | null; status: string | null },\n projectPath: string | null,\n ownedConfigDirs: string[],\n): RuleContext {\n let input: any = null;\n try {\n input = row.input_json ? JSON.parse(row.input_json) : null;\n } catch {\n input = null;\n }\n const command = typeof input?.command === \"string\" ? input.command : null;\n const commandCode = command != null ? codeOf(command) : null;\n const commandBare = command != null ? bareOf(command) : null;\n const filePath = typeof input?.file_path === \"string\" ? input.file_path : null;\n return { toolName: row.tool_name, command, commandCode, commandBare, filePath, input, resultSummary: row.result_summary, status: row.status, projectPath, ownedConfigDirs };\n}\n\ninterface ToolRow {\n id: string;\n session_id: string;\n event_uuid: string | null;\n turn_id: string | null;\n tool_name: string;\n input_json: string | null;\n result_summary: string | null;\n status: string | null;\n}\n\n/**\n * (Re)scan tool calls into the `findings` table. Returns the total finding count + engine version.\n * Deterministic; safe to run repeatedly. `dirty` (the expanded id set rebuildDerived returns) scopes an\n * incremental run to the touched sessions via delete-then-insert; null/undefined → scan everything.\n */\nexport function detect(db: DB, dirty?: Set<string> | null): { count: number; version: number } {\n const incremental = dirty != null;\n if (incremental) {\n db.exec(\"DROP TABLE IF EXISTS _dirty_sec\");\n db.exec(\"CREATE TEMP TABLE _dirty_sec (id TEXT PRIMARY KEY)\");\n const ins = db.prepare(\"INSERT OR IGNORE INTO _dirty_sec (id) VALUES (?)\");\n db.transaction((ids: Iterable<string>) => {\n for (const id of ids) ins.run(id);\n })(dirty);\n }\n const scope = incremental ? \" WHERE tc.session_id IN (SELECT id FROM _dirty_sec)\" : \"\";\n\n // Agent-owned config roots — the configured sources' `config_dir`s (seeded from the project config\n // file at ingest). Single source of truth for the write_outside_project allowlist, so relocated or\n // side-by-side installs (~/.claude, ~/.claude-isf, …) are covered without a hardcoded path pattern.\n const ownedConfigDirs = (db.prepare(\"SELECT config_dir FROM sources WHERE config_dir IS NOT NULL\").all() as Array<{ config_dir: string }>)\n .map((r) => r.config_dir.replace(/\\/$/, \"\"))\n .filter(Boolean);\n\n // Project path per (scoped) session — for outside-project checks.\n const projPath = new Map<string, string>();\n for (const r of db\n .prepare(\n `SELECT s.id id, p.path path FROM sessions s JOIN projects p ON p.id = s.project_id${\n incremental ? \" WHERE s.id IN (SELECT id FROM _dirty_sec)\" : \"\"\n }`,\n )\n .all() as Array<{ id: string; path: string }>) {\n projPath.set(r.id, r.path);\n }\n\n const rows = db\n .prepare(\n `SELECT tc.id, tc.session_id, tc.event_uuid, tc.turn_id, tc.tool_name, tc.input_json, tc.result_summary, tc.status\n FROM tool_calls tc${scope}`,\n )\n .all() as ToolRow[];\n\n const insert = db.prepare(\n `INSERT INTO findings (id, session_id, tool_call_id, event_uuid, turn_id, rule_id, category, framework_ref, severity, title, evidence, signals_json, detector_version)\n VALUES (@id, @session_id, @tool_call_id, @event_uuid, @turn_id, @rule_id, @category, @framework_ref, @severity, @title, @evidence, @signals_json, @detector_version)\n ON CONFLICT(id) DO NOTHING`,\n );\n const delScope = incremental\n ? \"DELETE FROM findings WHERE session_id IN (SELECT id FROM _dirty_sec)\"\n : \"DELETE FROM findings\";\n\n const tx = db.transaction(() => {\n db.exec(delScope);\n for (const row of rows) {\n const ctx = buildContext(row, projPath.get(row.session_id) ?? null, ownedConfigDirs);\n for (const rule of RULES) {\n if (!appliesTo(rule, ctx.toolName)) continue;\n const m = rule.test(ctx);\n if (!m) continue;\n let severity: Severity = m.severity ?? rule.baseSeverity;\n const mods: Record<string, unknown> = { ...(m.mods ?? {}) };\n // A failed/blocked tool call is an *attempt*, not an accomplished action — de-escalate one band.\n if (row.status === \"error\") {\n severity = bumpSeverity(severity, -1);\n mods.attempted = true;\n }\n const signals = {\n rule: rule.id,\n category: rule.category,\n framework_ref: rule.framework_ref,\n tool_name: ctx.toolName,\n base_severity: rule.baseSeverity,\n severity,\n status: row.status,\n modifiers: mods,\n detector_version: DETECTOR_VERSION,\n };\n insert.run({\n id: findingId(row.id, rule.id),\n session_id: row.session_id,\n tool_call_id: row.id,\n event_uuid: row.event_uuid,\n turn_id: row.turn_id,\n rule_id: rule.id,\n category: rule.category,\n framework_ref: rule.framework_ref,\n severity,\n title: rule.title,\n evidence: m.evidence,\n signals_json: JSON.stringify(signals),\n detector_version: DETECTOR_VERSION,\n });\n }\n }\n });\n tx();\n\n if (incremental) db.exec(\"DROP TABLE IF EXISTS _dirty_sec\");\n const count = (db.prepare(\"SELECT COUNT(*) n FROM findings\").get() as { n: number }).n;\n return { count, version: DETECTOR_VERSION };\n}\n","/**\n * Agent Lens — tool-error classification pass. Stamps `tool_calls.error_type` for every errored tool\n * call (`status='error'`) by running the deterministic core classifier (classifyToolError) over its\n * verbatim `result_summary`. Re-runnable and idempotent — like classify()/detect(), it re-derives from\n * signals already in the DB and yields identical rows every run.\n *\n * Why a stored column (not read-time): the sessions list filters by error type in SQL, and the\n * dashboard/detail aggregate on it — a column keeps one source of truth (the core classifier) while\n * letting SQL do the filtering/grouping. The raw `status='error'` count stays the authoritative signal;\n * `error_type` is the heuristic bucket (see packages/core/src/errors.ts for the authority boundary).\n */\nimport { classifyToolError, ERROR_CLASSIFIER_VERSION } from \"@agent-lens/core\";\nimport type { DB } from \"./db.js\";\n\n/**\n * (Re)classify errored tool calls into `error_type`. `dirty` (the expanded id set rebuildDerived\n * returns) scopes an incremental run to the touched sessions; null/undefined → reclassify everything.\n * Returns the number of rows stamped + the engine version.\n */\nexport function classifyErrors(db: DB, dirty?: Set<string> | null): { count: number; version: number } {\n const incremental = dirty != null;\n const scope = incremental ? \" AND session_id IN (SELECT id FROM _dirty_err)\" : \"\";\n\n if (incremental) {\n db.exec(\"DROP TABLE IF EXISTS _dirty_err\");\n db.exec(\"CREATE TEMP TABLE _dirty_err (id TEXT PRIMARY KEY)\");\n const ins = db.prepare(\"INSERT OR IGNORE INTO _dirty_err (id) VALUES (?)\");\n db.transaction((ids: Iterable<string>) => {\n for (const id of ids) ins.run(id);\n })(dirty);\n }\n\n const rows = db\n .prepare(`SELECT id, result_summary FROM tool_calls WHERE status = 'error'${scope}`)\n .all() as Array<{ id: string; result_summary: string | null }>;\n\n const update = db.prepare(\"UPDATE tool_calls SET error_type = ? WHERE id = ?\");\n const tx = db.transaction(() => {\n // Reset the scoped rows' error_type first so a row that stops being an error (or changes bucket) on\n // re-derive doesn't keep a stale label. Non-error rows always have NULL.\n db.exec(`UPDATE tool_calls SET error_type = NULL WHERE error_type IS NOT NULL${scope}`);\n for (const r of rows) update.run(classifyToolError(r.result_summary).type, r.id);\n });\n tx();\n\n if (incremental) db.exec(\"DROP TABLE IF EXISTS _dirty_err\");\n const count = (db.prepare(\"SELECT COUNT(*) n FROM tool_calls WHERE error_type IS NOT NULL\").get() as { n: number }).n;\n return { count, version: ERROR_CLASSIFIER_VERSION };\n}\n","/**\n * Agent Lens — ingest engine (the DB-writing core of Stage 2), separated from the CLI bootstrap in\n * index.ts so it can be driven in-process by tests against an :memory: DB. index.ts handles argv/env,\n * the disk walk, and the incremental-skip check; everything here operates on an open DB + file content.\n */\nimport { createHash } from \"node:crypto\";\nimport type Database from \"better-sqlite3\";\nimport { packRaw, type SourceAdapter, type SourceFile, type TurnRow } from \"@agent-lens/core\";\nimport { type DB } from \"./db.js\";\n\ntype Stmt = Database.Statement<any[]>;\n\nexport interface IngestStats {\n files: number;\n skipped: number;\n malformed: number;\n newEvents: number;\n}\n\nexport function newStats(): IngestStats {\n return { files: 0, skipped: 0, malformed: 0, newEvents: 0 };\n}\n\nexport function preview(text: string): string {\n return text.replace(/\\s+/g, \" \").trim().slice(0, 140);\n}\n\nconst SKILL_INJECT_PREFIX = \"Base directory for this skill:\";\n\n/** Final name token of a skill id/dir, ignoring path and plugin prefix: \"plugin:foo\" / \".../foo\" → \"foo\". */\nfunction skillNameKey(name: string): string {\n const afterSlash = name.split(\"/\").pop() ?? name;\n const afterColon = afterSlash.split(\":\").pop() ?? afterSlash;\n return afterColon.trim();\n}\n\n/**\n * Parse a skill-body injection event. A Skill firing injects an isMeta user message of the shape\n * `Base directory for this skill: <abs path>/<name>\\n\\n<SKILL.md body>\\n\\nARGUMENTS: <args>`\n * into the transcript — the only place the real skill content appears. We split off the\n * Base-directory line (path differs per install) and the trailing ARGUMENTS block (per-call) so the\n * remaining body is stable across installs/args and can be content-hashed into a version.\n */\nexport function parseSkillInjection(\n text: string,\n): { baseDir: string | null; nameKey: string; body: string; args: string | null } | null {\n if (!text.startsWith(SKILL_INJECT_PREFIX)) return null;\n const firstNl = text.indexOf(\"\\n\");\n const firstLine = (firstNl >= 0 ? text.slice(0, firstNl) : text).slice(SKILL_INJECT_PREFIX.length).trim();\n const baseDir = firstLine || null;\n let body = firstNl >= 0 ? text.slice(firstNl + 1) : \"\";\n // Strip the trailing per-call ARGUMENTS block (use lastIndexOf — the body may mention \"ARGUMENTS\").\n let args: string | null = null;\n const argIdx = body.lastIndexOf(\"\\nARGUMENTS:\");\n if (argIdx >= 0) {\n args = body.slice(argIdx + \"\\nARGUMENTS:\".length).trim() || null;\n body = body.slice(0, argIdx);\n }\n return { baseDir, nameKey: baseDir ? skillNameKey(baseDir) : \"\", body: body.trim(), args };\n}\n\n/** First markdown heading (or first non-empty line) of a body, for list/detail display. */\nfunction skillSummary(body: string): string | null {\n for (const line of body.split(\"\\n\")) {\n const t = line.trim();\n if (!t) continue;\n return (t.startsWith(\"#\") ? t.replace(/^#+\\s*/, \"\").trim() : t).slice(0, 200) || null;\n }\n return null;\n}\n\n/** Content-addressed skill version id: hash of name + normalized body (matches the project's sha1 id convention). */\nfunction skillVersionId(name: string, body: string): string {\n return createHash(\"sha1\").update(name).update(\"\\0\").update(body).digest(\"hex\").slice(0, 16);\n}\n\n/**\n * Remove every session whose project path is in `excludedPaths` — plus its subagent descendants —\n * along with all dependent rows (events → FTS via trigger, token_usage, tool_calls, turns,\n * classifications, findings) and the ingest_state for its files. This is what makes the global exclude list\n * take effect on the NEXT ingest, incremental or full: add a project and its data leaves the DB.\n * Matches projects.path exactly or by path-prefix. Returns the number of sessions pruned.\n */\nexport function pruneExcluded(db: DB, excludedPaths: string[]): number {\n if (!excludedPaths.length) return 0;\n const projects = db.prepare(\"SELECT id, path FROM projects\").all() as Array<{ id: string; path: string }>;\n const projIds = projects.filter((p) => excludedPaths.some((e) => p.path === e || p.path.startsWith(e + \"/\"))).map((p) => p.id);\n if (!projIds.length) return 0;\n\n db.exec(\"DROP TABLE IF EXISTS _prune\");\n db.exec(\"CREATE TEMP TABLE _prune (id TEXT PRIMARY KEY)\");\n const insP = db.prepare(\"INSERT OR IGNORE INTO _prune (id) VALUES (?)\");\n const seed = db.prepare(`SELECT id FROM sessions WHERE project_id IN (${projIds.map(() => \"?\").join(\",\")})`).all(...projIds) as Array<{ id: string }>;\n db.transaction(() => {\n for (const s of seed) insP.run(s.id);\n })();\n // Transitive: subagents whose parent is being pruned (covers cross-project linkage). Fixpoint.\n const expand = db.prepare(\"INSERT OR IGNORE INTO _prune (id) SELECT id FROM sessions WHERE parent_session_id IN (SELECT id FROM _prune)\");\n const cnt = db.prepare(\"SELECT COUNT(*) n FROM _prune\");\n for (let prev = -1; ; ) {\n const n = (cnt.get() as { n: number }).n;\n if (n === prev) break;\n prev = n;\n expand.run();\n }\n const total = (cnt.get() as { n: number }).n;\n if (!total) {\n db.exec(\"DROP TABLE IF EXISTS _prune\");\n return 0;\n }\n const files = (db.prepare(\"SELECT DISTINCT source_file f FROM events WHERE session_id IN (SELECT id FROM _prune) AND source_file IS NOT NULL\").all() as Array<{ f: string }>).map((r) => r.f);\n\n db.pragma(\"foreign_keys = OFF\");\n db.transaction(() => {\n db.exec(\"DELETE FROM findings WHERE session_id IN (SELECT id FROM _prune)\");\n for (const t of [\"token_usage\", \"tool_calls\", \"turns\", \"events\"]) db.exec(`DELETE FROM ${t} WHERE session_id IN (SELECT id FROM _prune)`);\n db.exec(\"DELETE FROM classifications WHERE scope = 'session' AND target_id IN (SELECT id FROM _prune)\");\n db.exec(\"DELETE FROM sessions WHERE id IN (SELECT id FROM _prune)\");\n const delState = db.prepare(\"DELETE FROM ingest_state WHERE file_path = ?\");\n for (const f of files) delState.run(f);\n // Drop now-empty projects (an excluded project leaves no sessions behind).\n db.exec(\"DELETE FROM projects WHERE id NOT IN (SELECT DISTINCT project_id FROM sessions WHERE project_id IS NOT NULL)\");\n })();\n db.pragma(\"foreign_keys = ON\");\n db.exec(\"DROP TABLE IF EXISTS _prune\");\n return total;\n}\n\nfunction durationMs(start: string | null, end: string | null): number | null {\n if (!start || !end) return null;\n const a = Date.parse(start);\n const b = Date.parse(end);\n if (Number.isNaN(a) || Number.isNaN(b)) return null;\n return Math.max(0, b - a);\n}\n\n/** Group a session's events into turns and return the turns + a uuid→turn_id map. */\nexport function buildTurns(\n sessionId: string,\n rows: Array<{ uuid: string; type: string; is_sidechain: number; is_meta: number; timestamp: string | null; model: string | null; text: string | null }>,\n): { turns: TurnRow[]; eventTurn: Map<string, string> } {\n rows.sort((x, y) => (x.timestamp ?? \"\").localeCompare(y.timestamp ?? \"\") || x.uuid.localeCompare(y.uuid));\n const turns: TurnRow[] = [];\n const eventTurn = new Map<string, string>();\n let cur: TurnRow | null = null;\n let seq = 0;\n\n for (const e of rows) {\n // A turn starts at a user prompt with real text. Sidechain (subagent) sessions are entirely\n // sidechain — verified no file mixes main+sidechain events — so their task prompt starts a turn\n // too; we don't filter on is_sidechain here. Tool-result carrier messages have empty text.\n const isPrompt = e.type === \"user\" && e.is_meta === 0 && !!e.text && e.text.trim().length > 0;\n if (isPrompt) {\n cur = {\n id: `${sessionId}:${seq}`,\n session_id: sessionId,\n seq,\n user_event_uuid: e.uuid,\n prompt_preview: preview(e.text!),\n model: null,\n started_at: e.timestamp,\n ended_at: e.timestamp,\n duration_ms: null,\n };\n turns.push(cur);\n seq++;\n }\n if (cur) {\n eventTurn.set(e.uuid, cur.id);\n if (e.model) cur.model = e.model;\n if (e.timestamp && (!cur.ended_at || e.timestamp > cur.ended_at)) cur.ended_at = e.timestamp;\n }\n }\n for (const t of turns) t.duration_ms = durationMs(t.started_at, t.ended_at);\n return { turns, eventTurn };\n}\n\n/**\n * Rebuild derived tables (turns, turn_id linkage, subagent parent linkage, session aggregates) from\n * the idempotent events/tool_calls tables.\n *\n * `dirty` is the set of session ids touched by this ingest run (incremental). When provided, only the\n * affected sessions are rebuilt — but the set is first expanded to its **linkage neighborhood** (the\n * subagent children spawned by a dirty session, and the parent session that spawned a dirty sidechain)\n * via a fixpoint, so cross-session parent/child links stay correct even when parent and child\n * transcripts arrive in different runs (ADR-010). When `dirty` is null/undefined (`--full`), every\n * session is rebuilt — the migration/reset path. Returns the expanded id set (or null for the full\n * path) so `classify()` can reuse it without re-expanding.\n */\nexport function rebuildDerived(db: DB, dirty?: Set<string> | null): Set<string> | null {\n const incremental = dirty != null;\n // Scope fragments: empty for the full rebuild, else restrict to the materialized _dirty temp table.\n const bySession = incremental ? \" WHERE session_id IN (SELECT id FROM _dirty)\" : \"\";\n const byId = incremental ? \" WHERE id IN (SELECT id FROM _dirty)\" : \"\";\n const andId = incremental ? \" AND id IN (SELECT id FROM _dirty)\" : \"\";\n const andSelfId = incremental ? \" AND sessions.id IN (SELECT id FROM _dirty)\" : \"\";\n\n if (incremental) {\n db.exec(\"DROP TABLE IF EXISTS _dirty\");\n db.exec(\"CREATE TEMP TABLE _dirty (id TEXT PRIMARY KEY)\");\n const ins = db.prepare(\"INSERT OR IGNORE INTO _dirty (id) VALUES (?)\");\n db.transaction((ids: Iterable<string>) => {\n for (const id of ids) ins.run(id);\n })(dirty);\n // Fixpoint expansion to the linkage neighborhood: a dirty parent pulls in the children it spawned,\n // and a dirty child pulls in its spawner parent. Loop until the set stops growing (covers nested\n // subagents). Bounded by the session count.\n const expand = db.prepare(`\n INSERT OR IGNORE INTO _dirty (id)\n SELECT spawned_session_id FROM tool_calls\n WHERE session_id IN (SELECT id FROM _dirty) AND spawned_session_id IS NOT NULL\n UNION\n SELECT session_id FROM tool_calls\n WHERE spawned_session_id IN (SELECT id FROM _dirty)\n UNION\n -- structural (path-based) workflow linkage: a dirty parent pulls in its subagent children,\n -- and a dirty child pulls in its structural parent, so the fallback link stays correct when\n -- parent and child transcripts arrive in different runs (ADR-010).\n SELECT id FROM sessions\n WHERE spawn_parent_id IN (SELECT id FROM _dirty)\n UNION\n SELECT spawn_parent_id FROM sessions\n WHERE id IN (SELECT id FROM _dirty) AND spawn_parent_id IS NOT NULL\n `);\n const count = db.prepare(\"SELECT COUNT(*) n FROM _dirty\");\n let prev = -1;\n for (;;) {\n const n = (count.get() as { n: number }).n;\n if (n === prev) break;\n prev = n;\n expand.run();\n }\n }\n\n // Recompute turns from the (idempotent) events table.\n // Null referencing turn_ids BEFORE deleting turns (FK: events/token_usage/tool_calls/sessions -> turns).\n db.exec(`UPDATE events SET turn_id = NULL${bySession}`);\n db.exec(`UPDATE token_usage SET turn_id = NULL${bySession}`);\n db.exec(`UPDATE tool_calls SET turn_id = NULL${bySession}`);\n // Clear skill-version links for the scope so changed/removed bodies don't leave a stale link.\n db.exec(`UPDATE tool_calls SET skill_id = NULL${bySession}`);\n db.exec(`UPDATE sessions SET parent_turn_id = NULL${byId}`);\n db.exec(`DELETE FROM turns${bySession}`);\n\n const sessionIds = db.prepare(`SELECT id FROM sessions${byId}`).all() as Array<{ id: string }>;\n const selEvents = db.prepare(\n \"SELECT uuid, type, is_sidechain, is_meta, timestamp, model, text FROM events WHERE session_id = ?\",\n );\n const insTurn = db.prepare(\n `INSERT INTO turns (id, session_id, seq, user_event_uuid, prompt_preview, model, started_at, ended_at, duration_ms)\n VALUES (@id, @session_id, @seq, @user_event_uuid, @prompt_preview, @model, @started_at, @ended_at, @duration_ms)`,\n );\n const updEventTurn = db.prepare(\"UPDATE events SET turn_id = ? WHERE uuid = ?\");\n\n const tx = db.transaction(() => {\n for (const { id } of sessionIds) {\n const rows = selEvents.all(id) as any[];\n const { turns, eventTurn } = buildTurns(id, rows);\n for (const t of turns) insTurn.run(t);\n for (const [uuid, turnId] of eventTurn) updEventTurn.run(turnId, uuid);\n }\n });\n tx();\n\n // Propagate turn_id to dependent tables.\n db.exec(`UPDATE token_usage SET turn_id = (SELECT turn_id FROM events WHERE events.uuid = token_usage.event_uuid)${bySession}`);\n db.exec(`UPDATE tool_calls SET turn_id = (SELECT turn_id FROM events WHERE events.uuid = tool_calls.event_uuid)${bySession}`);\n\n // Link each Skill tool_call to a content-addressed skill *version*. A firing injects the full\n // SKILL.md body as an isMeta user event right after the launch; we pair each Skill tool_call with\n // the following injection (by skill-name token, ARGUMENTS as tiebreak), normalize + hash the body,\n // UPSERT the version, and stamp tool_calls.skill_id. Firings without a captured body keep skill_id\n // NULL (skill_name stays set). Runs over the same scoped session set as the turn rebuild.\n {\n const selEventsOrdered = db.prepare(\n \"SELECT uuid, type, is_meta, timestamp, text FROM events WHERE session_id = ?\",\n );\n const selSkillCalls = db.prepare(\n \"SELECT id, event_uuid, skill_name, input_json FROM tool_calls WHERE session_id = ? AND tool_name = 'Skill'\",\n );\n const upsertSkill = db.prepare(\n `INSERT INTO skills (id, name, base_dir, body, summary, body_bytes, first_seen, last_seen)\n VALUES (@id, @name, @base_dir, @body, @summary, @body_bytes, @ts, @ts)\n ON CONFLICT(id) DO UPDATE SET\n last_seen = MAX(last_seen, excluded.last_seen),\n first_seen = MIN(first_seen, excluded.first_seen),\n base_dir = excluded.base_dir`,\n );\n const linkCall = db.prepare(\"UPDATE tool_calls SET skill_id = ? WHERE id = ?\");\n\n const linkTx = db.transaction(() => {\n for (const { id: sid } of sessionIds) {\n const calls = selSkillCalls.all(sid) as Array<{ id: string; event_uuid: string | null; skill_name: string | null; input_json: string | null }>;\n if (!calls.length) continue;\n const events = (selEventsOrdered.all(sid) as Array<{ uuid: string; type: string; is_meta: number; timestamp: string | null; text: string | null }>).sort(\n (x, y) => (x.timestamp ?? \"\").localeCompare(y.timestamp ?? \"\") || x.uuid.localeCompare(y.uuid),\n );\n const callByEvent = new Map<string, typeof calls>();\n for (const c of calls) {\n if (!c.event_uuid) continue;\n const arr = callByEvent.get(c.event_uuid) ?? [];\n arr.push(c);\n callByEvent.set(c.event_uuid, arr);\n }\n // Queue of pending Skill calls awaiting their body injection, keyed by skill-name token.\n const pending = new Map<string, Array<{ id: string; args: string | null }>>();\n for (const e of events) {\n const here = callByEvent.get(e.uuid);\n if (here) {\n for (const c of here) {\n if (!c.skill_name) continue;\n const key = skillNameKey(c.skill_name);\n let args: string | null = null;\n try {\n args = c.input_json ? (JSON.parse(c.input_json).args ?? null) : null;\n if (typeof args === \"string\") args = args.trim() || null;\n else if (args != null) args = JSON.stringify(args);\n } catch {\n /* keep null */\n }\n const arr = pending.get(key) ?? [];\n arr.push({ id: c.id, args });\n pending.set(key, arr);\n }\n }\n if (e.is_meta !== 1 || e.type !== \"user\" || !e.text) continue;\n const inj = parseSkillInjection(e.text);\n if (!inj) continue;\n const queue = pending.get(inj.nameKey);\n if (!queue || !queue.length) continue;\n // Prefer a call whose args match the injection's ARGUMENTS; else take the earliest pending.\n let idx = inj.args != null ? queue.findIndex((q) => q.args === inj.args) : -1;\n if (idx < 0) idx = 0;\n const [match] = queue.splice(idx, 1);\n const c = calls.find((x) => x.id === match.id)!;\n const name = c.skill_name!;\n const vid = skillVersionId(name, inj.body);\n upsertSkill.run({\n id: vid,\n name,\n base_dir: inj.baseDir,\n body: inj.body,\n summary: skillSummary(inj.body),\n body_bytes: Buffer.byteLength(inj.body, \"utf8\"),\n ts: e.timestamp,\n });\n linkCall.run(vid, match.id);\n }\n }\n });\n linkTx();\n }\n\n // Link subagent (sidechain) sessions back to the parent turn/session that spawned them. The\n // deterministic key is the spawning Task/Agent tool_call's spawned_session_id (== this session id).\n db.exec(`\n UPDATE sessions SET\n parent_session_id = (SELECT tc.session_id FROM tool_calls tc WHERE tc.spawned_session_id = sessions.id),\n parent_turn_id = (SELECT tc.turn_id FROM tool_calls tc WHERE tc.spawned_session_id = sessions.id)\n WHERE EXISTS (SELECT 1 FROM tool_calls tc WHERE tc.spawned_session_id = sessions.id)${andSelfId}\n `);\n\n // Workflow fan-out carries no toolUseResult.agentId, so the link above never fires for it\n // (finding #1). But the launching Workflow tool_call's result DOES carry a run id (wf_<id>), and the\n // run's subagents nest under …/subagents/workflows/<runId>/ — so sessions.workflow_run_id matches\n // tool_calls.workflow_run_id. That gives workflow agents BOTH their parent session and the exact\n // launching turn. (LIMIT 1: a run id maps to a single Workflow tool_call.)\n db.exec(`\n UPDATE sessions SET\n parent_session_id = COALESCE(parent_session_id, (SELECT tc.session_id FROM tool_calls tc WHERE tc.workflow_run_id = sessions.workflow_run_id LIMIT 1)),\n parent_turn_id = COALESCE(parent_turn_id, (SELECT tc.turn_id FROM tool_calls tc WHERE tc.workflow_run_id = sessions.workflow_run_id LIMIT 1))\n WHERE sessions.workflow_run_id IS NOT NULL\n AND EXISTS (SELECT 1 FROM tool_calls tc WHERE tc.workflow_run_id = sessions.workflow_run_id)${andSelfId}\n `);\n\n // Final fallback for any sidechain still unlinked (e.g. its Workflow tool_call/runId wasn't\n // captured, or a non-workflow nested agent): attribute to the structural parent captured at ingest\n // from the transcript's directory location (<parent>/subagents/…) — the deterministic,\n // redaction-surviving signal. parent_turn_id stays NULL here. Tokens stay siloed → no double-count.\n db.exec(`\n UPDATE sessions SET parent_session_id = spawn_parent_id\n WHERE parent_session_id IS NULL\n AND spawn_parent_id IS NOT NULL\n AND spawn_parent_id IN (SELECT id FROM sessions)${andSelfId}\n `);\n\n // Recompute session aggregates from events/turns.\n db.exec(`\n UPDATE sessions SET\n event_count = (SELECT COUNT(*) FROM events e WHERE e.session_id = sessions.id),\n started_at = (SELECT MIN(timestamp) FROM events e WHERE e.session_id = sessions.id),\n ended_at = (SELECT MAX(timestamp) FROM events e WHERE e.session_id = sessions.id),\n turn_count = (SELECT COUNT(*) FROM turns t WHERE t.session_id = sessions.id),\n is_sidechain = (SELECT CASE WHEN MIN(is_sidechain) = 1 THEN 1 ELSE 0 END FROM events e WHERE e.session_id = sessions.id)${byId}\n `);\n db.exec(`\n UPDATE sessions SET duration_ms =\n CAST((julianday(ended_at) - julianday(started_at)) * 86400000 AS INTEGER)\n WHERE started_at IS NOT NULL AND ended_at IS NOT NULL${andId}\n `);\n\n // Prune phantom sessions: any with zero events is not a real transcript. discover() walks every\n // *.jsonl under projects/, which sweeps up non-transcript files that happen to live there — e.g.\n // a Workflow tool's `journal.jsonl` (lines carry no `uuid`, so they yield no events). insSessionStub\n // still created a row (and they all share the basename → one phantom \"journal\" session). A zero-event\n // session has no events/turns/token_usage/tool_calls referencing it, so the delete is FK-safe; the\n // orphaned-classification sweep keeps the (no-FK) classifications table from accumulating dead rows\n // (kept global — it is one row per session and must catch any now-missing target).\n db.exec(`DELETE FROM sessions WHERE event_count = 0${andId}`);\n db.exec(\"DELETE FROM classifications WHERE scope = 'session' AND target_id NOT IN (SELECT id FROM sessions)\");\n // Security findings for a now-missing session (kept global — detect() runs after this rebuild and\n // only rescans the dirty scope, so a vanished non-dirty session's rows must be swept here).\n db.exec(\"DELETE FROM findings WHERE session_id NOT IN (SELECT id FROM sessions)\");\n // Sweep skill versions no longer referenced by any tool_call. Full path only: an incremental run\n // only re-derives dirty sessions, so a version could still be referenced by a non-dirty session.\n if (!incremental) {\n db.exec(\"DELETE FROM skills WHERE id NOT IN (SELECT skill_id FROM tool_calls WHERE skill_id IS NOT NULL)\");\n }\n\n if (incremental) {\n const expanded = new Set(\n (db.prepare(\"SELECT id FROM _dirty\").all() as Array<{ id: string }>).map((r) => r.id),\n );\n db.exec(\"DROP TABLE IF EXISTS _dirty\");\n return expanded;\n }\n return null;\n}\n\nexport interface IngestStatements {\n insAgent: Stmt;\n insSource: Stmt;\n getState: Stmt;\n setState: Stmt;\n insEvent: Stmt;\n insTokens: Stmt;\n insTool: Stmt;\n patchTool: Stmt;\n upsertProject: Stmt;\n insSessionStub: Stmt;\n upsertSession: Stmt;\n}\n\nexport function prepareStatements(db: DB): IngestStatements {\n return {\n insAgent: db.prepare(\"INSERT OR IGNORE INTO agents (id, name, kind) VALUES (?, ?, 'cli')\"),\n insSource: db.prepare(\n `INSERT INTO sources (id, label, agent_id, config_dir) VALUES (@id, @label, @agent_id, @config_dir)\n ON CONFLICT(id) DO UPDATE SET label=excluded.label, agent_id=excluded.agent_id, config_dir=excluded.config_dir`,\n ),\n getState: db.prepare(\"SELECT size, mtime_ms, sha256, events_ingested FROM ingest_state WHERE file_path = ?\"),\n setState: db.prepare(\n `INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)\n VALUES (@file_path, @size, @mtime_ms, @sha256, @events_ingested, @ingested_at)\n ON CONFLICT(file_path) DO UPDATE SET\n size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256,\n events_ingested=excluded.events_ingested, ingested_at=excluded.ingested_at`,\n ),\n insEvent: db.prepare(\n `INSERT INTO events (uuid, session_id, turn_id, parent_uuid, seq, type, role, timestamp, model, is_sidechain, is_meta, text, raw_json, source_file)\n VALUES (@uuid, @session_id, @turn_id, @parent_uuid, @seq, @type, @role, @timestamp, @model, @is_sidechain, @is_meta, @text, @raw_json, @source_file)\n ON CONFLICT(uuid) DO NOTHING`,\n ),\n insTokens: db.prepare(\n // Bare ON CONFLICT (no target) so the insert is skipped on EITHER the event_uuid PK or the\n // unique (session_id, message_id) index — the latter collapses a response's repeated\n // per-content-block usage lines into a single row.\n `INSERT INTO token_usage (event_uuid, session_id, turn_id, message_id, model, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, service_tier)\n VALUES (@event_uuid, @session_id, @turn_id, @message_id, @model, @input_tokens, @output_tokens, @cache_creation_input_tokens, @cache_read_input_tokens, @service_tier)\n ON CONFLICT DO NOTHING`,\n ),\n insTool: db.prepare(\n `INSERT INTO tool_calls (id, event_uuid, session_id, turn_id, tool_name, caller, skill_name, skill_id, agent_type, spawned_session_id, workflow_run_id, workflow_name, resolved_model, status, total_duration_ms, total_tokens, total_tool_use_count, input_json, result_summary)\n VALUES (@id, @event_uuid, @session_id, @turn_id, @tool_name, @caller, @skill_name, @skill_id, @agent_type, @spawned_session_id, @workflow_run_id, @workflow_name, @resolved_model, @status, @total_duration_ms, @total_tokens, @total_tool_use_count, @input_json, @result_summary)\n ON CONFLICT(id) DO NOTHING`,\n ),\n patchTool: db.prepare(\n `UPDATE tool_calls SET\n status = COALESCE(@status, status),\n agent_type = COALESCE(@agent_type, agent_type),\n spawned_session_id = COALESCE(@spawned_session_id, spawned_session_id),\n workflow_run_id = COALESCE(@workflow_run_id, workflow_run_id),\n workflow_name = COALESCE(@workflow_name, workflow_name),\n resolved_model = COALESCE(@resolved_model, resolved_model),\n total_duration_ms = COALESCE(@total_duration_ms, total_duration_ms),\n total_tokens = COALESCE(@total_tokens, total_tokens),\n total_tool_use_count = COALESCE(@total_tool_use_count, total_tool_use_count),\n result_summary = COALESCE(@result_summary, result_summary)\n WHERE id = @tool_use_id`,\n ),\n upsertProject: db.prepare(\n `INSERT INTO projects (id, agent_id, path, encoded_dir, first_seen, last_seen)\n VALUES (@id, @agent_id, @path, @encoded_dir, @now, @now)\n ON CONFLICT(id) DO UPDATE SET last_seen = @now, encoded_dir = COALESCE(excluded.encoded_dir, encoded_dir)`,\n ),\n insSessionStub: db.prepare(\"INSERT OR IGNORE INTO sessions (id, agent_id, source_id) VALUES (?, ?, ?)\"),\n upsertSession: db.prepare(\n `INSERT INTO sessions (id, agent_id, source_id, project_id, slug, ai_title, cli_version, entrypoint, git_branch, spawn_parent_id, workflow_run_id)\n VALUES (@id, @agent_id, @source_id, @project_id, @slug, @ai_title, @cli_version, @entrypoint, @git_branch, @spawn_parent_id, @workflow_run_id)\n ON CONFLICT(id) DO UPDATE SET\n source_id = COALESCE(excluded.source_id, source_id),\n project_id = COALESCE(excluded.project_id, project_id),\n slug = COALESCE(excluded.slug, slug),\n ai_title = COALESCE(excluded.ai_title, ai_title),\n cli_version = COALESCE(excluded.cli_version, cli_version),\n entrypoint = COALESCE(excluded.entrypoint, entrypoint),\n git_branch = COALESCE(excluded.git_branch, git_branch),\n spawn_parent_id = COALESCE(excluded.spawn_parent_id, spawn_parent_id),\n workflow_run_id = COALESCE(excluded.workflow_run_id, workflow_run_id)`,\n ),\n };\n}\n\n/**\n * Ingest one transcript file's lines in a single transaction: parse each line via the adapter,\n * insert events/token_usage/tool_calls (deduped by their keys), patch tool results, and upsert the\n * session + project metadata. `lines` is an iterable of newline-stripped lines — the caller chooses\n * whole-file read (small files) or a streaming reader (large files), so this engine never holds the\n * full file as one string. `meta` carries the disk stats recorded into ingest_state (tests pass\n * synthetic values). Derived tables (turns, linkage, aggregates) are rebuilt separately afterwards.\n */\nexport function ingestFile(\n db: DB,\n stmts: IngestStatements,\n adapter: SourceAdapter,\n file: SourceFile,\n lines: Iterable<string>,\n meta: { size: number; mtimeMs: number; hash: string },\n now: string,\n stats: IngestStats,\n): void {\n const sessionMeta: Record<string, any> = {};\n let eventsInFile = 0;\n\n const tx = db.transaction(() => {\n // Ensure the session row exists before any event references it (FK).\n stmts.insSessionStub.run(file.sessionId, adapter.agentId, file.sourceId);\n let seq = 0;\n for (const line of lines) {\n if (!line.trim()) continue;\n let raw: unknown;\n try {\n raw = JSON.parse(line);\n } catch {\n stats.malformed++;\n continue;\n }\n const parsed = adapter.parseLine(raw, file, seq++);\n if (parsed.meta) Object.assign(sessionMeta, parsed.meta);\n if (parsed.event) {\n // raw_json is stored gzip-compressed (ADR-011); compress at this single write chokepoint so\n // adapters stay agent-agnostic and emit a plain string.\n const info = stmts.insEvent.run({ ...parsed.event, raw_json: packRaw(parsed.event.raw_json) });\n if (info.changes > 0) {\n stats.newEvents++;\n eventsInFile++;\n }\n }\n if (parsed.tokenUsage) stmts.insTokens.run(parsed.tokenUsage);\n if (parsed.toolCalls) for (const tc of parsed.toolCalls) stmts.insTool.run(tc);\n if (parsed.toolResults)\n for (const tr of parsed.toolResults)\n stmts.patchTool.run({\n tool_use_id: tr.tool_use_id,\n status: tr.status ?? null,\n agent_type: tr.agent_type ?? null,\n spawned_session_id: tr.spawned_session_id ?? null,\n workflow_run_id: tr.workflow_run_id ?? null,\n workflow_name: tr.workflow_name ?? null,\n resolved_model: tr.resolved_model ?? null,\n total_duration_ms: tr.total_duration_ms ?? null,\n total_tokens: tr.total_tokens ?? null,\n total_tool_use_count: tr.total_tool_use_count ?? null,\n result_summary: tr.result_summary ?? null,\n });\n }\n\n // Persist session + project metadata (survives later skip runs).\n let projectId: string | null = null;\n if (sessionMeta.cwd) {\n projectId = createHash(\"sha1\").update(`${adapter.agentId}\\0${sessionMeta.cwd}`).digest(\"hex\").slice(0, 16);\n stmts.upsertProject.run({\n id: projectId,\n agent_id: adapter.agentId,\n path: sessionMeta.cwd,\n encoded_dir: file.encodedDir || null,\n now,\n });\n }\n stmts.upsertSession.run({\n id: file.sessionId,\n agent_id: adapter.agentId,\n source_id: file.sourceId,\n project_id: projectId,\n slug: sessionMeta.slug ?? null,\n ai_title: sessionMeta.ai_title ?? null,\n cli_version: sessionMeta.cli_version ?? null,\n entrypoint: sessionMeta.entrypoint ?? null,\n git_branch: sessionMeta.git_branch ?? null,\n spawn_parent_id: file.parentSessionId ?? null,\n workflow_run_id: file.workflowRunId ?? null,\n });\n\n stmts.setState.run({\n file_path: file.path,\n size: meta.size,\n mtime_ms: Math.trunc(meta.mtimeMs),\n sha256: meta.hash,\n events_ingested: eventsInFile,\n ingested_at: now,\n });\n });\n tx();\n}\n","/**\n * Agent Lens — Stage 2 ingest of Workflow-tool result sidecars.\n *\n * The Workflow tool writes a JSON sidecar next to the launching session when a run finishes:\n * <archive>/<source>/projects/<enc>/<sessionId>/workflows/wf_<id>.json\n * The transcript only carries the \"launched in background\" ack for an async run, so for those runs\n * this file is the ONLY record of how the run finished — status (completed/failed), summary, the\n * returned result payload, the model, phase structure, per-item logs, and roll-up\n * tokens/tool-calls/duration/agent-count. We project those fields into `workflow_results` (verbatim,\n * like tool_calls.input_json — the DB is local; redaction is an export concern). The big `script` and\n * `args` are intentionally NOT re-stored: they already live on the launching Workflow tool_call.\n *\n * Idempotent: keyed by run id, UPSERT on re-ingest; a stat short-circuit (ingest_state) skips\n * unchanged files. Excluded projects are filtered out at discovery.\n */\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { DB } from \"./db.js\";\nimport { isExcludedArchivePath } from \"./redact.js\";\nimport { sha256 } from \"./fileread.js\";\n\nexport interface WorkflowIngestStats {\n files: number;\n upserted: number;\n skipped: number;\n malformed: number;\n}\n\nexport function newWorkflowStats(): WorkflowIngestStats {\n return { files: 0, upserted: 0, skipped: 0, malformed: 0 };\n}\n\n/** Recursively find `wf_*.json` sidecars living directly inside a `workflows/` directory. */\nfunction walkSidecars(dir: string, inWorkflows: boolean, out: string[]): void {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const p = join(dir, e.name);\n if (e.isDirectory()) walkSidecars(p, inWorkflows || e.name === \"workflows\", out);\n // Only the run's own result file (wf_<id>.json); agent-*.meta.json / scripts are handled elsewhere.\n else if (e.isFile() && inWorkflows && /^wf_.+\\.json$/.test(e.name)) out.push(p);\n }\n}\n\n/** All sidecar paths under a source archive (mirror first, then each .versions snapshot). */\nfunction discoverSidecars(sourceArchiveDir: string): string[] {\n const out: string[] = [];\n walkSidecars(join(sourceArchiveDir, \"projects\"), false, out);\n try {\n for (const ts of readdirSync(join(sourceArchiveDir, \".versions\"), { withFileTypes: true }))\n if (ts.isDirectory()) walkSidecars(join(sourceArchiveDir, ts.name, \"projects\"), false, out);\n } catch {\n /* no versions yet */\n }\n return out;\n}\n\n/** Launching session id = the path segment immediately before `workflows/`. */\nfunction sessionFromPath(path: string): string | null {\n const parts = path.split(\"/\");\n const i = parts.indexOf(\"workflows\");\n return i > 0 ? parts[i - 1] : null;\n}\n\n/** Epoch-ms number or ISO/date string → ISO string (null when unparseable/absent). */\nfunction toIso(x: unknown): string | null {\n if (typeof x === \"number\" && Number.isFinite(x)) return new Date(x).toISOString();\n if (typeof x === \"string\" && x) {\n const t = Date.parse(x);\n return Number.isNaN(t) ? null : new Date(t).toISOString();\n }\n return null;\n}\n\nconst jsonOrNull = (v: unknown): string | null => (v == null ? null : JSON.stringify(v));\nconst intOrNull = (v: unknown): number | null => (typeof v === \"number\" && Number.isFinite(v) ? Math.trunc(v) : null);\nconst strOrNull = (v: unknown): string | null => (typeof v === \"string\" && v ? v : null);\n\n/** Strip terminal styling the runner sometimes bakes into defaultModel — both real ANSI escapes\n * (`\\x1b[1m`) and the de-escaped literal form (`claude-opus-4-8[1m]`) — so the stored model is clean. */\nfunction cleanStr(v: unknown): string | null {\n const s = strOrNull(v);\n if (!s) return null;\n return s\n .replace(/\\x1b\\[[0-9;]*[A-Za-z]/g, \"\") // real ANSI escape sequences\n .replace(/\\[[0-9;]+m\\]?/g, \"\") // de-escaped leftovers: \"[1m\", \"[1m]\"\n .trim() || null;\n}\n\n/**\n * Ingest all workflow result sidecars for one source into `workflow_results`. `excludedDirs` are the\n * parsed exclude tokens (same filter the transcript walk uses). Returns nothing; mutates `stats`.\n */\nexport function ingestWorkflowResults(\n db: DB,\n sourceArchiveDir: string,\n sourceId: string,\n excludedDirs: string[],\n now: string,\n stats: WorkflowIngestStats,\n full: boolean,\n): void {\n let paths = discoverSidecars(sourceArchiveDir);\n if (excludedDirs.length) paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));\n // Mirror last so it wins over any older .versions snapshot on UPSERT (a run that went\n // running→completed diverges: the completed copy is in the mirror).\n paths.sort((a, b) => Number(a.includes(\"/.versions/\")) - Number(b.includes(\"/.versions/\")) || (a < b ? -1 : 1));\n\n const getState = db.prepare(\"SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?\");\n const setState = db.prepare(\n `INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)\n VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)\n ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`,\n );\n const upsert = db.prepare(\n `INSERT INTO workflow_results\n (run_id, source_id, session_id, task_id, workflow_name, status, summary, default_model,\n result_json, phases_json, logs_json, progress_json,\n agent_count, total_tokens, total_tool_calls, duration_ms, started_at, ended_at, ingested_at)\n VALUES\n (@run_id, @source_id, @session_id, @task_id, @workflow_name, @status, @summary, @default_model,\n @result_json, @phases_json, @logs_json, @progress_json,\n @agent_count, @total_tokens, @total_tool_calls, @duration_ms, @started_at, @ended_at, @ingested_at)\n ON CONFLICT(run_id) DO UPDATE SET\n source_id=excluded.source_id, session_id=excluded.session_id, task_id=excluded.task_id,\n workflow_name=excluded.workflow_name, status=excluded.status, summary=excluded.summary,\n default_model=excluded.default_model, result_json=excluded.result_json, phases_json=excluded.phases_json,\n logs_json=excluded.logs_json, progress_json=excluded.progress_json, agent_count=excluded.agent_count,\n total_tokens=excluded.total_tokens, total_tool_calls=excluded.total_tool_calls,\n duration_ms=excluded.duration_ms, started_at=excluded.started_at, ended_at=excluded.ended_at,\n ingested_at=excluded.ingested_at`,\n );\n\n for (const path of paths) {\n stats.files++;\n const st = statSync(path);\n const mtimeMs = Math.trunc(st.mtimeMs);\n const prev = full ? undefined : (getState.get(path) as { size: number; mtime_ms: number; sha256: string } | undefined);\n if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {\n stats.skipped++;\n continue;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(path);\n } catch {\n stats.malformed++;\n continue;\n }\n const hash = sha256(buf);\n if (prev && prev.sha256 === hash) {\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.skipped++;\n continue;\n }\n\n let d: any;\n try {\n d = JSON.parse(buf.toString(\"utf8\"));\n } catch {\n stats.malformed++;\n continue;\n }\n if (!d || typeof d !== \"object\") {\n stats.malformed++;\n continue;\n }\n\n const runId = strOrNull(d.runId) ?? basename(path, \".json\");\n const startedAt = toIso(d.startTime) ?? toIso(d.timestamp);\n const durationMs = intOrNull(d.durationMs);\n const endedAt = startedAt && durationMs != null ? new Date(Date.parse(startedAt) + durationMs).toISOString() : null;\n\n upsert.run({\n run_id: runId,\n source_id: sourceId,\n session_id: sessionFromPath(path),\n task_id: strOrNull(d.taskId),\n workflow_name: strOrNull(d.workflowName),\n status: strOrNull(d.status),\n summary: strOrNull(d.summary),\n default_model: cleanStr(d.defaultModel),\n result_json: jsonOrNull(d.result),\n phases_json: Array.isArray(d.phases) ? JSON.stringify(d.phases) : null,\n logs_json: Array.isArray(d.logs) ? JSON.stringify(d.logs) : null,\n progress_json: Array.isArray(d.workflowProgress) ? JSON.stringify(d.workflowProgress) : null,\n agent_count: intOrNull(d.agentCount),\n total_tokens: intOrNull(d.totalTokens),\n total_tool_calls: intOrNull(d.totalToolCalls),\n duration_ms: durationMs,\n started_at: startedAt,\n ended_at: endedAt,\n ingested_at: now,\n });\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.upserted++;\n }\n}\n","/**\n * Agent Lens — metric-preserving transcript redactor (validation Layer 4).\n *\n * Produces a privacy-safe corpus that is realistic enough to validate the whole compute pipeline\n * end-to-end, by an explicit contract:\n *\n * PRESERVE (so metrics are identical to the raw input): uuid, parentUuid, type, timestamp,\n * isSidechain, isMeta, agentId (opaque hex, drives subagent linkage), message.{role,id,model,\n * usage,stop_reason}, toolUseResult numeric/linkage fields, tool_use {id,name}, and — crucially —\n * the *line count* of Edit/Write payloads + the *distinctness & extension* of file paths (these\n * feed the LoC / files complexity sub-scores).\n *\n * REDACT (deny-by-default): all message text/thinking, tool_result content, cwd & file paths\n * (pseudonymized, usernames stripped), slug/aiTitle/gitBranch, and any other free text. Stable\n * irreversible pseudonyms keep joins/counts intact (same input → same pseudonym).\n *\n * Token accounting, cost, durations, counts, subagent linkage, and the complexity *score* are\n * therefore identical between raw and redacted. The classifier *category* is text-derived and is\n * NOT preserved (validated separately by golden fixtures) — callers must exclude it from oracle\n * equality checks.\n *\n * HARD RULES (see memory test-corpus-redaction): never emit data from the agent-lens project\n * itself; scrub project names, paths, users, hosts, secrets. The CLI filters agent-lens sessions\n * and runs a final leak-scan before writing.\n */\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { parseSkillInjection } from \"./pipeline.js\";\n\nconst PLACEHOLDER = \"[redacted]\";\n\n/** Lines in a string, matching classify.ts/countLines exactly (trailing newline doesn't add a line). */\nfunction countLines(s: unknown): number {\n if (typeof s !== \"string\" || s.length === 0) return 0;\n const trimmed = s.endsWith(\"\\n\") ? s.slice(0, -1) : s;\n return trimmed.length === 0 ? 0 : trimmed.split(\"\\n\").length;\n}\n/** A dummy string whose countLines() == n (so LoC sub-scores are preserved). */\nfunction dummyLines(n: number): string {\n return n <= 0 ? \"\" : Array(n).fill(\"x\").join(\"\\n\");\n}\n\nexport class Redactor {\n private cache = new Map<string, string>();\n constructor(private salt: string = randomBytes(8).toString(\"hex\")) {}\n\n private hash(s: string, n = 6): string {\n return createHash(\"sha256\").update(this.salt).update(s).digest(\"hex\").slice(0, n);\n }\n private stable(kind: string, s: string): string {\n const key = `${kind}:${s}`;\n let v = this.cache.get(key);\n if (!v) {\n v = `${kind}-${this.hash(key)}`;\n this.cache.set(key, v);\n }\n return v;\n }\n\n /** Pseudonymize a path: strip the user's home, pseudonymize each segment, keep depth + extension. */\n path(p: unknown): string | null {\n if (typeof p !== \"string\" || !p) return typeof p === \"string\" ? p : null;\n const abs = p.startsWith(\"/\");\n const segs = p.split(\"/\").filter(Boolean);\n const out: string[] = [];\n for (let i = 0; i < segs.length; i++) {\n const seg = segs[i]!;\n // Drop the \"<user>\" right after a home root so usernames never survive.\n if ((segs[i - 1] === \"home\" || segs[i - 1] === \"Users\") && i >= 1) {\n out.push(\"user\");\n continue;\n }\n if (seg === \"home\" || seg === \"Users\") {\n out.push(seg);\n continue;\n }\n const last = i === segs.length - 1;\n const dot = last ? seg.lastIndexOf(\".\") : -1;\n if (dot > 0) out.push(`file-${this.hash(seg)}${seg.slice(dot)}`); // keep extension\n else out.push(this.stable(\"dir\", seg));\n }\n return (abs ? \"/\" : \"\") + out.join(\"/\");\n }\n\n /** Pseudonymize a free-form identifier (slug, label, branch). */\n ident(kind: string, s: unknown): string {\n return typeof s === \"string\" && s ? this.stable(kind, s) : `${kind}-none`;\n }\n\n /** Replace free text with a placeholder, PRESERVING emptiness (turn detection depends on it). */\n private text(s: unknown): string {\n return typeof s === \"string\" && s.length > 0 ? PLACEHOLDER : \"\";\n }\n\n /**\n * Preserve a skill-body injection (\"Base directory for this skill: …\\n\\n<SKILL.md body>\\n\\nARGUMENTS:…\")\n * instead of redacting it — the body is the only real skill content and drives version tracking.\n * We strip the per-call ARGUMENTS block, replace the home-path base-dir line with a non-leaking\n * `/skills/<name>` (keeping the skill-name tail so ingest still links it), keep the body verbatim,\n * and fail closed: if the result trips the leak scan, return null so the caller fully redacts it.\n * Returns null when `s` is not a skill injection.\n */\n private skillText(s: unknown): string | null {\n if (typeof s !== \"string\") return null;\n const inj = parseSkillInjection(s);\n if (!inj) return null;\n const nameTail = (inj.baseDir ?? \"\").split(\"/\").filter(Boolean).pop() ?? \"\";\n const rebuilt = `Base directory for this skill: /skills/${nameTail}\\n\\n${inj.body}\\n`;\n return findLeak(rebuilt) ? null : rebuilt;\n }\n\n private toolInput(name: string, input: any): any {\n if (!input || typeof input !== \"object\") return {};\n const out: any = {};\n // Skill name (skill_name metric); only the Skill tool's command is non-sensitive enough to keep.\n if (typeof input.skill === \"string\") out.skill = input.skill;\n if (name === \"Skill\" && typeof input.command === \"string\") out.command = input.command;\n // Subagent type (agent_type metric).\n if ((name === \"Task\" || name === \"Agent\") && typeof input.subagent_type === \"string\") out.subagent_type = input.subagent_type;\n // LoC-bearing payloads: keep line counts + a pseudonymized path with its extension.\n if (name === \"Write\") {\n out.file_path = this.path(input.file_path);\n out.content = dummyLines(countLines(input.content));\n } else if (name === \"Edit\") {\n out.file_path = this.path(input.file_path);\n out.old_string = dummyLines(countLines(input.old_string));\n out.new_string = dummyLines(countLines(input.new_string));\n }\n return out;\n }\n\n private block(b: any): any {\n if (!b || typeof b !== \"object\") return b;\n switch (b.type) {\n case \"text\":\n return { type: \"text\", text: this.skillText(b.text) ?? this.text(b.text) };\n case \"thinking\":\n return { type: \"thinking\", thinking: this.text(b.thinking) };\n case \"tool_use\":\n return { type: \"tool_use\", id: b.id, name: b.name, input: this.toolInput(String(b.name), b.input), ...(b.caller ? { caller: b.caller } : {}) };\n case \"tool_result\":\n return { type: \"tool_result\", tool_use_id: b.tool_use_id, content: PLACEHOLDER, ...(b.is_error ? { is_error: true } : {}) };\n default:\n return { type: b.type };\n }\n }\n\n private message(m: any): any {\n if (!m || typeof m !== \"object\") return m;\n const out: any = {};\n for (const k of [\"role\", \"id\", \"model\", \"type\", \"usage\", \"stop_reason\", \"stop_sequence\"]) if (k in m) out[k] = m[k];\n if (typeof m.content === \"string\") out.content = this.skillText(m.content) ?? this.text(m.content);\n else if (Array.isArray(m.content)) out.content = m.content.map((b: any) => this.block(b));\n return out;\n }\n\n /** Redact one parsed transcript line (object). */\n line(r: any): any {\n if (!r || typeof r !== \"object\") return r;\n const out: any = {};\n // Structural / metric-bearing scalars kept verbatim.\n for (const k of [\"uuid\", \"parentUuid\", \"type\", \"timestamp\", \"isSidechain\", \"isMeta\", \"agentId\", \"userType\", \"version\"]) if (k in r) out[k] = r[k];\n if (typeof r.cwd === \"string\") out.cwd = this.path(r.cwd);\n if (typeof r.gitBranch === \"string\") out.gitBranch = this.ident(\"branch\", r.gitBranch);\n if (typeof r.slug === \"string\") out.slug = this.ident(\"slug\", r.slug);\n if (r.type === \"ai-title\") out.aiTitle = \"Redacted session\";\n if (r.toolUseResult && typeof r.toolUseResult === \"object\") {\n const t = r.toolUseResult;\n const tu: any = {};\n for (const k of [\"status\", \"agentType\", \"agentId\", \"resolvedModel\", \"totalDurationMs\", \"totalTokens\", \"totalToolUseCount\"]) if (k in t) tu[k] = t[k];\n out.toolUseResult = tu;\n }\n if (\"message\" in r) out.message = this.message(r.message);\n return out;\n }\n\n /** Redact a whole JSONL transcript (string in, string out). Malformed lines are dropped. */\n transcript(content: string): string {\n const lines: string[] = [];\n for (const raw of content.split(\"\\n\")) {\n if (!raw.trim()) continue;\n let obj: any;\n try {\n obj = JSON.parse(raw);\n } catch {\n continue;\n }\n lines.push(JSON.stringify(this.line(obj)));\n }\n return lines.join(\"\\n\") + \"\\n\";\n }\n}\n\n// Canonical Claude Code project-dir encoder now lives in core so collect + ingest agree exactly.\nexport { encodeProjectPath } from \"@agent-lens/core\";\nimport { encodeProjectPath } from \"@agent-lens/core\";\n\n/** Parse the AGENT_LENS_REDACT_EXCLUDE CSV of real project paths into encoded-dir prefixes. */\nexport function parseExcludes(csv: string | undefined | null): string[] {\n return (csv ?? \"\")\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n .map(encodeProjectPath);\n}\n\n/** True if `encodedDir` is (or sits under) any excluded project path — used to keep data out of the corpus. */\nexport function isExcludedDir(encodedDir: string, excluded: string[]): boolean {\n return excluded.some((e) => encodedDir === e || encodedDir.startsWith(e + \"-\"));\n}\n\n/**\n * True if an archive file path belongs to an excluded project. Matches the `/projects/<encodedDir>/`\n * segment, so it catches both the main transcript AND its nested `<UUID>/subagents/agent-*.jsonl`.\n * `excluded` is the encoded-dir list from parseExcludes().\n */\nexport function isExcludedArchivePath(filePath: string, excluded: string[]): boolean {\n return excluded.some((e) => filePath.includes(`/projects/${e}/`));\n}\n\n/** Patterns that must NEVER appear in redacted output (final defense-in-depth leak scan). */\nexport const LEAK_PATTERNS: Array<{ name: string; re: RegExp }> = [\n { name: \"email\", re: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/ },\n { name: \"home-path-with-user\", re: /\\/(?:home|Users)\\/(?!user\\b)[A-Za-z0-9._-]+/ },\n { name: \"ipv4\", re: /\\b\\d{1,3}(?:\\.\\d{1,3}){3}\\b/ },\n { name: \"url\", re: /https?:\\/\\/[^\\s\"']+/ },\n { name: \"aws-key\", re: /AKIA[0-9A-Z]{16}/ },\n { name: \"bearer/token\", re: /\\b(?:sk|pk|ghp|gho|xox[abp])[-_][A-Za-z0-9]{8,}/ },\n];\n\n/** Return the first leak found in `text`, or null. Used to fail closed before writing the corpus. */\nexport function findLeak(text: string): { name: string; sample: string } | null {\n for (const { name, re } of LEAK_PATTERNS) {\n const m = re.exec(text);\n if (m) return { name, sample: m[0].slice(0, 40) };\n }\n return null;\n}\n","/**\n * Agent Lens — transcript file reading (ADR-010, impact 4).\n *\n * Small files are read whole (fast path); files larger than STREAM_THRESHOLD are streamed in fixed\n * chunks so ingest memory stays bounded regardless of transcript size. The streaming SHA-256 is\n * byte-identical to hashing a whole-file `readFileSync` buffer, so the incremental-skip check is\n * unaffected by which path a file takes. Kept separate from the CLI bootstrap so it is unit-testable.\n */\nimport { createHash } from \"node:crypto\";\nimport { openSync, readSync, closeSync } from \"node:fs\";\nimport { StringDecoder } from \"node:string_decoder\";\n\nexport const STREAM_THRESHOLD = 8 * 1024 * 1024;\nconst CHUNK = 64 * 1024;\n\n/** SHA-256 hex of a buffer. */\nexport function sha256(buf: Buffer): string {\n return createHash(\"sha256\").update(buf).digest(\"hex\");\n}\n\n/** SHA-256 hex of a file's bytes, computed by streaming. Equals sha256(readFileSync(path)). */\nexport function sha256File(path: string): string {\n const fd = openSync(path, \"r\");\n const h = createHash(\"sha256\");\n const buf = Buffer.allocUnsafe(CHUNK);\n try {\n let n: number;\n while ((n = readSync(fd, buf, 0, CHUNK, null)) > 0) h.update(buf.subarray(0, n));\n } finally {\n closeSync(fd);\n }\n return h.digest(\"hex\");\n}\n\n/**\n * Lazily yield a file's lines (newline-stripped) by streaming. A StringDecoder handles multibyte\n * characters split across chunk boundaries. Holds at most one line at a time, never the whole file.\n * Yields the same sequence as `readFileSync(path).toString(\"utf8\").split(\"\\n\")` minus the trailing\n * empty element a final newline would produce (ingest skips blank lines anyway).\n */\nexport function* streamLines(path: string): Generator<string> {\n const fd = openSync(path, \"r\");\n const decoder = new StringDecoder(\"utf8\");\n const buf = Buffer.allocUnsafe(CHUNK);\n let leftover = \"\";\n try {\n let n: number;\n while ((n = readSync(fd, buf, 0, CHUNK, null)) > 0) {\n leftover += decoder.write(buf.subarray(0, n));\n let idx: number;\n while ((idx = leftover.indexOf(\"\\n\")) !== -1) {\n yield leftover.slice(0, idx);\n leftover = leftover.slice(idx + 1);\n }\n }\n leftover += decoder.end();\n if (leftover.length > 0) yield leftover;\n } finally {\n closeSync(fd);\n }\n}\n","/**\n * Agent Lens — Stage 2 ingest of per-subagent metadata sidecars.\n *\n * When a subagent (Task/Agent, or a Workflow fan-out agent) runs, Claude Code writes a metadata\n * sidecar next to its transcript:\n * <archive>/<source>/projects/<enc>/<sessionId>/subagents/agent-<id>.meta.json\n * carrying { agentType, description, spawnDepth?, toolUseId }. This is the authoritative source of a\n * subagent's type and human title — Workflow fan-out agents carry no subagent_type on the launching\n * tool_call, so ~700 of them are otherwise untyped. The filename stem `agent-<id>` IS the subagent's\n * session id (the same id `discover()` assigns the transcript), so we key `session_meta` on it and\n * LEFT JOIN at read time (no dependency on the session row existing yet).\n *\n * Idempotent: keyed by session id, UPSERT on re-ingest; a stat short-circuit (ingest_state) skips\n * unchanged files. Excluded projects are filtered out at discovery. Mirrors `workflows.ts`.\n */\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { DB } from \"./db.js\";\nimport { isExcludedArchivePath } from \"./redact.js\";\nimport { sha256 } from \"./fileread.js\";\n\nexport interface MetaIngestStats {\n files: number;\n upserted: number;\n skipped: number;\n malformed: number;\n}\n\nexport function newMetaStats(): MetaIngestStats {\n return { files: 0, upserted: 0, skipped: 0, malformed: 0 };\n}\n\n/** Recursively find `agent-*.meta.json` sidecars living directly inside a `subagents/` directory. */\nfunction walkMeta(dir: string, inSubagents: boolean, out: string[]): void {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const p = join(dir, e.name);\n if (e.isDirectory()) walkMeta(p, inSubagents || e.name === \"subagents\", out);\n else if (e.isFile() && inSubagents && /^agent-.+\\.meta\\.json$/.test(e.name)) out.push(p);\n }\n}\n\n/** All meta paths under a source archive (mirror first, then each .versions snapshot). */\nfunction discoverMeta(sourceArchiveDir: string): string[] {\n const out: string[] = [];\n walkMeta(join(sourceArchiveDir, \"projects\"), false, out);\n try {\n for (const ts of readdirSync(join(sourceArchiveDir, \".versions\"), { withFileTypes: true }))\n if (ts.isDirectory()) walkMeta(join(sourceArchiveDir, ts.name, \"projects\"), false, out);\n } catch {\n /* no versions yet */\n }\n return out;\n}\n\nconst intOrNull = (v: unknown): number | null => (typeof v === \"number\" && Number.isFinite(v) ? Math.trunc(v) : null);\nconst strOrNull = (v: unknown): string | null => (typeof v === \"string\" && v ? v : null);\n\n/**\n * Ingest all subagent meta sidecars for one source into `session_meta`. `excludedDirs` are the parsed\n * exclude tokens (same filter the transcript walk uses). Returns nothing; mutates `stats`.\n */\nexport function ingestSubagentMeta(\n db: DB,\n sourceArchiveDir: string,\n sourceId: string,\n excludedDirs: string[],\n now: string,\n stats: MetaIngestStats,\n full: boolean,\n): void {\n let paths = discoverMeta(sourceArchiveDir);\n if (excludedDirs.length) paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));\n // Mirror last so it wins over any older .versions snapshot on UPSERT.\n paths.sort((a, b) => Number(a.includes(\"/.versions/\")) - Number(b.includes(\"/.versions/\")) || (a < b ? -1 : 1));\n\n const getState = db.prepare(\"SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?\");\n const setState = db.prepare(\n `INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)\n VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)\n ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`,\n );\n const upsert = db.prepare(\n `INSERT INTO session_meta\n (session_id, source_id, agent_type, agent_description, spawn_depth, tool_use_id, ingested_at)\n VALUES\n (@session_id, @source_id, @agent_type, @agent_description, @spawn_depth, @tool_use_id, @ingested_at)\n ON CONFLICT(session_id) DO UPDATE SET\n source_id=excluded.source_id, agent_type=excluded.agent_type,\n agent_description=excluded.agent_description, spawn_depth=excluded.spawn_depth,\n tool_use_id=excluded.tool_use_id, ingested_at=excluded.ingested_at`,\n );\n\n for (const path of paths) {\n stats.files++;\n const st = statSync(path);\n const mtimeMs = Math.trunc(st.mtimeMs);\n const prev = full ? undefined : (getState.get(path) as { size: number; mtime_ms: number; sha256: string } | undefined);\n if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {\n stats.skipped++;\n continue;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(path);\n } catch {\n stats.malformed++;\n continue;\n }\n const hash = sha256(buf);\n if (prev && prev.sha256 === hash) {\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.skipped++;\n continue;\n }\n\n let d: any;\n try {\n d = JSON.parse(buf.toString(\"utf8\"));\n } catch {\n stats.malformed++;\n continue;\n }\n if (!d || typeof d !== \"object\") {\n stats.malformed++;\n continue;\n }\n\n // Filename stem is the subagent's session id: `agent-<id>.meta.json` → `agent-<id>`.\n const sessionId = basename(path, \".meta.json\");\n upsert.run({\n session_id: sessionId,\n source_id: sourceId,\n agent_type: strOrNull(d.agentType),\n agent_description: strOrNull(d.description),\n spawn_depth: intOrNull(d.spawnDepth),\n tool_use_id: strOrNull(d.toolUseId),\n ingested_at: now,\n });\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.upserted++;\n }\n}\n","/**\n * Agent Lens — Stage 2 ingest of spilled full tool-result files.\n *\n * When a tool's output is too large for the transcript, Claude Code writes the full output to disk and\n * keeps only a short summary in the transcript naming the file:\n * <archive>/<source>/projects/<enc>/<sessionId>/tool-results/<name>.txt\n * (the summary reads \"Output too large (…). Full output saved to: …/tool-results/<name>.txt\"). The\n * basename <name> is NOT the tool_use id — it's a short random token — so we key `tool_results` on\n * (session_id, name) and join at read time by parsing that marker out of tool_calls.result_summary.\n *\n * Idempotent: keyed by (session_id, name), UPSERT on re-ingest; a stat short-circuit (ingest_state)\n * skips unchanged files. Excluded projects are filtered out at discovery. Mirrors `workflows.ts`.\n */\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { DB } from \"./db.js\";\nimport { isExcludedArchivePath } from \"./redact.js\";\nimport { sha256 } from \"./fileread.js\";\n\nexport interface ToolResultIngestStats {\n files: number;\n upserted: number;\n skipped: number;\n malformed: number;\n}\n\nexport function newToolResultStats(): ToolResultIngestStats {\n return { files: 0, upserted: 0, skipped: 0, malformed: 0 };\n}\n\n/** Recursively find `*.txt` files living directly inside a `tool-results/` directory. */\nfunction walkResults(dir: string, inResults: boolean, out: string[]): void {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const e of entries) {\n const p = join(dir, e.name);\n if (e.isDirectory()) walkResults(p, inResults || e.name === \"tool-results\", out);\n else if (e.isFile() && inResults && e.name.endsWith(\".txt\")) out.push(p);\n }\n}\n\n/** All tool-result paths under a source archive (mirror first, then each .versions snapshot). */\nfunction discoverResults(sourceArchiveDir: string): string[] {\n const out: string[] = [];\n walkResults(join(sourceArchiveDir, \"projects\"), false, out);\n try {\n for (const ts of readdirSync(join(sourceArchiveDir, \".versions\"), { withFileTypes: true }))\n if (ts.isDirectory()) walkResults(join(sourceArchiveDir, ts.name, \"projects\"), false, out);\n } catch {\n /* no versions yet */\n }\n return out;\n}\n\n/** Session id = the path segment immediately before `tool-results/`. */\nfunction sessionFromPath(path: string): string | null {\n const parts = path.split(\"/\");\n const i = parts.indexOf(\"tool-results\");\n return i > 0 ? parts[i - 1] : null;\n}\n\n/**\n * Ingest all spilled tool-result files for one source into `tool_results`. `excludedDirs` are the\n * parsed exclude tokens (same filter the transcript walk uses). Returns nothing; mutates `stats`.\n */\nexport function ingestToolResults(\n db: DB,\n sourceArchiveDir: string,\n excludedDirs: string[],\n now: string,\n stats: ToolResultIngestStats,\n full: boolean,\n): void {\n let paths = discoverResults(sourceArchiveDir);\n if (excludedDirs.length) paths = paths.filter((p) => !isExcludedArchivePath(p, excludedDirs));\n // Mirror last so it wins over any older .versions snapshot on UPSERT.\n paths.sort((a, b) => Number(a.includes(\"/.versions/\")) - Number(b.includes(\"/.versions/\")) || (a < b ? -1 : 1));\n\n const getState = db.prepare(\"SELECT size, mtime_ms, sha256 FROM ingest_state WHERE file_path = ?\");\n const setState = db.prepare(\n `INSERT INTO ingest_state (file_path, size, mtime_ms, sha256, events_ingested, ingested_at)\n VALUES (@file_path, @size, @mtime_ms, @sha256, 0, @ingested_at)\n ON CONFLICT(file_path) DO UPDATE SET size=excluded.size, mtime_ms=excluded.mtime_ms, sha256=excluded.sha256, ingested_at=excluded.ingested_at`,\n );\n const upsert = db.prepare(\n `INSERT INTO tool_results (session_id, name, path, bytes, text, ingested_at)\n VALUES (@session_id, @name, @path, @bytes, @text, @ingested_at)\n ON CONFLICT(session_id, name) DO UPDATE SET\n path=excluded.path, bytes=excluded.bytes, text=excluded.text, ingested_at=excluded.ingested_at`,\n );\n\n for (const path of paths) {\n const sessionId = sessionFromPath(path);\n if (!sessionId) continue; // outside the expected …/<sessionId>/tool-results/ layout\n stats.files++;\n const st = statSync(path);\n const mtimeMs = Math.trunc(st.mtimeMs);\n const prev = full ? undefined : (getState.get(path) as { size: number; mtime_ms: number; sha256: string } | undefined);\n if (prev && prev.size === st.size && prev.mtime_ms === mtimeMs) {\n stats.skipped++;\n continue;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(path);\n } catch {\n stats.malformed++;\n continue;\n }\n const hash = sha256(buf);\n if (prev && prev.sha256 === hash) {\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.skipped++;\n continue;\n }\n\n upsert.run({\n session_id: sessionId,\n name: basename(path, \".txt\"),\n path,\n bytes: st.size,\n text: buf.toString(\"utf8\"),\n ingested_at: now,\n });\n setState.run({ file_path: path, size: st.size, mtime_ms: mtimeMs, sha256: hash, ingested_at: now });\n stats.upserted++;\n }\n}\n","/**\n * Agent Lens — Stage 3 local server, as an importable function (ADR-005: 127.0.0.1 only, no egress).\n *\n * `startServer` is the library entrypoint so the unified `agent-lens` CLI can bundle and call it\n * in-process; the thin bin in index.ts just forwards to it. Read-only REST over the SQLite store +\n * Markdown export, and serves the built web SPA. (env: AGENT_LENS_DB, AGENT_LENS_PORT, AGENT_LENS_HOST)\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { findRepoRoot, resolveDataDir, resolveWebDist } from \"@agent-lens/core\";\nimport { openReadonly } from \"./db.js\";\nimport { createApp } from \"./app.js\";\n\nexport async function startServer(): Promise<void> {\n // Resolve relative to this module: in dev it's packages/server/dist; bundled into the CLI it's the\n // published package root (web SPA sits at ../web, data falls back to the per-user dir).\n const here = dirname(fileURLToPath(import.meta.url));\n const repoRoot = findRepoRoot(here);\n const dbPath = process.env.AGENT_LENS_DB || join(resolveDataDir(repoRoot), \"agent-lens.db\");\n const host = process.env.AGENT_LENS_HOST || \"127.0.0.1\"; // loopback only by default\n const port = Number(process.env.AGENT_LENS_PORT || 4477);\n const webDist = resolveWebDist(here, repoRoot);\n\n if (host !== \"127.0.0.1\" && host !== \"localhost\" && !process.env.AGENT_LENS_ALLOW_NONLOCAL) {\n console.error(\n `agent-lens-server: refusing to bind non-loopback host '${host}' (privacy). ` +\n `Set AGENT_LENS_ALLOW_NONLOCAL=1 to override.`,\n );\n process.exit(1);\n }\n if (!existsSync(dbPath)) {\n console.error(`agent-lens-server: db not found: ${dbPath} (run ingest first)`);\n process.exit(1);\n }\n\n // Triage store (ADR-018) sits beside the analytics DB and is never touched by ingest, so user triage\n // survives `ingest --full`. Opened read-write by createApp; the analytics handle stays read-only.\n const triageDbPath = process.env.AGENT_LENS_TRIAGE_DB || join(dirname(dbPath), \"triage.db\");\n\n const db = openReadonly(dbPath);\n const app = await createApp(db, { webDist, triageDbPath });\n\n try {\n await app.listen({ host, port });\n console.log(`agent-lens-server: http://${host}:${port} (db: ${dbPath})`);\n if (!existsSync(webDist)) console.log(\" note: web SPA not built — run `pnpm --filter @agent-lens/web build`\");\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n}\n","import Database from \"better-sqlite3\";\nimport { costForUsage, SCHEMA_VERSION, unpackRaw, severityRank, SECURITY_CATEGORIES, errorKind } from \"@agent-lens/core\";\n\nexport type DB = Database.Database;\n\nexport function openReadonly(file: string): DB {\n const db = new Database(file, { readonly: true, fileMustExist: true });\n db.pragma(\"query_only = ON\");\n return db;\n}\n\n/**\n * Compare the DB's stamped schema version against the version this server build expects. `stale` means\n * the on-disk DB was written by an older schema (a bump requires `agent-lens ingest --full` to rebuild)\n * — the UI surfaces this so a mismatch doesn't silently show missing/empty columns. `db_version` is null\n * on an unstamped/never-ingested DB (treated as not-stale: nothing to warn about yet).\n */\nexport function schemaStatus(db: DB): { db_version: number | null; expected: number; stale: boolean } {\n const row = db.prepare(\"SELECT value FROM meta WHERE key = 'schema_version'\").get() as\n | { value: string }\n | undefined;\n const dbVersion = row ? Number(row.value) : null;\n const db_version = dbVersion != null && Number.isFinite(dbVersion) ? dbVersion : null;\n return { db_version, expected: SCHEMA_VERSION, stale: db_version != null && db_version !== SCHEMA_VERSION };\n}\n\n/**\n * Wall-clock time of the most recent ingest run that wrote anything, as an ISO8601 string (or null on\n * a never-ingested DB). Every archive file touched by a run gets the same `ingested_at` stamp, so the\n * max across `ingest_state` is exactly \"data last refreshed at\". Powers the header freshness readout.\n */\nexport function lastIngested(db: DB): string | null {\n const row = db.prepare(\"SELECT MAX(ingested_at) AS last FROM ingest_state\").get() as { last: string | null };\n return row?.last ?? null;\n}\n\n/**\n * Split a raw transcript line's message content into natural text vs thinking. Accepts the stored\n * `events.raw_json` value, which is a gzip BLOB (Buffer) post-ADR-011 but may be a legacy plain string;\n * `unpackRaw` normalizes both.\n */\nexport function extractParts(rawJson: string | Buffer): { text: string | null; thinking: string | null } {\n let text = \"\";\n let thinking = \"\";\n try {\n const content = JSON.parse(unpackRaw(rawJson))?.message?.content;\n if (typeof content === \"string\") text = content;\n else if (Array.isArray(content)) {\n for (const b of content) {\n if (b?.type === \"text\" && typeof b.text === \"string\") text += (text ? \"\\n\" : \"\") + b.text;\n else if (b?.type === \"thinking\" && typeof b.thinking === \"string\")\n thinking += (thinking ? \"\\n\" : \"\") + b.thinking;\n }\n }\n } catch {\n /* ignore */\n }\n return { text: text || null, thinking: thinking || null };\n}\n\nexport interface SessionFilters {\n source?: string;\n project?: string;\n model?: string;\n q?: string;\n from?: string;\n to?: string;\n kind?: \"main\" | \"subagent\";\n /** Keep only sessions that have ≥1 finding of one of these severities. */\n severity?: string[];\n /** Keep only sessions that have ≥1 errored tool call of one of these error types. */\n errorType?: string[];\n /** Column to sort the whole filtered list by (before pagination). Defaults to \"started\". */\n sort?: \"started\" | \"title\" | \"turns\" | \"tokens\" | \"cost\" | \"duration\" | \"errors\" | \"security\";\n dir?: \"asc\" | \"desc\";\n limit: number;\n offset: number;\n}\n\nexport function listSources(db: DB) {\n return db\n .prepare(\n // Count MAIN sessions only (is_sidechain = 0): the dropdown filters the session list, which\n // defaults to main-only, so a source's count must match what you'd see — not be inflated by the\n // many subagent sidechains each task spawns.\n `SELECT s.id, s.label, s.agent_id, s.config_dir,\n (SELECT COUNT(*) FROM sessions x WHERE x.source_id = s.id AND x.is_sidechain = 0) AS session_count\n FROM sources s ORDER BY s.label`,\n )\n .all();\n}\n\nexport function listProjects(db: DB) {\n return db\n .prepare(\n `SELECT p.id, p.path,\n (SELECT COUNT(*) FROM sessions x WHERE x.project_id = p.id AND x.is_sidechain = 0) AS session_count\n FROM projects p ORDER BY session_count DESC, p.path`,\n )\n .all();\n}\n\nexport function listModels(db: DB) {\n return db\n .prepare(`SELECT DISTINCT model FROM token_usage WHERE model IS NOT NULL ORDER BY model`)\n .all()\n .map((r: any) => r.model);\n}\n\n/**\n * Turn a user's search box input into a safe FTS5 MATCH expression. The raw string is otherwise\n * parsed as FTS5 *query syntax*, so a hyphen, colon (column filter), or bareword operator throws\n * (e.g. \"swe-workflow\" → `SQLITE_ERROR: no such column: workflow`). We treat the input as literal\n * terms: split on whitespace and wrap each token in double quotes (doubling embedded quotes per\n * FTS5 escaping), joined by space (implicit AND). Phrasing also lets the tokenizer split intra-token\n * punctuation, so \"swe-workflow\" matches the adjacent tokens \"swe workflow\". Returns \"\" for input\n * with no usable tokens (caller guards on a non-empty trimmed query). */\nfunction toFtsQuery(raw: string): string {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`)\n .join(\" \");\n}\n\nexport function listSessions(db: DB, f: SessionFilters) {\n const where: string[] = [];\n const params: any[] = [];\n if (f.source) (where.push(\"s.source_id = ?\"), params.push(f.source));\n if (f.project) (where.push(\"s.project_id = ?\"), params.push(f.project));\n // Date-inclusive on both ends (see findingWhere): a picked `to` day must include that day's sessions.\n if (f.from) (where.push(\"date(s.started_at) >= date(?)\"), params.push(f.from));\n if (f.to) (where.push(\"date(s.started_at) <= date(?)\"), params.push(f.to));\n if (f.kind === \"main\") where.push(\"s.is_sidechain = 0\");\n if (f.kind === \"subagent\") where.push(\"s.is_sidechain = 1\");\n // Multi-select: session has ≥1 finding of any listed severity / ≥1 errored tool call of any listed type.\n if (f.severity?.length) {\n where.push(`EXISTS (SELECT 1 FROM findings fd WHERE fd.session_id = s.id AND fd.severity IN (${f.severity.map(() => \"?\").join(\",\")}))`);\n params.push(...f.severity);\n }\n if (f.errorType?.length) {\n where.push(`EXISTS (SELECT 1 FROM tool_calls tc WHERE tc.session_id = s.id AND tc.error_type IN (${f.errorType.map(() => \"?\").join(\",\")}))`);\n params.push(...f.errorType);\n }\n if (f.model) {\n where.push(\"EXISTS (SELECT 1 FROM token_usage t WHERE t.session_id = s.id AND t.model = ?)\");\n params.push(f.model);\n }\n if (f.q && f.q.trim()) {\n // Match transcript text (FTS) OR the session's own name (slug/ai_title) OR its project path, so a\n // query like \"fix-youtube-embed-csp-policy\" finds the session by slug even when that string never\n // appears verbatim in an event. A project-path *subquery* (not a join on p.path) keeps the\n // un-joined COUNT(*) query above valid. SQLite LIKE is case-insensitive for ASCII.\n const like = `%${f.q.trim()}%`;\n where.push(\n `(s.id IN (SELECT DISTINCT e.session_id FROM events e JOIN events_fts f ON f.rowid = e.rowid WHERE events_fts MATCH ?)\n OR s.slug LIKE ? OR s.ai_title LIKE ?\n OR s.project_id IN (SELECT id FROM projects WHERE path LIKE ?))`,\n );\n params.push(toFtsQuery(f.q), like, like, like);\n }\n const whereSql = where.length ? `WHERE ${where.join(\" AND \")}` : \"\";\n\n const total = (db.prepare(`SELECT COUNT(*) n FROM sessions s ${whereSql}`).get(...params) as any).n;\n\n const baseSelect = `SELECT s.id, s.ai_title, s.slug, s.source_id, s.is_sidechain, s.started_at, s.ended_at,\n s.duration_ms, s.event_count, s.turn_count, p.path AS project_path,\n (SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models,\n (SELECT COUNT(*) FROM tool_calls tc WHERE tc.session_id = s.id) AS tool_call_count,\n (SELECT COUNT(*) FROM tool_calls tc WHERE tc.session_id = s.id AND tc.status = 'error') AS tool_error_count,\n (SELECT COUNT(*) FROM findings fd WHERE fd.session_id = s.id) AS finding_count,\n (SELECT fd.severity FROM findings fd WHERE fd.session_id = s.id\n ORDER BY CASE fd.severity WHEN 'critical' THEN 4 WHEN 'high' THEN 3 WHEN 'medium' THEN 2 WHEN 'low' THEN 1 WHEN 'info' THEN 0 ELSE -1 END DESC\n LIMIT 1) AS worst_severity\n FROM sessions s LEFT JOIN projects p ON p.id = s.project_id\n ${whereSql}`;\n\n // Columns sortable directly in SQL (stable-paginated with an id tiebreak). tokens/cost aren't stored\n // — they're derived from token_usage with per-model pricing — so those sort over the whole matching\n // set in JS below.\n const NATIVE_ORDER: Record<string, string> = {\n started: \"s.started_at\",\n title: \"COALESCE(s.ai_title, s.slug)\",\n turns: \"s.turn_count\",\n duration: \"s.duration_ms\",\n };\n const sortKey = f.sort ?? \"started\";\n const sqlDir = f.dir === \"asc\" ? \"ASC\" : \"DESC\";\n\n // Columns sorted in JS over the whole matching set (derived/subquery metrics that the paged SQL\n // ORDER BY can't reach consistently): tokens/cost (costed below) plus the error/finding roll-ups.\n const JS_SORT = new Set([\"tokens\", \"cost\", \"errors\", \"security\"]);\n let rows: any[];\n if (JS_SORT.has(sortKey)) {\n // Whole-list sort by a derived metric: materialize every matching session, cost them all, sort,\n // then take the page. Heavier than the SQL path but the only way to page a JS-computed column\n // consistently. Fine at this tool's scale (personal analytics).\n const all = db.prepare(baseSelect).all(...params) as any[];\n attachSessionCost(db, all);\n const sign = f.dir === \"asc\" ? 1 : -1;\n const metric = (r: any): number => {\n switch (sortKey) {\n case \"tokens\": return r.tokens;\n case \"cost\": return r.cost;\n case \"errors\": return r.tool_error_count ?? 0;\n // Security: rank by worst severity (info=0 … critical=4; none = -1), finding count as tiebreak.\n case \"security\": return (severityRank(r.worst_severity ?? \"\") + 1) * 1e6 + (r.finding_count ?? 0);\n default: return 0;\n }\n };\n all.sort((a, b) => {\n const c = (metric(a) - metric(b)) * sign;\n return c !== 0 ? c : String(a.id).localeCompare(String(b.id));\n });\n rows = all.slice(f.offset, f.offset + f.limit);\n } else {\n const orderCol = NATIVE_ORDER[sortKey] ?? NATIVE_ORDER.started;\n rows = db\n .prepare(`${baseSelect} ORDER BY ${orderCol} ${sqlDir}, s.id ASC LIMIT ? OFFSET ?`)\n .all(...params, f.limit, f.offset) as any[];\n attachSessionCost(db, rows);\n }\n\n return { total, sessions: rows };\n}\n\n/** Attach per-session `tokens`, `token_split`, `cost` (USD, cache-aware) and `title` in place. Costs\n * are grouped by model so per-model rates apply. IDs are chunked so the whole-list sort path (which can\n * pass thousands of sessions) stays under SQLite's bound-parameter limit. */\nfunction attachSessionCost(db: DB, rows: any[]) {\n type Acc = { tokens: number; cost: number; split: { input: number; output: number; cache_creation: number; cache_read: number } };\n const costBySession = new Map<string, Acc>();\n const ids = rows.map((r) => r.id);\n const CHUNK = 800;\n for (let i = 0; i < ids.length; i += CHUNK) {\n const batch = ids.slice(i, i + CHUNK);\n const ph = batch.map(() => \"?\").join(\",\");\n const usage = db\n .prepare(\n `SELECT session_id, model,\n SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage WHERE session_id IN (${ph}) GROUP BY session_id, model`,\n )\n .all(...batch) as any[];\n for (const u of usage) {\n const acc = costBySession.get(u.session_id) ?? { tokens: 0, cost: 0, split: { input: 0, output: 0, cache_creation: 0, cache_read: 0 } };\n acc.tokens += u.i + u.o + u.cw + u.cr;\n acc.split.input += u.i;\n acc.split.output += u.o;\n acc.split.cache_creation += u.cw;\n acc.split.cache_read += u.cr;\n acc.cost += costForUsage(u.model, {\n input_tokens: u.i,\n output_tokens: u.o,\n cache_creation_input_tokens: u.cw,\n cache_read_input_tokens: u.cr,\n });\n costBySession.set(u.session_id, acc);\n }\n }\n for (const r of rows) {\n const c = costBySession.get(r.id);\n r.tokens = c?.tokens ?? 0;\n r.token_split = c?.split ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };\n r.cost = c ? Number(c.cost.toFixed(4)) : 0;\n r.title = r.ai_title || r.slug || null;\n }\n}\n\nexport function getSession(db: DB, id: string) {\n const session = db\n .prepare(\n `SELECT s.*, p.path AS project_path FROM sessions s\n LEFT JOIN projects p ON p.id = s.project_id WHERE s.id = ?`,\n )\n .get(id) as any;\n if (!session) return null;\n\n const turns = db.prepare(\"SELECT * FROM turns WHERE session_id = ? ORDER BY seq\").all(id);\n const eventRows = db\n .prepare(\n `SELECT uuid, type, role, timestamp, model, is_sidechain, turn_id, raw_json\n FROM events WHERE session_id = ? ORDER BY timestamp, seq`,\n )\n .all(id) as any[];\n const toolRows = db\n .prepare(\n `SELECT id, event_uuid, tool_name, skill_name, skill_id, agent_type, spawned_session_id,\n workflow_run_id, workflow_name, status, error_type,\n total_duration_ms, total_tokens, input_json, result_summary,\n (SELECT COUNT(*) FROM sessions s WHERE s.workflow_run_id = tool_calls.workflow_run_id) AS workflow_agent_count\n FROM tool_calls WHERE session_id = ?`,\n )\n .all(id) as any[];\n\n // Spilled full tool outputs: when a result_summary is the \"Full output saved to: …/tool-results/\n // <name>.txt\" marker (the transcript's 280-char stand-in), attach the un-truncated text from\n // tool_results so the UI can expand it. Keyed by (session_id, name); guarded for a pre-ingest DB.\n if (tableExists(db, \"tool_results\")) {\n const getFull = db.prepare(\"SELECT text, bytes FROM tool_results WHERE session_id = ? AND name = ?\");\n for (const t of toolRows) {\n const m = typeof t.result_summary === \"string\" ? t.result_summary.match(/tool-results\\/([A-Za-z0-9_-]+)\\.txt/) : null;\n if (!m) continue;\n const full = getFull.get(id, m[1]) as { text: string; bytes: number } | undefined;\n if (full) t.full_result = { text: full.text, bytes: full.bytes };\n }\n }\n\n // Security findings (ADR-017): attach each tool call's findings inline (for the transcript severity\n // badge + \"why\" panel) and collect a session-level list for the header summary. Guarded for a\n // read-only pre-ingest DB whose schema predates the findings table.\n const sessionFindings: any[] = [];\n if (tableExists(db, \"findings\")) {\n const findingRows = db\n .prepare(\n `SELECT id, tool_call_id, event_uuid, turn_id, rule_id, category, framework_ref, severity, title, evidence, signals_json\n FROM findings WHERE session_id = ?`,\n )\n .all(id) as any[];\n const byToolCall = new Map<string, any[]>();\n for (const fr of findingRows) {\n const f = {\n id: fr.id,\n tool_call_id: fr.tool_call_id,\n event_uuid: fr.event_uuid,\n turn_id: fr.turn_id,\n rule_id: fr.rule_id,\n category: fr.category,\n framework_ref: fr.framework_ref,\n severity: fr.severity,\n title: fr.title,\n evidence: fr.evidence,\n signals: safeJson(fr.signals_json),\n };\n sessionFindings.push(f);\n if (fr.tool_call_id) (byToolCall.get(fr.tool_call_id) ?? byToolCall.set(fr.tool_call_id, []).get(fr.tool_call_id))!.push(f);\n }\n for (const t of toolRows) t.findings = byToolCall.get(t.id) ?? [];\n // Most-severe first so a session banner can lead with the worst.\n sessionFindings.sort((a, b) => severityRank(b.severity) - severityRank(a.severity));\n }\n\n const toolsByEvent = new Map<string, any[]>();\n for (const t of toolRows) {\n if (!t.event_uuid) continue;\n (toolsByEvent.get(t.event_uuid) ?? toolsByEvent.set(t.event_uuid, []).get(t.event_uuid))!.push(t);\n }\n\n const events = eventRows.map((e) => {\n const { text, thinking } = extractParts(e.raw_json);\n return {\n uuid: e.uuid,\n type: e.type,\n role: e.role,\n timestamp: e.timestamp,\n model: e.model,\n is_sidechain: e.is_sidechain,\n turn_id: e.turn_id,\n text,\n thinking,\n toolCalls: toolsByEvent.get(e.uuid) ?? [],\n };\n });\n\n const usage = db\n .prepare(\n `SELECT model, SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage WHERE session_id = ? GROUP BY model`,\n )\n .all(id) as any[];\n // Keep the token categories split so the UI can show input/output/cache-write/cache-read\n // separately. Cache stays IN the total and IS cost-attributed (at its discounted rate) — it is\n // differentiated, never dropped.\n const split = { input: 0, output: 0, cache_creation: 0, cache_read: 0 };\n let cost = 0;\n for (const u of usage) {\n split.input += u.i;\n split.output += u.o;\n split.cache_creation += u.cw;\n split.cache_read += u.cr;\n cost += costForUsage(u.model, {\n input_tokens: u.i,\n output_tokens: u.o,\n cache_creation_input_tokens: u.cw,\n cache_read_input_tokens: u.cr,\n });\n }\n session.tokens = split.input + split.output + split.cache_creation + split.cache_read;\n session.token_split = split;\n session.cost = Number(cost.toFixed(4));\n session.title = session.ai_title || session.slug || null;\n\n // Heuristic classification (Phase 4): category + complexity + the signals that produced them\n // (tool/skill mix, LoC, files, subagent count) — already captured in signals_json by the classifier.\n const cls = db\n .prepare(\n `SELECT category, complexity_score, complexity_band, signals_json, classifier_version\n FROM classifications WHERE scope = 'session' AND target_id = ?`,\n )\n .get(id) as any;\n let classification: any = null;\n if (cls) {\n let signals: any = null;\n try {\n signals = cls.signals_json ? JSON.parse(cls.signals_json) : null;\n } catch {\n /* ignore malformed */\n }\n classification = {\n category: cls.category,\n complexity_score: cls.complexity_score,\n complexity_band: cls.complexity_band,\n classifier_version: cls.classifier_version,\n signals,\n };\n }\n\n // Subagent linkage (schema v3): if this is a sidechain session, resolve the parent turn/session\n // that spawned it so the UI can offer a \"spawned by\" crumb back to the originating turn.\n let parent: { id: string; title: string | null; turn_seq: number | null } | null = null;\n if (session.parent_session_id) {\n const p = db\n .prepare(\n `SELECT ps.id, ps.ai_title, ps.slug, t.seq AS turn_seq\n FROM sessions ps LEFT JOIN turns t ON t.id = ?\n WHERE ps.id = ?`,\n )\n .get(session.parent_turn_id, session.parent_session_id) as any;\n if (p) parent = { id: p.id, title: p.ai_title || p.slug || null, turn_seq: p.turn_seq ?? null };\n }\n\n // Spawned subagents (schema v3): sessions whose parent_session_id points back here. Nesting them\n // under the parent is why the flat session list defaults to main-only — a task with N subagents is\n // one row with N children, not N+1 sibling rows sharing the same slug.\n // Subagent metadata sidecars (session_meta) supply the authoritative agentType/description/spawnDepth\n // — LEFT JOIN so a subagent still lists when its meta hasn't been ingested (or the table is absent on\n // a pre-ingest read-only DB).\n const hasMeta = tableExists(db, \"session_meta\");\n const children = db\n .prepare(\n `SELECT s.id, s.ai_title, s.slug, s.turn_count, s.started_at, s.workflow_run_id,\n (SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models\n ${hasMeta ? \", sm.agent_type, sm.agent_description, sm.spawn_depth\" : \", NULL AS agent_type, NULL AS agent_description, NULL AS spawn_depth\"}\n FROM sessions s ${hasMeta ? \"LEFT JOIN session_meta sm ON sm.session_id = s.id\" : \"\"}\n WHERE s.parent_session_id = ? ORDER BY s.started_at`,\n )\n .all(id) as any[];\n for (const ch of children) {\n ch.title = ch.ai_title || ch.slug || null;\n const u = db\n .prepare(\n `SELECT model, SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage WHERE session_id = ? GROUP BY model`,\n )\n .all(ch.id) as any[];\n ch.tokens = u.reduce((a, r) => a + r.i + r.o + r.cw + r.cr, 0);\n ch.cost = Number(\n u.reduce((a, r) => a + costForUsage(r.model, { input_tokens: r.i, output_tokens: r.o, cache_creation_input_tokens: r.cw, cache_read_input_tokens: r.cr }), 0).toFixed(4),\n );\n }\n\n // Workflow runs launched from THIS session: each Workflow tool_call carries a run id + name and sits\n // on a turn. This lets the UI group the spawned subagents by run and link each group back to the\n // exact launching turn (turn_seq) — instead of one flat, unattributed fan-out list.\n // Run status rides along from the result sidecar (workflow_results) when ingested, so the session's\n // workflow-run groups can flag a failed/completed run at a glance. NULL when no sidecar (async, still\n // pending) or the table isn't present yet (read-only pre-ingest DB).\n const hasWR = tableExists(db, \"workflow_results\");\n const workflow_runs = db\n .prepare(\n `SELECT tc.workflow_run_id AS run_id, tc.workflow_name AS name, t.seq AS turn_seq,\n (SELECT COUNT(*) FROM sessions s WHERE s.workflow_run_id = tc.workflow_run_id) AS agent_count\n ${hasWR ? \", (SELECT status FROM workflow_results wr WHERE wr.run_id = tc.workflow_run_id) AS status\" : \", NULL AS status\"}\n FROM tool_calls tc LEFT JOIN turns t ON t.id = tc.turn_id\n WHERE tc.session_id = ? AND tc.workflow_run_id IS NOT NULL\n ORDER BY t.seq`,\n )\n .all(id) as any[];\n\n // Tool-call error roll-up for the detail header. Computed from the already-fetched toolRows so no extra\n // query is needed. The raw error count is authoritative; the failure-vs-rejection split uses the stored\n // heuristic error_type (rejection = the user/guardrail declined the call, not an agent failure).\n session.tool_call_count = toolRows.length;\n session.tool_error_count = toolRows.filter((t) => t.status === \"error\").length;\n session.tool_rejection_count = toolRows.filter((t) => t.status === \"error\" && t.error_type && errorKind(t.error_type) === \"rejection\").length;\n session.tool_failure_count = session.tool_error_count - session.tool_rejection_count;\n\n return { session, turns, events, classification, parent, children, workflow_runs, findings: sessionFindings };\n}\n\n/**\n * A Workflow-tool run's detail: the launching tool_call (name, status, returned result) + the turn\n * and session it was launched from, every subagent session fanned out under its `workflow_run_id`,\n * and roll-up stats (agent count, tokens, cost, wall-clock span). Powers the /workflow/:run_id page.\n * Returns null when no Workflow tool_call carries this run id (→ 404).\n */\n/** Pull the inner text of a single `<tag>…</tag>` from a flattened message (non-greedy). */\nfunction xmlTag(text: string, tag: string): string | null {\n return text.match(new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`))?.[1]?.trim() ?? null;\n}\n\n/** Whether a table exists — the server opens the DB read-only, so a not-yet-ingested schema (no\n * workflow_results table) must degrade gracefully rather than throw. */\nfunction tableExists(db: DB, name: string): boolean {\n return !!db.prepare(\"SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?\").get(name);\n}\n\n/** Parse a stored JSON column back to a value, or null when absent/malformed. */\nfunction safeJson(s: string | null): unknown {\n if (!s) return null;\n try {\n return JSON.parse(s);\n } catch {\n return null;\n }\n}\n\nexport function getWorkflow(db: DB, runId: string) {\n const wf = db\n .prepare(\n `SELECT tc.id AS tool_use_id, tc.workflow_run_id AS run_id, tc.workflow_name AS name, tc.status, tc.result_summary,\n tc.input_json AS input_json,\n tc.session_id AS parent_session_id, t.seq AS turn_seq,\n ps.ai_title AS parent_ai_title, ps.slug AS parent_slug\n FROM tool_calls tc\n LEFT JOIN turns t ON t.id = tc.turn_id\n LEFT JOIN sessions ps ON ps.id = tc.session_id\n WHERE tc.workflow_run_id = ? AND tc.tool_name = 'Workflow'\n ORDER BY t.seq\n LIMIT 1`,\n )\n .get(runId) as any;\n if (!wf) return null;\n\n // Subagent sessions in this run (same projection as getSession's children, plus the wall-clock\n // fields so the page can show each agent's span and a run-level min/max). LEFT JOIN session_meta for\n // the authoritative type/description/depth — these workflow agents carry none in-transcript.\n const hasMeta = tableExists(db, \"session_meta\");\n const agents = db\n .prepare(\n `SELECT s.id, s.ai_title, s.slug, s.turn_count, s.started_at, s.ended_at, s.duration_ms,\n (SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models\n ${hasMeta ? \", sm.agent_type, sm.agent_description, sm.spawn_depth\" : \", NULL AS agent_type, NULL AS agent_description, NULL AS spawn_depth\"}\n FROM sessions s ${hasMeta ? \"LEFT JOIN session_meta sm ON sm.session_id = s.id\" : \"\"}\n WHERE s.workflow_run_id = ? ORDER BY s.started_at`,\n )\n .all(runId) as any[];\n\n let total_tokens = 0;\n let total_cost = 0;\n let started_at: string | null = null;\n let ended_at: string | null = null;\n for (const a of agents) {\n a.title = a.ai_title || a.slug || null;\n const u = db\n .prepare(\n `SELECT model, SUM(input_tokens) i, SUM(output_tokens) o,\n SUM(cache_creation_input_tokens) cw, SUM(cache_read_input_tokens) cr\n FROM token_usage WHERE session_id = ? GROUP BY model`,\n )\n .all(a.id) as any[];\n a.tokens = u.reduce((acc, r) => acc + r.i + r.o + r.cw + r.cr, 0);\n a.cost = Number(\n u.reduce((acc, r) => acc + costForUsage(r.model, { input_tokens: r.i, output_tokens: r.o, cache_creation_input_tokens: r.cw, cache_read_input_tokens: r.cr }), 0).toFixed(4),\n );\n total_tokens += a.tokens;\n total_cost += a.cost;\n if (a.started_at && (!started_at || a.started_at < started_at)) started_at = a.started_at;\n if (a.ended_at && (!ended_at || a.ended_at > ended_at)) ended_at = a.ended_at;\n }\n const duration_ms = started_at && ended_at ? new Date(ended_at).getTime() - new Date(started_at).getTime() : null;\n\n // The workflow's ACTUAL result arrives later as a `<task-notification>` user message in the\n // launching session — tc.result_summary is only the \"launched in background\" ack. Find that\n // message by the Workflow tool-use id and surface its completion status, summary, result, and\n // failures. (The same task can notify more than once; take the most recent.)\n let completion: { status: string | null; summary: string | null; result: string | null; failures: string | null } | null = null;\n if (wf.tool_use_id && wf.parent_session_id) {\n const ev = db\n .prepare(\n `SELECT raw_json FROM events\n WHERE session_id = ? AND text LIKE '%<task-notification>%' AND text LIKE ?\n ORDER BY timestamp DESC LIMIT 1`,\n )\n .get(wf.parent_session_id, `%${wf.tool_use_id}%`) as any;\n if (ev) {\n const { text } = extractParts(ev.raw_json);\n const t = text ?? \"\";\n completion = { status: xmlTag(t, \"status\"), summary: xmlTag(t, \"summary\"), result: xmlTag(t, \"result\"), failures: xmlTag(t, \"failures\") };\n }\n }\n\n // The runner's own result sidecar (workflow_results), ingested from wf_<id>.json. It's the\n // authoritative record of how the run finished — especially for async runs that never posted a\n // task-notification (completion above stays null) or that failed before fanning out any agents.\n // Sidecar-preferred: it supplies status + the returned result and fills the completion when the\n // transcript has none. Its self-reported roll-up (model, tokens, tool calls, phases, per-item logs,\n // duration, agent count) rides along as `run` so the page can show it even with zero ingested agents.\n const wr = tableExists(db, \"workflow_results\")\n ? (db\n .prepare(\n `SELECT status, summary, default_model, result_json, phases_json, logs_json, progress_json,\n agent_count, total_tokens, total_tool_calls, duration_ms, started_at, ended_at\n FROM workflow_results WHERE run_id = ?`,\n )\n .get(wf.run_id) as any)\n : null;\n\n let run: {\n status: string | null;\n summary: string | null;\n default_model: string | null;\n agent_count: number | null;\n total_tokens: number | null;\n total_tool_calls: number | null;\n duration_ms: number | null;\n started_at: string | null;\n ended_at: string | null;\n phases: unknown;\n logs: unknown;\n progress: unknown;\n } | null = null;\n if (wr) {\n run = {\n status: wr.status ?? null,\n summary: wr.summary ?? null,\n default_model: wr.default_model ?? null,\n agent_count: wr.agent_count ?? null,\n total_tokens: wr.total_tokens ?? null,\n total_tool_calls: wr.total_tool_calls ?? null,\n duration_ms: wr.duration_ms ?? null,\n started_at: wr.started_at ?? null,\n ended_at: wr.ended_at ?? null,\n phases: safeJson(wr.phases_json),\n logs: safeJson(wr.logs_json),\n // The per-phase/per-agent event timeline (workflowProgress) — lets the page render a phase graph\n // with a per-phase descriptor (agent count, models). Absent on older/failed runs → UI falls back.\n progress: safeJson(wr.progress_json),\n };\n if (wr.result_json != null || wr.status) {\n completion = {\n status: wr.status ?? completion?.status ?? null,\n summary: wr.summary ?? completion?.summary ?? null,\n result: wr.result_json ?? completion?.result ?? null,\n failures: completion?.failures ?? null,\n };\n }\n }\n\n return {\n run_id: wf.run_id,\n name: wf.name ?? null,\n status: wr?.status ?? wf.status ?? null,\n result_summary: wf.result_summary ?? null,\n input_json: wf.input_json ?? null,\n completion,\n run,\n parent: { id: wf.parent_session_id, title: wf.parent_ai_title || wf.parent_slug || null, turn_seq: wf.turn_seq ?? null },\n agents,\n stats: { agent_count: agents.length, total_tokens, total_cost: Number(total_cost.toFixed(4)), started_at, ended_at, duration_ms },\n };\n}\n\nexport interface SkillFilters {\n q?: string;\n source?: string;\n project?: string;\n}\n\n/**\n * One row per fired skill (grouped by name), for the Skills list. `version_count` is the number of\n * distinct captured content versions (COUNT(DISTINCT skill_id) ignores firings with no body), while\n * `call_count` counts every firing — so a skill whose body was never captured still appears. Filters\n * (name search, source, project) mirror the sessions list and apply to the firing session.\n */\nexport function listSkills(db: DB, f: SkillFilters = {}) {\n const where = [\"tc.tool_name = 'Skill'\", \"tc.skill_name IS NOT NULL\"];\n const params: any[] = [];\n // Search matches the skill name OR any captured version body, so a query for a phrase that only\n // appears inside a SKILL.md still surfaces the skill. (skills.body is the normalized body; joined by\n // name rather than skill_id so firings with no captured body still match on name.)\n if (f.q && f.q.trim()) {\n const like = `%${f.q.trim()}%`;\n where.push(\"(tc.skill_name LIKE ? OR EXISTS (SELECT 1 FROM skills sk WHERE sk.name = tc.skill_name AND sk.body LIKE ?))\");\n params.push(like, like);\n }\n if (f.source) (where.push(\"s.source_id = ?\"), params.push(f.source));\n if (f.project) (where.push(\"s.project_id = ?\"), params.push(f.project));\n const rows = db\n .prepare(\n `SELECT tc.skill_name AS name,\n COUNT(*) AS call_count,\n COUNT(DISTINCT tc.skill_id) AS version_count,\n MAX(e.timestamp) AS last_fired,\n GROUP_CONCAT(DISTINCT s.source_id) AS sources\n FROM tool_calls tc\n JOIN sessions s ON s.id = tc.session_id\n LEFT JOIN events e ON e.uuid = tc.event_uuid\n WHERE ${where.join(\" AND \")}\n GROUP BY tc.skill_name\n ORDER BY call_count DESC, name`,\n )\n .all(...params) as any[];\n for (const r of rows) r.sources = r.sources ? String(r.sources).split(\",\").filter(Boolean) : [];\n return rows;\n}\n\n/**\n * All data for one skill (by name): its content versions (most-recent first, each with body +\n * firing count) and the sessions that fired it, tagged with which version (`version_id`) so the UI\n * can map the session list to the selected version. Returns null (→ 404) when the name never fired.\n */\nexport function getSkill(db: DB, name: string) {\n const versions = db\n .prepare(\n `SELECT sk.id, sk.base_dir, sk.summary, sk.body, sk.body_bytes, sk.first_seen, sk.last_seen,\n (SELECT COUNT(*) FROM tool_calls tc WHERE tc.skill_id = sk.id) AS call_count\n FROM skills sk WHERE sk.name = ? ORDER BY sk.last_seen DESC, sk.id`,\n )\n .all(name) as any[];\n\n const sessions = db\n .prepare(\n `SELECT s.id, s.ai_title, s.slug, s.source_id, s.started_at, p.path AS project_path,\n tc.skill_id AS version_id, MIN(e.timestamp) AS fired_at, COUNT(*) AS fire_count\n FROM tool_calls tc\n JOIN sessions s ON s.id = tc.session_id\n LEFT JOIN projects p ON p.id = s.project_id\n LEFT JOIN events e ON e.uuid = tc.event_uuid\n WHERE tc.tool_name = 'Skill' AND tc.skill_name = ?\n GROUP BY s.id, tc.skill_id\n ORDER BY fired_at DESC`,\n )\n .all(name) as any[];\n\n if (!versions.length && !sessions.length) return null;\n for (const s of sessions) s.title = s.ai_title || s.slug || null;\n const call_count = sessions.reduce((a, s) => a + (s.fire_count as number), 0);\n return { name, versions, sessions, call_count };\n}\n\n// ---- Security findings (ADR-017) + triage (ADR-018) ----------------------\n\nexport interface FindingFilters {\n severity?: string;\n category?: string;\n rule?: string;\n session?: string;\n source?: string;\n project?: string;\n from?: string;\n to?: string;\n /** open (default; excludes dismissed + muted) | dismissed | muted | all. */\n status?: \"open\" | \"dismissed\" | \"muted\" | \"all\";\n /** Sort the whole filtered list before pagination. Defaults to \"severity\" (most-severe first). */\n sort?: \"severity\" | \"session\" | \"rule\" | \"category\" | \"time\";\n dir?: \"asc\" | \"desc\";\n limit: number;\n offset: number;\n}\n\n// Rank severities inside SQL so the list can be severity-ordered and paginated without materializing\n// everything (mirrors SEVERITY_ORDER in core: info=1 … critical=5).\nconst SEVERITY_RANK_SQL =\n \"CASE f.severity WHEN 'critical' THEN 5 WHEN 'high' THEN 4 WHEN 'medium' THEN 3 WHEN 'low' THEN 2 WHEN 'info' THEN 1 ELSE 0 END\";\n\n// A finding is muted when a muted_rules row matches its rule at global/project/source scope. Only valid\n// when the triage DB is ATTACHed (createApp does this); guarded by triageAttached() everywhere it's used.\nconst MUTED_SQL = `EXISTS (SELECT 1 FROM triage.muted_rules m WHERE m.rule_id = f.rule_id\n AND (m.scope='global' OR (m.scope='project' AND m.scope_id = s.project_id) OR (m.scope='source' AND m.scope_id = s.source_id)))`;\n\n/** Whether the writable triage store is ATTACHed to this (read) connection — gates all triage SQL. */\nexport function triageAttached(db: DB): boolean {\n return (db.prepare(\"PRAGMA database_list\").all() as Array<{ name: string }>).some((r) => r.name === \"triage\");\n}\n\n/** WHERE fragments shared by list + summary: user filters (no status) over findings f / sessions s. */\nfunction findingWhere(f: FindingFilters): { sql: string[]; params: any[] } {\n const sql: string[] = [];\n const params: any[] = [];\n if (f.severity) (sql.push(\"f.severity = ?\"), params.push(f.severity));\n if (f.category) (sql.push(\"f.category = ?\"), params.push(f.category));\n if (f.rule) (sql.push(\"f.rule_id = ?\"), params.push(f.rule));\n if (f.session) (sql.push(\"f.session_id = ?\"), params.push(f.session));\n if (f.source) (sql.push(\"s.source_id = ?\"), params.push(f.source));\n if (f.project) (sql.push(\"s.project_id = ?\"), params.push(f.project));\n // Compare on the DATE part so a picked day is inclusive on both ends — `to = 2026-07-14` must include\n // 2026-07-14 events (a plain `started_at <= '2026-07-14'` would exclude that whole day's timestamps).\n if (f.from) (sql.push(\"date(s.started_at) >= date(?)\"), params.push(f.from));\n if (f.to) (sql.push(\"date(s.started_at) <= date(?)\"), params.push(f.to));\n return { sql, params };\n}\n\n/** Status → WHERE fragment over the dismissed LEFT JOIN (`d`) + MUTED_SQL. Empty when triage absent. */\nfunction statusWhere(status: FindingFilters[\"status\"], attached: boolean): string {\n if (!attached) return status === \"dismissed\" || status === \"muted\" ? \"1 = 0\" : \"\"; // no triage → nothing dismissed/muted\n switch (status) {\n case \"dismissed\":\n return \"d.finding_id IS NOT NULL\";\n case \"muted\":\n return `d.finding_id IS NULL AND ${MUTED_SQL}`;\n case \"all\":\n return \"\";\n default: // open\n return `d.finding_id IS NULL AND NOT ${MUTED_SQL}`;\n }\n}\n\n/**\n * The browsable findings list (GET /api/security/findings), filtered/sorted/paged. Each row carries\n * enough session context (title, project, source, when) to render and link without a second fetch, plus\n * its triage state (dismissed flag/note/when) when the triage store is attached. Default status hides\n * dismissed + muted so real, un-triaged findings surface. Guarded for a pre-ingest read-only DB.\n */\nexport function listFindings(db: DB, f: FindingFilters) {\n if (!tableExists(db, \"findings\")) return { total: 0, findings: [] };\n const attached = triageAttached(db);\n\n const w = findingWhere(f);\n const st = statusWhere(f.status ?? \"open\", attached);\n const where = [...w.sql, ...(st ? [st] : [])];\n const whereSql = where.length ? `WHERE ${where.join(\" AND \")}` : \"\";\n const dismissJoin = attached ? \"LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id\" : \"\";\n const triageCols = attached\n ? `, (d.finding_id IS NOT NULL) AS dismissed, d.note AS dismiss_note, d.dismissed_at, ${MUTED_SQL} AS muted`\n : `, 0 AS dismissed, NULL AS dismiss_note, NULL AS dismissed_at, 0 AS muted`;\n\n const total = (\n db\n .prepare(`SELECT COUNT(*) n FROM findings f JOIN sessions s ON s.id = f.session_id ${dismissJoin} ${whereSql}`)\n .get(...w.params) as any\n ).n;\n\n const ORDER: Record<string, string> = {\n severity: SEVERITY_RANK_SQL,\n session: \"f.session_id\",\n rule: \"f.rule_id\",\n category: \"f.category\",\n time: \"s.started_at\",\n };\n const orderCol = ORDER[f.sort ?? \"severity\"] ?? SEVERITY_RANK_SQL;\n const dir = f.dir === \"asc\" ? \"ASC\" : \"DESC\";\n\n const findings = db\n .prepare(\n `SELECT f.id, f.session_id, f.tool_call_id, f.event_uuid, f.turn_id, f.rule_id, f.category,\n f.framework_ref, f.severity, f.title, f.evidence, tc.tool_name,\n COALESCE(s.ai_title, s.slug) AS session_title, s.source_id, s.is_sidechain,\n s.started_at, p.path AS project_path, s.project_id ${triageCols}\n FROM findings f\n JOIN sessions s ON s.id = f.session_id\n LEFT JOIN projects p ON p.id = s.project_id\n LEFT JOIN tool_calls tc ON tc.id = f.tool_call_id\n ${dismissJoin}\n ${whereSql}\n ORDER BY ${orderCol} ${dir}, ${SEVERITY_RANK_SQL} DESC, f.id ASC\n LIMIT ? OFFSET ?`,\n )\n .all(...w.params, f.limit, f.offset);\n\n return { total, findings };\n}\n\n/**\n * Roll-up for the /security page header + the Dashboard KPI. Counts (by severity/category/rule,\n * sessions flagged) are computed over **open** findings — dismissed + muted excluded — so the KPIs show\n * what still needs review; `dismissed` and `muted` totals ride along for the triage affordances. The\n * framework reference content (core) powers the \"what & why\" explainers. Guarded for a pre-ingest DB.\n */\nexport function securitySummary(db: DB) {\n const categories = SECURITY_CATEGORIES;\n if (!tableExists(db, \"findings\")) {\n return { total: 0, sessions_flagged: 0, dismissed: 0, muted: 0, by_severity: [], by_category: [], by_rule: [], categories };\n }\n const attached = triageAttached(db);\n const dismissJoin = attached ? \"LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id\" : \"\";\n const base = `FROM findings f JOIN sessions s ON s.id = f.session_id ${dismissJoin}`;\n const openWhere = attached ? `WHERE d.finding_id IS NULL AND NOT ${MUTED_SQL}` : \"\";\n\n const scalar = (sql: string) => (db.prepare(sql).get() as any).n as number;\n const total = scalar(`SELECT COUNT(*) n ${base} ${openWhere}`);\n const sessions_flagged = scalar(`SELECT COUNT(DISTINCT f.session_id) n ${base} ${openWhere}`);\n const dismissed = attached ? scalar(`SELECT COUNT(*) n FROM findings f LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id WHERE d.finding_id IS NOT NULL`) : 0;\n const muted = attached ? scalar(`SELECT COUNT(*) n ${base} WHERE d.finding_id IS NULL AND ${MUTED_SQL}`) : 0;\n\n const by_severity = db.prepare(`SELECT f.severity AS severity, COUNT(*) n ${base} ${openWhere} GROUP BY f.severity ORDER BY ${SEVERITY_RANK_SQL} DESC`).all();\n const by_category = db.prepare(`SELECT f.category AS category, COUNT(*) n ${base} ${openWhere} GROUP BY f.category ORDER BY n DESC`).all();\n const by_rule = db\n .prepare(\n `SELECT f.rule_id AS rule_id, f.category AS category, f.title AS title, COUNT(*) n, MAX(${SEVERITY_RANK_SQL}) AS rank\n ${base} ${openWhere} GROUP BY f.rule_id ORDER BY rank DESC, n DESC`,\n )\n .all();\n return { total, sessions_flagged, dismissed, muted, by_severity, by_category, by_rule, categories };\n}\n","/**\n * Agent Lens — Fastify app factory (ADR-005: read-only over the SQLite store).\n *\n * `createApp(db, opts)` builds the route tree against an already-open DB handle so the same app can\n * be driven by the CLI entry (index.ts, with .listen) and by tests (app.inject, no socket).\n */\nimport { existsSync } from \"node:fs\";\nimport Fastify, { type FastifyInstance } from \"fastify\";\nimport fastifyStatic from \"@fastify/static\";\nimport { sessionToMarkdown, type MarkdownEvent } from \"@agent-lens/core\";\nimport { type DB, lastIngested, schemaStatus, listSources, listProjects, listModels, listSessions, getSession, getWorkflow, listSkills, getSkill, listFindings, securitySummary } from \"./db.js\";\nimport { dashboardOverview, dashboardTimeseries, dashboardBreakdowns, type DashFilters } from \"./dashboard.js\";\nimport { originAllowed, runRefresh } from \"./refresh.js\";\nimport { openTriage, dismiss, reopen, muteRule, unmute, listMutes, type TriageDB, type MuteScope } from \"./triage.js\";\n\nexport interface CreateAppOpts {\n /** Absolute path to the built web SPA; when present it is served with a history fallback. */\n webDist?: string | null;\n /** Path to the writable security-triage store (ADR-018). When set, it is opened read-write for\n * triage writes and ATTACHed to the read handle so the findings list can JOIN triage state. */\n triageDbPath?: string | null;\n}\n\nexport async function createApp(db: DB, opts: CreateAppOpts = {}): Promise<FastifyInstance> {\n const app = Fastify({ logger: false });\n\n // Security triage store (ADR-018): a separate writable SQLite, ATTACHed to the read handle so the\n // findings queries can JOIN dismissed/muted state, while writes go through `triageDb`. The analytics\n // handle stays read-only. Absent → the security endpoints degrade to the un-triaged (all-open) view.\n let triageDb: TriageDB | null = null;\n if (opts.triageDbPath) {\n triageDb = openTriage(opts.triageDbPath); // creates the file + schema before we ATTACH it\n db.exec(`ATTACH DATABASE '${opts.triageDbPath.replace(/'/g, \"''\")}' AS triage`);\n app.addHook(\"onClose\", async () => triageDb?.close());\n }\n\n // Shared CSRF + availability guard for the triage write routes (same posture as /api/refresh).\n const guardWrite = (req: any, reply: any): boolean => {\n if (!originAllowed(req.headers.origin)) {\n reply.code(403).send({ error: { code: \"FORBIDDEN_ORIGIN\", message: \"cross-origin write blocked\" } });\n return false;\n }\n if (!triageDb) {\n reply.code(503).send({ error: { code: \"TRIAGE_UNAVAILABLE\", message: \"triage store not configured\" } });\n return false;\n }\n return true;\n };\n\n app.get(\"/api/health\", async () => {\n const s = schemaStatus(db);\n return { ok: true, last_ingested: lastIngested(db), schema_version: s.db_version, schema_stale: s.stale };\n });\n\n // The one write-action on this read-only server: run a collect + ingest pass on the host so the UI\n // can pull in new transcripts on demand (ADR-015). Guarded against cross-site CSRF (Origin) and\n // concurrent runs (single-instance lock → 409). See refresh.ts.\n app.post(\"/api/refresh\", async (req, reply) => {\n if (!originAllowed(req.headers.origin)) {\n return reply.code(403).send({ error: { code: \"FORBIDDEN_ORIGIN\", message: \"cross-origin refresh blocked\" } });\n }\n let result;\n try {\n result = runRefresh();\n } catch (e: any) {\n return reply.code(500).send({ error: { code: \"REFRESH_FAILED\", message: String(e?.message ?? e) } });\n }\n if (!result) {\n return reply.code(409).send({ error: { code: \"REFRESH_IN_PROGRESS\", message: \"a collect/ingest run is already in progress\" } });\n }\n return { ok: true, collected: result.collected, last_ingested: lastIngested(db) };\n });\n app.get(\"/api/sources\", async () => listSources(db));\n app.get(\"/api/projects\", async () => listProjects(db));\n app.get(\"/api/models\", async () => listModels(db));\n\n // Dashboard aggregates (Phase 4). All read-only; filters: source, from, to.\n const dashFilters = (req: any): DashFilters => {\n const q = req.query as Record<string, string>;\n return { source: q.source, from: q.from, to: q.to };\n };\n app.get(\"/api/dashboard/overview\", async (req) => dashboardOverview(db, dashFilters(req)));\n app.get(\"/api/dashboard/timeseries\", async (req) => {\n const q = req.query as Record<string, string>;\n return dashboardTimeseries(db, dashFilters(req), q.bucket);\n });\n app.get(\"/api/dashboard/breakdowns\", async (req) => dashboardBreakdowns(db, dashFilters(req)));\n\n app.get(\"/api/sessions\", async (req) => {\n const q = req.query as Record<string, string>;\n return listSessions(db, {\n source: q.source,\n project: q.project,\n model: q.model,\n q: q.q,\n from: q.from,\n to: q.to,\n kind: q.kind === \"main\" || q.kind === \"subagent\" ? q.kind : undefined,\n severity: q.severity ? q.severity.split(\",\").filter(Boolean) : undefined,\n errorType: q.error_type ? q.error_type.split(\",\").filter(Boolean) : undefined,\n sort: ([\"started\", \"title\", \"turns\", \"tokens\", \"cost\", \"duration\", \"errors\", \"security\"] as const).find((s) => s === q.sort),\n dir: q.dir === \"asc\" ? \"asc\" : q.dir === \"desc\" ? \"desc\" : undefined,\n limit: Math.min(Number(q.limit) || 50, 200),\n offset: Number(q.offset) || 0,\n });\n });\n\n app.get(\"/api/sessions/:id\", async (req, reply) => {\n const { id } = req.params as { id: string };\n const result = getSession(db, id);\n if (!result) return reply.code(404).send({ error: \"not found\" });\n return result;\n });\n\n app.get(\"/api/workflows/:run_id\", async (req, reply) => {\n const { run_id } = req.params as { run_id: string };\n const result = getWorkflow(db, run_id);\n if (!result) return reply.code(404).send({ error: \"not found\" });\n return result;\n });\n\n // Security findings (ADR-017). Read-only; browsable list + roll-up summary for the /security page,\n // the transcript inline badges (served via /api/sessions/:id), and the Dashboard KPI.\n app.get(\"/api/security/summary\", async () => securitySummary(db));\n const findingFilters = (q: Record<string, string>) => ({\n severity: q.severity,\n category: q.category,\n rule: q.rule,\n session: q.session,\n source: q.source,\n project: q.project,\n from: q.from,\n to: q.to,\n status: ([\"open\", \"dismissed\", \"muted\", \"all\"] as const).find((s) => s === q.status),\n });\n app.get(\"/api/security/findings\", async (req) => {\n const q = req.query as Record<string, string>;\n return listFindings(db, {\n ...findingFilters(q),\n sort: ([\"severity\", \"session\", \"rule\", \"category\", \"time\"] as const).find((s) => s === q.sort),\n dir: q.dir === \"asc\" ? \"asc\" : q.dir === \"desc\" ? \"desc\" : undefined,\n limit: Math.min(Number(q.limit) || 50, 5000),\n offset: Number(q.offset) || 0,\n });\n });\n\n // Triage writes (ADR-018): CSRF-guarded, loopback-only, same posture as /api/refresh.\n app.get(\"/api/security/mutes\", async () => (triageDb ? listMutes(triageDb) : []));\n app.post(\"/api/security/dismiss\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { ids?: string[]; note?: string };\n const n = dismiss(triageDb!, Array.isArray(b.ids) ? b.ids : [], b.note ?? null);\n return { ok: true, dismissed: n };\n });\n app.post(\"/api/security/reopen\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { ids?: string[] };\n const n = reopen(triageDb!, Array.isArray(b.ids) ? b.ids : []);\n return { ok: true, reopened: n };\n });\n // Dismiss every OPEN finding matching a filter (bulk cleanup) — collects ids server-side, then marks.\n app.post(\"/api/security/dismiss-matching\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { filter?: Record<string, string>; note?: string };\n const page = listFindings(db, { ...findingFilters(b.filter ?? {}), status: \"open\", limit: 100000, offset: 0 });\n const ids = (page.findings as Array<{ id: string }>).map((r) => r.id);\n const n = dismiss(triageDb!, ids, b.note ?? null);\n return { ok: true, dismissed: n };\n });\n app.post(\"/api/security/mute\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { rule_id?: string; scope?: string; scope_id?: string; note?: string };\n if (!b.rule_id) return reply.code(400).send({ error: { code: \"BAD_REQUEST\", message: \"rule_id required\" } });\n const scope = ([\"global\", \"project\", \"source\"] as const).find((s) => s === b.scope) ?? \"global\";\n muteRule(triageDb!, b.rule_id, scope as MuteScope, b.scope_id ?? \"\", b.note ?? null);\n return { ok: true };\n });\n app.post(\"/api/security/unmute\", async (req, reply) => {\n if (!guardWrite(req, reply)) return reply;\n const b = (req.body ?? {}) as { rule_id?: string; scope?: string; scope_id?: string };\n if (!b.rule_id) return reply.code(400).send({ error: { code: \"BAD_REQUEST\", message: \"rule_id required\" } });\n const scope = ([\"global\", \"project\", \"source\"] as const).find((s) => s === b.scope) ?? \"global\";\n const n = unmute(triageDb!, b.rule_id, scope as MuteScope, b.scope_id ?? \"\");\n return { ok: true, unmuted: n };\n });\n\n app.get(\"/api/skills\", async (req) => {\n const q = req.query as Record<string, string>;\n return listSkills(db, { q: q.q, source: q.source, project: q.project });\n });\n\n app.get(\"/api/skills/:name\", async (req, reply) => {\n const { name } = req.params as { name: string };\n const result = getSkill(db, decodeURIComponent(name));\n if (!result) return reply.code(404).send({ error: \"not found\" });\n return result;\n });\n\n app.get(\"/api/sessions/:id/export.md\", async (req, reply) => {\n const { id } = req.params as { id: string };\n const result = getSession(db, id);\n if (!result) return reply.code(404).send({ error: \"not found\" });\n const s = result.session;\n const events: MarkdownEvent[] = result.events.map((e) => ({\n type: e.type,\n role: e.role,\n timestamp: e.timestamp,\n text: e.text,\n thinking: e.thinking,\n toolCalls: e.toolCalls.map((t: any) => ({\n tool_name: t.tool_name,\n skill_name: t.skill_name,\n agent_type: t.agent_type,\n input_json: t.input_json,\n status: t.status,\n })),\n }));\n const md = sessionToMarkdown(\n {\n id: s.id,\n title: s.title,\n source: s.source_id,\n project: s.project_path,\n model: null,\n started_at: s.started_at,\n ended_at: s.ended_at,\n },\n events,\n );\n reply\n .header(\"content-type\", \"text/markdown; charset=utf-8\")\n .header(\"content-disposition\", `attachment; filename=\"session-${id.slice(0, 8)}.md\"`)\n .send(md);\n });\n\n // Serve the built SPA (if present) with a history fallback for client routes.\n if (opts.webDist && existsSync(opts.webDist)) {\n await app.register(fastifyStatic, { root: opts.webDist });\n app.setNotFoundHandler((req, reply) => {\n if (req.url.startsWith(\"/api/\")) return reply.code(404).send({ error: \"not found\" });\n return reply.sendFile(\"index.html\");\n });\n }\n\n return app;\n}\n","/**\n * Agent Lens — dashboard aggregation queries (Phase 4). Read-only; everything is computed as\n * server-side aggregates (GROUP BY over indexed columns) so the charts plot bounded series, not\n * raw rows — this is what lets the UI scale to years of data. Token series are always kept split\n * (input / output / cache-creation / cache-read); cache-read is never folded into a single\n * \"tokens\" number because it dominates and misleads. Cost is derived via the shared pricing table.\n */\nimport { costForUsage, rateForModel, errorKind } from \"@agent-lens/core\";\nimport type { DB } from \"./db.js\";\n\nexport interface DashFilters {\n source?: string;\n from?: string;\n to?: string;\n}\n\n/** WHERE clause + params over the `sessions` alias `s` (started_at / source_id). */\nfunction sessionWhere(f: DashFilters): { sql: string; params: any[] } {\n const where: string[] = [];\n const params: any[] = [];\n if (f.source) (where.push(\"s.source_id = ?\"), params.push(f.source));\n // Date-inclusive on both ends (compare the DATE part) so a picked `to` day includes that day's\n // events — a plain `started_at <= '2026-07-14'` would drop everything after 2026-07-14T00:00.\n if (f.from) (where.push(\"date(s.started_at) >= date(?)\"), params.push(f.from));\n if (f.to) (where.push(\"date(s.started_at) <= date(?)\"), params.push(f.to));\n return { sql: where.length ? `WHERE ${where.join(\" AND \")}` : \"\", params };\n}\n\ninterface Split {\n input: number;\n output: number;\n cache_creation: number;\n cache_read: number;\n}\nconst zeroSplit = (): Split => ({ input: 0, output: 0, cache_creation: 0, cache_read: 0 });\n\n/** Sum a grouped usage row into a split accumulator. */\nfunction addUsage(acc: Split, r: any) {\n acc.input += r.i ?? 0;\n acc.output += r.o ?? 0;\n acc.cache_creation += r.cw ?? 0;\n acc.cache_read += r.cr ?? 0;\n}\n\nfunction pct(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const rank = Math.ceil((p / 100) * sorted.length) - 1;\n return sorted[Math.min(Math.max(rank, 0), sorted.length - 1)]!;\n}\n\nexport function dashboardOverview(db: DB, f: DashFilters) {\n const w = sessionWhere(f);\n\n const counts = db\n .prepare(\n `SELECT\n COUNT(*) sessions,\n SUM(CASE WHEN is_sidechain=0 THEN 1 ELSE 0 END) main,\n SUM(CASE WHEN is_sidechain=1 THEN 1 ELSE 0 END) subagent,\n SUM(turn_count) turns,\n COUNT(DISTINCT project_id) projects\n FROM sessions s ${w.sql}`,\n )\n .get(...w.params) as any;\n\n const toolCount = (\n db\n .prepare(`SELECT COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id ${w.sql}`)\n .get(...w.params) as any\n ).n;\n\n // Per-model usage → token split + cache-aware cost; track unpriced models honestly.\n const usage = db\n .prepare(\n `SELECT t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,\n SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr\n FROM token_usage t JOIN sessions s ON s.id = t.session_id ${w.sql}\n GROUP BY t.model`,\n )\n .all(...w.params) as any[];\n const tokens = zeroSplit();\n let cost = 0;\n const unpriced: string[] = [];\n for (const u of usage) {\n addUsage(tokens, u);\n cost += costForUsage(u.model, { input_tokens: u.i, output_tokens: u.o, cache_creation_input_tokens: u.cw, cache_read_input_tokens: u.cr });\n if (u.model && !rateForModel(u.model) && (u.i || u.o || u.cw || u.cr)) unpriced.push(u.model);\n }\n const totalTokens = tokens.input + tokens.output + tokens.cache_creation + tokens.cache_read;\n\n // Turn-duration percentiles (work cadence), excluding null durations.\n const durs = (\n db\n .prepare(\n `SELECT tn.duration_ms d FROM turns tn JOIN sessions s ON s.id = tn.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tn.duration_ms IS NOT NULL ORDER BY tn.duration_ms`,\n )\n .all(...w.params) as any[]\n ).map((r) => r.d as number);\n\n return {\n range: { from: f.from ?? null, to: f.to ?? null, source: f.source ?? null },\n sessions: counts.sessions ?? 0,\n sessions_main: counts.main ?? 0,\n sessions_subagent: counts.subagent ?? 0,\n turns: counts.turns ?? 0,\n projects: counts.projects ?? 0,\n tool_calls: toolCount ?? 0,\n tokens, // {input, output, cache_creation, cache_read}\n total_tokens: totalTokens,\n cache_read_ratio: totalTokens ? tokens.cache_read / totalTokens : 0,\n cost: Number(cost.toFixed(4)),\n unpriced_models: [...new Set(unpriced)].sort(),\n turn_duration_ms: { p50: pct(durs, 50), p95: pct(durs, 95), count: durs.length },\n };\n}\n\ntype Bucket = \"day\" | \"week\" | \"month\";\n\n/** Pick a bucket so the series stays small regardless of how wide the range is. */\nfunction chooseBucket(db: DB, f: DashFilters): Bucket {\n const w = sessionWhere(f);\n const r = db\n .prepare(`SELECT MIN(started_at) mn, MAX(started_at) mx FROM sessions s ${w.sql}`)\n .get(...w.params) as any;\n if (!r?.mn || !r?.mx) return \"day\";\n const spanDays = (Date.parse(r.mx) - Date.parse(r.mn)) / 86_400_000;\n if (spanDays <= 92) return \"day\";\n if (spanDays <= 730) return \"week\";\n return \"month\";\n}\n\nconst BUCKET_EXPR: Record<Bucket, string> = {\n day: \"strftime('%Y-%m-%d', s.started_at)\",\n week: \"strftime('%Y-W%W', s.started_at)\",\n month: \"strftime('%Y-%m', s.started_at)\",\n};\n\nexport function dashboardTimeseries(db: DB, f: DashFilters, bucketParam?: string) {\n const bucket: Bucket = bucketParam === \"day\" || bucketParam === \"week\" || bucketParam === \"month\" ? bucketParam : chooseBucket(db, f);\n const w = sessionWhere(f);\n const expr = BUCKET_EXPR[bucket];\n\n type Row = { bucket: string } & Split & { cost: number; sessions: number; turns: number; failures: number; rejections: number };\n const byBucket = new Map<string, Row>();\n const get = (b: string): Row => {\n let r = byBucket.get(b);\n if (!r) {\n r = { bucket: b, ...zeroSplit(), cost: 0, sessions: 0, turns: 0, failures: 0, rejections: 0 };\n byBucket.set(b, r);\n }\n return r;\n };\n\n for (const u of db\n .prepare(\n `SELECT ${expr} b, t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,\n SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr\n FROM token_usage t JOIN sessions s ON s.id = t.session_id\n ${w.sql} GROUP BY b, t.model`,\n )\n .all(...w.params) as any[]) {\n if (!u.b) continue;\n const r = get(u.b);\n addUsage(r, u);\n r.cost += costForUsage(u.model, { input_tokens: u.i, output_tokens: u.o, cache_creation_input_tokens: u.cw, cache_read_input_tokens: u.cr });\n }\n for (const s of db.prepare(`SELECT ${expr} b, COUNT(*) n FROM sessions s ${w.sql} GROUP BY b`).all(...w.params) as any[]) {\n if (s.b) get(s.b).sessions = s.n;\n }\n for (const t of db\n .prepare(`SELECT ${expr} b, COUNT(*) n FROM turns tn JOIN sessions s ON s.id = tn.session_id ${w.sql} GROUP BY b`)\n .all(...w.params) as any[]) {\n if (t.b) get(t.b).turns = t.n;\n }\n // Errored tool calls per bucket, split into genuine failures vs user/guardrail rejections (kind\n // derived from the stored error_type). Bucketed by the session's date, same as every other series.\n for (const e of db\n .prepare(\n `SELECT ${expr} b, tc.error_type et, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tc.status = 'error' GROUP BY b, tc.error_type`,\n )\n .all(...w.params) as any[]) {\n if (!e.b) continue;\n const r = get(e.b);\n if (e.et && errorKind(e.et) === \"rejection\") r.rejections += e.n;\n else r.failures += e.n;\n }\n\n const series = [...byBucket.values()].sort((a, b) => a.bucket.localeCompare(b.bucket));\n for (const r of series) r.cost = Number(r.cost.toFixed(4));\n return { bucket, series };\n}\n\nexport function dashboardBreakdowns(db: DB, f: DashFilters) {\n const w = sessionWhere(f);\n\n const modelRows = db\n .prepare(\n `SELECT t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,\n SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr,\n COUNT(DISTINCT t.session_id) sessions\n FROM token_usage t JOIN sessions s ON s.id = t.session_id ${w.sql}\n GROUP BY t.model ORDER BY (SUM(t.input_tokens)+SUM(t.output_tokens)+SUM(t.cache_creation_input_tokens)+SUM(t.cache_read_input_tokens)) DESC`,\n )\n .all(...w.params) as any[];\n const byModel = modelRows.map((u) => ({\n model: u.model,\n tokens: { input: u.i ?? 0, output: u.o ?? 0, cache_creation: u.cw ?? 0, cache_read: u.cr ?? 0 },\n total_tokens: (u.i ?? 0) + (u.o ?? 0) + (u.cw ?? 0) + (u.cr ?? 0),\n cost: Number(costForUsage(u.model, { input_tokens: u.i, output_tokens: u.o, cache_creation_input_tokens: u.cw, cache_read_input_tokens: u.cr }).toFixed(4)),\n sessions: u.sessions ?? 0,\n priced: !!rateForModel(u.model),\n }));\n\n const bySource = db\n .prepare(\n `SELECT COALESCE(s.source_id, '(none)') source, COUNT(*) sessions, SUM(s.turn_count) turns\n FROM sessions s ${w.sql} GROUP BY s.source_id ORDER BY sessions DESC`,\n )\n .all(...w.params);\n\n // Category / complexity over MAIN sessions only (subagents inherit the parent's task).\n const mainW = { sql: w.sql ? w.sql + \" AND s.is_sidechain = 0\" : \"WHERE s.is_sidechain = 0\", params: w.params };\n const byCategory = db\n .prepare(\n `SELECT c.category, COUNT(*) n FROM classifications c JOIN sessions s ON s.id = c.target_id\n ${mainW.sql} GROUP BY c.category ORDER BY n DESC`,\n )\n .all(...mainW.params);\n const byComplexity = db\n .prepare(\n `SELECT c.complexity_band band, COUNT(*) n FROM classifications c JOIN sessions s ON s.id = c.target_id\n ${mainW.sql} GROUP BY c.complexity_band ORDER BY n DESC`,\n )\n .all(...mainW.params);\n\n const tools = db\n .prepare(\n `SELECT tc.tool_name name, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql} GROUP BY tc.tool_name ORDER BY n DESC LIMIT 20`,\n )\n .all(...w.params);\n\n const skills = db\n .prepare(\n `SELECT tc.skill_name name, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tc.skill_name IS NOT NULL GROUP BY tc.skill_name ORDER BY n DESC LIMIT 20`,\n )\n .all(...w.params);\n\n // Per-version breakdown for the grouped skill bar chart: each captured version's firing count,\n // with the version id + its last-seen time so the chart can stack/hover and deep-link to\n // /skill/:name?v=<id>. Same source/from/to filter as the by-name `skills` query above.\n const skillVersions = db\n .prepare(\n `SELECT tc.skill_name name, sk.id version_id, sk.summary, sk.last_seen, COUNT(*) n\n FROM tool_calls tc\n JOIN skills sk ON sk.id = tc.skill_id\n JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tc.skill_id IS NOT NULL\n GROUP BY tc.skill_name, sk.id ORDER BY name, n DESC`,\n )\n .all(...w.params);\n\n // Subagent fan-out: spawns by type, plus a per-(main-)session histogram of subagent calls.\n const subagentByType = db\n .prepare(\n `SELECT tc.agent_type type, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tc.agent_type IS NOT NULL GROUP BY tc.agent_type ORDER BY n DESC`,\n )\n .all(...w.params);\n const perSession = db\n .prepare(\n `SELECT COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} s.is_sidechain = 0 AND tc.tool_name IN ('Agent','Task')\n GROUP BY tc.session_id`,\n )\n .all(...w.params) as any[];\n const counts = perSession.map((r) => r.n as number);\n const subagentFanout = {\n by_type: subagentByType,\n sessions_with_subagents: counts.length,\n total_spawns: counts.reduce((a, b) => a + b, 0),\n max_per_session: counts.length ? Math.max(...counts) : 0,\n avg_per_session: counts.length ? Number((counts.reduce((a, b) => a + b, 0) / counts.length).toFixed(2)) : 0,\n };\n\n // Errored tool calls by heuristic error_type, with failures-vs-rejections totals. error_type is\n // populated only for status='error' rows (see errors.ts); the raw count is authoritative, the bucket\n // is the heuristic. Ordered most-frequent first.\n const errorRows = db\n .prepare(\n `SELECT COALESCE(tc.error_type, 'other') type, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id\n ${w.sql ? w.sql + \" AND\" : \"WHERE\"} tc.status = 'error' GROUP BY tc.error_type ORDER BY n DESC`,\n )\n .all(...w.params) as Array<{ type: string; n: number }>;\n let failures = 0;\n let rejections = 0;\n const byType = errorRows.map((r) => {\n const kind = errorKind(r.type as any);\n if (kind === \"rejection\") rejections += r.n;\n else failures += r.n;\n return { type: r.type, kind, n: r.n };\n });\n const errorTypes = { by_type: byType, failures, rejections };\n\n return { by_model: byModel, by_source: bySource, by_category: byCategory, by_complexity: byComplexity, tools, skills, skill_versions: skillVersions, subagent_fanout: subagentFanout, error_types: errorTypes };\n}\n","/**\n * `POST /api/refresh` support — the one write-action on an otherwise read-only server (ADR-005).\n * Runs a single collect + ingest pass on the host so the UI can pull in new transcripts on demand,\n * without waiting for the scheduled/`watch` collector. See ADR-015 for the scoped exception.\n *\n * Two guards keep this safe on an unauthenticated loopback server:\n * - `originAllowed` blocks cross-site CSRF (a web page you visit POSTing to 127.0.0.1).\n * - the shared single-instance lock stops it racing a scheduled/`watch`/CLI run.\n * The server's own DB handle stays read-only; ingest opens its own short-lived write connection\n * (WAL permits one writer alongside readers), so the read-only invariant of THIS handle holds.\n */\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { acquireLock, collectAll, findRepoRoot, resolveDataDir, type CollectStats } from \"@agent-lens/core\";\nimport { runIngest } from \"@agent-lens/ingest\";\n\nexport interface RefreshResult {\n ok: true;\n collected: CollectStats;\n}\n\n/**\n * CSRF guard for the loopback server. Allow requests with no `Origin` (non-browser callers like curl\n * or the CLI — not a CSRF vector) and same-machine origins; reject anything cross-site. The server\n * already binds loopback only (ADR-005); this stops a page you visit from driving localhost.\n */\nexport function originAllowed(origin: string | undefined): boolean {\n if (!origin) return true;\n try {\n const h = new URL(origin).hostname;\n return h === \"127.0.0.1\" || h === \"localhost\" || h === \"::1\";\n } catch {\n return false;\n }\n}\n\n/**\n * Run one collect + ingest pass under the shared single-instance lock. Returns `null` if a run is\n * already in progress (caller → 409). Throws on collect/ingest failure (caller → 500). Synchronous\n * and blocking: on a large archive this briefly stalls the server, acceptable for a local\n * single-user tool (the clicking user is waiting on it anyway).\n */\nexport function runRefresh(): RefreshResult | null {\n const dataDir = resolveDataDir(findRepoRoot());\n const lock = acquireLock(join(dataDir, \".agent-lens.lock\"));\n if (!lock) return null;\n try {\n const collected = collectAll();\n // runIngest process.exit(1)s if the archive dir is missing — which would kill the server. Ensure\n // it exists first (collectAll creates it when there's anything to copy; this covers the empty case).\n const archiveRoot = process.env.AGENT_LENS_ARCHIVE || join(dataDir, \"archive\");\n if (!existsSync(archiveRoot)) mkdirSync(archiveRoot, { recursive: true });\n runIngest([]);\n return { ok: true, collected };\n } finally {\n lock.release();\n }\n}\n","/**\n * Agent Lens — security-finding triage store (ADR-018). User-authored triage state (which findings are\n * dismissed as safe, which rules are muted) is NOT derivable from the archive, so it can't live in the\n * rebuildable analytics DB — `detect()` wipes and re-inserts `findings` on every ingest, and\n * `ingest --full` drops every analytics table. Instead it lives in a **separate, writable `triage.db`**\n * that ingest never touches, keyed by the *stable* finding id (`sha1(tool_call_id, rule_id)`), so triage\n * survives re-detection and full rebuilds automatically.\n *\n * The analytics server handle stays read-only (ADR-005): it only ATTACHes this file to JOIN triage\n * state into the findings list. Writes go through this dedicated handle, guarded by the same CSRF check\n * as `POST /api/refresh`. WAL lets the writer run alongside the read handle's queries.\n */\nimport Database from \"better-sqlite3\";\n\nexport type TriageDB = Database.Database;\n\nexport const TRIAGE_SCHEMA_SQL = /* sql */ `\nPRAGMA journal_mode = WAL;\n\n-- Findings the user reviewed and marked safe. Keyed by the stable finding id so it survives re-ingest.\nCREATE TABLE IF NOT EXISTS dismissed_findings (\n finding_id TEXT PRIMARY KEY, -- findings.id = sha1(tool_call_id, rule_id)\n tool_call_id TEXT, -- reference only (may be null)\n rule_id TEXT, -- reference only (may be null)\n note TEXT,\n dismissed_at TEXT NOT NULL\n);\n\n-- Rules the user muted (suppressed as noise), optionally scoped to a project or source. A muted rule\n-- hides its current AND future findings from the open view — the scalable lever for a noisy rule.\nCREATE TABLE IF NOT EXISTS muted_rules (\n rule_id TEXT NOT NULL,\n scope TEXT NOT NULL DEFAULT 'global', -- 'global' | 'project' | 'source'\n scope_id TEXT NOT NULL DEFAULT '', -- project_id / source_id; '' for global (NOT NULL keeps it in the PK)\n note TEXT,\n muted_at TEXT NOT NULL,\n PRIMARY KEY (rule_id, scope, scope_id)\n);\n`;\n\n/** Open (creating if needed) the writable triage DB and ensure its schema. */\nexport function openTriage(file: string): TriageDB {\n const db = new Database(file);\n db.pragma(\"journal_mode = WAL\");\n db.exec(TRIAGE_SCHEMA_SQL);\n return db;\n}\n\nconst now = () => new Date().toISOString();\n\n/** Mark findings safe (idempotent upsert). Returns the number of ids processed. */\nexport function dismiss(db: TriageDB, ids: string[], note?: string | null): number {\n const stmt = db.prepare(\n `INSERT INTO dismissed_findings (finding_id, note, dismissed_at) VALUES (?, ?, ?)\n ON CONFLICT(finding_id) DO UPDATE SET note = excluded.note, dismissed_at = excluded.dismissed_at`,\n );\n const ts = now();\n const tx = db.transaction((list: string[]) => {\n for (const id of list) stmt.run(id, note ?? null, ts);\n });\n tx(ids);\n return ids.length;\n}\n\n/** Reopen (un-dismiss) findings. Returns the number of rows removed. */\nexport function reopen(db: TriageDB, ids: string[]): number {\n const stmt = db.prepare(\"DELETE FROM dismissed_findings WHERE finding_id = ?\");\n let n = 0;\n const tx = db.transaction((list: string[]) => {\n for (const id of list) n += stmt.run(id).changes;\n });\n tx(ids);\n return n;\n}\n\nexport type MuteScope = \"global\" | \"project\" | \"source\";\n\n/** Mute a rule (optionally scoped). Idempotent. */\nexport function muteRule(db: TriageDB, ruleId: string, scope: MuteScope = \"global\", scopeId = \"\", note?: string | null): void {\n db.prepare(\n `INSERT INTO muted_rules (rule_id, scope, scope_id, note, muted_at) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(rule_id, scope, scope_id) DO UPDATE SET note = excluded.note, muted_at = excluded.muted_at`,\n ).run(ruleId, scope, scope === \"global\" ? \"\" : scopeId, note ?? null, now());\n}\n\n/** Unmute a rule. Returns rows removed (0 if it wasn't muted at that scope). */\nexport function unmute(db: TriageDB, ruleId: string, scope: MuteScope = \"global\", scopeId = \"\"): number {\n return db\n .prepare(\"DELETE FROM muted_rules WHERE rule_id = ? AND scope = ? AND scope_id = ?\")\n .run(ruleId, scope, scope === \"global\" ? \"\" : scopeId).changes;\n}\n\nexport interface MuteRow {\n rule_id: string;\n scope: string;\n scope_id: string;\n note: string | null;\n muted_at: string;\n}\n\n/** All muted rules, newest first — for the management panel. */\nexport function listMutes(db: TriageDB): MuteRow[] {\n return db.prepare(\"SELECT rule_id, scope, scope_id, note, muted_at FROM muted_rules ORDER BY muted_at DESC\").all() as MuteRow[];\n}\n","/**\n * `agent-lens watch` — a resident process that collects + ingests whenever a source's transcripts\n * change (debounced), and optionally on a fixed interval. This is the Node-native periodic option;\n * `agent-lens service install collector` layers OS-level persistence (reboot survival) on top.\n *\n * Overlap safety: an in-process guard collapses a burst of file events into one cycle and never runs\n * two cycles at once; the shared single-instance lock additionally guards against a scheduled run\n * firing concurrently.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport chokidar from \"chokidar\";\nimport { acquireLock, collectAll, findRepoRoot, loadSources, resolveDataDir, type Source } from \"@agent-lens/core\";\nimport { runIngest } from \"@agent-lens/ingest\";\n\nexport interface WatchOptions {\n intervalSec?: number;\n poll?: boolean;\n}\n\nexport async function runWatch(opts: WatchOptions = {}): Promise<void> {\n const sources: Source[] = loadSources();\n const lockPath = join(resolveDataDir(findRepoRoot()), \".agent-lens.lock\");\n\n let running = false;\n let pending = false;\n const cycle = () => {\n if (running) {\n pending = true; // a change arrived mid-cycle — run once more when this finishes\n return;\n }\n running = true;\n const lock = acquireLock(lockPath);\n if (!lock) {\n running = false;\n console.error(\"agent-lens watch: another collect/ingest run is in progress — skipping this cycle\");\n return;\n }\n try {\n const stats = collectAll({ sources });\n console.log(\n `agent-lens watch: collected ${stats.scanned} files (${stats.appended} appended, ${stats.snapshots} snapshots) — ingesting`,\n );\n runIngest([]);\n } catch (e) {\n console.error(\"agent-lens watch: cycle failed:\", e);\n } finally {\n lock.release();\n running = false;\n if (pending) {\n pending = false;\n setTimeout(cycle, 0);\n }\n }\n };\n\n cycle(); // initial catch-up\n\n const watchPaths = sources.map((s) => join(s.configDir, \"projects\")).filter((p) => existsSync(p));\n if (watchPaths.length === 0) {\n console.error(\"agent-lens watch: no source 'projects' directories found — nothing to watch\");\n }\n const watcher = chokidar.watch(watchPaths, {\n ignoreInitial: true,\n usePolling: !!opts.poll,\n awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },\n });\n let debounce: NodeJS.Timeout | null = null;\n const trigger = () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(cycle, 3000); // coalesce bursts of writes into one cycle\n };\n watcher.on(\"add\", trigger).on(\"change\", trigger);\n\n if (opts.intervalSec && opts.intervalSec > 0) {\n setInterval(cycle, opts.intervalSec * 1000);\n }\n\n console.log(\n `agent-lens watch: watching ${watchPaths.length} source dir(s)` +\n (opts.intervalSec ? ` + every ${opts.intervalSec}s` : \"\") +\n \" — Ctrl-C to stop\",\n );\n // Keep the process alive indefinitely (chokidar + timers hold it open, but be explicit).\n return new Promise<void>(() => {});\n}\n"],"mappings":"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","names":["readFileSync","dirname","join","fileURLToPath","cli","version","readFileSync","readFileSync","existsSync","dirname","join","platform","platform","dirname","join","existsSync","existsSync","mkdirSync","readFileSync","unlinkSync","dirname","mkdirSync","writeFileSync","homedir","platform","join","cli","join","homedir","mkdirSync","writeFileSync","platform","readFileSync","statSync","mkdirSync","existsSync","dirname","join","readdirSync","basename","join","createHash","createHash","c","now","readdirSync","readFileSync","statSync","basename","join","createHash","createHash","openSync","readSync","closeSync","CHUNK","readdirSync","join","now","statSync","readFileSync","basename","durationMs","readdirSync","readFileSync","statSync","basename","join","readdirSync","join","intOrNull","strOrNull","now","statSync","readFileSync","basename","readdirSync","readFileSync","statSync","basename","join","readdirSync","join","sessionFromPath","now","statSync","readFileSync","basename","join","existsSync","mkdirSync","dirname","now","statSync","readFileSync","existsSync","dirname","join","fileURLToPath","Database","Database","CHUNK","existsSync","existsSync","mkdirSync","join","join","existsSync","mkdirSync","Database","existsSync","dirname","fileURLToPath","join","existsSync","existsSync","join","join","existsSync","readFileSync","join","dirname","fileURLToPath"]}