@swestash/agent-lens 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-lens.js +43 -12
- package/dist/agent-lens.js.map +1 -1
- package/package.json +1 -1
- package/web/assets/{Dashboard-CPjOOCyK.js → Dashboard-BxZigTdE.js} +1 -1
- package/web/assets/SessionView-DW8drCh_.js +4 -0
- package/web/assets/{SkillView-C6vlyg0Z.js → SkillView-Zs7vZQPU.js} +1 -1
- package/web/assets/SkillsView-5RaEi7vd.js +1 -0
- package/web/assets/{WorkflowView-D5N-tAZr.js → WorkflowView-BZgguHyQ.js} +1 -1
- package/web/assets/{index-RKLKBrv5.js → index-753CcEbN.js} +1 -1
- package/web/assets/index-CH9ukarY.js +68 -0
- package/web/assets/index-CtWea52P.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/SessionView-iEtAsqjR.js +0 -2
- package/web/assets/SkillsView-B2W0OtAw.js +0 -1
- package/web/assets/index-B0Y86jYR.css +0 -1
- package/web/assets/index-DaWyEmDk.js +0 -68
package/dist/agent-lens.js.map
CHANGED
|
@@ -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/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/pipeline.ts","../../ingest/src/redact.ts","../../ingest/src/fileread.ts","../../server/src/run.ts","../../server/src/db.ts","../../server/src/app.ts","../../server/src/dashboard.ts","../../server/src/refresh.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 = 8;\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-- 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 * 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/** Recursively collect *.jsonl files under `dir`. */\nfunction walkJsonl(dir: string, out: string[]): void {\n for (const e of readdirSync(dir, { withFileTypes: true })) {\n const p = join(dir, e.name);\n if (e.isDirectory()) walkJsonl(p, out);\n else if (e.isFile() && e.name.endsWith(\".jsonl\")) out.push(p);\n }\n}\n\n/** The single-file sync decision (the heart of the append-verify port). */\nfunction syncFile(src: string, dst: string, relParts: string[], archive: string, versionsDir: string, stats: CollectStats): 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 // Pure append (or identical when ssize === asize → appends nothing). Always verify the full\n // prefix before appending; never trust size alone.\n if (ssize > asize) {\n appendRange(src, dst, asize, ssize);\n stats.appended++;\n }\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)) walkJsonl(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);\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 type SourceAdapter,\n} from \"@agent-lens/core\";\nimport { openDb, openRaw, resetSchema } from \"./db.js\";\nimport { ClaudeCodeAdapter } from \"./adapters/claude-code.js\";\nimport { classify, CLASSIFIER_VERSION } from \"./classify.js\";\nimport { ingestFile, newStats, prepareStatements, pruneExcluded, rebuildDerived } from \"./pipeline.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) resetSchema(db);\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 // 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\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 // 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 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 ` 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 db.close();\n console.log(`agent-lens-metrics: classified=${r.count} classifier_version=${CLASSIFIER_VERSION} db=${dbPath}`);\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/** Apply the current schema (idempotent CREATE IF NOT EXISTS) and stamp the version. */\nfunction applySchema(db: DB): void {\n db.exec(SCHEMA_SQL);\n db.prepare(\n \"INSERT INTO meta(key, value) VALUES ('schema_version', ?) \" +\n \"ON CONFLICT(key) DO UPDATE SET value = excluded.value\",\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 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 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/** Short, single-line summary of a tool result (kept small to avoid dumping large/secret output). */\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 s = s.replace(/\\s+/g, \" \").trim();\n return s.slice(0, 280) || 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 toolResults.push({\n tool_use_id: b.tool_use_id,\n result_summary: 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 — 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) 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 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 // 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 — 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 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 const db = openReadonly(dbPath);\n const app = await createApp(db, { webDist });\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, unpackRaw } 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 * 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 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 if (f.from) (where.push(\"s.started_at >= ?\"), params.push(f.from));\n if (f.to) (where.push(\"s.started_at <= ?\"), 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 rows = db\n .prepare(\n `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 ORDER BY s.started_at DESC\n LIMIT ? OFFSET ?`,\n )\n .all(...params, f.limit, f.offset) as any[];\n\n // Cost + tokens per session for this page (grouped by model so rates apply correctly).\n const ids = rows.map((r) => r.id);\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 if (ids.length) {\n const ph = ids.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(...ids) 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 return { total, sessions: rows };\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 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 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 FROM sessions s 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 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 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 };\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\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.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).\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 FROM sessions s 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 return {\n run_id: wf.run_id,\n name: wf.name ?? null,\n status: wf.status ?? null,\n result_summary: wf.result_summary ?? null,\n completion,\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 if (f.q && f.q.trim()) (where.push(\"tc.skill_name LIKE ?\"), params.push(`%${f.q.trim()}%`));\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 * 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, listSources, listProjects, listModels, listSessions, getSession, getWorkflow, listSkills, getSkill } from \"./db.js\";\nimport { dashboardOverview, dashboardTimeseries, dashboardBreakdowns, type DashFilters } from \"./dashboard.js\";\nimport { originAllowed, runRefresh } from \"./refresh.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}\n\nexport async function createApp(db: DB, opts: CreateAppOpts = {}): Promise<FastifyInstance> {\n const app = Fastify({ logger: false });\n\n app.get(\"/api/health\", async () => ({ ok: true, last_ingested: lastIngested(db) }));\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 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 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 if (f.from) (where.push(\"s.started_at >= ?\"), params.push(f.from));\n if (f.to) (where.push(\"s.started_at <= ?\"), 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 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","c","createHash","createHash","openSync","readSync","closeSync","CHUNK","join","existsSync","mkdirSync","dirname","statSync","readFileSync","existsSync","dirname","join","fileURLToPath","Database","Database","existsSync","existsSync","mkdirSync","join","join","existsSync","mkdirSync","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/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/pipeline.ts","../../ingest/src/redact.ts","../../ingest/src/fileread.ts","../../server/src/run.ts","../../server/src/db.ts","../../server/src/app.ts","../../server/src/dashboard.ts","../../server/src/refresh.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 = 8;\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-- 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 * 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/** Recursively collect *.jsonl files under `dir`. */\nfunction walkJsonl(dir: string, out: string[]): void {\n for (const e of readdirSync(dir, { withFileTypes: true })) {\n const p = join(dir, e.name);\n if (e.isDirectory()) walkJsonl(p, out);\n else if (e.isFile() && e.name.endsWith(\".jsonl\")) out.push(p);\n }\n}\n\n/** The single-file sync decision (the heart of the append-verify port). */\nfunction syncFile(src: string, dst: string, relParts: string[], archive: string, versionsDir: string, stats: CollectStats): 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 // Pure append (or identical when ssize === asize → appends nothing). Always verify the full\n // prefix before appending; never trust size alone.\n if (ssize > asize) {\n appendRange(src, dst, asize, ssize);\n stats.appended++;\n }\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)) walkJsonl(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);\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 type SourceAdapter,\n} from \"@agent-lens/core\";\nimport { openDb, openRaw, resetSchema } from \"./db.js\";\nimport { ClaudeCodeAdapter } from \"./adapters/claude-code.js\";\nimport { classify, CLASSIFIER_VERSION } from \"./classify.js\";\nimport { ingestFile, newStats, prepareStatements, pruneExcluded, rebuildDerived } from \"./pipeline.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) resetSchema(db);\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 // 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\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 // 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 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 ` 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 db.close();\n console.log(`agent-lens-metrics: classified=${r.count} classifier_version=${CLASSIFIER_VERSION} db=${dbPath}`);\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/** Apply the current schema (idempotent CREATE IF NOT EXISTS) and stamp the version. */\nfunction applySchema(db: DB): void {\n db.exec(SCHEMA_SQL);\n db.prepare(\n \"INSERT INTO meta(key, value) VALUES ('schema_version', ?) \" +\n \"ON CONFLICT(key) DO UPDATE SET value = excluded.value\",\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 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 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/** Short, single-line summary of a tool result (kept small to avoid dumping large/secret output). */\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 s = s.replace(/\\s+/g, \" \").trim();\n return s.slice(0, 280) || 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 — 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) 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 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 // 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 — 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 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 const db = openReadonly(dbPath);\n const app = await createApp(db, { webDist });\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, unpackRaw } 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 * 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 if (f.from) (where.push(\"s.started_at >= ?\"), params.push(f.from));\n if (f.to) (where.push(\"s.started_at <= ?\"), 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 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 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 FROM sessions s 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 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 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 };\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\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.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).\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 FROM sessions s 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 return {\n run_id: wf.run_id,\n name: wf.name ?? null,\n status: wf.status ?? null,\n result_summary: wf.result_summary ?? null,\n completion,\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 * 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, listSources, listProjects, listModels, listSessions, getSession, getWorkflow, listSkills, getSkill } from \"./db.js\";\nimport { dashboardOverview, dashboardTimeseries, dashboardBreakdowns, type DashFilters } from \"./dashboard.js\";\nimport { originAllowed, runRefresh } from \"./refresh.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}\n\nexport async function createApp(db: DB, opts: CreateAppOpts = {}): Promise<FastifyInstance> {\n const app = Fastify({ logger: false });\n\n app.get(\"/api/health\", async () => ({ ok: true, last_ingested: lastIngested(db) }));\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 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 if (f.from) (where.push(\"s.started_at >= ?\"), params.push(f.from));\n if (f.to) (where.push(\"s.started_at <= ?\"), 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 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","c","createHash","createHash","openSync","readSync","closeSync","CHUNK","join","existsSync","mkdirSync","dirname","statSync","readFileSync","existsSync","dirname","join","fileURLToPath","Database","Database","CHUNK","existsSync","existsSync","mkdirSync","join","join","existsSync","mkdirSync","existsSync","dirname","fileURLToPath","join","existsSync","existsSync","join","join","existsSync","readFileSync","join","dirname","fileURLToPath"]}
|