nexusmem 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -1
- package/dist/cli/index.js +39 -7
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts","../../src/config/workspace.ts","../../src/slm/provider.ts","../../src/core/version.ts","../../src/git/exec.ts","../../src/git/repo.ts","../../src/hooks/install.ts","../../src/shell/paths.ts","../../src/config/paths.ts","../../src/hooks/powershell.ts","../../src/cli/commands/hook.ts","../../src/cli/commands/init.ts","../../src/config/registry.ts","../../src/core/ids.ts","../../src/core/project.ts","../../src/store/store.ts","../../src/store/fts.ts","../../src/store/schema.ts","../../src/cli/commands/projects.ts","../../src/mcp/server.ts","../../src/mcp/tools.ts","../../src/core/text.ts","../../src/retrieval/pack.ts","../../src/correlate/failure-fix.ts","../../src/retrieval/fuse.ts","../../src/retrieval/rank.ts","../../src/retrieval/query-pipeline.ts","../../src/retrieval/sources.ts","../../src/vector/embed.ts","../../src/cli/commands/sync.ts","../../src/conversation/chunk.ts","../../src/conversation/redact.ts","../../src/collectors/conversation.ts","../../src/git/parse.ts","../../src/git/diff.ts","../../src/git/log.ts","../../src/collectors/git-commits.ts","../../src/collectors/diffs.ts","../../src/collectors/docs.ts","../../src/slm/summarize.ts","../../src/collectors/sessions.ts","../../src/collectors/shell-history.ts","../../src/conversation/claude-code-reader.ts","../../src/conversation/paths.ts","../../src/docs/read.ts","../../src/shell/detect.ts","../../src/shell/hook-log.ts","../../src/shell/parse-bash.ts","../../src/shell/parse-psreadline.ts","../../src/shell/parse-zsh.ts","../../src/store/reconcile.ts","../../src/vector/sync.ts","../../src/cli/context.ts","../../src/cli/commands/query.ts","../../src/cli/commands/scan-conversation.ts","../../src/cli/format.ts","../../src/cli/commands/scan-diff.ts","../../src/cli/commands/scan-git.ts","../../src/cli/commands/scan-docs.ts","../../src/cli/commands/scan-session.ts","../../src/cli/commands/scan-shell.ts","../../src/cli/commands/status.ts"],"sourcesContent":["import { Command } from 'commander';\r\nimport pc from 'picocolors';\r\nimport { ConfigError } from '../config/workspace.js';\r\nimport { readOwnVersion } from '../core/version.js';\r\nimport { GitCrashError, GitSpawnError } from '../git/exec.js';\r\nimport { NotAGitRepositoryError } from '../git/repo.js';\r\nimport { ProfileNotFoundError } from '../hooks/install.js';\r\nimport { runHookInstall, runHookRemove, runHookStatus } from './commands/hook.js';\r\nimport { runInit } from './commands/init.js';\r\nimport { runProjects } from './commands/projects.js';\r\nimport { runMcpServer } from '../mcp/server.js';\r\nimport { runQuery } from './commands/query.js';\r\nimport { runScanConversation } from './commands/scan-conversation.js';\r\nimport { runScanDiff } from './commands/scan-diff.js';\r\nimport { runScanDocs } from './commands/scan-docs.js';\r\nimport { runScanGit } from './commands/scan-git.js';\r\nimport { runScanSession, SCAN_SESSION_DEFAULT_MODEL } from './commands/scan-session.js';\r\nimport { runScanShell } from './commands/scan-shell.js';\r\nimport { runStatus } from './commands/status.js';\r\nimport { runSync } from './commands/sync.js';\r\n\r\n/** Failures the user can act on, reported without a stack trace. */\r\nfunction isExpected(err: unknown): err is Error {\r\n return (\r\n err instanceof NotAGitRepositoryError ||\r\n // \"git isn't installed\" and \"the spawn failed, try again\" are both things\r\n // the user fixes, not stack traces they debug.\r\n err instanceof GitSpawnError ||\r\n // Survived every retry, so git is genuinely unstable on this machine\r\n // (antivirus, a bad install). Actionable, and not our stack to print.\r\n err instanceof GitCrashError ||\r\n err instanceof ConfigError ||\r\n err instanceof ProfileNotFoundError\r\n );\r\n}\r\n\r\n/** Wrap a command so expected failures exit 1 with a clean message. */\r\nfunction guard(run: () => Promise<number>): () => Promise<void> {\r\n return async () => {\r\n try {\r\n process.exitCode = await run();\r\n } catch (err) {\r\n if (isExpected(err)) {\r\n process.stderr.write(`${pc.red('error')} ${err.message}\\n`);\r\n process.exitCode = 1;\r\n return;\r\n }\r\n throw err;\r\n }\r\n };\r\n}\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('nexusmem')\r\n .description('NexusMem — local-first persistent memory for AI coding agents')\r\n .version(readOwnVersion());\r\n\r\nprogram\r\n .command('init')\r\n .description('Create the .nexusmem workspace and database for this repository')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--force', 'overwrite an existing config (the database is kept)', false)\r\n .option('--hook', 'also install the opt-in PowerShell hook (cwd + exit code + timestamp)', false)\r\n .option('--enable-conversation', 'opt in to the conversation-transcript source (off by default -- see docs/phase-2-spec.md)', false)\r\n .action((options) =>\r\n guard(() =>\r\n runInit({ cwd: options.cwd, force: options.force, hook: options.hook, enableConversation: options.enableConversation }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('sync')\r\n .description('Ingest new history into the local database')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--full', 'ignore the stored cursor and re-walk all history', false)\r\n .option('--rebuild', 'drop this project\\'s nodes and re-ingest from scratch', false)\r\n .option('--since <date>', 'override the configured git cutoff, e.g. 1.year.ago')\r\n .option('--shell-lines <count>', 'override the configured shell tail-window size', (v) => Number.parseInt(v, 10))\r\n .option('--conversation', 'force the conversation source on for this run, without persisting it to config', false)\r\n .option('--no-embed', 'skip the vector-embedding pass for this run')\r\n .option('--embed-limit <count>', 'stop embedding after this many nodes (default: embed everything pending)', (v) =>\r\n Number.parseInt(v, 10),\r\n )\r\n .option('--prune-source <name>', 'delete every node from this exact source (e.g. shell:pwsh) instead of syncing -- dry-run unless --yes is also given')\r\n .option(\r\n '--prune-stale-shell',\r\n 'shortcut for --prune-source on shell:pwsh, shell:bash and shell:zsh at once -- the dead pre-hook scrape sources -- dry-run unless --yes is also given',\r\n false,\r\n )\r\n .option('--yes', 'confirm an irreversible --prune-source/--prune-stale-shell delete', false)\r\n .option(\r\n '--link-failures',\r\n 'opt-in (experimental): after ingest, link failed shell commands to whatever later resolved them',\r\n false,\r\n )\r\n .option('-q, --quiet', 'only print the final summary', false)\r\n .action((options) =>\r\n guard(() =>\r\n runSync({\r\n cwd: options.cwd,\r\n full: options.full,\r\n rebuild: options.rebuild,\r\n since: options.since,\r\n shellTailLines: options.shellLines,\r\n conversationOverride: options.conversation ? true : undefined,\r\n noEmbed: !options.embed,\r\n embedLimit: options.embedLimit,\r\n pruneSource: options.pruneSource,\r\n pruneStaleShell: options.pruneStaleShell,\r\n yes: options.yes,\r\n linkFailures: options.linkFailures,\r\n quiet: options.quiet,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('hook')\r\n .description('Manage the opt-in PowerShell hook that logs cwd + exit code + timestamp')\r\n .addCommand(\r\n new Command('install')\r\n .description('Install (or update) the hook in your PowerShell profile')\r\n .option('--profile <path>', 'override the auto-detected $PROFILE path')\r\n .action((options) => guard(() => runHookInstall({ profile: options.profile }))()),\r\n )\r\n .addCommand(\r\n new Command('remove')\r\n .description('Remove the hook block from your PowerShell profile')\r\n .option('--profile <path>', 'override the auto-detected $PROFILE path')\r\n .action((options) => guard(() => runHookRemove({ profile: options.profile }))()),\r\n )\r\n .addCommand(\r\n new Command('status')\r\n .description('Show whether the hook is installed')\r\n .option('--profile <path>', 'override the auto-detected $PROFILE path')\r\n .action((options) => guard(() => runHookStatus({ profile: options.profile }))()),\r\n );\r\n\r\nprogram\r\n .command('status')\r\n .description('Show what is currently remembered for this repository')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .action((options) => guard(() => runStatus({ cwd: options.cwd }))());\r\n\r\nprogram\r\n .command('query')\r\n .description('Search remembered history and print a token-budgeted context block')\r\n .argument('<text>', 'free-text query')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('-b, --budget <tokens>', 'max tokens in the packed context', (v) => Number.parseInt(v, 10), 2000)\r\n .option('-n, --candidates <count>', 'how many search hits to rank before packing', (v) => Number.parseInt(v, 10), 30)\r\n .option('--half-life <days>', 'days for a node\\'s recency weight to halve', (v) => Number.parseFloat(v))\r\n .option('--no-vector', 'BM25 only -- skip embedding the query and vector search')\r\n .option('-a, --all-projects', 'search every registered repository, not just this one', false)\r\n .option('--json', 'emit the packed result as JSON on stdout', false)\r\n .action((text: string, options) =>\r\n guard(() =>\r\n runQuery({\r\n cwd: options.cwd,\r\n query: text,\r\n budget: options.budget,\r\n candidates: options.candidates,\r\n halfLifeDays: options.halfLife,\r\n noVector: !options.vector,\r\n allProjects: options.allProjects,\r\n json: options.json,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('projects')\r\n .description('List the repositories `query --all-projects` would search')\r\n .option('--prune', 'forget registered projects whose database is no longer on disk', false)\r\n .option('--json', 'emit the registry as JSON on stdout', false)\r\n .action((options) => guard(() => runProjects({ prune: options.prune, json: options.json }))());\r\n\r\nprogram\r\n .command('scan-git')\r\n .description('Preview the MemoryNodes git history would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--since <date>', 'only commits newer than this git date expression, e.g. 90.days.ago')\r\n .option('-n, --limit <count>', 'stop after N commits', (v) => Number.parseInt(v, 10))\r\n .option('--no-merges', 'skip merge commits')\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() =>\r\n runScanGit({\r\n cwd: options.cwd,\r\n since: options.since,\r\n limit: options.limit,\r\n merges: options.merges,\r\n json: options.json,\r\n minSignal: options.minSignal,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('scan-diff')\r\n .description('Preview the MemoryNodes commit patches would produce, one per changed file (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--since <date>', 'only commits newer than this git date expression, e.g. 90.days.ago')\r\n .option('-n, --limit <count>', 'stop after N commits (not N nodes)', (v) => Number.parseInt(v, 10))\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() =>\r\n runScanDiff({\r\n cwd: options.cwd,\r\n since: options.since,\r\n limit: options.limit,\r\n json: options.json,\r\n minSignal: options.minSignal,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('scan-shell')\r\n .description('Preview the MemoryNodes shell history would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('-n, --tail-lines <count>', 'lines kept from each scrape-based source', (v) => Number.parseInt(v, 10), 300)\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() =>\r\n runScanShell({ cwd: options.cwd, tailLines: options.tailLines, minSignal: options.minSignal, json: options.json }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('scan-conversation')\r\n .description('Preview the MemoryNodes the conversation transcript would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() => runScanConversation({ cwd: options.cwd, minSignal: options.minSignal, json: options.json }))(),\r\n );\r\n\r\nprogram\r\n .command('scan-session')\r\n .description('Preview the session summaries a local model would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--model <name>', 'Ollama model to summarize with', SCAN_SESSION_DEFAULT_MODEL)\r\n .option('--settle-minutes <count>', 'minutes of quiet before a session counts as finished', (v) => Number.parseInt(v, 10), 30)\r\n .option('-n, --max-sessions <count>', 'how many sessions to summarize', (v) => Number.parseInt(v, 10), 3)\r\n .option('--dry-run', 'print the prompts instead of calling the model', false)\r\n .option('--json', 'emit as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() =>\r\n runScanSession({\r\n cwd: options.cwd,\r\n model: options.model,\r\n settleMinutes: options.settleMinutes,\r\n maxSessions: options.maxSessions,\r\n dryRun: options.dryRun,\r\n json: options.json,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('scan-docs')\r\n .description('Preview the MemoryNodes tracked .md files would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) => guard(() => runScanDocs({ cwd: options.cwd, minSignal: options.minSignal, json: options.json }))());\r\n\r\nprogram\r\n .command('mcp')\r\n .description('Start the MCP server (stdio transport) for Claude Desktop, Cursor, Windsurf, etc.')\r\n .action(() => guard(() => runMcpServer().then(() => 0))());\r\n\r\nprogram.parseAsync(process.argv).catch((err: unknown) => {\r\n const message = err instanceof Error ? err.message : String(err);\r\n process.stderr.write(`${pc.red('error')} ${message}\\n`);\r\n process.exitCode = 1;\r\n});\r\n","import { existsSync } from 'node:fs';\r\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\r\nimport { join } from 'node:path';\r\nimport { z } from 'zod';\r\nimport { DEFAULT_SLM_MODEL } from '../slm/provider.js';\r\n\r\n/** Everything NexusMem stores lives under this directory in the repo root. */\r\nexport const WORKSPACE_DIR = '.nexusmem';\r\n\r\nexport interface Workspace {\r\n /** Repository root. */\r\n root: string;\r\n /** `<root>/.nexusmem` */\r\n dir: string;\r\n dbPath: string;\r\n configPath: string;\r\n}\r\n\r\nexport function resolveWorkspace(repoRoot: string): Workspace {\r\n const dir = join(repoRoot, WORKSPACE_DIR);\r\n return {\r\n root: repoRoot,\r\n dir,\r\n dbPath: join(dir, 'memory.db'),\r\n configPath: join(dir, 'config.json'),\r\n };\r\n}\r\n\r\nexport function isInitialized(ws: Workspace): boolean {\r\n return existsSync(ws.configPath);\r\n}\r\n\r\nexport const ConfigSchema = z.object({\r\n version: z.literal(1),\r\n projectId: z.string().min(1),\r\n sources: z\r\n .object({\r\n git: z\r\n .object({\r\n enabled: z.boolean().default(true),\r\n /** Git date expression bounding how far back to ingest; null = all history. */\r\n since: z.string().nullable().default(null),\r\n includeMerges: z.boolean().default(true),\r\n })\r\n .default({ enabled: true, since: null, includeMerges: true }),\r\n shell: z\r\n .object({\r\n enabled: z.boolean().default(true),\r\n /** Lines kept from scrape-based (no-hook) history files each sync. */\r\n tailLines: z.number().int().positive().default(300),\r\n })\r\n .default({ enabled: true, tailLines: 300 }),\r\n /**\r\n * Opt-in, unlike git/shell: conversation transcripts are the source\r\n * most likely to contain something sensitive (a pasted credential,\r\n * confidential discussion), so this must be a deliberate choice, not\r\n * an automatic default. See docs/phase-2-spec.md.\r\n */\r\n conversation: z\r\n .object({\r\n enabled: z.boolean().default(false),\r\n })\r\n .default({ enabled: false }),\r\n /**\r\n * One distilled node per finished working session, written by a local\r\n * small language model.\r\n *\r\n * Opt-in for the same reason as `conversation` -- it reads the same\r\n * transcripts -- and additionally because it is the only source that\r\n * costs real compute. Independent of `conversation.enabled`: summaries\r\n * without the raw exchanges is a legitimate, and much smaller, way to\r\n * remember a session.\r\n */\r\n session: z\r\n .object({\r\n enabled: z.boolean().default(false),\r\n /** Ollama model tag. Must be pulled locally; nothing is downloaded automatically. */\r\n model: z.string().default(DEFAULT_SLM_MODEL),\r\n /** Minutes of quiet before a session counts as finished and can be summarized. */\r\n settleMinutes: z.number().int().nonnegative().default(30),\r\n /** Sessions summarized per sync. Each is a model call measured in seconds. */\r\n maxSessions: z.number().int().positive().default(10),\r\n maxPromptChars: z.number().int().positive().default(12_000),\r\n })\r\n .default({\r\n enabled: false,\r\n model: DEFAULT_SLM_MODEL,\r\n settleMinutes: 30,\r\n maxSessions: 10,\r\n maxPromptChars: 12_000,\r\n }),\r\n /** Tracked `.md` files -- README, architecture docs. On by default like git/shell: no secrets risk, just project prose. */\r\n docs: z\r\n .object({\r\n enabled: z.boolean().default(true),\r\n /** git pathspecs passed to `git ls-files`. */\r\n include: z.array(z.string()).default(['*.md']),\r\n })\r\n .default({ enabled: true, include: ['*.md'] }),\r\n /**\r\n * The patch text of each commit, one node per changed file.\r\n *\r\n * Bounded by `maxCommits` rather than by `git.since`, because patches\r\n * are an order of magnitude bulkier than commit messages: an unbounded\r\n * first sync of a long-lived repository would spend most of its time\r\n * and database on code nobody will ask about. Later syncs walk only\r\n * `cursor..HEAD`, so the cap effectively applies to the first run.\r\n */\r\n diff: z\r\n .object({\r\n enabled: z.boolean().default(true),\r\n maxCommits: z.number().int().positive().default(200),\r\n maxFilesPerCommit: z.number().int().positive().default(20),\r\n contextLines: z.number().int().nonnegative().default(3),\r\n })\r\n .default({ enabled: true, maxCommits: 200, maxFilesPerCommit: 20, contextLines: 3 }),\r\n })\r\n .default({\r\n git: { enabled: true, since: null, includeMerges: true },\r\n shell: { enabled: true, tailLines: 300 },\r\n conversation: { enabled: false },\r\n session: {\r\n enabled: false,\r\n model: DEFAULT_SLM_MODEL,\r\n settleMinutes: 30,\r\n maxSessions: 10,\r\n maxPromptChars: 12_000,\r\n },\r\n docs: { enabled: true, include: ['*.md'] },\r\n diff: { enabled: true, maxCommits: 200, maxFilesPerCommit: 20, contextLines: 3 },\r\n }),\r\n limits: z\r\n .object({\r\n maxFilesPerNode: z.number().int().positive().default(40),\r\n maxBodyChars: z.number().int().positive().default(4000),\r\n })\r\n .default({ maxFilesPerNode: 40, maxBodyChars: 4000 }),\r\n});\r\n\r\nexport type NexusConfig = z.infer<typeof ConfigSchema>;\r\n\r\nexport function defaultConfig(projectId: string): NexusConfig {\r\n return ConfigSchema.parse({ version: 1, projectId });\r\n}\r\n\r\nexport class ConfigError extends Error {\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'ConfigError';\r\n }\r\n}\r\n\r\nexport async function readConfig(ws: Workspace): Promise<NexusConfig> {\r\n let raw: string;\r\n try {\r\n raw = await readFile(ws.configPath, 'utf8');\r\n } catch {\r\n throw new ConfigError(`Not initialized: ${ws.configPath} not found. Run \\`nexusmem init\\` first.`);\r\n }\r\n\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(raw);\r\n } catch (err) {\r\n throw new ConfigError(`${ws.configPath} is not valid JSON: ${(err as Error).message}`);\r\n }\r\n\r\n const result = ConfigSchema.safeParse(parsed);\r\n if (!result.success) {\r\n const issues = result.error.issues.map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`).join('\\n');\r\n throw new ConfigError(`${ws.configPath} is invalid:\\n${issues}`);\r\n }\r\n return result.data;\r\n}\r\n\r\nexport async function writeConfig(ws: Workspace, config: NexusConfig): Promise<void> {\r\n await mkdir(ws.dir, { recursive: true });\r\n await writeFile(ws.configPath, `${JSON.stringify(config, null, 2)}\\n`, 'utf8');\r\n}\r\n\r\n/**\r\n * Make the workspace ignore itself.\r\n *\r\n * A self-ignoring directory means `init` never has to edit the user's own\r\n * .gitignore -- one less surprising write into a repo we do not own.\r\n */\r\nexport async function writeWorkspaceGitignore(ws: Workspace): Promise<void> {\r\n await mkdir(ws.dir, { recursive: true });\r\n await writeFile(join(ws.dir, '.gitignore'), '# Machine-local derived data.\\n*\\n', 'utf8');\r\n}\r\n","/**\r\n * Small-language-model abstraction, used only for session summarization.\r\n *\r\n * Mirrors the embedding provider's contract on purpose: `complete` returns\r\n * `null` rather than throwing for every failure -- server down, model not\r\n * pulled, timeout, malformed response -- so a machine without a chat model\r\n * loses summaries and nothing else. Everything NexusMem does apart from this\r\n * one collector must keep working with no SLM present at all.\r\n *\r\n * Local-only by construction. There is no API-key option and no hosted\r\n * fallback: the input is verbatim conversation transcript, and the whole\r\n * argument for summarizing it at all is that the text never leaves the\r\n * machine.\r\n */\r\nexport interface SummarizationProvider {\r\n /** Stable name for the model behind this provider, recorded on the nodes it produces. */\r\n readonly identity: string;\r\n complete(prompt: string): Promise<string | null>;\r\n}\r\n\r\nexport interface OllamaChatProviderOptions {\r\n baseUrl?: string;\r\n model?: string;\r\n /** Milliseconds before giving up on one completion. Default 120s. */\r\n timeoutMs?: number;\r\n /** Upper bound on generated tokens. Default 400 -- a summary, not an essay. */\r\n maxTokens?: number;\r\n}\r\n\r\nconst DEFAULT_BASE_URL = 'http://127.0.0.1:11434';\r\n/**\r\n * Chosen for a 12GB VRAM budget shared with the embedding model: ~1.9GB on\r\n * disk, and reliable enough at holding a fixed output shape that the parser\r\n * in summarize.ts does not need to be clever.\r\n */\r\nexport const DEFAULT_SLM_MODEL = 'qwen2.5:3b';\r\nconst DEFAULT_TIMEOUT_MS = 120_000;\r\nconst DEFAULT_MAX_TOKENS = 400;\r\n\r\nexport class OllamaChatProvider implements SummarizationProvider {\r\n readonly identity: string;\r\n private readonly baseUrl: string;\r\n private readonly model: string;\r\n private readonly timeoutMs: number;\r\n private readonly maxTokens: number;\r\n\r\n constructor(opts: OllamaChatProviderOptions = {}) {\r\n this.baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;\r\n this.model = opts.model ?? DEFAULT_SLM_MODEL;\r\n this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n this.maxTokens = opts.maxTokens ?? DEFAULT_MAX_TOKENS;\r\n this.identity = `ollama:${this.model}`;\r\n }\r\n\r\n async complete(prompt: string): Promise<string | null> {\r\n const controller = new AbortController();\r\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\r\n\r\n try {\r\n const res = await fetch(`${this.baseUrl}/api/generate`, {\r\n method: 'POST',\r\n headers: { 'content-type': 'application/json' },\r\n body: JSON.stringify({\r\n model: this.model,\r\n prompt,\r\n stream: false,\r\n options: {\r\n // Deterministic: the same session must summarize to the same text\r\n // across syncs, or the content hash that suppresses re-work would\r\n // never match and every sync would rewrite every summary node.\r\n temperature: 0,\r\n seed: 1,\r\n num_predict: this.maxTokens,\r\n },\r\n }),\r\n signal: controller.signal,\r\n });\r\n\r\n if (!res.ok) return null;\r\n\r\n const data = (await res.json()) as { response?: unknown };\r\n if (typeof data.response !== 'string') return null;\r\n\r\n const text = data.response.trim();\r\n return text.length > 0 ? text : null;\r\n } catch {\r\n return null;\r\n } finally {\r\n clearTimeout(timeout);\r\n }\r\n }\r\n}\r\n\r\n/** Deterministic, network-free provider for tests. */\r\nexport class FakeSummarizationProvider implements SummarizationProvider {\r\n readonly identity = 'fake-slm';\r\n readonly prompts: string[] = [];\r\n\r\n constructor(private readonly reply: (prompt: string) => string | null = () => 'A fake summary.') {}\r\n\r\n async complete(prompt: string): Promise<string | null> {\r\n this.prompts.push(prompt);\r\n return this.reply(prompt);\r\n }\r\n}\r\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Read straight from package.json rather than a literal, so a reported\n * version cannot drift from what was actually published -- which is exactly\n * what happened to the literals this replaces: 0.1.1 shipped with the CLI's\n * `--version` and the MCP server's `initialize` response both still reporting\n * 0.1.0, because the release only bumped package.json.\n *\n * `../../package.json` resolves correctly from both the source location\n * (`src/<subdir>/*.ts`, dev via tsx) and the built location\n * (`dist/cli/index.js`, published), since tsup bundles every entry to the\n * same two-levels-deep location either way. npm always includes package.json\n * in a published tarball regardless of the `files` field, so this is safe to\n * rely on post-publish too.\n */\nexport function readOwnVersion(): string {\n const pkgPath = fileURLToPath(new URL('../../package.json', import.meta.url));\n return (JSON.parse(readFileSync(pkgPath, 'utf8')) as { version: string }).version;\n}\n","import { spawn } from 'node:child_process';\r\n\r\nexport class GitError extends Error {\r\n constructor(\r\n message: string,\r\n readonly args: string[],\r\n readonly exitCode: number,\r\n readonly stderr: string,\r\n ) {\r\n super(message);\r\n this.name = 'GitError';\r\n }\r\n}\r\n\r\n/**\r\n * `git` could not be started at all -- distinct from git starting and then\r\n * exiting non-zero (`GitError`).\r\n *\r\n * Worth its own type because the two have nothing in common diagnostically:\r\n * a `GitError` is git's own verdict about the repository, while this means we\r\n * never got git's opinion. Collapsing them is what previously let a failed\r\n * process spawn be reported as \"not a git repository\", pointing the user at\r\n * their repo when the repo was fine.\r\n */\r\nexport class GitSpawnError extends Error {\r\n constructor(\r\n message: string,\r\n readonly args: string[],\r\n /** libuv errno string (`ENOENT`, `EPERM`, ...), or `undefined` if the platform gave none. */\r\n readonly code: string | undefined,\r\n /** Whether retrying the identical command could plausibly succeed. */\r\n readonly transient: boolean,\r\n override readonly cause: unknown,\r\n ) {\r\n super(message);\r\n this.name = 'GitSpawnError';\r\n }\r\n}\r\n\r\n/**\r\n * `git` started, then died without reaching an exit of its own -- a segfault\r\n * or an access violation, not a verdict.\r\n *\r\n * Distinct from `GitError` for the same reason `GitSpawnError` is: a\r\n * `GitError` carries git's opinion about the repository and must be shown to\r\n * the user as such, while this means git never formed one. Reported as a\r\n * `GitError` it reads as \"git rev-parse exited with code 3221225477\", which\r\n * sends the reader looking for a git problem that does not exist.\r\n */\r\nexport class GitCrashError extends Error {\r\n constructor(\r\n message: string,\r\n readonly args: string[],\r\n /** `0xC0000005` on Windows, or the POSIX signal name. */\r\n readonly status: string,\r\n readonly exitCode: number,\r\n readonly signal: NodeJS.Signals | null,\r\n ) {\r\n super(message);\r\n this.name = 'GitCrashError';\r\n }\r\n}\r\n\r\n/**\r\n * A Windows process torn down by an unhandled exception exits with its\r\n * NTSTATUS value, and every failure NTSTATUS sits at or above this bound.\r\n * git's own exit codes are small (1, 2, 128, 129), so the boundary separates\r\n * \"the OS killed git\" from \"git ran and disagreed\" without guesswork.\r\n *\r\n * Observed here as 0xC0000005 (STATUS_ACCESS_VIOLATION) from `git init` and\r\n * `git commit` in test fixtures, at roughly two runs in five on this machine.\r\n */\r\nconst NTSTATUS_FAILURE_BASE = 0xc0000000;\r\n\r\n/**\r\n * Ctrl+C arrives as an NTSTATUS too, and is the one that must not be retried:\r\n * it is the user's instruction to stop, not a fault.\r\n */\r\nconst STATUS_CONTROL_C_EXIT = 0xc000013a;\r\n\r\n/** POSIX counterpart -- the process died on a signal instead of returning a code. */\r\nconst FATAL_SIGNALS = new Set<string>(['SIGSEGV', 'SIGBUS', 'SIGABRT', 'SIGILL', 'SIGFPE']);\r\n\r\n/** A short label for how git died, or `null` if it exited normally (however unhappily). */\r\nfunction crashStatus(code: number, signal: NodeJS.Signals | null): string | null {\r\n if (signal) return FATAL_SIGNALS.has(signal) ? signal : null;\r\n if (code >= NTSTATUS_FAILURE_BASE && code !== STATUS_CONTROL_C_EXIT) {\r\n return `0x${code.toString(16).toUpperCase()}`;\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Spawn failures that are worth retrying rather than reporting as a broken\r\n * setup.\r\n *\r\n * Windows is the reason this list exists: under process-creation pressure\r\n * (antivirus scanning a new image, handle exhaustion) `uv_spawn` intermittently\r\n * returns `EPERM` or `EAGAIN` for a binary that is present and runnable, and\r\n * succeeds on an immediate retry. `ENOENT` is deliberately absent -- git being\r\n * missing, or the cwd not existing, does not fix itself.\r\n */\r\nconst TRANSIENT_SPAWN_CODES = new Set(['EAGAIN', 'EPERM', 'EACCES', 'EMFILE', 'ENFILE', 'ENOMEM', 'EBUSY', 'ETXTBSY']);\r\n\r\nfunction toSpawnError(err: unknown, cwd: string, args: string[]): GitSpawnError {\r\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\r\n\r\n if (code === 'ENOENT') {\r\n // Ambiguous by design in libuv: the missing thing is either the binary or\r\n // the cwd, and the error carries nothing that tells them apart.\r\n return new GitSpawnError(\r\n `Could not run git: either git is not on PATH, or the directory does not exist: ${cwd}`,\r\n args,\r\n code,\r\n false,\r\n err,\r\n );\r\n }\r\n\r\n const transient = code !== undefined && TRANSIENT_SPAWN_CODES.has(code);\r\n const suffix = transient ? ' This is usually transient on Windows -- retrying the same command often succeeds.' : '';\r\n\r\n return new GitSpawnError(\r\n `Could not start git (${code ?? 'unknown spawn failure'}) in ${cwd}.${suffix}`,\r\n args,\r\n code,\r\n transient,\r\n err,\r\n );\r\n}\r\n\r\n/**\r\n * Flags applied to every invocation.\r\n *\r\n * - `core.quotePath=false` keeps non-ASCII paths readable instead of `\\303\\251`.\r\n * - `--no-pager` / `core.pager=` stops git from ever trying to spawn `less`.\r\n */\r\nconst BASE_ARGS = ['-c', 'core.quotePath=false', '-c', 'core.pager=', '--no-pager'];\r\n\r\n/**\r\n * Backoff between spawn retries, in milliseconds. Length sets the retry count.\r\n *\r\n * The failures this covers are short-lived contention (an antivirus scanner\r\n * holding a new image, momentary handle exhaustion), so the useful waits are\r\n * tens to low hundreds of milliseconds. A worst-case run adds ~600ms before\r\n * giving up, and only on a path that was going to fail outright before.\r\n */\r\nconst RETRY_DELAYS_MS = [50, 150, 400];\r\n\r\nconst realSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\r\n\r\nexport interface GitExecOptions {\r\n /** Injectable for deterministic tests; defaults to `child_process.spawn`. */\r\n spawn?: typeof spawn;\r\n /** Injectable for deterministic tests; defaults to a real timer. */\r\n sleep?: (ms: number) => Promise<void>;\r\n}\r\n\r\n/**\r\n * Stream `git <args>` stdout as UTF-8 chunks, retrying the two failures that\r\n * are the environment's fault rather than git's: a transient failure to start\r\n * (`GitSpawnError`), and git being killed mid-run (`GitCrashError`).\r\n *\r\n * A non-zero exit is never retried. That is git's own answer, and running the\r\n * same command again will get the same one.\r\n *\r\n * The two retryable cases differ in where they can strike. A spawn failure is\r\n * necessarily before any output exists; a crash can happen part-way through a\r\n * long `git log`. The `produced` guard is what makes retrying safe in both:\r\n * once a single chunk has reached the consumer, re-running would replay output\r\n * into a parser that has already consumed it, so from that point the error\r\n * propagates instead.\r\n *\r\n * Retrying is also safe at the command level here because every git invocation\r\n * in this codebase reads (`log`, `rev-parse`, `merge-base`, `ls-files`,\r\n * `remote get-url`). Nothing writes, so a half-finished attempt leaves no lock\r\n * file or partial state for the next one to trip over. A future write command\r\n * would need that reasoning revisited.\r\n */\r\nexport async function* gitStream(cwd: string, args: string[], opts: GitExecOptions = {}): AsyncGenerator<string> {\r\n const sleep = opts.sleep ?? realSleep;\r\n\r\n for (let attempt = 0; ; attempt += 1) {\r\n let produced = false;\r\n try {\r\n for await (const chunk of runGitOnce(cwd, args, opts)) {\r\n produced = true;\r\n yield chunk;\r\n }\r\n return;\r\n } catch (err) {\r\n const retryable = (err instanceof GitSpawnError && err.transient) || err instanceof GitCrashError;\r\n if (produced || !retryable || attempt >= RETRY_DELAYS_MS.length) throw err;\r\n await sleep(RETRY_DELAYS_MS[attempt]!);\r\n }\r\n }\r\n}\r\n\r\nasync function* runGitOnce(cwd: string, args: string[], opts: GitExecOptions): AsyncGenerator<string> {\r\n const fullArgs = [...BASE_ARGS, ...args];\r\n const child = (opts.spawn ?? spawn)('git', fullArgs, { cwd, windowsHide: true });\r\n\r\n child.stdout.setEncoding('utf8');\r\n child.stderr.setEncoding('utf8');\r\n\r\n let stderr = '';\r\n child.stderr.on('data', (chunk: string) => {\r\n // Bounded, so a pathological repo cannot blow up memory via stderr.\r\n if (stderr.length < 64 * 1024) stderr += chunk;\r\n });\r\n\r\n const exited = new Promise<{ code: number; signal: NodeJS.Signals | null }>((resolve, reject) => {\r\n child.once('error', (err) => reject(toSpawnError(err, cwd, fullArgs)));\r\n child.once('close', (code, signal) => resolve({ code: code ?? 0, signal: signal ?? null }));\r\n });\r\n // A spawn failure rejects `exited` before the stdout loop below has a\r\n // consumer for it; without this the rejection is unhandled for a tick even\r\n // though we do await it further down.\r\n exited.catch(() => {});\r\n\r\n try {\r\n for await (const chunk of child.stdout) {\r\n yield chunk as string;\r\n }\r\n } finally {\r\n // Consumer broke out early (e.g. hit --limit): don't leave git running.\r\n if (child.exitCode === null) child.kill();\r\n }\r\n\r\n const { code, signal } = await exited;\r\n\r\n const crash = crashStatus(code, signal);\r\n if (crash) {\r\n throw new GitCrashError(\r\n `git ${args.join(' ')} was killed before it could answer (${crash}) in ${cwd}.` +\r\n ' This is an environment fault, not a problem with the repository.',\r\n fullArgs,\r\n crash,\r\n code,\r\n signal,\r\n );\r\n }\r\n\r\n if (code !== 0) {\r\n const trimmed = stderr.trim();\r\n // Lead with git's own first line: now that callers no longer rewrite every\r\n // failure into \"not a git repository\", this message is what the user sees\r\n // for the cases that aren't specifically handled.\r\n const detail = trimmed.split('\\n')[0];\r\n throw new GitError(\r\n `git ${args.join(' ')} exited with code ${code}${detail ? `: ${detail}` : ''}`,\r\n fullArgs,\r\n code,\r\n trimmed,\r\n );\r\n }\r\n}\r\n\r\n/** Buffered variant, for commands with small, bounded output. */\r\nexport async function git(cwd: string, args: string[], opts: GitExecOptions = {}): Promise<string> {\r\n let out = '';\r\n for await (const chunk of gitStream(cwd, args, opts)) out += chunk;\r\n return out;\r\n}\r\n\r\n/** Buffered variant that returns `null` instead of throwing (e.g. no origin remote). */\r\nexport async function gitOrNull(cwd: string, args: string[], opts: GitExecOptions = {}): Promise<string | null> {\r\n try {\r\n return await git(cwd, args, opts);\r\n } catch (err) {\r\n if (err instanceof GitError) return null;\r\n throw err;\r\n }\r\n}\r\n","import { resolve } from 'node:path';\r\nimport { git, gitOrNull, GitError } from './exec.js';\r\n\r\nexport class NotAGitRepositoryError extends Error {\r\n constructor(readonly cwd: string) {\r\n super(`Not a git repository: ${cwd}`);\r\n this.name = 'NotAGitRepositoryError';\r\n }\r\n}\r\n\r\n/**\r\n * git's own wording when the path simply isn't inside a work tree, e.g.\r\n * `fatal: not a git repository (or any of the parent directories): .git`.\r\n *\r\n * Matching on it is what keeps this error meaning exactly one thing. Every\r\n * other non-zero exit from `rev-parse` -- dubious ownership, a corrupt object\r\n * store, an unreadable config -- is a different problem with a different fix,\r\n * and is now surfaced with git's own message instead of being relabelled.\r\n */\r\nconst NOT_A_REPO = /not a git repository/i;\r\n\r\nexport interface RepoInfo {\r\n /** Absolute, platform-native path to the work tree root. */\r\n root: string;\r\n /** `null` on a detached HEAD. */\r\n branch: string | null;\r\n /** `null` in a repo with no commits yet. */\r\n head: string | null;\r\n originUrl: string | null;\r\n}\r\n\r\n/**\r\n * Whether `ancestor` is reachable from `descendant`.\r\n *\r\n * Used to validate a stored sync cursor: after a rebase, amend or branch\r\n * switch the old HEAD may no longer be in history, and `cursor..HEAD` would\r\n * then silently skip commits. Falling back to a full resync is the safe move.\r\n */\r\nexport async function isAncestor(cwd: string, ancestor: string, descendant: string): Promise<boolean> {\r\n try {\r\n await git(cwd, ['merge-base', '--is-ancestor', ancestor, descendant]);\r\n return true;\r\n } catch (err) {\r\n if (err instanceof GitError) return false;\r\n throw err;\r\n }\r\n}\r\n\r\nexport async function readRepoInfo(cwd: string): Promise<RepoInfo> {\r\n let rootRaw: string;\r\n try {\r\n rootRaw = await git(cwd, ['rev-parse', '--show-toplevel']);\r\n } catch (err) {\r\n if (err instanceof GitError && NOT_A_REPO.test(err.stderr)) {\r\n throw new NotAGitRepositoryError(cwd);\r\n }\r\n // Anything else -- a `GitSpawnError` (git missing, or a transient Windows\r\n // spawn failure), or git failing for some reason of its own -- keeps its\r\n // own type and message. Reporting those as \"not a git repository\" sent\r\n // the reader to inspect a repository that was never the problem.\r\n throw err;\r\n }\r\n\r\n // git always prints forward slashes; resolve() gives us a native path back.\r\n const root = resolve(rootRaw.trim());\r\n\r\n const [branchRaw, headRaw, originRaw] = await Promise.all([\r\n gitOrNull(root, ['rev-parse', '--abbrev-ref', 'HEAD']),\r\n gitOrNull(root, ['rev-parse', 'HEAD']),\r\n gitOrNull(root, ['remote', 'get-url', 'origin']),\r\n ]);\r\n\r\n const branch = branchRaw?.trim() ?? null;\r\n\r\n return {\r\n root,\r\n branch: branch && branch !== 'HEAD' ? branch : null,\r\n head: headRaw?.trim() || null,\r\n originUrl: originRaw?.trim() || null,\r\n };\r\n}\r\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { hookLogPath, resolvePowerShellProfilePath } from '../shell/paths.js';\nimport { isHookInstalled, stripHookSnippet, upsertHookSnippet } from './powershell.js';\n\nexport interface HookTarget {\n profilePath: string;\n logPath: string;\n}\n\nexport class ProfileNotFoundError extends Error {\n constructor() {\n super('Could not resolve a PowerShell profile path (tried `powershell -Command $PROFILE`). Pass --profile explicitly.');\n this.name = 'ProfileNotFoundError';\n }\n}\n\nexport async function resolveHookTarget(profileOverride?: string, logPathOverride?: string): Promise<HookTarget> {\n const profilePath = profileOverride ?? (await resolvePowerShellProfilePath());\n if (!profilePath) throw new ProfileNotFoundError();\n return { profilePath, logPath: logPathOverride ?? hookLogPath() };\n}\n\nasync function readProfile(path: string): Promise<string> {\n try {\n return await readFile(path, 'utf8');\n } catch {\n return '';\n }\n}\n\nexport async function installHook(target: HookTarget): Promise<{ changed: boolean; alreadyInstalled: boolean }> {\n const current = await readProfile(target.profilePath);\n const alreadyInstalled = isHookInstalled(current);\n const next = upsertHookSnippet(current, target.logPath);\n\n if (next === current) return { changed: false, alreadyInstalled };\n\n await mkdir(dirname(target.profilePath), { recursive: true });\n await writeFile(target.profilePath, next, 'utf8');\n return { changed: true, alreadyInstalled };\n}\n\nexport async function removeHook(target: HookTarget): Promise<{ changed: boolean }> {\n const current = await readProfile(target.profilePath);\n if (!isHookInstalled(current)) return { changed: false };\n\n await writeFile(target.profilePath, stripHookSnippet(current), 'utf8');\n return { changed: true };\n}\n\nexport async function hookStatus(target: HookTarget): Promise<{ installed: boolean }> {\n const current = await readProfile(target.profilePath);\n return { installed: isHookInstalled(current) };\n}\n","import { execFile } from 'node:child_process';\r\nimport { homedir } from 'node:os';\r\nimport { join } from 'node:path';\r\nimport { promisify } from 'node:util';\r\nimport { globalWorkspaceDir } from '../config/paths.js';\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nexport function psReadLineHistoryPath(): string {\r\n const appData = process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming');\r\n return join(appData, 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt');\r\n}\r\n\r\nexport function bashHistoryPath(): string {\r\n return process.env.HISTFILE_BASH ?? join(homedir(), '.bash_history');\r\n}\r\n\r\nexport function zshHistoryPath(): string {\r\n return process.env.HISTFILE ?? join(homedir(), '.zsh_history');\r\n}\r\n\r\n/**\r\n * The hook log lives in the user-scoped directory rather than under any one\r\n * repo's `.nexusmem/`, because a shell session moves between projects -- the\r\n * log is one growing stream shared across every repo, filtered to each repo's\r\n * cwd at read time.\r\n */\r\nexport function hookLogPath(): string {\r\n return join(globalWorkspaceDir(), 'shell-history.jsonl');\r\n}\r\n\r\n/**\r\n * Resolve `$PROFILE` by asking PowerShell itself.\r\n *\r\n * The exact path depends on host (Windows PowerShell vs. PowerShell 7) and\r\n * is not worth hardcoding when the shell will just tell us.\r\n */\r\nexport async function resolvePowerShellProfilePath(exe: 'pwsh' | 'powershell' = 'powershell'): Promise<string | null> {\r\n try {\r\n const { stdout } = await execFileAsync(exe, ['-NoLogo', '-NoProfile', '-Command', '$PROFILE'], {\r\n windowsHide: true,\r\n });\r\n const path = stdout.trim();\r\n return path.length > 0 ? path : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n","import { homedir } from 'node:os';\r\nimport { join } from 'node:path';\r\n\r\n/**\r\n * The user-scoped (not repo-scoped) NexusMem directory.\r\n *\r\n * Lived in `shell/paths.ts` while the hook log was the only thing in it. The\r\n * project registry is the second, and it has nothing to do with shells, so\r\n * the location moved somewhere neither feature owns.\r\n *\r\n * `NEXUSMEM_HOME` overrides it. That is not a convenience flag: without it a\r\n * test of anything user-scoped would read and write the developer's real home\r\n * directory, which is exactly the kind of test this project does not have.\r\n */\r\nexport function globalWorkspaceDir(): string {\r\n return process.env.NEXUSMEM_HOME ?? join(homedir(), '.nexusmem');\r\n}\r\n","/**\n * Generates and manages the block NexusMem inserts into a PowerShell profile\n * to log every command with its real timestamp, cwd and exit code.\n *\n * The block wraps the existing `prompt` function rather than replacing it,\n * so an already-customized prompt (oh-my-posh, posh-git, ...) keeps\n * rendering exactly as before -- logging piggybacks on the fact that\n * `prompt` runs once per command, it does not own the prompt's appearance.\n */\n\nconst MARK_START = '# >>> nexusmem shell hook >>>';\nconst MARK_END = '# <<< nexusmem shell hook <<<';\n\n/**\n * PowerShell single-quoted strings have exactly one escape rule (a literal\n * `'` doubles to `''`) and no backslash processing at all -- unlike a JSON\n * or JS string. `JSON.stringify` would leave a Windows path's backslashes\n * doubled in the resulting PowerShell literal, since JSON escaping and\n * PowerShell escaping are different rules applied to the same character.\n */\nfunction toPowerShellLiteral(s: string): string {\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nexport function renderHookSnippet(logPath: string): string {\n return [\n MARK_START,\n 'if (Test-Path Function:\\\\prompt) { $function:__ssd_original_prompt = $function:prompt }',\n '$global:__ssd_last_history_id = -1',\n `$global:__ssd_log_path = ${toPowerShellLiteral(logPath)}`,\n 'function global:prompt {',\n ' $__ssd_h = Get-History -Count 1 -ErrorAction SilentlyContinue',\n ' if ($__ssd_h -and $__ssd_h.Id -ne $global:__ssd_last_history_id) {',\n ' $global:__ssd_last_history_id = $__ssd_h.Id',\n ' try {',\n ' $__ssd_entry = [ordered]@{',\n ' ts = (Get-Date).ToString(\"o\")',\n ' cwd = (Get-Location).Path',\n ' exitCode = $LASTEXITCODE',\n ' durationMs = [int](($__ssd_h.EndExecutionTime - $__ssd_h.StartExecutionTime).TotalMilliseconds)',\n ' command = $__ssd_h.CommandLine',\n ' }',\n ' $__ssd_dir = Split-Path -Parent $global:__ssd_log_path',\n ' if (-not (Test-Path $__ssd_dir)) { New-Item -ItemType Directory -Force -Path $__ssd_dir | Out-Null }',\n ' Add-Content -LiteralPath $global:__ssd_log_path -Value ($__ssd_entry | ConvertTo-Json -Compress) -Encoding utf8',\n ' } catch {}',\n ' }',\n ' if (Test-Path Function:\\\\__ssd_original_prompt) { & $function:__ssd_original_prompt }',\n \" else { \\\"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) \\\" }\",\n '}',\n MARK_END,\n '',\n ].join('\\n');\n}\n\nexport function isHookInstalled(profileContent: string): boolean {\n return profileContent.includes(MARK_START);\n}\n\nexport function stripHookSnippet(profileContent: string): string {\n const startIdx = profileContent.indexOf(MARK_START);\n const endIdx = profileContent.indexOf(MARK_END);\n if (startIdx === -1 || endIdx === -1) return profileContent;\n\n const afterBlock = profileContent.slice(endIdx + MARK_END.length).replace(/^\\r?\\n/, '');\n return profileContent.slice(0, startIdx) + afterBlock;\n}\n\n/** Idempotent: strips any existing block first, so re-running with a new log path updates cleanly. */\nexport function upsertHookSnippet(profileContent: string, logPath: string): string {\n const stripped = stripHookSnippet(profileContent).replace(/\\s+$/, '');\n const prefix = stripped.length > 0 ? `${stripped}\\n\\n` : '';\n return `${prefix}${renderHookSnippet(logPath)}`;\n}\n","import pc from 'picocolors';\nimport { hookStatus, installHook, removeHook, resolveHookTarget } from '../../hooks/install.js';\n\nexport interface HookOptions {\n profile?: string;\n logPath?: string;\n}\n\nexport async function runHookInstall(opts: HookOptions): Promise<number> {\n const target = await resolveHookTarget(opts.profile, opts.logPath);\n const result = await installHook(target);\n\n process.stdout.write(\n [\n result.changed\n ? `${pc.green(result.alreadyInstalled ? 'updated' : 'installed')} shell hook`\n : `${pc.dim('already up to date')}`,\n ` profile ${target.profilePath}`,\n ` log ${target.logPath}`,\n '',\n `New commands in any PowerShell session using this profile will now log their timestamp, cwd and exit code.`,\n `Open a new PowerShell window (or run \\`. $PROFILE\\`) for it to take effect.`,\n `Run ${pc.bold('nexusmem hook remove')} to undo this.`,\n '',\n ].join('\\n'),\n );\n\n return 0;\n}\n\nexport async function runHookRemove(opts: HookOptions): Promise<number> {\n const target = await resolveHookTarget(opts.profile, opts.logPath);\n const result = await removeHook(target);\n\n process.stdout.write(\n result.changed\n ? `${pc.green('removed')} shell hook from ${target.profilePath}\\n`\n : `${pc.dim('nothing to remove')} — no hook block found in ${target.profilePath}\\n`,\n );\n\n return 0;\n}\n\nexport async function runHookStatus(opts: HookOptions): Promise<number> {\n const target = await resolveHookTarget(opts.profile, opts.logPath);\n const result = await hookStatus(target);\n\n process.stdout.write(\n [\n `${pc.dim('profile')} ${target.profilePath}`,\n `${pc.dim('log ')} ${target.logPath}`,\n `${pc.dim('status ')} ${result.installed ? pc.green('installed') : pc.yellow('not installed')}`,\n '',\n ].join('\\n'),\n );\n\n return 0;\n}\n","import { relative } from 'node:path';\r\nimport pc from 'picocolors';\r\nimport {\r\n defaultConfig,\r\n isInitialized,\r\n readConfig,\r\n resolveWorkspace,\r\n writeConfig,\r\n writeWorkspaceGitignore,\r\n} from '../../config/workspace.js';\r\nimport { recordProject } from '../../config/registry.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { installHook, ProfileNotFoundError, resolveHookTarget } from '../../hooks/install.js';\r\nimport { MemoryStore } from '../../store/store.js';\r\nimport { LATEST_SCHEMA_VERSION } from '../../store/schema.js';\r\n\r\nexport interface InitOptions {\r\n cwd: string;\r\n force: boolean;\r\n /** Also install the opt-in PowerShell hook that logs cwd + exit code + timestamp. */\r\n hook: boolean;\r\n /** Persist `sources.conversation.enabled = true` in config.json. */\r\n enableConversation: boolean;\r\n /**\r\n * Where the result summary goes. Defaults to real stdout for the CLI.\r\n *\r\n * Callers that are not a terminal pass their own sink. The MCP server is\r\n * the reason this exists rather than a capture wrapper: there, `stdout` is\r\n * the JSON-RPC transport, so borrowing it for human-readable output is not\r\n * a formatting choice but a protocol hazard.\r\n */\r\n out?: (chunk: string) => void;\r\n}\r\n\r\nexport async function runInit(opts: InitOptions): Promise<number> {\r\n const out = opts.out ?? ((chunk: string) => void process.stdout.write(chunk));\r\n const repo = await readRepoInfo(opts.cwd);\r\n const ws = resolveWorkspace(repo.root);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const already = isInitialized(ws);\r\n if (already && !opts.force) {\r\n const existing = await readConfig(ws);\r\n process.stderr.write(\r\n `${pc.yellow('already initialized')} ${relative(process.cwd(), ws.configPath) || ws.configPath}\\n` +\r\n ` project ${pc.cyan(existing.projectId)}\\n` +\r\n ` use ${pc.bold('--force')} to reset the config (the database is kept)\\n`,\r\n );\r\n return 0;\r\n }\r\n\r\n await writeWorkspaceGitignore(ws);\r\n const config = defaultConfig(projectId);\r\n if (opts.enableConversation) config.sources.conversation.enabled = true;\r\n await writeConfig(ws, config);\r\n\r\n // Creating the database here means `init` surfaces native-module or\r\n // filesystem problems immediately, rather than halfway through a long sync.\r\n const store = MemoryStore.open(ws.dbPath);\r\n try {\r\n store.upsertProject({ id: projectId, root: repo.root, originUrl: repo.originUrl });\r\n } finally {\r\n store.close();\r\n }\r\n\r\n // Cross-project recall can only find a database it has been told about --\r\n // nothing else on the machine points at `<repo>/.nexusmem/`.\r\n await recordProject({ projectId, root: repo.root, dbPath: ws.dbPath, originUrl: repo.originUrl });\r\n\r\n const lines = [\r\n `${pc.green('initialized')} ${ws.dir}`,\r\n ` project ${pc.cyan(projectId)}`,\r\n ` repo ${repo.root}`,\r\n ` branch ${repo.branch ?? pc.yellow('(detached)')}`,\r\n ` schema v${LATEST_SCHEMA_VERSION}`,\r\n ];\r\n\r\n if (opts.enableConversation) {\r\n lines.push(` ${pc.yellow('conversation source enabled')} -- transcripts will be redacted-but-indexed on sync`);\r\n }\r\n\r\n if (opts.hook) {\r\n try {\r\n const target = await resolveHookTarget();\r\n const result = await installHook(target);\r\n lines.push(\r\n '',\r\n `${pc.green(result.changed ? 'installed' : 'already installed')} shell hook`,\r\n ` profile ${target.profilePath}`,\r\n ` log ${target.logPath}`,\r\n ` open a new PowerShell window (or run \\`. $PROFILE\\`) for it to take effect`,\r\n );\r\n } catch (err) {\r\n if (err instanceof ProfileNotFoundError) {\r\n lines.push('', `${pc.yellow('hook not installed')} ${err.message}`);\r\n } else {\r\n throw err;\r\n }\r\n }\r\n }\r\n\r\n lines.push('', `Next: ${pc.bold('nexusmem sync')}`, '');\r\n out(lines.join('\\n'));\r\n\r\n return 0;\r\n}\r\n","import { existsSync } from 'node:fs';\r\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\r\nimport { join } from 'node:path';\r\nimport { z } from 'zod';\r\nimport { globalWorkspaceDir } from './paths.js';\r\n\r\n/**\r\n * The list of repositories NexusMem has been run in on this machine.\r\n *\r\n * Cross-project recall needs it because every database is repo-scoped: a\r\n * query run inside one repo has no way of knowing another repo's memory\r\n * exists, since `.nexusmem/memory.db` lives under a directory it never looks\r\n * at. The alternative -- one shared global database -- was rejected for\r\n * giving up the property that deleting `<repo>/.nexusmem/` removes that\r\n * repo's memory and nothing else.\r\n *\r\n * The registry is therefore an *index*, never a source of truth. Every entry\r\n * is a pointer that may already be wrong (repo deleted, directory moved), so\r\n * reads verify the database is still on disk instead of trusting the file.\r\n */\r\n\r\nconst ENTRY_SCHEMA = z.object({\r\n projectId: z.string().min(1),\r\n root: z.string().min(1),\r\n dbPath: z.string().min(1),\r\n originUrl: z.string().nullable().default(null),\r\n /** Epoch ms of the last `init`/`sync` that recorded this entry. */\r\n lastSeenAt: z.number().int().nonnegative(),\r\n});\r\n\r\nconst REGISTRY_SCHEMA = z.object({\r\n version: z.literal(1),\r\n projects: z.array(ENTRY_SCHEMA).default([]),\r\n});\r\n\r\nexport type RegistryEntry = z.infer<typeof ENTRY_SCHEMA>;\r\n\r\nexport function registryPath(): string {\r\n return join(globalWorkspaceDir(), 'projects.json');\r\n}\r\n\r\n/**\r\n * Every recorded entry, most recently seen first.\r\n *\r\n * A missing, unreadable or corrupt file reads as an empty registry rather\r\n * than an error: this is a derived index that `sync` rebuilds by being run,\r\n * and failing a query because a cache file was truncated would be the wrong\r\n * trade in both directions.\r\n */\r\nexport async function readRegistry(): Promise<RegistryEntry[]> {\r\n let raw: string;\r\n try {\r\n raw = await readFile(registryPath(), 'utf8');\r\n } catch {\r\n return [];\r\n }\r\n\r\n try {\r\n const parsed = REGISTRY_SCHEMA.safeParse(JSON.parse(raw));\r\n if (!parsed.success) return [];\r\n return [...parsed.data.projects].sort((a, b) => b.lastSeenAt - a.lastSeenAt);\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\nexport interface LiveRegistry {\r\n /** Entries whose database is still on disk. */\r\n entries: RegistryEntry[];\r\n /** Entries whose database has since disappeared. Reported, never silently dropped from the file. */\r\n missing: RegistryEntry[];\r\n}\r\n\r\n/**\r\n * The registry split into what can still be searched and what cannot.\r\n *\r\n * Stale entries are *not* rewritten out of the file here. A database can be\r\n * temporarily unreachable -- an unmounted drive, a network share, a repo on a\r\n * USB disk -- and a query is the wrong moment to decide a project is gone\r\n * forever. `nexusmem projects --prune` is the explicit way to forget one.\r\n */\r\nexport async function readLiveRegistry(): Promise<LiveRegistry> {\r\n const all = await readRegistry();\r\n const entries: RegistryEntry[] = [];\r\n const missing: RegistryEntry[] = [];\r\n\r\n for (const entry of all) {\r\n (existsSync(entry.dbPath) ? entries : missing).push(entry);\r\n }\r\n\r\n return { entries, missing };\r\n}\r\n\r\nexport interface RecordProjectInput {\r\n projectId: string;\r\n root: string;\r\n dbPath: string;\r\n originUrl: string | null;\r\n}\r\n\r\n/**\r\n * Add or refresh one project's entry.\r\n *\r\n * Keyed by `projectId`, so re-cloning a repository to a new path moves the\r\n * entry rather than adding a second one -- the same identity rule the store\r\n * itself uses. Written to a temporary file and renamed, so a crash or a\r\n * second process mid-write cannot leave a half-written registry behind; two\r\n * concurrent syncs can still race, and the later writer simply wins.\r\n */\r\nexport async function recordProject(input: RecordProjectInput): Promise<RegistryEntry[]> {\r\n const existing = await readRegistry();\r\n const entry: RegistryEntry = { ...input, lastSeenAt: Date.now() };\r\n const projects = [entry, ...existing.filter((e) => e.projectId !== input.projectId)];\r\n\r\n await writeRegistry(projects);\r\n return projects;\r\n}\r\n\r\n/** Drop entries by project id. Returns how many were removed. */\r\nexport async function forgetProjects(projectIds: readonly string[]): Promise<number> {\r\n const existing = await readRegistry();\r\n const drop = new Set(projectIds);\r\n const kept = existing.filter((e) => !drop.has(e.projectId));\r\n\r\n if (kept.length === existing.length) return 0;\r\n\r\n await writeRegistry(kept);\r\n return existing.length - kept.length;\r\n}\r\n\r\nasync function writeRegistry(projects: readonly RegistryEntry[]): Promise<void> {\r\n const path = registryPath();\r\n const tmp = `${path}.${process.pid}.tmp`;\r\n\r\n await mkdir(globalWorkspaceDir(), { recursive: true });\r\n await writeFile(tmp, `${JSON.stringify({ version: 1, projects }, null, 2)}\\n`, 'utf8');\r\n await rename(tmp, path);\r\n}\r\n","import { createHash } from 'node:crypto';\nimport type { NodeKind } from './types.js';\n\n/** NUL cannot appear in any of our key components, so it is a safe joiner. */\nconst KEY_SEP = '\\u0000';\n\nexport function sha256Hex(input: string): string {\n return createHash('sha256').update(input, 'utf8').digest('hex');\n}\n\n/**\n * Content-addressed node id.\n *\n * `naturalKey` must be whatever uniquely identifies the event at its source\n * (a commit sha, a `timestamp:command` pair, ...). Re-running `sync` then\n * re-derives the exact same id, which makes ingestion idempotent without\n * relying on a cursor being correct.\n */\nexport function makeNodeId(projectId: string, kind: NodeKind, naturalKey: string): string {\n return sha256Hex([projectId, kind, naturalKey].join(KEY_SEP)).slice(0, 24);\n}\n","import { sha256Hex } from './ids.js';\n\n/**\n * Normalise a git remote URL so that the same repo yields the same id whether\n * it was cloned over ssh, https, with or without a `.git` suffix.\n *\n * git@github.com:acme/Repo.git -> github.com/acme/repo\n * https://github.com/acme/repo -> github.com/acme/repo\n */\nexport function normalizeGitUrl(url: string): string {\n let s = url.trim();\n\n // scp-like syntax: [user@]host:path\n const scp = /^(?:[^@/]+@)?([^/:]+):(.+)$/.exec(s);\n if (scp && !s.includes('://')) {\n s = `${scp[1]}/${scp[2]}`;\n } else {\n s = s.replace(/^[a-z+]+:\\/\\//i, '').replace(/^[^@/]+@/, '');\n }\n\n return s\n .replace(/\\/+$/, '')\n .replace(/\\.git$/i, '')\n .replace(/\\/+$/, '')\n .replace(/\\/{2,}/g, '/')\n .toLowerCase();\n}\n\nexport interface ProjectIdentity {\n /** Absolute path to the repo root on this machine. */\n root: string;\n originUrl?: string | null;\n}\n\n/**\n * Stable identity for a project.\n *\n * Prefers the origin URL so that the same repo checked out twice (or on two\n * machines) shares a memory namespace; falls back to the absolute path for\n * repos with no remote.\n */\nexport function makeProjectId({ root, originUrl }: ProjectIdentity): string {\n const basis = originUrl ? `remote:${normalizeGitUrl(originUrl)}` : `path:${root.replace(/\\\\/g, '/').toLowerCase()}`;\n return sha256Hex(basis).slice(0, 16);\n}\n","import Database from 'better-sqlite3';\r\nimport { mkdirSync } from 'node:fs';\r\nimport { dirname } from 'node:path';\r\nimport * as sqliteVec from 'sqlite-vec';\r\nimport type { MemoryNode, NodeKind } from '../core/types.js';\r\nimport { toMatchQuery } from './fts.js';\r\nimport { migrate } from './schema.js';\r\n\r\n/** Enough of a node's content to pack it, without the `node_files`/`meta` join a full `MemoryNode` carries. */\r\nexport interface LinkedNode {\r\n id: string;\r\n kind: NodeKind;\r\n projectId: string;\r\n ts: string;\r\n title: string;\r\n body: string;\r\n signal: number;\r\n}\r\n\r\nexport interface IngestStats {\r\n inserted: number;\r\n updated: number;\r\n unchanged: number;\r\n}\r\n\r\nexport interface ProjectRecord {\r\n id: string;\r\n root: string;\r\n originUrl: string | null;\r\n}\r\n\r\nexport interface StoreStats {\r\n total: number;\r\n byKind: Record<string, number>;\r\n oldest: string | null;\r\n newest: string | null;\r\n distinctFiles: number;\r\n}\r\n\r\nexport interface SearchHit {\r\n id: string;\r\n kind: NodeKind;\r\n ts: string;\r\n title: string;\r\n body: string;\r\n signal: number;\r\n /** bm25 score; lower is a better lexical match. */\r\n rank: number;\r\n /**\r\n * Human-readable name of the project this hit came from.\r\n *\r\n * Never set by the store, which is always querying one project and has\r\n * nothing to disambiguate. The cross-project pipeline attaches it so a\r\n * packed context block can say which repository each line is from.\r\n */\r\n project?: string;\r\n}\r\n\r\ninterface NodeRow {\r\n id: string;\r\n kind: NodeKind;\r\n ts: string;\r\n title: string;\r\n body: string;\r\n signal: number;\r\n rank: number;\r\n}\r\n\r\nexport interface VectorHit {\r\n id: string;\r\n kind: NodeKind;\r\n ts: string;\r\n title: string;\r\n body: string;\r\n signal: number;\r\n /** Euclidean distance from the query vector; lower is closer. */\r\n distance: number;\r\n}\r\n\r\n/** Enough of a node to list it, without the `node_files`/`meta`/body a full `MemoryNode` carries. */\r\nexport interface RecentNode {\r\n id: string;\r\n kind: NodeKind;\r\n ts: string;\r\n source: string;\r\n title: string;\r\n signal: number;\r\n}\r\n\r\nexport interface EmbeddableNode {\r\n rowid: number;\r\n id: string;\r\n title: string;\r\n body: string;\r\n}\r\n\r\nfunction epochOf(ts: string): number {\r\n const parsed = Date.parse(ts);\r\n return Number.isNaN(parsed) ? Date.now() : parsed;\r\n}\r\n\r\nexport class MemoryStore {\r\n private constructor(private readonly db: Database.Database) {}\r\n\r\n static open(dbPath: string): MemoryStore {\r\n mkdirSync(dirname(dbPath), { recursive: true });\r\n const db = new Database(dbPath);\r\n\r\n // WAL lets a long `sync` write while an agent reads via `query`.\r\n db.pragma('journal_mode = WAL');\r\n // NORMAL is the right durability trade for a rebuildable derived index:\r\n // worst case after a crash we re-run sync, which is idempotent anyway.\r\n db.pragma('synchronous = NORMAL');\r\n db.pragma('foreign_keys = ON');\r\n\r\n // Must load before migrate(): the nodes_vec migration's CREATE VIRTUAL\r\n // TABLE ... USING vec0 needs the module registered first.\r\n sqliteVec.load(db);\r\n\r\n migrate(db);\r\n return new MemoryStore(db);\r\n }\r\n\r\n close(): void {\r\n this.db.close();\r\n }\r\n\r\n upsertProject(project: ProjectRecord): void {\r\n this.db\r\n .prepare(\r\n `INSERT INTO projects (id, root, origin_url, created_at)\r\n VALUES (@id, @root, @originUrl, @now)\r\n ON CONFLICT(id) DO UPDATE SET root = excluded.root, origin_url = excluded.origin_url`,\r\n )\r\n .run({ ...project, now: Date.now() });\r\n }\r\n\r\n markSynced(projectId: string): void {\r\n this.db.prepare('UPDATE projects SET last_synced_at = ? WHERE id = ?').run(Date.now(), projectId);\r\n }\r\n\r\n /**\r\n * Every other project id ever recorded in THIS repo's own database.\r\n *\r\n * A repo's `.nexusmem/memory.db` is never shared with another repo (each\r\n * gets its own, gitignored), so any id here besides `currentProjectId` is\r\n * evidence of a prior identity for this same repo -- typically its git\r\n * remote URL changed since the last sync. See `reconcileProjectId` in\r\n * `store/reconcile.ts`.\r\n */\r\n listOtherProjectIds(currentProjectId: string): string[] {\r\n return (this.db.prepare('SELECT id FROM projects WHERE id != ?').all(currentProjectId) as Array<{ id: string }>).map(\r\n (r) => r.id,\r\n );\r\n }\r\n\r\n /**\r\n * Write a batch of nodes in one transaction.\r\n *\r\n * Ids are content-addressed, so re-ingesting the same event is a no-op --\r\n * a node is only rewritten when the derived content actually changed (which\r\n * happens when scoring or body composition is improved between releases).\r\n */\r\n upsertNodes(nodes: readonly MemoryNode[]): IngestStats {\r\n const exists = this.db.prepare('SELECT body, signal, title FROM nodes WHERE id = ?');\r\n // vec0 has no triggers to keep itself in sync (see schema.ts) -- when a\r\n // node's indexed text actually changes, its old embedding is stale and\r\n // must be dropped so the embedding pass in vector/embed.ts re-embeds it.\r\n const dropStaleEmbedding = this.db.prepare(\r\n 'DELETE FROM nodes_vec WHERE rowid = (SELECT rowid FROM nodes WHERE id = ?)',\r\n );\r\n const insertNode = this.db.prepare(\r\n `INSERT INTO nodes (id, kind, project_id, ts, ts_epoch, source, title, body, signal, meta, created_at)\r\n VALUES (@id, @kind, @projectId, @ts, @tsEpoch, @source, @title, @body, @signal, @meta, @now)\r\n ON CONFLICT(id) DO UPDATE SET\r\n ts = excluded.ts, ts_epoch = excluded.ts_epoch, source = excluded.source,\r\n title = excluded.title, body = excluded.body, signal = excluded.signal, meta = excluded.meta`,\r\n );\r\n const clearFiles = this.db.prepare('DELETE FROM node_files WHERE node_id = ?');\r\n const insertFile = this.db.prepare(\r\n `INSERT INTO node_files (node_id, path, previous_path, insertions, deletions, is_binary)\r\n VALUES (@nodeId, @path, @previousPath, @insertions, @deletions, @isBinary)\r\n ON CONFLICT(node_id, path) DO UPDATE SET\r\n previous_path = excluded.previous_path, insertions = excluded.insertions,\r\n deletions = excluded.deletions, is_binary = excluded.is_binary`,\r\n );\r\n\r\n const stats: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n const run = this.db.transaction((batch: readonly MemoryNode[]) => {\r\n const now = Date.now();\r\n\r\n for (const node of batch) {\r\n const prior = exists.get(node.id) as { body: string; signal: number; title: string } | undefined;\r\n\r\n if (prior) {\r\n if (prior.body === node.body && prior.signal === node.signal && prior.title === node.title) {\r\n stats.unchanged += 1;\r\n continue;\r\n }\r\n stats.updated += 1;\r\n dropStaleEmbedding.run(node.id);\r\n } else {\r\n stats.inserted += 1;\r\n }\r\n\r\n insertNode.run({\r\n id: node.id,\r\n kind: node.kind,\r\n projectId: node.projectId,\r\n ts: node.ts,\r\n tsEpoch: epochOf(node.ts),\r\n source: node.source,\r\n title: node.title,\r\n body: node.body,\r\n signal: node.signal,\r\n meta: JSON.stringify(node.meta),\r\n now,\r\n });\r\n\r\n clearFiles.run(node.id);\r\n for (const file of node.files) {\r\n insertFile.run({\r\n nodeId: node.id,\r\n path: file.path,\r\n previousPath: file.previousPath ?? null,\r\n insertions: file.insertions,\r\n deletions: file.deletions,\r\n isBinary: file.binary ? 1 : 0,\r\n });\r\n }\r\n }\r\n });\r\n\r\n run(nodes);\r\n return stats;\r\n }\r\n\r\n /**\r\n * The stored `meta` blob for one node, or null if it has never been\r\n * written. Used by the session summarizer to recognise work it has\r\n * already done without re-reading the node's whole body.\r\n */\r\n getNodeMeta(id: string): Record<string, unknown> | null {\r\n const row = this.db.prepare('SELECT meta FROM nodes WHERE id = ?').get(id) as { meta: string } | undefined;\r\n if (!row) return null;\r\n try {\r\n return JSON.parse(row.meta) as Record<string, unknown>;\r\n } catch {\r\n return null; // a meta blob we cannot read is treated as absent, never as a reason to fail a sync\r\n }\r\n }\r\n\r\n getSyncCursor(projectId: string, source: string): string | null {\r\n const row = this.db\r\n .prepare('SELECT cursor FROM sync_state WHERE project_id = ? AND source = ?')\r\n .get(projectId, source) as { cursor: string | null } | undefined;\r\n return row?.cursor ?? null;\r\n }\r\n\r\n setSyncCursor(projectId: string, source: string, cursor: string | null): void {\r\n this.db\r\n .prepare(\r\n `INSERT INTO sync_state (project_id, source, cursor, last_run_at)\r\n VALUES (?, ?, ?, ?)\r\n ON CONFLICT(project_id, source) DO UPDATE SET cursor = excluded.cursor, last_run_at = excluded.last_run_at`,\r\n )\r\n .run(projectId, source, cursor, Date.now());\r\n }\r\n\r\n /** Every source that has ever synced for this project, most recently run first. */\r\n listSyncState(projectId: string): Array<{ source: string; cursor: string | null; lastRunAt: number | null }> {\r\n return this.db\r\n .prepare('SELECT source, cursor, last_run_at AS lastRunAt FROM sync_state WHERE project_id = ? ORDER BY last_run_at DESC')\r\n .all(projectId) as Array<{ source: string; cursor: string | null; lastRunAt: number | null }>;\r\n }\r\n\r\n /** Drop every node for a project. Used by `sync --rebuild`. */\r\n clearProject(projectId: string): number {\r\n // nodes_vec has no FK/trigger relationship to nodes (see schema.ts) --\r\n // clean it up explicitly, before the rows it points at disappear.\r\n this.db\r\n .prepare('DELETE FROM nodes_vec WHERE rowid IN (SELECT rowid FROM nodes WHERE project_id = ?)')\r\n .run(projectId);\r\n const info = this.db.prepare('DELETE FROM nodes WHERE project_id = ?').run(projectId);\r\n this.db.prepare('DELETE FROM sync_state WHERE project_id = ?').run(projectId);\r\n return info.changes;\r\n }\r\n\r\n /**\r\n * Record a directed relationship between two existing nodes -- e.g. a\r\n * failed `shell_command` and whatever node later resolved it\r\n * (`relation = 'resolved_by'`). A relation, not a new content node: the\r\n * correlation *is* the relationship, and duplicating either side's content\r\n * into a third node would just be another independently-ranked candidate.\r\n *\r\n * Idempotent by design (`INSERT OR IGNORE` against the table's own primary\r\n * key) so re-running a correlation pass over already-linked nodes is a\r\n * no-op, not a duplicate-row error.\r\n */\r\n linkNodes(fromNodeId: string, toNodeId: string, relation: string): void {\r\n this.db\r\n .prepare('INSERT OR IGNORE INTO node_links (from_node_id, to_node_id, relation, created_at) VALUES (?, ?, ?, ?)')\r\n .run(fromNodeId, toNodeId, relation, Date.now());\r\n }\r\n\r\n /** Ids linked from `fromNodeId` under one relation, most recently linked first. Empty if none exist. */\r\n getLinkedNodeIds(fromNodeId: string, relation: string): string[] {\r\n return (\r\n this.db\r\n .prepare('SELECT to_node_id FROM node_links WHERE from_node_id = ? AND relation = ? ORDER BY created_at DESC')\r\n .all(fromNodeId, relation) as Array<{ to_node_id: string }>\r\n ).map((row) => row.to_node_id);\r\n }\r\n\r\n /**\r\n * Hydrate full content for a set of node ids, e.g. to pack a linked\r\n * resolution alongside the failure node that points at it. Order is not\r\n * guaranteed to match `ids`; ids with no matching row are silently omitted\r\n * rather than erroring. `node_links` has `ON DELETE CASCADE` on both\r\n * columns, so an individual node delete (e.g. `reconcile.ts` migrating a\r\n * node to a freshly-computed id) removes any link pointing at the old id\r\n * along with it -- correct as a safety default, though note that reconcile\r\n * does not currently re-create the link under the migrated node's new id;\r\n * that gap is not addressed here.\r\n */\r\n getNodesByIds(ids: readonly string[]): LinkedNode[] {\r\n if (ids.length === 0) return [];\r\n return this.db\r\n .prepare(\r\n `SELECT id, kind, project_id AS projectId, ts, title, body, signal\r\n FROM nodes WHERE id IN (SELECT value FROM json_each(?))`,\r\n )\r\n .all(JSON.stringify(ids)) as LinkedNode[];\r\n }\r\n\r\n /**\r\n * The most recently-remembered nodes for a project, newest event first --\r\n * chronology, not relevance. No `body`: a listing (e.g. a sidebar) needs\r\n * the title and enough metadata to label each row, not the full text.\r\n * `idx_nodes_project_ts` already exists for exactly this access pattern.\r\n */\r\n listRecentNodes(projectId: string, limit = 20): RecentNode[] {\r\n return this.db\r\n .prepare(\r\n `SELECT id, kind, ts, source, title, signal\r\n FROM nodes\r\n WHERE project_id = ?\r\n ORDER BY ts_epoch DESC\r\n LIMIT ?`,\r\n )\r\n .all(projectId, limit) as RecentNode[];\r\n }\r\n\r\n /** How many nodes of one source exist for a project. Used to preview a `pruneSourceNodes` wipe before running it. */\r\n countSourceNodes(projectId: string, source: string): number {\r\n const row = this.db\r\n .prepare('SELECT COUNT(*) AS count FROM nodes WHERE project_id = ? AND source = ?')\r\n .get(projectId, source) as { count: number };\r\n return row.count;\r\n }\r\n\r\n /**\r\n * Delete the nodes of one source that its latest full scan did not produce.\r\n *\r\n * Needed by any source whose node ids are derived from content that can be\r\n * *edited in place* rather than only appended to. A `doc_section` id comes\r\n * from `path + heading slug`, so renaming a markdown heading mints a new node\r\n * and strands the old one: `sync` reports `+1 new`, and the corpus then holds\r\n * two contradictory versions of the same section, both of which come back for\r\n * the same query. Git and shell nodes describe events that already happened\r\n * and are never restated, so they have nothing to prune.\r\n *\r\n * Scoping is the whole safety story here, and it is deliberately narrow:\r\n *\r\n * - `project_id` -- never reaches another repository's memory.\r\n * - `source` -- an exact match on the collector's own key, so pruning `docs`\r\n * cannot touch `conversation:claude-code`, `shell:pwsh` or `git` nodes even\r\n * though they share the table.\r\n * - `keepIds` -- everything this scan produced.\r\n * - `keepPaths` -- files the scan could not read. Their nodes are kept\r\n * because an unreadable file is not evidence that its sections are gone.\r\n *\r\n * Callers must pass the ids from a *complete* scan of the source. A partial\r\n * or filtered scan would read as \"these nodes no longer exist\" and delete\r\n * real history.\r\n */\r\n pruneSourceNodes(\r\n projectId: string,\r\n source: string,\r\n keepIds: readonly string[],\r\n opts: { keepPaths?: readonly string[] } = {},\r\n ): number {\r\n // json_each keeps this one statement regardless of how many sections a\r\n // repo has, instead of an id list that grows into SQLite's parameter cap.\r\n const scope = `project_id = @projectId AND source = @source\r\n AND id NOT IN (SELECT value FROM json_each(@keepIds))\r\n AND id NOT IN (SELECT node_id FROM node_files WHERE path IN (SELECT value FROM json_each(@keepPaths)))`;\r\n\r\n const params = {\r\n projectId,\r\n source,\r\n keepIds: JSON.stringify(keepIds),\r\n keepPaths: JSON.stringify(opts.keepPaths ?? []),\r\n };\r\n\r\n return this.db.transaction(() => {\r\n // Same ordering constraint as clearProject: nodes_vec is not reachable by\r\n // FK or trigger, so its rows must go while their rowids still resolve.\r\n // nodes_fts *is* trigger-backed (schema.ts) and cleans itself up on\r\n // DELETE, and node_files cascades.\r\n this.db.prepare(`DELETE FROM nodes_vec WHERE rowid IN (SELECT rowid FROM nodes WHERE ${scope})`).run(params);\r\n return this.db.prepare(`DELETE FROM nodes WHERE ${scope}`).run(params).changes;\r\n })();\r\n }\r\n\r\n /**\r\n * Nodes for this project that have no embedding yet (new, or invalidated\r\n * by a content change).\r\n *\r\n * `afterRowid` makes paging monotonic: the pass walks rowids strictly\r\n * upward instead of re-reading \"the first N still pending\". That matters\r\n * because a node the provider *failed* on stays pending -- an offset-free\r\n * loop would fetch the same failures forever, which is exactly the shape\r\n * of an infinite sync.\r\n */\r\n findNodesNeedingEmbedding(projectId: string, limit = 200, afterRowid = 0): EmbeddableNode[] {\r\n return this.db\r\n .prepare(\r\n `SELECT n.rowid AS rowid, n.id AS id, n.title AS title, n.body AS body\r\n FROM nodes n\r\n LEFT JOIN nodes_vec v ON v.rowid = n.rowid\r\n WHERE n.project_id = ? AND v.rowid IS NULL AND n.rowid > ?\r\n ORDER BY n.rowid\r\n LIMIT ?`,\r\n )\r\n .all(projectId, afterRowid, limit) as EmbeddableNode[];\r\n }\r\n\r\n /** How many of this project's nodes still need a vector. For progress reporting. */\r\n countNodesNeedingEmbedding(projectId: string): number {\r\n const row = this.db\r\n .prepare(\r\n `SELECT COUNT(*) AS n\r\n FROM nodes n\r\n LEFT JOIN nodes_vec v ON v.rowid = n.rowid\r\n WHERE n.project_id = ? AND v.rowid IS NULL`,\r\n )\r\n .get(projectId) as { n: number };\r\n return row.n;\r\n }\r\n\r\n upsertEmbedding(rowid: number, embedding: Float32Array): void {\r\n this.db\r\n .prepare('INSERT OR REPLACE INTO nodes_vec (rowid, embedding) VALUES (?, ?)')\r\n .run(BigInt(rowid), embedding);\r\n }\r\n\r\n /**\r\n * Drop every vector in this database, across all projects.\r\n *\r\n * Whole-database on purpose: `nodes_vec` is shared and holds no\r\n * provenance, so once the vectors in it stopped being comparable there is\r\n * no subset that is still trustworthy. Nodes are untouched, so the next\r\n * embedding pass simply rebuilds them.\r\n */\r\n dropAllEmbeddings(): number {\r\n return this.db.prepare('DELETE FROM nodes_vec').run().changes;\r\n }\r\n\r\n getMeta(key: string): string | null {\r\n const row = this.db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\r\n return row?.value ?? null;\r\n }\r\n\r\n setMeta(key: string, value: string): void {\r\n this.db\r\n .prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value')\r\n .run(key, value);\r\n }\r\n\r\n /**\r\n * Nearest-neighbour search over the corpus.\r\n *\r\n * `nodes_vec` has no `project_id` column of its own (embeddings are\r\n * generic; project scoping lives on `nodes`), so this over-fetches `k`\r\n * before joining and filtering, then caps to `limit`. Simple and correct;\r\n * not the efficient way to do this at a scale this project isn't at yet.\r\n */\r\n vectorSearch(projectId: string, embedding: Float32Array, limit = 20): VectorHit[] {\r\n const overfetch = Math.max(limit * 8, 50);\r\n return this.db\r\n .prepare(\r\n `SELECT n.id, n.kind, n.ts, n.title, n.body, n.signal, v.distance AS distance\r\n FROM nodes_vec v\r\n JOIN nodes n ON n.rowid = v.rowid\r\n WHERE v.embedding MATCH ? AND k = ? AND n.project_id = ?\r\n ORDER BY v.distance\r\n LIMIT ?`,\r\n )\r\n .all(embedding, overfetch, projectId, limit) as VectorHit[];\r\n }\r\n\r\n stats(projectId: string): StoreStats {\r\n const kinds = this.db\r\n .prepare('SELECT kind, COUNT(*) AS n FROM nodes WHERE project_id = ? GROUP BY kind')\r\n .all(projectId) as Array<{ kind: string; n: number }>;\r\n\r\n const range = this.db\r\n .prepare('SELECT MIN(ts) AS oldest, MAX(ts) AS newest FROM nodes WHERE project_id = ?')\r\n .get(projectId) as { oldest: string | null; newest: string | null };\r\n\r\n const files = this.db\r\n .prepare(\r\n `SELECT COUNT(DISTINCT f.path) AS n\r\n FROM node_files f JOIN nodes n ON n.id = f.node_id\r\n WHERE n.project_id = ?`,\r\n )\r\n .get(projectId) as { n: number };\r\n\r\n return {\r\n total: kinds.reduce((sum, k) => sum + k.n, 0),\r\n byKind: Object.fromEntries(kinds.map((k) => [k.kind, k.n])),\r\n oldest: range.oldest,\r\n newest: range.newest,\r\n distinctFiles: files.n,\r\n };\r\n }\r\n\r\n /**\r\n * Lexical search over the corpus.\r\n *\r\n * Title is weighted 10x body: a commit subject that names the thing you asked\r\n * about is far stronger evidence than the same word buried in a file list.\r\n * Ranking by `relevance x signal` happens a layer up, in retrieval.\r\n */\r\n search(projectId: string, query: string, limit = 20): SearchHit[] {\r\n const match = toMatchQuery(query);\r\n if (!match) return [];\r\n\r\n const rows = this.db\r\n .prepare(\r\n `SELECT n.id, n.kind, n.ts, n.title, n.body, n.signal,\r\n bm25(nodes_fts, 10.0, 1.0) AS rank\r\n FROM nodes_fts\r\n JOIN nodes n ON n.rowid = nodes_fts.rowid\r\n WHERE nodes_fts MATCH ? AND n.project_id = ?\r\n ORDER BY rank\r\n LIMIT ?`,\r\n )\r\n .all(match, projectId, limit) as NodeRow[];\r\n\r\n return rows;\r\n }\r\n\r\n /** Escape hatch for tests and future modules. */\r\n get raw(): Database.Database {\r\n return this.db;\r\n }\r\n}\r\n","/**\n * FTS5 has its own query language (`AND`, `OR`, `NEAR`, `*`, `^`, `:` column\n * filters). User input must never reach it raw -- a stray `\"` or a bare `OR`\n * turns a search into a syntax error.\n */\n\n/** Characters FTS5 treats as syntax rather than text. */\nconst FTS_SYNTAX = /[\"'()*:^{}[\\]-]/g;\n\n/**\n * Tokens too generic to carry search signal on their own. bm25 rewards rare\n * terms, so a token that is common in *meaning* but happens to be rare in\n * *this* corpus gets an inflated score purely from scarcity, not relevance --\n * verified live: a query ending in \"...PSID sender id\" pulled an unrelated\n * `winget install --id ...` shell command into the results, because \"id\"\n * alone prefix-matched \"--id\" with nothing else to weigh it down.\n *\n * Deliberately not a general English stopword list -- a real signal token\n * lost is worse than a little noise kept, so this only excludes what\n * dogfooding has actually shown to be a problem. Short technical terms like\n * \"ai\"/\"ui\"/\"db\" are left alone.\n */\nconst LOW_SIGNAL_TOKENS = new Set(['id']);\n\nfunction significantTokens(input: string): string[] {\n const tokens = input\n .replace(FTS_SYNTAX, ' ')\n .split(/\\s+/)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n\n if (tokens.length === 0) return [];\n\n // Drop low-signal tokens, but never down to zero: a query that is only\n // \"id\" must still search for something rather than matching nothing.\n const signal = tokens.filter((t) => !LOW_SIGNAL_TOKENS.has(t.toLowerCase()));\n return signal.length > 0 ? signal : tokens;\n}\n\n/**\n * Turn free-form user text into a safe FTS5 MATCH expression.\n *\n * Each token becomes a quoted prefix term, and tokens are OR-ed so that a\n * multi-word question still finds partially matching nodes -- bm25 ranking\n * then rewards the nodes that matched more of them.\n */\nexport function toMatchQuery(input: string): string | null {\n const kept = significantTokens(input);\n if (kept.length === 0) return null;\n\n return kept.map((t) => `\"${t}\"*`).join(' OR ');\n}\n\n/**\n * Same tokenization as `toMatchQuery`, but AND-ed rather than OR-ed --\n * every significant token must appear for a match. Built for\n * `failure-fix.ts`'s discussion-bridge heuristic, which found (dogfooding\n * against this repo's real history) that a single shared generic token was\n * enough to link a failure to a wholly unrelated discussion, e.g. an\n * \"npm whoami\" failure matched to a summary that merely mentions \"npm\" in\n * passing. Requiring every token cuts recall -- a discussion that paraphrases\n * the command instead of naming it will not match -- but a false positive\n * here silently attaches the wrong \"fix\" to a real failure, which is worse\n * than finding none.\n */\nexport function toStrictMatchQuery(input: string): string | null {\n const kept = significantTokens(input);\n if (kept.length === 0) return null;\n\n return kept.map((t) => `\"${t}\"*`).join(' AND ');\n}\n","import type { Database } from 'better-sqlite3';\n\n/**\n * Schema migrations, applied in order and tracked via `PRAGMA user_version`.\n *\n * Migrations are append-only: never edit a shipped migration, add a new one.\n */\n\nconst V1 = `\nCREATE TABLE meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\nCREATE TABLE projects (\n id TEXT PRIMARY KEY,\n root TEXT NOT NULL,\n origin_url TEXT,\n created_at INTEGER NOT NULL,\n last_synced_at INTEGER\n);\n\nCREATE TABLE nodes (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n project_id TEXT NOT NULL,\n -- Human-readable ISO-8601 with offset, kept verbatim from the source event.\n ts TEXT NOT NULL,\n -- Same instant as epoch ms, so range scans and ordering never parse strings.\n ts_epoch INTEGER NOT NULL,\n source TEXT NOT NULL,\n title TEXT NOT NULL,\n body TEXT NOT NULL,\n signal REAL NOT NULL,\n meta TEXT NOT NULL DEFAULT '{}',\n created_at INTEGER NOT NULL\n);\n\nCREATE INDEX idx_nodes_project_ts ON nodes (project_id, ts_epoch DESC);\nCREATE INDEX idx_nodes_project_kind ON nodes (project_id, kind, ts_epoch DESC);\n\nCREATE TABLE node_files (\n node_id TEXT NOT NULL REFERENCES nodes (id) ON DELETE CASCADE,\n path TEXT NOT NULL,\n previous_path TEXT,\n insertions INTEGER,\n deletions INTEGER,\n is_binary INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (node_id, path)\n);\n\n-- Path-scoped recall (\"what happened to src/store/db.ts?\") is a first-class\n-- query, so it gets its own index rather than a scan over node_files.\nCREATE INDEX idx_node_files_path ON node_files (path);\n\n-- External-content FTS: the index stores no copy of the text, it points back\n-- at nodes.rowid. Halves the on-disk footprint of the searchable corpus.\nCREATE VIRTUAL TABLE nodes_fts USING fts5 (\n title,\n body,\n content = 'nodes',\n content_rowid = 'rowid',\n tokenize = 'unicode61 remove_diacritics 2'\n);\n\nCREATE TRIGGER nodes_fts_ai AFTER INSERT ON nodes BEGIN\n INSERT INTO nodes_fts (rowid, title, body) VALUES (new.rowid, new.title, new.body);\nEND;\n\nCREATE TRIGGER nodes_fts_ad AFTER DELETE ON nodes BEGIN\n INSERT INTO nodes_fts (nodes_fts, rowid, title, body) VALUES ('delete', old.rowid, old.title, old.body);\nEND;\n\nCREATE TRIGGER nodes_fts_au AFTER UPDATE ON nodes BEGIN\n INSERT INTO nodes_fts (nodes_fts, rowid, title, body) VALUES ('delete', old.rowid, old.title, old.body);\n INSERT INTO nodes_fts (rowid, title, body) VALUES (new.rowid, new.title, new.body);\nEND;\n\nCREATE TABLE sync_state (\n project_id TEXT NOT NULL,\n -- Collector identity, e.g. 'git' or 'shell:pwsh'.\n source TEXT NOT NULL,\n -- Opaque to the store; for git this is the HEAD sha at last successful sync.\n cursor TEXT,\n last_run_at INTEGER,\n PRIMARY KEY (project_id, source)\n);\n`;\n\n/**\n * nomic-embed-text produces 768-dimensional vectors (confirmed against a\n * live Ollama call, not assumed). `vec0` fixes a table's dimension at\n * creation time, so switching embedding models later means a new\n * migration and a re-embed, not an in-place change to this one.\n */\nexport const EMBEDDING_DIM = 768;\n\nconst V2 = `\n-- Unlike nodes_fts, this is NOT trigger-populated: computing an embedding\n-- means an async call to an external model, which a synchronous SQL trigger\n-- cannot make. Rows are written explicitly by the embedding pass in\n-- vector/embed.ts, keyed by the same rowid nodes_fts already uses.\nCREATE VIRTUAL TABLE nodes_vec USING vec0 (\n embedding float[${EMBEDDING_DIM}]\n);\n`;\n\nconst V3 = `\n-- A relation between two existing nodes, not a new content node -- the\n-- \"failure -> fix\" correlation is the relationship itself, and duplicating\n-- either side's content into a third node would just be another\n-- independently-ranked candidate instead of the link the feature needs.\n-- One physical table, multiple relation kinds; 'resolved_by' is the first.\nCREATE TABLE node_links (\n from_node_id TEXT NOT NULL REFERENCES nodes (id) ON DELETE CASCADE,\n to_node_id TEXT NOT NULL REFERENCES nodes (id) ON DELETE CASCADE,\n relation TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (from_node_id, to_node_id, relation)\n);\n\n-- Packing a failure node needs its resolutions; nothing needs the reverse\n-- direction yet, so only the forward lookup gets an index.\nCREATE INDEX idx_node_links_from ON node_links (from_node_id);\n`;\n\ninterface Migration {\n version: number;\n up: (db: Database) => void;\n}\n\nconst MIGRATIONS: Migration[] = [\n { version: 1, up: (db) => db.exec(V1) },\n { version: 2, up: (db) => db.exec(V2) },\n { version: 3, up: (db) => db.exec(V3) },\n];\n\nexport const LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;\n\nexport function currentSchemaVersion(db: Database): number {\n return Number(db.pragma('user_version', { simple: true }) ?? 0);\n}\n\nexport function migrate(db: Database): { from: number; to: number } {\n const from = currentSchemaVersion(db);\n\n for (const migration of MIGRATIONS) {\n if (migration.version <= from) continue;\n db.transaction(() => {\n migration.up(db);\n db.pragma(`user_version = ${migration.version}`);\n })();\n }\n\n return { from, to: currentSchemaVersion(db) };\n}\n","import pc from 'picocolors';\r\nimport { forgetProjects, readLiveRegistry, registryPath } from '../../config/registry.js';\r\nimport { MemoryStore } from '../../store/store.js';\r\n\r\nexport interface ProjectsOptions {\r\n /** Forget registered projects whose database is no longer on disk. */\r\n prune: boolean;\r\n json: boolean;\r\n}\r\n\r\n/**\r\n * What `query --all-projects` would search, and what it would skip.\r\n *\r\n * Cross-project recall reads from databases outside the current repository,\r\n * which is exactly the kind of thing a user should be able to inspect before\r\n * trusting it. This is that inspection.\r\n */\r\nexport async function runProjects(opts: ProjectsOptions): Promise<number> {\r\n const { entries, missing } = await readLiveRegistry();\r\n\r\n const rows = entries.map((entry) => {\r\n let nodes: number | null = null;\r\n try {\r\n const store = MemoryStore.open(entry.dbPath);\r\n try {\r\n nodes = store.stats(entry.projectId).total;\r\n } finally {\r\n store.close();\r\n }\r\n } catch {\r\n // Present but unopenable (corrupt file, a lock we cannot take, a\r\n // native module mismatch). Reported as unknown rather than as zero,\r\n // which would read as \"synced and empty\".\r\n nodes = null;\r\n }\r\n return { ...entry, nodes };\r\n });\r\n\r\n if (opts.prune) {\r\n const removed = await forgetProjects(missing.map((entry) => entry.projectId));\r\n process.stderr.write(`${pc.yellow('pruned')} ${removed} project(s) whose database is gone\\n`);\r\n }\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify({ registry: registryPath(), projects: rows, missing }, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n process.stderr.write(`${pc.dim('registry')} ${registryPath()}\\n\\n`);\r\n\r\n if (rows.length === 0) {\r\n process.stderr.write(`${pc.yellow('no projects registered')} -- run ${pc.bold('nexusmem sync')} in a repository\\n`);\r\n return 0;\r\n }\r\n\r\n for (const row of rows) {\r\n const seen = new Date(row.lastSeenAt).toISOString().slice(0, 16).replace('T', ' ');\r\n const count = row.nodes === null ? pc.yellow('unreadable') : `${row.nodes} node(s)`;\r\n process.stdout.write(`${pc.cyan(row.projectId.slice(0, 8))} ${row.root}\\n ${pc.dim(`${count}, last seen ${seen}`)}\\n`);\r\n }\r\n\r\n if (!opts.prune && missing.length > 0) {\r\n process.stderr.write(\r\n `\\n${pc.yellow(`${missing.length} registered project(s) have no database on disk`)}` +\r\n ` ${pc.dim('-- run with --prune to forget them')}\\n`,\r\n );\r\n for (const entry of missing) process.stderr.write(` ${pc.dim(entry.root)}\\n`);\r\n }\r\n\r\n return 0;\r\n}\r\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\r\nimport { z } from 'zod';\r\nimport { readOwnVersion } from '../core/version.js';\r\nimport { getStatus, listRecentMemory, searchMemory, syncProject } from './tools.js';\r\n\r\n/**\r\n * Local-only, stdio-transport MCP server exposing NexusMem's memory to any\r\n * MCP-capable client (Claude Desktop, Cursor, Windsurf, ...). No auth layer:\r\n * a client that can spawn this process already has the same filesystem\r\n * access the process itself would use.\r\n */\r\nexport function createServer(): McpServer {\r\n const server = new McpServer({ name: 'nexusmem', version: readOwnVersion() });\r\n\r\n server.registerTool(\r\n 'search_memory',\r\n {\r\n title: 'Search remembered project history',\r\n description:\r\n 'Search a NexusMem-tracked repository\\'s remembered history: git commits, code diffs (the patch of each changed file), shell commands, tracked markdown docs, and (if enabled) conversation transcripts and per-session summaries. Returns a token-budgeted, ranked context block -- not raw search results.',\r\n inputSchema: {\r\n projectRoot: z.string().describe('Absolute path to the repository root'),\r\n query: z.string().describe('Free-text question or search terms'),\r\n budget: z.number().int().positive().optional().describe('Max tokens in the returned context block. Default 2000.'),\r\n allProjects: z\r\n .boolean()\r\n .optional()\r\n .describe(\r\n 'Search every repository NexusMem has been run in on this machine, not just projectRoot. Use when the answer may live in a different project (a pattern solved elsewhere, a tool that failed the same way before). Each result is tagged with its repository.',\r\n ),\r\n },\r\n },\r\n async ({ projectRoot, query, budget, allProjects }) => {\r\n const result = await searchMemory({ projectRoot, query, budget, allProjects });\r\n // The packed context block goes in BOTH fields: clients differ on which\r\n // one they surface to the model, and a client that prefers\r\n // structuredContent would otherwise see only the match stats -- the\r\n // block itself (the tool's entire value) silently dropped.\r\n return {\r\n content: [{ type: 'text', text: result.text }],\r\n structuredContent: {\r\n text: result.text,\r\n matched: result.matched,\r\n bm25Matched: result.bm25Matched,\r\n vectorMatched: result.vectorMatched,\r\n tokensUsed: result.tokensUsed,\r\n tokensBudget: result.tokensBudget,\r\n projectsSearched: result.projectsSearched,\r\n } as Record<string, unknown>,\r\n };\r\n },\r\n );\r\n\r\n server.registerTool(\r\n 'sync_project',\r\n {\r\n title: 'Sync remembered history',\r\n description:\r\n 'Ingest new git, diff, shell, docs and (if enabled) conversation history for a NexusMem-tracked repository into its local database. ' +\r\n 'Pass pruneSource or pruneStaleShell instead to delete a dead source\\'s nodes (e.g. the pre-hook shell scrape) rather than syncing -- ' +\r\n 'dry-run unless yes is also true, since this is an irreversible full wipe of that source.',\r\n inputSchema: {\r\n projectRoot: z.string().describe('Absolute path to the repository root'),\r\n pruneSource: z.string().optional().describe('Delete every node from this exact source (e.g. \"shell:pwsh\") instead of syncing'),\r\n pruneStaleShell: z\r\n .boolean()\r\n .optional()\r\n .describe('Shortcut for pruneSource on shell:pwsh, shell:bash and shell:zsh at once -- the dead pre-hook scrape sources'),\r\n yes: z.boolean().optional().describe('Confirms the delete. Without it, pruneSource/pruneStaleShell only report the matching count.'),\r\n },\r\n },\r\n async ({ projectRoot, pruneSource, pruneStaleShell, yes }) => {\r\n const result = await syncProject({ projectRoot, pruneSource, pruneStaleShell, yes });\r\n return { content: [{ type: 'text', text: result.summary }] };\r\n },\r\n );\r\n\r\n server.registerTool(\r\n 'get_status',\r\n {\r\n title: 'Show what is remembered',\r\n description: 'Report how many nodes NexusMem currently remembers for a repository, broken down by kind and source.',\r\n inputSchema: {\r\n projectRoot: z.string().describe('Absolute path to the repository root'),\r\n },\r\n },\r\n async ({ projectRoot }) => {\r\n const result = await getStatus({ projectRoot });\r\n return {\r\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\r\n structuredContent: result as unknown as Record<string, unknown>,\r\n };\r\n },\r\n );\r\n\r\n server.registerTool(\r\n 'list_recent_memory',\r\n {\r\n title: 'List recently remembered items',\r\n description:\r\n 'List the most recently remembered items for a NexusMem-tracked repository -- git commits, code diffs, shell commands, tracked docs, and (if enabled) conversation transcripts and session summaries -- newest first. Chronological, not relevance-ranked: use search_memory instead for a specific question.',\r\n inputSchema: {\r\n projectRoot: z.string().describe('Absolute path to the repository root'),\r\n limit: z.number().int().positive().optional().describe('Max items to return, newest first. Default 20.'),\r\n },\r\n },\r\n async ({ projectRoot, limit }) => {\r\n const result = await listRecentMemory({ projectRoot, limit });\r\n return {\r\n content: [{ type: 'text', text: JSON.stringify(result.items, null, 2) }],\r\n structuredContent: { items: result.items } as unknown as Record<string, unknown>,\r\n };\r\n },\r\n );\r\n\r\n return server;\r\n}\r\n\r\nexport async function runMcpServer(): Promise<void> {\r\n const server = createServer();\r\n const transport = new StdioServerTransport();\r\n await server.connect(transport);\r\n}\r\n","import { basename } from 'node:path';\r\nimport { makeProjectId } from '../core/project.js';\r\nimport { readRepoInfo } from '../git/repo.js';\r\nimport { renderContextBlock } from '../retrieval/pack.js';\r\nimport { runCrossProjectQuery, runHybridQuery } from '../retrieval/query-pipeline.js';\r\nimport { openAllProjectSources } from '../retrieval/sources.js';\r\nimport { resolveWorkspace } from '../config/workspace.js';\r\nimport { MemoryStore, type RecentNode } from '../store/store.js';\r\nimport { OllamaEmbeddingProvider } from '../vector/embed.js';\r\nimport { runInit } from '../cli/commands/init.js';\r\nimport { runSync, type SyncOptions } from '../cli/commands/sync.js';\r\n\r\n/**\r\n * Thin MCP-facing wrappers over the same CLI logic (`runSync`, the hybrid\r\n * query pipeline, `MemoryStore.stats`) -- no new business logic here. Every\r\n * tool takes an explicit `projectRoot` because, unlike a terminal command,\r\n * an MCP tool call carries no implicit shell cwd.\r\n */\r\n\r\nexport interface SearchMemoryInput {\r\n projectRoot: string;\r\n query: string;\r\n budget?: number;\r\n candidates?: number;\r\n /** BM25 only -- skip embedding the query and vector search. Mainly for tests; real callers want hybrid retrieval. */\r\n noVector?: boolean;\r\n /** Search every repository NexusMem has been run in on this machine, not just `projectRoot`. */\r\n allProjects?: boolean;\r\n}\r\n\r\nexport interface SearchMemoryOutput {\r\n text: string;\r\n matched: number;\r\n bm25Matched: number;\r\n vectorMatched: number;\r\n tokensUsed: number;\r\n tokensBudget: number;\r\n /** Names of the repositories actually searched -- one entry unless `allProjects` was set. */\r\n projectsSearched: string[];\r\n}\r\n\r\nexport async function searchMemory(input: SearchMemoryInput): Promise<SearchMemoryOutput> {\r\n const repo = await readRepoInfo(input.projectRoot);\r\n const ws = resolveWorkspace(repo.root);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n const budget = input.budget ?? 2000;\r\n const candidates = input.candidates ?? 30;\r\n const queryOpts = {\r\n budget,\r\n candidates,\r\n embeddingProvider: input.noVector ? null : new OllamaEmbeddingProvider(),\r\n };\r\n\r\n if (input.allProjects) {\r\n const opened = await openAllProjectSources({ projectId, root: repo.root, dbPath: ws.dbPath });\r\n try {\r\n const { bm25Count, vectorCount, hits, packed } = await runCrossProjectQuery(opened.sources, input.query, queryOpts);\r\n return {\r\n text: renderContextBlock(input.query, packed),\r\n matched: hits.length,\r\n bm25Matched: bm25Count,\r\n vectorMatched: vectorCount,\r\n tokensUsed: packed.tokensUsed,\r\n tokensBudget: packed.tokensBudget,\r\n projectsSearched: opened.sources.map((s) => s.label),\r\n };\r\n } finally {\r\n opened.close();\r\n }\r\n }\r\n\r\n const store = MemoryStore.open(ws.dbPath);\r\n try {\r\n const { bm25Count, vectorCount, hits, packed } = await runHybridQuery(store, projectId, input.query, queryOpts);\r\n\r\n return {\r\n text: renderContextBlock(input.query, packed),\r\n matched: hits.length,\r\n bm25Matched: bm25Count,\r\n vectorMatched: vectorCount,\r\n tokensUsed: packed.tokensUsed,\r\n tokensBudget: packed.tokensBudget,\r\n projectsSearched: [basename(repo.root) || repo.root],\r\n };\r\n } finally {\r\n store.close();\r\n }\r\n}\r\n\r\nexport interface SyncProjectInput {\r\n projectRoot: string;\r\n /** Skip the embedding pass for this sync. Mainly for tests; real callers want vectors kept fresh. */\r\n noEmbed?: boolean;\r\n /** Delete every node from this exact source (e.g. `shell:pwsh`) instead of syncing. Dry-run unless `yes` is also set. */\r\n pruneSource?: string;\r\n /** Shortcut for `pruneSource` on shell:pwsh, shell:bash and shell:zsh at once -- the dead pre-hook scrape sources. */\r\n pruneStaleShell?: boolean;\r\n /** Confirms an irreversible `pruneSource`/`pruneStaleShell` delete. Without it, the matching count is returned and nothing is removed. */\r\n yes?: boolean;\r\n}\r\n\r\nexport interface SyncProjectOutput {\r\n summary: string;\r\n}\r\n\r\n/**\r\n * Collects `runInit`/`runSync`'s summary through an explicit sink rather than\r\n * by reassigning `process.stdout.write`, which is what this used to do.\r\n *\r\n * That mattered more than it looked: under the stdio transport, `stdout` *is*\r\n * the JSON-RPC channel. `StdioServerTransport` holds the stream object and\r\n * resolves `.write` at send time, so a patched `write` also intercepts\r\n * protocol traffic -- a response emitted while a sync was running would land\r\n * in the capture buffer, and the patch's unconditional `return true` would\r\n * report it as delivered. Overlapping syncs compounded it: the second call\r\n * saved the first call's patch as \"the original\" and restored that instead.\r\n *\r\n * Passing a sink keeps the single shared implementation (the reason for the\r\n * original trade) without borrowing a global that something else owns.\r\n *\r\n * Always runs `init` first: an MCP client has no reason to know this tool\r\n * needs a separate init step, and `runInit` is already a safe no-op (just a\r\n * printed notice) when the project is initialized already.\r\n */\r\nexport async function syncProject(input: SyncProjectInput): Promise<SyncProjectOutput> {\r\n const chunks: string[] = [];\r\n const out = (chunk: string) => {\r\n chunks.push(chunk);\r\n };\r\n\r\n await runInit({ cwd: input.projectRoot, force: false, hook: false, enableConversation: false, out });\r\n\r\n const opts: SyncOptions = {\r\n cwd: input.projectRoot,\r\n full: false,\r\n rebuild: false,\r\n quiet: true,\r\n noEmbed: input.noEmbed,\r\n pruneSource: input.pruneSource,\r\n pruneStaleShell: input.pruneStaleShell,\r\n yes: input.yes,\r\n out,\r\n };\r\n await runSync(opts);\r\n\r\n return { summary: stripAnsi(chunks.join('').trim()) };\r\n}\r\n\r\n/**\r\n * Strips ANSI SGR color codes (`\\x1b[...m`) from CLI-formatted text before\r\n * it leaves the MCP boundary.\r\n *\r\n * `runInit`/`runSync` format their `out` stream with picocolors for\r\n * terminal display -- correct there. But picocolors' own source\r\n * (`node_modules/picocolors/picocolors.js`) treats `platform === 'win32'`\r\n * as sufficient evidence of color support on its own; it never checks\r\n * `process.stdout.isTTY`. This process's stdout is the MCP JSON-RPC\r\n * channel, piped, never a terminal, on every platform -- so on Windows the\r\n * summary came out colorized regardless. Confirmed live, not just reasoned\r\n * about: a real MCP client (the VS Code extension's Output channel)\r\n * rendered the raw escape codes as literal text.\r\n */\r\nfunction stripAnsi(text: string): string {\r\n return text.replace(/\\x1b\\[[0-9;]*m/g, '');\r\n}\r\n\r\nexport interface ListRecentMemoryInput {\r\n projectRoot: string;\r\n /** Max items to return, newest first. Default 20. */\r\n limit?: number;\r\n}\r\n\r\nexport interface ListRecentMemoryOutput {\r\n items: RecentNode[];\r\n}\r\n\r\n/**\r\n * Chronology, not relevance -- \"what has this project's memory recorded\r\n * lately\" rather than \"what answers this question\" (that's `searchMemory`).\r\n * Built for the VS Code extension's sidebar view, which lists rather than\r\n * searches.\r\n */\r\nexport async function listRecentMemory(input: ListRecentMemoryInput): Promise<ListRecentMemoryOutput> {\r\n const repo = await readRepoInfo(input.projectRoot);\r\n const ws = resolveWorkspace(repo.root);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const store = MemoryStore.open(ws.dbPath);\r\n try {\r\n return { items: store.listRecentNodes(projectId, input.limit) };\r\n } finally {\r\n store.close();\r\n }\r\n}\r\n\r\nexport interface GetStatusInput {\r\n projectRoot: string;\r\n}\r\n\r\nexport interface GetStatusOutput {\r\n total: number;\r\n byKind: Record<string, number>;\r\n sources: Array<{ source: string; cursor: string | null; lastRunAt: number | null }>;\r\n}\r\n\r\nexport async function getStatus(input: GetStatusInput): Promise<GetStatusOutput> {\r\n const repo = await readRepoInfo(input.projectRoot);\r\n const ws = resolveWorkspace(repo.root);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const store = MemoryStore.open(ws.dbPath);\r\n try {\r\n const stats = store.stats(projectId);\r\n return { total: stats.total, byKind: stats.byKind, sources: store.listSyncState(projectId) };\r\n } finally {\r\n store.close();\r\n }\r\n}\r\n","export function truncate(s: string, max: number): string {\n return s.length <= max ? s : `${s.slice(0, max - 1).trimEnd()}…`;\n}\n\n/** Rough heuristic for English-dominant text: ~4 chars per token. */\nexport function approxTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n","import { approxTokens, truncate } from '../core/text.js';\r\nimport type { RankedHit } from './rank.js';\r\n\r\nexport interface PackedNode {\r\n id: string;\r\n kind: string;\r\n ts: string;\r\n title: string;\r\n signal: number;\r\n score: number;\r\n summary: string;\r\n tokens: number;\r\n /** Set only for a cross-project query, where a line's repository is not implied by context. */\r\n project?: string;\r\n}\r\n\r\nexport interface PackResult {\r\n nodes: PackedNode[];\r\n tokensUsed: number;\r\n tokensBudget: number;\r\n consideredNodes: number;\r\n droppedForBudget: number;\r\n droppedForDiversity: number;\r\n}\r\n\r\nconst DEFAULT_SUMMARY_CHARS = 320;\r\n/** Formatting overhead per node (date prefix, bullet, line breaks) counted as tokens. */\r\nconst NODE_OVERHEAD_TOKENS = 8;\r\n\r\nconst CONVERSATION_ANSWER_MARKER = '\\n\\nA: ';\r\n\r\n/**\r\n * At most this many hits from the same original document may appear in one\r\n * packed result.\r\n *\r\n * `conversation_turn` and `doc_section` both chunk one long reply or file\r\n * into several nodes that share the *same* `ts` (see\r\n * `collectors/conversation.ts` and `collectors/docs.ts`) -- there is no\r\n * per-chunk parent id available at this layer, but the shared timestamp\r\n * already identifies \"pieces of the same original text\" in practice, since\r\n * two unrelated exchanges or files are never indexed in the same\r\n * millisecond. Verified live: a query for \"token\" returned 9 of its top 12\r\n * hits as different chunks of one heavily-sectioned conversation reply,\r\n * crowding out every other node -- including the actually relevant one.\r\n * Kept at 2 rather than 1 so a reply that is genuinely relevant in two\r\n * places still shows both, without letting either dominate.\r\n */\r\nconst MAX_PER_FAMILY = 2;\r\nconst CHUNKED_KINDS = new Set(['conversation_turn', 'doc_section']);\r\n\r\n/** Start of a hunk, at the beginning of a line. */\r\nconst HUNK_BOUNDARY = '\\n@@ ';\r\n\r\n/**\r\n * Words carried by almost every question, and therefore by almost every hunk.\r\n *\r\n * Only used to choose between hunks of one node -- BM25 has its own view of\r\n * term weight and is untouched by this list.\r\n */\r\nconst STOPWORDS = new Set([\r\n 'the', 'and', 'for', 'are', 'was', 'were', 'that', 'this', 'with', 'from', 'into', 'not', 'but',\r\n 'what', 'why', 'how', 'when', 'where', 'which', 'who', 'does', 'did', 'has', 'have', 'had', 'can',\r\n 'could', 'would', 'should', 'all', 'any', 'every', 'each', 'its', 'our', 'you', 'your', 'about',\r\n]);\r\n\r\nfunction queryTerms(query: string): string[] {\r\n const words = query.toLowerCase().match(/[a-z0-9_]{3,}/g) ?? [];\r\n return [...new Set(words.filter((w) => !STOPWORDS.has(w)).map(singularize))];\r\n}\r\n\r\n/**\r\n * Word pieces of a line of code.\r\n *\r\n * Splitting on case transitions as well as punctuation is what lets a natural\r\n * question meet an identifier: `RETRY_DELAYS_MS` and `SpawnOptions` become\r\n * `retry delays ms` and `spawn options`, so \"retry delays\" and \"spawn\" find\r\n * them. A plain `\\b` word match finds neither, because `_` is a word character\r\n * and a camel hump is not a boundary at all.\r\n */\r\nfunction codeTokens(text: string): Set<string> {\r\n const spaced = text.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();\r\n return new Set((spaced.match(/[a-z0-9]{2,}/g) ?? []).map(singularize));\r\n}\r\n\r\n/** Crude, deliberately: enough to let \"spawns\" meet `spawn`, and nothing more. */\r\nfunction singularize(word: string): string {\r\n return word.length > 3 && word.endsWith('s') && !word.endsWith('ss') ? word.slice(0, -1) : word;\r\n}\r\n\r\n/**\r\n * The part of a patch worth spending the budget on.\r\n *\r\n * A summary is a few hundred characters and a real patch is thousands, so\r\n * taking the head means showing whichever hunk happens to sit at the top of\r\n * the file -- an import block, or the licence comment. Dogfooding this on\r\n * `src/git/exec.ts` returned the right file for \"what flags are passed to\r\n * every git invocation\" and then showed a class definition seventy lines above\r\n * the answer.\r\n *\r\n * Term overlap is a crude relevance measure, but it is measured against the\r\n * *hunks of one already-retrieved node*, where ranking has done its job and\r\n * the only question left is which few lines to show.\r\n */\r\nfunction pickHunk(patch: string, query: string): string {\r\n const first = patch.indexOf('@@ ');\r\n if (first === -1) return patch;\r\n\r\n const hunks: string[] = [];\r\n let rest = patch.slice(first);\r\n for (;;) {\r\n const next = rest.indexOf(HUNK_BOUNDARY, 1);\r\n if (next === -1) {\r\n hunks.push(rest);\r\n break;\r\n }\r\n hunks.push(rest.slice(0, next));\r\n rest = rest.slice(next + 1);\r\n }\r\n\r\n const terms = queryTerms(query);\r\n if (terms.length === 0) return hunks[0] ?? patch;\r\n\r\n let best = hunks[0] ?? patch;\r\n let bestScore = 0;\r\n for (const hunk of hunks) {\r\n const tokens = codeTokens(hunk);\r\n // Whole tokens, not substrings: a query about git must not score every\r\n // hunk in a repository alike because the letters appear inside some\r\n // longer identifier.\r\n const score = terms.reduce((n, term) => n + (tokens.has(term) ? 1 : 0), 0);\r\n if (score > bestScore) {\r\n best = hunk;\r\n bestScore = score;\r\n }\r\n }\r\n return focusHunk(best, terms);\r\n}\r\n\r\n/**\r\n * Drop leading context so the summary starts at the change.\r\n *\r\n * A hunk opens with up to three unchanged lines, and a 320-character summary\r\n * is about six lines: taken from the top, a hunk can spend its entire budget\r\n * on code that did not change and stop one line short of the one that did.\r\n * The hunk header is kept -- it names the enclosing function, which is how a\r\n * reader locates the excerpt.\r\n */\r\nfunction focusHunk(hunk: string, terms: string[]): string {\r\n const lines = hunk.split('\\n');\r\n const header = lines[0] ?? '';\r\n const body = lines.slice(1);\r\n const isChange = (line: string) => line.startsWith('+') || line.startsWith('-');\r\n\r\n let idx = terms.length\r\n ? body.findIndex((line) => {\r\n if (!isChange(line)) return false;\r\n const tokens = codeTokens(line);\r\n return terms.some((term) => tokens.has(term));\r\n })\r\n : -1;\r\n if (idx === -1) idx = body.findIndex(isChange);\r\n if (idx <= 1) return hunk;\r\n\r\n // One line of context above the change, so it does not read as free-floating.\r\n return [header, ...body.slice(idx - 1)].join('\\n');\r\n}\r\n\r\nfunction summarize(hit: RankedHit, maxChars: number, query: string): string {\r\n // Conversation nodes always shape their body as \"Q: <question>\\n\\nA:\r\n // <answer>\" (collectors/conversation.ts repeats the full original question\r\n // in every chunk so each node is self-contained). The answer is the part\r\n // worth showing -- truncating from the start of the body would otherwise\r\n // spend the whole summary on a long question and never reach it.\r\n const answerIdx = hit.body.indexOf(CONVERSATION_ANSWER_MARKER);\r\n if (answerIdx !== -1) {\r\n const answer = hit.body.slice(answerIdx + CONVERSATION_ANSWER_MARKER.length).trim();\r\n if (answer) return truncate(answer, maxChars);\r\n }\r\n\r\n // A diff body is \"<subject>\\n<file line>\\n\\n<hunks>\": keep the two header\r\n // lines, which say what changed and by how much, then spend the rest of the\r\n // budget on the hunk that matches the question.\r\n if (hit.kind === 'code_diff') {\r\n const patchStart = hit.body.indexOf(HUNK_BOUNDARY);\r\n if (patchStart !== -1) {\r\n const head = hit.body.slice(0, patchStart).trim();\r\n const hunk = pickHunk(hit.body.slice(patchStart + 1), query);\r\n return truncate(`${head}\\n${hunk}`, maxChars);\r\n }\r\n }\r\n\r\n // Body already leads with the title; skip straight to whatever follows it\r\n // so the summary doesn't repeat text that's already shown as the heading.\r\n const rest = hit.body.startsWith(hit.title) ? hit.body.slice(hit.title.length).trim() : hit.body;\r\n return truncate(rest || hit.title, maxChars);\r\n}\r\n\r\n/**\r\n * Greedily fill a token budget with the highest-scoring hits.\r\n *\r\n * Nodes are tried strictly in score order. One that doesn't fit is skipped,\r\n * not a stopping point -- a later, smaller, lower-priority node may still\r\n * fit the remaining budget. This is best-effort packing, not knapsack-\r\n * optimal, but it keeps \"why is node X included\" answerable by score alone.\r\n */\r\nexport function packContext(\r\n ranked: readonly RankedHit[],\r\n tokensBudget: number,\r\n opts: { summaryChars?: number; query?: string } = {},\r\n): PackResult {\r\n const summaryChars = opts.summaryChars ?? DEFAULT_SUMMARY_CHARS;\r\n const query = opts.query ?? '';\r\n const nodes: PackedNode[] = [];\r\n let tokensUsed = 0;\r\n let droppedForBudget = 0;\r\n let droppedForDiversity = 0;\r\n const familyCounts = new Map<string, number>();\r\n\r\n for (const hit of ranked) {\r\n const familyKey = CHUNKED_KINDS.has(hit.kind) ? `${hit.kind}:${hit.ts}` : null;\r\n if (familyKey && (familyCounts.get(familyKey) ?? 0) >= MAX_PER_FAMILY) {\r\n droppedForDiversity += 1;\r\n continue;\r\n }\r\n\r\n const summary = summarize(hit, summaryChars, query);\r\n const tokens = approxTokens(hit.title) + approxTokens(summary) + NODE_OVERHEAD_TOKENS;\r\n\r\n if (tokensUsed + tokens > tokensBudget) {\r\n droppedForBudget += 1;\r\n continue;\r\n }\r\n\r\n nodes.push({\r\n id: hit.id,\r\n kind: hit.kind,\r\n ts: hit.ts,\r\n title: hit.title,\r\n signal: hit.signal,\r\n score: hit.score,\r\n summary,\r\n tokens,\r\n ...(hit.project ? { project: hit.project } : {}),\r\n });\r\n tokensUsed += tokens;\r\n // Only a hit that actually made it in spends its family's slot -- one\r\n // skipped for budget must not block a smaller sibling later in the list.\r\n if (familyKey) familyCounts.set(familyKey, (familyCounts.get(familyKey) ?? 0) + 1);\r\n }\r\n\r\n return { nodes, tokensUsed, tokensBudget, consideredNodes: ranked.length, droppedForBudget, droppedForDiversity };\r\n}\r\n\r\n/** Render a packed result as plain text, ready to paste into an agent's context. */\r\nexport function renderContextBlock(query: string, result: PackResult): string {\r\n if (result.nodes.length === 0) return `No remembered context matched \"${query}\".`;\r\n\r\n const lines = [`Relevant history for: ${query}`, ''];\r\n for (const node of result.nodes) {\r\n // The project tag is the whole point of a cross-project answer: without\r\n // it the reader cannot tell which repository a line describes, and two\r\n // repositories' conventions read as one contradictory history.\r\n const project = node.project ? `[${node.project}] ` : '';\r\n lines.push(`- ${node.ts.slice(0, 10)} ${project}${node.title}`);\r\n if (node.summary && node.summary !== node.title) {\r\n // A patch is the one body whose line structure *is* the content:\r\n // flattened onto one line, `- return a;` and `+ return b;` become an\r\n // unreadable run of tokens. Every other kind is prose, where collapsing\r\n // whitespace keeps one node to one line.\r\n if (node.kind === 'code_diff') {\r\n for (const line of node.summary.split('\\n')) lines.push(` ${line}`);\r\n } else {\r\n lines.push(` ${node.summary.replace(/\\n+/g, ' ')}`);\r\n }\r\n }\r\n }\r\n return lines.join('\\n');\r\n}\r\n","import { toStrictMatchQuery } from '../store/fts.js';\nimport type { MemoryStore } from '../store/store.js';\n\n/**\n * Links a failed `shell_command` node to whatever later resolved it --\n * Phase 7's \"failure -> fix chain\" building block. Two independent,\n * deliberately narrow heuristics; a failure can be linked by either, both,\n * or neither. Both are unvalidated until dogfooded against a real corpus\n * (see ROADMAP.local.md's Phase 7 entry) -- this is a first pass sized for\n * that validation, not a claim that either heuristic is correct yet.\n *\n * - **Same-command retry.** A later `shell_command` in the same project and\n * `cwd`, the *exact* normalized command text (trim + collapse whitespace +\n * lowercase), `exitCode === 0`, within `retryWindowMs`. High precision by\n * construction, low recall: a fix that changes the command itself (a typo\n * correction, an added flag) is invisible to an exact-text match. Not\n * attempted here -- fuzzy matching is a stretch goal, not this pass's job.\n * - **Conversation bridge.** The best FTS match (via `toStrictMatchQuery`,\n * an AND of every significant token in the failing command) among\n * `conversation_turn`/`session_summary` nodes in the following\n * `discussionWindowMs`. Originally used `toMatchQuery` (OR-of-tokens) and\n * was dogfooded against this repo's real history 2026-08-15: roughly half\n * the links were wrong, and the confirmed false positives were all driven\n * by a single shared generic token (e.g. an \"npm whoami\" failure linked to\n * an unrelated summary that just happens to mention \"npm\"). Tightened to\n * AND -- still loose in the other direction, since a discussion that\n * paraphrases the command instead of naming its words will not match, but\n * an unvalidated false positive is worse than a missed true positive here.\n * Does not chain further to whatever commit that conversation might cite;\n * linking failure -> discussion is the whole claim this heuristic makes.\n */\n\nexport interface CorrelateOptions {\n /** How long after a failure a same-command retry may count as its resolution. Default 24h. */\n retryWindowMs?: number;\n /** How long after a failure a conversation may count as discussing it. Default 24h. */\n discussionWindowMs?: number;\n}\n\nexport interface CorrelateStats {\n failuresExamined: number;\n linkedByRetry: number;\n linkedByDiscussion: number;\n}\n\nconst DEFAULT_RETRY_WINDOW_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_DISCUSSION_WINDOW_MS = 24 * 60 * 60 * 1000;\n\n/**\n * One relation string per heuristic, not a shared `resolved_by` -- dogfooding\n * against this repo's real history (2026-08-15) found the retry heuristic\n * correct on every manually-checked link, but the discussion heuristic wrong\n * on roughly half. A consumer (e.g. `pack.ts`) needs to trust one and ignore\n * the other; a single relation string could not express that distinction\n * without also tagging every row, which the relation string already does\n * for free.\n */\nexport const RESOLVED_BY_RETRY = 'resolved_by:retry';\nexport const RESOLVED_BY_DISCUSSION = 'resolved_by:discussion';\n\ninterface FailureRow {\n id: string;\n ts_epoch: number;\n command: string | null;\n cwd: string | null;\n}\n\nfunction normalizeCommand(command: string): string {\n return command.trim().replace(/\\s+/g, ' ').toLowerCase();\n}\n\nexport function correlateFailures(store: MemoryStore, projectId: string, opts: CorrelateOptions = {}): CorrelateStats {\n const retryWindowMs = opts.retryWindowMs ?? DEFAULT_RETRY_WINDOW_MS;\n const discussionWindowMs = opts.discussionWindowMs ?? DEFAULT_DISCUSSION_WINDOW_MS;\n\n const db = store.raw;\n\n const failures = db\n .prepare(\n `SELECT id, ts_epoch, json_extract(meta, '$.command') AS command, json_extract(meta, '$.cwd') AS cwd\n FROM nodes\n WHERE project_id = ? AND kind = 'shell_command'\n AND json_extract(meta, '$.exitCode') IS NOT NULL\n AND json_extract(meta, '$.exitCode') != 0`,\n )\n .all(projectId) as FailureRow[];\n\n const findRetry = db.prepare(\n `SELECT id FROM nodes\n WHERE project_id = ? AND kind = 'shell_command'\n AND json_extract(meta, '$.exitCode') = 0\n AND ts_epoch > ? AND ts_epoch <= ?\n AND lower(trim(json_extract(meta, '$.command'))) = ?\n AND (json_extract(meta, '$.cwd') IS ? OR json_extract(meta, '$.cwd') = ?)\n ORDER BY ts_epoch ASC LIMIT 1`,\n );\n\n const findDiscussion = db.prepare(\n `SELECT n.id FROM nodes_fts\n JOIN nodes n ON n.rowid = nodes_fts.rowid\n WHERE nodes_fts MATCH ? AND n.project_id = ? AND n.kind IN ('conversation_turn', 'session_summary')\n AND n.ts_epoch > ? AND n.ts_epoch <= ?\n ORDER BY bm25(nodes_fts, 10.0, 1.0)\n LIMIT 1`,\n );\n\n let linkedByRetry = 0;\n let linkedByDiscussion = 0;\n\n for (const failure of failures) {\n if (!failure.command) continue;\n\n const retry = findRetry.get(\n projectId,\n failure.ts_epoch,\n failure.ts_epoch + retryWindowMs,\n normalizeCommand(failure.command),\n failure.cwd,\n failure.cwd,\n ) as { id: string } | undefined;\n if (retry) {\n store.linkNodes(failure.id, retry.id, RESOLVED_BY_RETRY);\n linkedByRetry += 1;\n }\n\n const match = toStrictMatchQuery(failure.command);\n if (match) {\n const discussion = findDiscussion.get(match, projectId, failure.ts_epoch, failure.ts_epoch + discussionWindowMs) as\n | { id: string }\n | undefined;\n if (discussion) {\n store.linkNodes(failure.id, discussion.id, RESOLVED_BY_DISCUSSION);\n linkedByDiscussion += 1;\n }\n }\n }\n\n return { failuresExamined: failures.length, linkedByRetry, linkedByDiscussion };\n}\n","import type { SearchHit, VectorHit } from '../store/store.js';\n\n/**\n * Reciprocal Rank Fusion: combine several best-first ranked lists into one\n * relevance score per item, using only each item's *position* in each list,\n * never the raw scores.\n *\n * This is what makes fusing BM25 (a cost, lower is better) with vector\n * distance (also lower is better, but on a completely different, unbounded\n * scale) safe without any manual normalization between them -- position is\n * the only thing the two scales agree on.\n */\n\n/** Standard RRF constant. Large enough that rank 1 doesn't overwhelmingly dominate rank 2. */\nconst RRF_K = 60;\n\nexport interface RankedItem {\n id: string;\n}\n\n/**\n * `lists` are each assumed already sorted best-first. An id absent from a\n * list simply contributes nothing from it -- appearing in multiple lists\n * compounds, which is the intended behavior: a node both BM25 *and* vector\n * search agree on should outrank one only one of them found.\n */\nexport function reciprocalRankFusion(lists: readonly (readonly RankedItem[])[]): Map<string, number> {\n const scores = new Map<string, number>();\n\n for (const list of lists) {\n list.forEach((item, index) => {\n const contribution = 1 / (RRF_K + index + 1);\n scores.set(item.id, (scores.get(item.id) ?? 0) + contribution);\n });\n }\n\n return scores;\n}\n\n/**\n * Union two hit sets by id for a combined ranking pass.\n *\n * A vector-only match (found by semantic similarity, sharing no keywords\n * with the query at all) has no real BM25 rank -- it gets a placeholder\n * `rank` of 0, which is never read: once `relevanceScores` (from\n * `reciprocalRankFusion`) is passed to `rankHits`, the BM25-derived\n * relevance path is bypassed entirely for every hit in the set, fused or not.\n */\nexport function mergeSearchAndVectorHits(bm25Hits: readonly SearchHit[], vectorHits: readonly VectorHit[]): SearchHit[] {\n const byId = new Map<string, SearchHit>();\n\n for (const hit of bm25Hits) byId.set(hit.id, hit);\n\n for (const hit of vectorHits) {\n if (byId.has(hit.id)) continue;\n byId.set(hit.id, { id: hit.id, kind: hit.kind, ts: hit.ts, title: hit.title, body: hit.body, signal: hit.signal, rank: 0 });\n }\n\n return [...byId.values()];\n}\n","import type { SearchHit } from '../store/store.js';\r\n\r\nexport interface RankedHit extends SearchHit {\r\n /** 0..1, normalized from bm25 within this result set. 1 = best lexical match. */\r\n relevance: number;\r\n /** 0..1, structural importance rescaled so it can never zero out relevance. */\r\n signalWeight: number;\r\n /** 0..1, decays with age but never below the floor. */\r\n recencyFactor: number;\r\n ageDays: number;\r\n /**\r\n * `relevance * signalWeight**SIGNAL_EXPONENT * recencyFactor**RECENCY_EXPONENT`.\r\n * Sort key, best first. The reported `signalWeight`/`recencyFactor` are the\r\n * raw factors, not the exponentiated ones, so they stay readable as \"how\r\n * important\" and \"how fresh\" independent of how much weight ranking gives them.\r\n */\r\n score: number;\r\n}\r\n\r\nexport interface RankOptions {\r\n /** Days for the recency factor to halve. Default 30. */\r\n halfLifeDays?: number;\r\n /** Injectable for deterministic tests; defaults to the real clock. */\r\n now?: Date;\r\n /**\r\n * Pre-fused relevance (e.g. from `reciprocalRankFusion` over BM25 +\r\n * vector search), keyed by node id, higher-is-better. When provided, this\r\n * replaces the BM25-only `relevance` derivation entirely -- vector search\r\n * changes what counts as relevant, not the rest of the ranking formula.\r\n */\r\n relevanceScores?: ReadonlyMap<string, number>;\r\n}\r\n\r\n/**\r\n * Floors on each factor.\r\n *\r\n * Every factor lives in [floor, 1] rather than [0, 1]. Multiplying three\r\n * [0,1] terms lets any single dimension crush the other two to zero -- an\r\n * old-but-perfect match would lose to a recent-but-mediocre one purely on\r\n * age. Floors keep the combination a *reordering* within each dimension\r\n * instead of an on/off gate.\r\n */\r\nconst RELEVANCE_FLOOR = 0.15;\r\nconst SIGNAL_FLOOR = 0.2;\r\nconst RECENCY_FLOOR = 0.3;\r\nconst DEFAULT_HALF_LIFE_DAYS = 30;\r\nconst MS_PER_DAY = 86_400_000;\r\n\r\n/**\r\n * How far the *priors*, together, may overturn the *query*.\r\n *\r\n * `relevance` is the only factor derived from what was asked; `signal` and\r\n * `recency` are query-independent priors that hold before any query exists.\r\n * Multiplying all three as equals let the priors win outright: signal spans\r\n * 5x (0.2 -> 1) and recency 3.33x (0.3 -> 1), while relevance spans 6.7x, so a\r\n * well-scored recent commit could outrank a document that matched the question\r\n * far better. Observed on a real query: a `fix:` commit (signal .9) took rank 1\r\n * from the top-fused doc section (signal .55) on a 44% signal edge against a\r\n * 15% relevance deficit.\r\n *\r\n * Exponents bound that instead of banning it. Priors still order\r\n * equally-relevant hits exactly as before (the transform is monotonic); they\r\n * simply cannot overturn a large relevance gap.\r\n *\r\n * **The budget is shared, not per-prior.** Capping each prior at\r\n * `MAX_PRIOR_OVERTURN` separately caps neither the pair: the score multiplies\r\n * them, so two priors each worth 2x are worth 4x together. That is not a corner\r\n * case -- it describes every commit made during an active working day, both\r\n * fresh and high-signal at once, so the failure concentrated on exactly the days\r\n * with the most worth remembering. Found by dogfooding: a query about the\r\n * PowerShell hook returned two unrelated same-day `fix:` commits at ranks 3 and\r\n * 4 while the section that answered it sat at rank 6.\r\n *\r\n * So `MAX_PRIOR_OVERTURN` is the budget for all priors *jointly*, split evenly\r\n * between them (`sqrt(2)` each), and each prior is then raised to the power that\r\n * makes its entire range worth exactly its share -- solving\r\n * `span^exponent = PER_PRIOR_OVERTURN`. Adding a third prior re-divides the same\r\n * budget rather than enlarging it, which is the property that was missing.\r\n */\r\nconst MAX_PRIOR_OVERTURN = 2;\r\n/** signal and recency. Update when a query-independent factor joins the score. */\r\nconst PRIOR_COUNT = 2;\r\nconst PER_PRIOR_OVERTURN = MAX_PRIOR_OVERTURN ** (1 / PRIOR_COUNT);\r\nconst SIGNAL_EXPONENT = Math.log(PER_PRIOR_OVERTURN) / Math.log(1 / SIGNAL_FLOOR);\r\nconst RECENCY_EXPONENT = Math.log(PER_PRIOR_OVERTURN) / Math.log(1 / RECENCY_FLOOR);\r\n\r\n/**\r\n * bm25() in SQLite is a *cost*: smaller (more negative) is a better match.\r\n * Min-max normalize within this result set so the scale is comparable across\r\n * queries, then rescale into [RELEVANCE_FLOOR, 1].\r\n */\r\nfunction normalizeRelevance(hits: readonly SearchHit[]): number[] {\r\n const costs = hits.map((h) => h.rank);\r\n const min = Math.min(...costs);\r\n const max = Math.max(...costs);\r\n\r\n if (min === max) return hits.map(() => 1);\r\n\r\n return costs.map((cost) => {\r\n const normalized = (max - cost) / (max - min); // best cost -> 1, worst -> 0\r\n return RELEVANCE_FLOOR + (1 - RELEVANCE_FLOOR) * normalized;\r\n });\r\n}\r\n\r\n/**\r\n * Same rescale-into-[floor,1] treatment as `normalizeRelevance`, but for an\r\n * externally supplied score where *higher* is better (RRF's convention),\r\n * unlike bm25's cost convention.\r\n */\r\nfunction normalizeExternalRelevance(hits: readonly SearchHit[], scores: ReadonlyMap<string, number>): number[] {\r\n const values = hits.map((h) => scores.get(h.id) ?? 0);\r\n const min = Math.min(...values);\r\n const max = Math.max(...values);\r\n\r\n if (min === max) return hits.map(() => 1);\r\n\r\n return values.map((v) => {\r\n const normalized = (v - min) / (max - min); // best (highest) -> 1\r\n return RELEVANCE_FLOOR + (1 - RELEVANCE_FLOOR) * normalized;\r\n });\r\n}\r\n\r\nfunction ageDaysOf(ts: string, now: Date): number {\r\n const parsed = Date.parse(ts);\r\n if (Number.isNaN(parsed)) return 0;\r\n return Math.max(0, (now.getTime() - parsed) / MS_PER_DAY);\r\n}\r\n\r\n/**\r\n * Combine lexical match quality, structural importance and recency into one\r\n * score, and return hits sorted best-first.\r\n *\r\n * This is the layer the whole \"signal at ingest time\" design pays off in:\r\n * `signalWeight` is what keeps a `fix:` commit ahead of an equally-relevant\r\n * `chore:` one, without a query-time re-analysis of either.\r\n */\r\nexport function rankHits(hits: readonly SearchHit[], opts: RankOptions = {}): RankedHit[] {\r\n if (hits.length === 0) return [];\r\n\r\n const halfLife = opts.halfLifeDays ?? DEFAULT_HALF_LIFE_DAYS;\r\n const now = opts.now ?? new Date();\r\n const relevances = opts.relevanceScores ? normalizeExternalRelevance(hits, opts.relevanceScores) : normalizeRelevance(hits);\r\n\r\n const ranked = hits.map((hit, i) => {\r\n const relevance = relevances[i] ?? RELEVANCE_FLOOR;\r\n const signalWeight = SIGNAL_FLOOR + (1 - SIGNAL_FLOOR) * hit.signal;\r\n const ageDays = ageDaysOf(hit.ts, now);\r\n const recencyFactor = RECENCY_FLOOR + (1 - RECENCY_FLOOR) * 2 ** (-ageDays / halfLife);\r\n\r\n const score = relevance * signalWeight ** SIGNAL_EXPONENT * recencyFactor ** RECENCY_EXPONENT;\r\n\r\n return { ...hit, relevance, signalWeight, ageDays, recencyFactor, score };\r\n });\r\n\r\n return ranked.sort((a, b) => b.score - a.score);\r\n}\r\n","import { RESOLVED_BY_DISCUSSION, RESOLVED_BY_RETRY } from '../correlate/failure-fix.js';\r\nimport type { MemoryStore, SearchHit, VectorHit } from '../store/store.js';\r\nimport type { EmbeddingProvider } from '../vector/embed.js';\r\nimport { mergeSearchAndVectorHits, reciprocalRankFusion } from './fuse.js';\r\nimport { packContext, type PackResult } from './pack.js';\r\nimport { rankHits, type RankedHit } from './rank.js';\r\n\r\n/** Both chain relations are surfaced -- see the doc comment on `pullLinkedResolutions` for why. */\r\nconst SURFACED_RELATIONS = [RESOLVED_BY_RETRY, RESOLVED_BY_DISCUSSION] as const;\r\n\r\n/**\r\n * Pull a `shell_command` failure's linked resolution(s) into the ranked\r\n * list, immediately after the failure, so they survive `packContext`'s\r\n * budget/diversity cuts alongside it instead of needing to earn their own\r\n * place on query relevance alone. That is the whole point of Phase 7's chain\r\n * feature: a resolution the query never mentioned should still ride along\r\n * with the failure it resolves.\r\n *\r\n * Both `RESOLVED_BY_RETRY` and `RESOLVED_BY_DISCUSSION` are surfaced.\r\n * Discussion links were excluded through Phase 7 -- dogfooding against this\r\n * repo's real history found roughly half of them wrong, driven by a single\r\n * shared generic token. `toStrictMatchQuery` (an AND of every significant\r\n * token) fixed that at the source: re-dogfooded against the same real\r\n * corpus, all 5 resulting links (3 distinct failure/discussion pairs) were\r\n * verified correct by hand, full body read, not just the summary. Given a\r\n * false discussion link is now no more likely than a false retry link, there\r\n * is no remaining reason to treat them differently at this layer.\r\n *\r\n * The pulled-in node inherits its failure's relevance/signalWeight/\r\n * recencyFactor/score wholesale rather than computing its own: it has no\r\n * independent relevance to this query, and is included *because* it resolves\r\n * a hit that already matched, not because it matched on its own. A\r\n * resolution already present in `ranked` on its own merits is left\r\n * untouched, never duplicated.\r\n *\r\n * `resolveStore` maps a hit back to the `MemoryStore` its links live in --\r\n * always the same store for `runHybridQuery`, but per-source for\r\n * `runCrossProjectQuery`, since links are only ever recorded within one\r\n * project's own database (node ids are project-scoped, so a link cannot\r\n * cross databases in the first place).\r\n */\r\nfunction pullLinkedResolutions(\r\n resolveStore: (hit: RankedHit) => MemoryStore | undefined,\r\n ranked: readonly RankedHit[],\r\n): RankedHit[] {\r\n const present = new Set(ranked.map((hit) => hit.id));\r\n const withLinks: RankedHit[] = [];\r\n\r\n for (const hit of ranked) {\r\n withLinks.push(hit);\r\n if (hit.kind !== 'shell_command') continue;\r\n\r\n const store = resolveStore(hit);\r\n if (!store) continue;\r\n\r\n for (const relation of SURFACED_RELATIONS) {\r\n for (const linkedId of store.getLinkedNodeIds(hit.id, relation)) {\r\n if (present.has(linkedId)) continue;\r\n const [resolution] = store.getNodesByIds([linkedId]);\r\n if (!resolution) continue;\r\n\r\n present.add(linkedId);\r\n withLinks.push({\r\n id: resolution.id,\r\n kind: resolution.kind,\r\n ts: resolution.ts,\r\n title: resolution.title,\r\n body: resolution.body,\r\n signal: resolution.signal,\r\n rank: 0, // no bm25/vector rank of its own -- never read again past this point\r\n relevance: hit.relevance,\r\n signalWeight: hit.signalWeight,\r\n recencyFactor: hit.recencyFactor,\r\n ageDays: hit.ageDays,\r\n score: hit.score,\r\n ...(hit.project ? { project: hit.project } : {}),\r\n });\r\n }\r\n }\r\n }\r\n\r\n return withLinks;\r\n}\r\n\r\nexport interface HybridQueryOptions {\r\n budget: number;\r\n candidates: number;\r\n halfLifeDays?: number;\r\n /** `null`/omitted skips vector search entirely -- BM25-only, same behavior as before hybrid retrieval existed. */\r\n embeddingProvider?: EmbeddingProvider | null;\r\n}\r\n\r\nexport interface HybridQueryResult {\r\n bm25Count: number;\r\n vectorCount: number;\r\n /** The full candidate set that was ranked (bm25 ∪ vector, deduped) -- for callers that need the raw, unpacked bodies (e.g. a \"tokens if sent unpacked\" comparison). */\r\n hits: SearchHit[];\r\n packed: PackResult;\r\n}\r\n\r\n/** One repository's memory, as an input to a cross-project query. */\r\nexport interface QuerySource {\r\n store: MemoryStore;\r\n projectId: string;\r\n /** Shown to the user next to each hit; must be unique across the sources of one query. */\r\n label: string;\r\n}\r\n\r\nexport interface CrossProjectQueryResult extends HybridQueryResult {\r\n /** Per-source match counts, for reporting which repositories actually contributed. */\r\n perProject: Array<{ label: string; bm25: number; vector: number }>;\r\n}\r\n\r\n/**\r\n * Search several repositories at once and rank the results together.\r\n *\r\n * Node ids are `sha256(projectId + kind + naturalKey)`, so hits from\r\n * different databases cannot collide and the union needs no renaming.\r\n *\r\n * The one thing that does *not* survive the union is BM25's scale. A bm25()\r\n * cost is computed against its own corpus statistics, so -8.1 in a\r\n * ten-thousand-node repository and -8.1 in a fifty-node one are not the same\r\n * quality of match, and min-max normalizing them together silently invents a\r\n * comparison. RRF is therefore always applied here -- even with vector search\r\n * off, where a single-project query would normalize raw bm25 directly --\r\n * because each project's list is only ever compared against itself, by\r\n * position.\r\n *\r\n * Its known bias, stated rather than hidden: a project whose best match is\r\n * mediocre still contributes a rank-1 item, and rank 1 pays the same in every\r\n * list. Cross-project recall therefore favours breadth. `signal`, recency and\r\n * the budget are what keep that in check.\r\n */\r\nexport async function runCrossProjectQuery(\r\n sources: readonly QuerySource[],\r\n query: string,\r\n opts: HybridQueryOptions,\r\n): Promise<CrossProjectQueryResult> {\r\n // One embedding for every source: the query is the same, and this is the\r\n // only network call on the path.\r\n const queryVector = opts.embeddingProvider ? await opts.embeddingProvider.embed(query) : null;\r\n\r\n const lists: SearchHit[][] = [];\r\n const hits: SearchHit[] = [];\r\n const perProject: CrossProjectQueryResult['perProject'] = [];\r\n let bm25Count = 0;\r\n let vectorCount = 0;\r\n\r\n for (const source of sources) {\r\n const label = (hit: SearchHit): SearchHit => ({ ...hit, project: source.label });\r\n\r\n const bm25Hits = source.store.search(source.projectId, query, opts.candidates).map(label);\r\n const vectorHits = queryVector\r\n ? source.store.vectorSearch(source.projectId, queryVector, opts.candidates)\r\n : [];\r\n\r\n bm25Count += bm25Hits.length;\r\n vectorCount += vectorHits.length;\r\n perProject.push({ label: source.label, bm25: bm25Hits.length, vector: vectorHits.length });\r\n\r\n if (bm25Hits.length > 0) lists.push(bm25Hits);\r\n if (vectorHits.length > 0) {\r\n lists.push(vectorHits.map((hit) => ({ ...hit, rank: 0, project: source.label })));\r\n }\r\n\r\n hits.push(...mergeSearchAndVectorHits(bm25Hits, vectorHits).map(label));\r\n }\r\n\r\n const storeByLabel = new Map(sources.map((source) => [source.label, source.store]));\r\n const relevanceScores = reciprocalRankFusion(lists);\r\n const ranked = pullLinkedResolutions(\r\n (hit) => (hit.project ? storeByLabel.get(hit.project) : undefined),\r\n rankHits(hits, { halfLifeDays: opts.halfLifeDays, relevanceScores }),\r\n );\r\n const packed = packContext(ranked, opts.budget, { query });\r\n\r\n return { bm25Count, vectorCount, hits, packed, perProject };\r\n}\r\n\r\n/**\r\n * The one retrieval pipeline both `nexusmem query` and the MCP `search_memory`\r\n * tool run -- BM25 search, optional vector search, RRF fusion when both\r\n * fired, then rank and pack. Kept in one place so the CLI and the MCP server\r\n * can never quietly drift into answering the same query differently.\r\n */\r\nexport async function runHybridQuery(\r\n store: MemoryStore,\r\n projectId: string,\r\n query: string,\r\n opts: HybridQueryOptions,\r\n): Promise<HybridQueryResult> {\r\n const bm25Hits = store.search(projectId, query, opts.candidates);\r\n\r\n let vectorHits: VectorHit[] = [];\r\n if (opts.embeddingProvider) {\r\n const queryVector = await opts.embeddingProvider.embed(query);\r\n if (queryVector) vectorHits = store.vectorSearch(projectId, queryVector, opts.candidates);\r\n }\r\n\r\n const hits = vectorHits.length > 0 ? mergeSearchAndVectorHits(bm25Hits, vectorHits) : bm25Hits;\r\n const relevanceScores = vectorHits.length > 0 ? reciprocalRankFusion([bm25Hits, vectorHits]) : undefined;\r\n\r\n const ranked = pullLinkedResolutions(\r\n () => store,\r\n rankHits(hits, { halfLifeDays: opts.halfLifeDays, relevanceScores }),\r\n );\r\n // The query reaches the packer as well as the searcher: for a diff node it\r\n // decides which hunk of an already-retrieved patch is worth the budget.\r\n const packed = packContext(ranked, opts.budget, { query });\r\n\r\n return { bm25Count: bm25Hits.length, vectorCount: vectorHits.length, hits, packed };\r\n}\r\n","import { basename } from 'node:path';\r\nimport { readLiveRegistry, type RegistryEntry } from '../config/registry.js';\r\nimport { MemoryStore } from '../store/store.js';\r\nimport type { QuerySource } from './query-pipeline.js';\r\n\r\n/**\r\n * Turning the registry into a set of open databases to search.\r\n *\r\n * Every failure mode here is non-fatal by design: a cross-project query that\r\n * refuses to answer because one of six repositories is on an unplugged drive\r\n * would be worse than one that answers from five and says so.\r\n */\r\n\r\nexport interface OpenedSources {\r\n sources: QuerySource[];\r\n /** Registered projects whose database file is not currently on disk. */\r\n missing: RegistryEntry[];\r\n /** Registered projects whose database exists but could not be opened. */\r\n unreadable: Array<{ entry: RegistryEntry; reason: string }>;\r\n close(): void;\r\n}\r\n\r\nexport interface CurrentProject {\r\n projectId: string;\r\n root: string;\r\n dbPath: string;\r\n}\r\n\r\n/**\r\n * Open the current repository plus every other registered one.\r\n *\r\n * The current project is included even when the registry has never heard of\r\n * it -- a repo initialized before the registry existed would otherwise be\r\n * missing from its own query.\r\n */\r\nexport async function openAllProjectSources(current: CurrentProject): Promise<OpenedSources> {\r\n const { entries, missing } = await readLiveRegistry();\r\n\r\n const wanted: CurrentProject[] = [current];\r\n for (const entry of entries) {\r\n if (entry.projectId === current.projectId) continue;\r\n wanted.push({ projectId: entry.projectId, root: entry.root, dbPath: entry.dbPath });\r\n }\r\n\r\n const labels = labelProjects(wanted);\r\n const sources: QuerySource[] = [];\r\n const unreadable: OpenedSources['unreadable'] = [];\r\n\r\n wanted.forEach((project, index) => {\r\n try {\r\n sources.push({\r\n store: MemoryStore.open(project.dbPath),\r\n projectId: project.projectId,\r\n label: labels[index] ?? project.projectId.slice(0, 8),\r\n });\r\n } catch (err) {\r\n // Never the current project's database: `loadContext` has already\r\n // opened that one by the time this runs.\r\n const entry = entries.find((e) => e.projectId === project.projectId);\r\n if (entry) unreadable.push({ entry, reason: (err as Error).message });\r\n }\r\n });\r\n\r\n return {\r\n sources,\r\n missing,\r\n unreadable,\r\n close: () => {\r\n for (const source of sources) source.store.close();\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Short, unique names for a set of projects.\r\n *\r\n * A directory basename is what a person calls their repo, so it is the right\r\n * label right up until two of them are called `api`. Collisions get the head\r\n * of the project id appended rather than the full path: the label is a\r\n * disambiguator in a context block, not an address.\r\n */\r\nexport function labelProjects(projects: readonly CurrentProject[]): string[] {\r\n const counts = new Map<string, number>();\r\n for (const project of projects) {\r\n const name = basename(project.root) || project.root;\r\n counts.set(name, (counts.get(name) ?? 0) + 1);\r\n }\r\n\r\n return projects.map((project) => {\r\n const name = basename(project.root) || project.root;\r\n return (counts.get(name) ?? 0) > 1 ? `${name}#${project.projectId.slice(0, 6)}` : name;\r\n });\r\n}\r\n","/**\r\n * Embedding provider abstraction.\r\n *\r\n * The real implementation calls a local Ollama server; tests and any\r\n * environment without Ollama running use a fake. `embed` returns `null`\r\n * (never throws) on any failure -- connection refused, model not pulled,\r\n * malformed response -- so a missing embedding provider degrades the whole\r\n * system to BM25-only search, not a broken `sync`.\r\n */\r\nexport interface EmbeddingProvider {\r\n readonly dimension: number;\r\n /**\r\n * Stable name for \"what produced these vectors\".\r\n *\r\n * Persisted alongside the corpus so a provider swap can be *detected*\r\n * rather than silently mixed in. Two vectors from different models --\r\n * or, less obviously, from two endpoints of the same model that differ\r\n * in normalisation -- are not comparable, and `nodes_vec` stores no\r\n * per-row provenance to tell them apart after the fact. Must change\r\n * whenever the produced vectors change meaning.\r\n */\r\n readonly identity: string;\r\n embed(text: string): Promise<Float32Array | null>;\r\n /**\r\n * Embed several texts in one round trip, positionally aligned with the\r\n * input. Optional: a provider without it is driven one call at a time.\r\n * A failure is per-request, so a rejected batch yields all-`null`.\r\n */\r\n embedBatch?(texts: readonly string[]): Promise<(Float32Array | null)[]>;\r\n}\r\n\r\nexport interface OllamaEmbeddingProviderOptions {\r\n baseUrl?: string;\r\n model?: string;\r\n dimension?: number;\r\n /**\r\n * Milliseconds allowed *per text*. A request's real budget is this times\r\n * the number of texts in it, capped by `maxTimeoutMs` -- a batch of 32\r\n * legitimately takes longer than a single embed and must not be aborted\r\n * for being a batch. Default 10s.\r\n */\r\n timeoutMs?: number;\r\n /** Upper bound on any single request's timeout, however large the batch. Default 120s. */\r\n maxTimeoutMs?: number;\r\n}\r\n\r\nconst DEFAULT_BASE_URL = 'http://127.0.0.1:11434';\r\nconst DEFAULT_MODEL = 'nomic-embed-text';\r\n/** Confirmed against a live Ollama call -- see store/schema.ts's EMBEDDING_DIM. */\r\nconst DEFAULT_DIMENSION = 768;\r\nconst DEFAULT_TIMEOUT_MS = 10_000;\r\nconst DEFAULT_MAX_TIMEOUT_MS = 120_000;\r\n\r\n/**\r\n * Ollama's batch embedding endpoint.\r\n *\r\n * Deliberately NOT the older `/api/embeddings`, and the difference is not\r\n * only that this one takes an array: **`/api/embed` returns L2-normalised\r\n * vectors and `/api/embeddings` does not** (measured on this machine against\r\n * nomic-embed-text: norm 1.0 vs 20.7 for the same input). `nodes_vec` ranks\r\n * by Euclidean distance, so a corpus holding both is not merely\r\n * inconsistent -- every normalised vector sits ~20x nearer the origin than\r\n * every unnormalised one, and the two groups separate by scale instead of by\r\n * meaning. Hence `EMBEDDING_IDENTITY` below names the endpoint, and the\r\n * embedding pass re-embeds from scratch when it changes.\r\n */\r\nconst EMBED_PATH = '/api/embed';\r\n\r\nexport class OllamaEmbeddingProvider implements EmbeddingProvider {\r\n readonly dimension: number;\r\n readonly identity: string;\r\n private readonly baseUrl: string;\r\n private readonly model: string;\r\n private readonly timeoutMs: number;\r\n private readonly maxTimeoutMs: number;\r\n\r\n constructor(opts: OllamaEmbeddingProviderOptions = {}) {\r\n this.baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;\r\n this.model = opts.model ?? DEFAULT_MODEL;\r\n this.dimension = opts.dimension ?? DEFAULT_DIMENSION;\r\n this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n this.maxTimeoutMs = opts.maxTimeoutMs ?? DEFAULT_MAX_TIMEOUT_MS;\r\n // `baseUrl` is deliberately absent: pointing at a different host running\r\n // the same model is not a different embedding space, so it must not\r\n // trigger a re-embed.\r\n this.identity = `ollama${EMBED_PATH}:${this.model}:${this.dimension}`;\r\n }\r\n\r\n async embed(text: string): Promise<Float32Array | null> {\r\n const [only] = await this.embedBatch([text]);\r\n return only ?? null;\r\n }\r\n\r\n async embedBatch(texts: readonly string[]): Promise<(Float32Array | null)[]> {\r\n if (texts.length === 0) return [];\r\n\r\n const controller = new AbortController();\r\n const budget = Math.min(this.timeoutMs * texts.length, this.maxTimeoutMs);\r\n const timeout = setTimeout(() => controller.abort(), budget);\r\n\r\n try {\r\n const res = await fetch(`${this.baseUrl}${EMBED_PATH}`, {\r\n method: 'POST',\r\n headers: { 'content-type': 'application/json' },\r\n body: JSON.stringify({ model: this.model, input: texts }),\r\n signal: controller.signal,\r\n });\r\n\r\n if (!res.ok) return texts.map(() => null);\r\n\r\n const data = (await res.json()) as { embeddings?: unknown };\r\n if (!Array.isArray(data.embeddings)) return texts.map(() => null);\r\n\r\n // Positional alignment with `texts` is the contract callers rely on to\r\n // know which node each vector belongs to. Ollama returns one row per\r\n // input, but a short or long array would silently shift that mapping\r\n // and mislabel every embedding after the gap -- refuse instead.\r\n if (data.embeddings.length !== texts.length) return texts.map(() => null);\r\n\r\n return data.embeddings.map((row) =>\r\n Array.isArray(row) && row.length === this.dimension ? new Float32Array(row as number[]) : null,\r\n );\r\n } catch {\r\n return texts.map(() => null); // Ollama not running, model not pulled, network hiccup -- all degrade the same way\r\n } finally {\r\n clearTimeout(timeout);\r\n }\r\n }\r\n}\r\n\r\n/** Deterministic, network-free provider for tests. */\r\nexport class FakeEmbeddingProvider implements EmbeddingProvider {\r\n readonly identity: string;\r\n\r\n constructor(readonly dimension = 8) {\r\n this.identity = `fake:${dimension}`;\r\n }\r\n\r\n async embed(text: string): Promise<Float32Array | null> {\r\n // A cheap hash-based vector: stable per input, distinct enough across\r\n // different inputs to exercise real KNN ordering in tests.\r\n const v = new Float32Array(this.dimension);\r\n for (let i = 0; i < text.length; i += 1) {\r\n const idx = i % this.dimension;\r\n v[idx] = (v[idx] ?? 0) + text.charCodeAt(i);\r\n }\r\n return v;\r\n }\r\n}\r\n","import pc from 'picocolors';\r\nimport { correlateFailures } from '../../correlate/failure-fix.js';\r\nimport { collectConversationTurns } from '../../collectors/conversation.js';\r\nimport { collectCommitDiffs, DIFF_SOURCE } from '../../collectors/diffs.js';\r\nimport { collectDocFiles } from '../../collectors/docs.js';\r\nimport { collectGitCommits } from '../../collectors/git-commits.js';\r\nimport { collectSessionSummaries } from '../../collectors/sessions.js';\r\nimport { collectShellHistory } from '../../collectors/shell-history.js';\r\nimport { forgetProjects, recordProject } from '../../config/registry.js';\r\nimport { writeConfig } from '../../config/workspace.js';\r\nimport { collectClaudeCodeTranscripts } from '../../conversation/claude-code-reader.js';\r\nimport type { RawConversationTurn } from '../../conversation/types.js';\r\nimport { makeNodeId } from '../../core/ids.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readDocFiles } from '../../docs/read.js';\r\nimport { isAncestor } from '../../git/repo.js';\r\nimport { collectAvailableShellHistory } from '../../shell/detect.js';\r\nimport { OllamaChatProvider } from '../../slm/provider.js';\r\nimport { reconcileProjectId } from '../../store/reconcile.js';\r\nimport { MemoryStore, type IngestStats } from '../../store/store.js';\r\nimport { OllamaEmbeddingProvider } from '../../vector/embed.js';\r\nimport { embedPendingNodes } from '../../vector/sync.js';\r\nimport { loadContext } from '../context.js';\r\n\r\nexport interface SyncOptions {\r\n cwd: string;\r\n /** Ignore the stored cursor and re-walk all history (still deduplicated). */\r\n full: boolean;\r\n /** Delete this project's nodes first, then re-ingest from scratch. */\r\n rebuild: boolean;\r\n /** Overrides `sources.git.since` from config for this run. */\r\n since?: string;\r\n /** Overrides `sources.shell.tailLines` from config for this run. */\r\n shellTailLines?: number;\r\n /** Forces the (opt-in) conversation source on for this run without persisting it to config. */\r\n conversationOverride?: boolean;\r\n /** Skip the embedding pass entirely -- useful when Ollama isn't running and you don't want to wait out its timeout. */\r\n noEmbed?: boolean;\r\n /** Stop the embedding pass after this many nodes. Unset means drain the backlog. */\r\n embedLimit?: number;\r\n /** Wipe every node of this exact source (e.g. `shell:pwsh`) instead of syncing. Dry-run unless `yes` is also set. */\r\n pruneSource?: string;\r\n /** Shortcut for `pruneSource` on all three dead pre-hook shell-scrape sources at once. Combines with `pruneSource` if both are set. */\r\n pruneStaleShell?: boolean;\r\n /** Confirms an irreversible `pruneSource`/`pruneStaleShell` delete. Without it, the matching count is printed and nothing is removed. */\r\n yes?: boolean;\r\n /**\r\n * Opt-in (Phase 7): after ingest, run `correlateFailures` to link failed\r\n * shell commands to whatever later resolved them. Off by default -- both\r\n * heuristics are new and unvalidated, matching how session summarization\r\n * shipped opt-in first before being trusted as a default.\r\n */\r\n linkFailures?: boolean;\r\n quiet: boolean;\r\n /**\r\n * Where the final summary goes. Defaults to real stdout for the CLI.\r\n *\r\n * Progress lines keep going to stderr regardless (see `log` below); this is\r\n * only the result. The MCP server passes its own sink because there `stdout`\r\n * carries the JSON-RPC transport -- see `InitOptions.out`.\r\n */\r\n out?: (chunk: string) => void;\r\n}\r\n\r\n/**\r\n * Rows per transaction.\r\n *\r\n * Large enough that per-transaction overhead disappears, small enough that a\r\n * huge repository does not hold every node in memory before the first write.\r\n */\r\nconst BATCH_SIZE = 500;\r\n\r\n/** Below this backlog the embedding pass finishes fast enough that progress lines are just noise. */\r\nconst PROGRESS_THRESHOLD = 200;\r\nconst PROGRESS_EVERY = 100;\r\n\r\nconst GIT_SOURCE = 'git';\r\n\r\nfunction addStats(into: IngestStats, from: IngestStats): void {\r\n into.inserted += from.inserted;\r\n into.updated += from.updated;\r\n into.unchanged += from.unchanged;\r\n}\r\n\r\nasync function syncGit(\r\n store: MemoryStore,\r\n projectId: string,\r\n opts: SyncOptions,\r\n repo: Awaited<ReturnType<typeof loadContext>>['repo'],\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n if (!repo.head) {\r\n log(`${pc.yellow('git')} skipped -- repository has no commits yet`);\r\n return { totals, seen: 0 };\r\n }\r\n if (!config.sources.git.enabled) {\r\n log(`${pc.dim('git')} disabled in config`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n let cursor = opts.full || opts.rebuild ? null : store.getSyncCursor(projectId, GIT_SOURCE);\r\n\r\n if (cursor && !(await isAncestor(repo.root, cursor, repo.head))) {\r\n log(`${pc.yellow('git cursor stale')} ${cursor.slice(0, 7)} is not an ancestor of HEAD — falling back to a full walk`);\r\n cursor = null;\r\n }\r\n\r\n if (cursor === repo.head) {\r\n log(`${pc.green('git up to date')} at ${repo.head.slice(0, 7)}`);\r\n store.setSyncCursor(projectId, GIT_SOURCE, repo.head);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n log(\r\n `${pc.dim('git syncing')} ${repo.branch ?? 'HEAD'} ${cursor ? `${cursor.slice(0, 7)}..${repo.head.slice(0, 7)}` : '(full history)'}`,\r\n );\r\n\r\n let batch: MemoryNode[] = [];\r\n let seen = 0;\r\n\r\n const flush = () => {\r\n if (batch.length === 0) return;\r\n addStats(totals, store.upsertNodes(batch));\r\n batch = [];\r\n log(` ${pc.dim(`${seen} commits read, ${totals.inserted} new`)}`);\r\n };\r\n\r\n const nodes = collectGitCommits(repo.root, projectId, {\r\n afterCommit: cursor,\r\n since: opts.since ?? config.sources.git.since,\r\n includeMerges: config.sources.git.includeMerges,\r\n maxFilesPerNode: config.limits.maxFilesPerNode,\r\n maxBodyChars: config.limits.maxBodyChars,\r\n });\r\n\r\n for await (const node of nodes) {\r\n batch.push(node);\r\n seen += 1;\r\n if (batch.length >= BATCH_SIZE) flush();\r\n }\r\n flush();\r\n\r\n // Only advance the cursor once the walk completed without throwing -- a\r\n // crash mid-sync leaves the old cursor, and the next run redoes the range\r\n // (harmlessly, because ingestion is idempotent).\r\n store.setSyncCursor(projectId, GIT_SOURCE, repo.head);\r\n return { totals, seen };\r\n}\r\n\r\nasync function syncDiffs(\r\n store: MemoryStore,\r\n projectId: string,\r\n opts: SyncOptions,\r\n repo: Awaited<ReturnType<typeof loadContext>>['repo'],\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n if (!repo.head) return { totals, seen: 0 };\r\n if (!config.sources.diff.enabled) {\r\n log(`${pc.dim('diff')} disabled in config`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n // Its own cursor, not git's: the two sources walk the same history but are\r\n // enabled independently, so a repository that had diffs turned on later must\r\n // not inherit git's \"already up to date\" position and skip everything.\r\n let cursor = opts.full || opts.rebuild ? null : store.getSyncCursor(projectId, DIFF_SOURCE);\r\n\r\n if (cursor && !(await isAncestor(repo.root, cursor, repo.head))) {\r\n log(`${pc.yellow('diff cursor stale')} ${cursor.slice(0, 7)} is not an ancestor of HEAD — falling back to a bounded walk`);\r\n cursor = null;\r\n }\r\n\r\n if (cursor === repo.head) {\r\n store.setSyncCursor(projectId, DIFF_SOURCE, repo.head);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n let batch: MemoryNode[] = [];\r\n let seen = 0;\r\n\r\n const flush = () => {\r\n if (batch.length === 0) return;\r\n addStats(totals, store.upsertNodes(batch));\r\n batch = [];\r\n };\r\n\r\n const nodes = collectCommitDiffs(repo.root, projectId, {\r\n afterCommit: cursor,\r\n since: opts.since ?? config.sources.git.since,\r\n maxCount: config.sources.diff.maxCommits,\r\n maxFilesPerCommit: config.sources.diff.maxFilesPerCommit,\r\n contextLines: config.sources.diff.contextLines,\r\n maxBodyChars: config.limits.maxBodyChars,\r\n });\r\n\r\n for await (const node of nodes) {\r\n batch.push(node);\r\n seen += 1;\r\n if (batch.length >= BATCH_SIZE) flush();\r\n }\r\n flush();\r\n\r\n // Same rule as git: advance only after a walk that completed, so a crash\r\n // mid-sync redoes the range instead of silently skipping it.\r\n store.setSyncCursor(projectId, DIFF_SOURCE, repo.head);\r\n log(` ${pc.dim(`${DIFF_SOURCE}: ${seen} file diff(s) read`)}`);\r\n\r\n return { totals, seen };\r\n}\r\n\r\nasync function syncShell(\r\n store: MemoryStore,\r\n projectId: string,\r\n opts: SyncOptions,\r\n repoRoot: string,\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n if (!config.sources.shell.enabled) {\r\n log(`${pc.dim('shell')} disabled in config`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n const results = await collectAvailableShellHistory({\r\n tailLines: opts.shellTailLines ?? config.sources.shell.tailLines,\r\n repoRoot,\r\n hookCursor: store.getSyncCursor(projectId, 'shell:pwsh-hook'),\r\n });\r\n\r\n if (results.length === 0) {\r\n log(`${pc.dim('shell')} no history source found on this machine`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n let seen = 0;\r\n for (const result of results) {\r\n const sourceKey = `shell:${result.name}`;\r\n const nodes = collectShellHistory(result.entries, projectId, { maxBodyChars: config.limits.maxBodyChars });\r\n seen += nodes.length;\r\n\r\n if (nodes.length > 0) {\r\n addStats(totals, store.upsertNodes(nodes));\r\n }\r\n\r\n // Hook source is a real append-only log: advance a walk-forward cursor.\r\n // Scrape sources re-read their tail window every run (bounded, cheap,\r\n // and self-deduplicating via content-addressed ids) so their \"cursor\" is\r\n // informational only, for `status` to show a last-synced marker.\r\n store.setSyncCursor(projectId, sourceKey, result.cursorAfter ?? `scanned:${result.entries.length}`);\r\n log(` ${pc.dim(`${sourceKey}: ${nodes.length} entr${nodes.length === 1 ? 'y' : 'ies'} read`)}`);\r\n }\r\n\r\n return { totals, seen };\r\n}\r\n\r\nconst CONVERSATION_SOURCE = 'conversation:claude-code';\r\n\r\nfunction syncConversation(\r\n store: MemoryStore,\r\n projectId: string,\r\n turns: readonly RawConversationTurn[],\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n forceEnabled: boolean | undefined,\r\n): { totals: IngestStats; seen: number } {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n const enabled = forceEnabled ?? config.sources.conversation.enabled;\r\n\r\n if (!enabled) {\r\n // Opt-in and silent by default -- this source is off for almost every\r\n // sync, and it would be noise to announce that on every single run.\r\n return { totals, seen: 0 };\r\n }\r\n\r\n if (turns.length === 0) {\r\n log(`${pc.dim('conversation')} no transcripts found`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n const nodes = collectConversationTurns(turns, projectId, { maxBodyChars: config.limits.maxBodyChars });\r\n if (nodes.length > 0) addStats(totals, store.upsertNodes(nodes));\r\n\r\n // Re-read in full each sync (see claude-code-reader.ts) -- the cursor here\r\n // is informational only, matching the shell scrape sources.\r\n store.setSyncCursor(projectId, CONVERSATION_SOURCE, `scanned:${nodes.length}`);\r\n log(` ${pc.dim(`${CONVERSATION_SOURCE}: ${nodes.length} of ${turns.length} exchange(s) kept`)}`);\r\n\r\n return { totals, seen: nodes.length };\r\n}\r\n\r\nconst SESSION_SOURCE = 'session:claude-code';\r\n\r\nasync function syncSessions(\r\n store: MemoryStore,\r\n projectId: string,\r\n turns: readonly RawConversationTurn[],\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n const settings = config.sources.session;\r\n\r\n // Opt-in and silent when off, same as the conversation source.\r\n if (!settings.enabled) return { totals, seen: 0 };\r\n\r\n if (turns.length === 0) {\r\n log(`${pc.dim('session')} no transcripts found`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n const result = await collectSessionSummaries(turns, projectId, new OllamaChatProvider({ model: settings.model }), {\r\n settleMinutes: settings.settleMinutes,\r\n maxSessions: settings.maxSessions,\r\n maxPromptChars: settings.maxPromptChars,\r\n maxBodyChars: config.limits.maxBodyChars,\r\n knownHash: (sessionKey) => {\r\n const meta = store.getNodeMeta(makeNodeId(projectId, 'session_summary', sessionKey));\r\n return typeof meta?.contentHash === 'string' ? meta.contentHash : null;\r\n },\r\n onProgress: (done, total) => log(` ${pc.dim(`session: summarizing ${done}/${total}`)}`),\r\n });\r\n\r\n if (result.nodes.length > 0) addStats(totals, store.upsertNodes(result.nodes));\r\n\r\n if (result.providerUnavailable) {\r\n log(\r\n `${pc.dim('session')} summarization model unavailable (is Ollama running with \\`${settings.model}\\` pulled?) -- skipped`,\r\n );\r\n } else {\r\n const parts = [`${result.nodes.length} summarized`];\r\n if (result.cached > 0) parts.push(`${result.cached} unchanged`);\r\n if (result.deferred > 0) parts.push(`${result.deferred} queued for the next sync`);\r\n if (result.unsettled > 0) parts.push(`${result.unsettled} still active`);\r\n if (result.failed > 0) parts.push(`${result.failed} failed`);\r\n log(` ${pc.dim(`${SESSION_SOURCE}: ${parts.join(', ')}`)}`);\r\n }\r\n\r\n // Informational only, like the other full-rescan sources.\r\n store.setSyncCursor(projectId, SESSION_SOURCE, `scanned:${result.nodes.length}`);\r\n\r\n return { totals, seen: result.nodes.length };\r\n}\r\n\r\nconst DOCS_SOURCE = 'docs';\r\n\r\nasync function syncDocs(\r\n store: MemoryStore,\r\n projectId: string,\r\n repoRoot: string,\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n if (!config.sources.docs.enabled) {\r\n log(`${pc.dim('docs')} disabled in config`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n const { files, unreadable } = await readDocFiles(repoRoot, { include: config.sources.docs.include });\r\n\r\n const nodes = collectDocFiles(files, projectId, { maxBodyChars: config.limits.maxBodyChars });\r\n if (nodes.length > 0) addStats(totals, store.upsertNodes(nodes));\r\n\r\n // Prune *after* the upsert, so a renamed heading's replacement is already in\r\n // place and only the stranded original is left to remove.\r\n //\r\n // This scan is always a complete one -- every tracked .md file, re-read in\r\n // full -- which is what makes the delete safe: anything of this source not in\r\n // `nodes` genuinely no longer exists in the repository. An empty scan is a\r\n // legitimate outcome (every .md file deleted) and prunes accordingly; files\r\n // that could not be read are excluded rather than treated as gone.\r\n const pruned = store.pruneSourceNodes(\r\n projectId,\r\n DOCS_SOURCE,\r\n nodes.map((node) => node.id),\r\n { keepPaths: unreadable },\r\n );\r\n\r\n // Re-read in full each sync, the same trade the conversation source makes:\r\n // content-addressed ids make it idempotent, and a doc file has no cheap\r\n // append-only cursor to walk incrementally.\r\n store.setSyncCursor(projectId, DOCS_SOURCE, `scanned:${nodes.length}`);\r\n\r\n if (files.length === 0 && unreadable.length === 0) {\r\n log(`${pc.dim('docs')} no tracked .md files found`);\r\n } else {\r\n const prunedPart = pruned > 0 ? `, ${pc.yellow(`${pruned} stale removed`)}` : '';\r\n const skippedPart = unreadable.length > 0 ? `, ${unreadable.length} unreadable (kept)` : '';\r\n log(` ${pc.dim(`${DOCS_SOURCE}: ${nodes.length} section(s) from ${files.length} file(s)`)}${prunedPart}${pc.dim(skippedPart)}`);\r\n }\r\n\r\n return { totals, seen: nodes.length };\r\n}\r\n\r\n/**\r\n * The three sources `collectAvailableShellHistory` produced before the\r\n * PowerShell hook existed. Nothing has written to them since the hook took\r\n * over (it always returns `pwsh-hook` results once installed -- see the\r\n * `hookCursor`-driven branch in `syncShell` below), so on a machine with the\r\n * hook installed these are pure dead weight with no live collector to diff\r\n * against, unlike `docs`.\r\n */\r\nconst STALE_SHELL_SOURCES = ['shell:pwsh', 'shell:bash', 'shell:zsh'] as const;\r\n\r\n/** Resolves `--prune-source`/`--prune-stale-shell` into a deduplicated list of exact source strings. */\r\nfunction collectPruneSources(opts: SyncOptions): string[] {\r\n const sources = new Set<string>();\r\n if (opts.pruneStaleShell) {\r\n for (const source of STALE_SHELL_SOURCES) sources.add(source);\r\n }\r\n if (opts.pruneSource?.trim()) sources.add(opts.pruneSource.trim());\r\n return [...sources];\r\n}\r\n\r\n/**\r\n * Handles `--prune-source`/`--prune-stale-shell` as a standalone maintenance\r\n * action -- it never falls through into the rest of `runSync`'s ingest\r\n * pipeline, so a single invocation either inspects/deletes the named\r\n * source(s) or does a normal sync, never both in one run.\r\n *\r\n * Dry-run by default: without `--yes` this only counts and prints, matching\r\n * the user's explicit call that a scoped, easy-to-typo delete needs a shown\r\n * number before anything irreversible happens -- unlike `--rebuild`, which\r\n * has no such gate because its own name already states the full-project\r\n * scope.\r\n *\r\n * Sweeps `otherProjectIds` (this same sync's own `listOtherProjectIds`\r\n * result) in addition to the live `projectId`, not just the live id alone.\r\n * Found live, 2026-08-15: this repo's own dead `shell:pwsh` rows were\r\n * invisible to a live-id-only prune because they were left stranded under\r\n * the pre-rename project id by [[nexusmem-project-id-fragmentation]]'s\r\n * reconcile step (deliberately -- see `reconcile.ts`'s doc comment, which\r\n * already called this \"no different in effect from pruning it\" without\r\n * anything actually able to reach it). `reconcile.ts` already treats every\r\n * id `listOtherProjectIds` returns as a prior identity of this same repo,\r\n * never another repository's data (`db` is one file per repo) -- this reuses\r\n * that exact invariant rather than inventing a new one.\r\n */\r\nfunction runPruneSources(\r\n store: MemoryStore,\r\n projectId: string,\r\n otherProjectIds: readonly string[],\r\n sources: readonly string[],\r\n yes: boolean,\r\n out: (chunk: string) => void,\r\n): number {\r\n const scopeIds = [projectId, ...otherProjectIds];\r\n const counts = sources.flatMap((source) => scopeIds.map((id) => ({ source, id, count: store.countSourceNodes(id, source) })));\r\n const total = counts.reduce((sum, c) => sum + c.count, 0);\r\n\r\n if (total === 0) {\r\n out(`${pc.dim('prune-source')} no node(s) match ${sources.join(', ')} -- nothing to do\\n`);\r\n return 0;\r\n }\r\n\r\n const describe = (c: { source: string; id: string; count: number }) =>\r\n ` ${pc.dim(c.source)}${c.id !== projectId ? pc.dim(` (prior identity ${c.id.slice(0, 8)})`) : ''}: ${c.count} node(s)`;\r\n\r\n if (!yes) {\r\n const lines = counts.filter((c) => c.count > 0).map(describe);\r\n out(\r\n [`${pc.yellow('would remove')} ${total} node(s):`, ...lines, pc.dim('re-run with --yes to actually delete these -- this cannot be undone'), ''].join(\r\n '\\n',\r\n ),\r\n );\r\n return 0;\r\n }\r\n\r\n // Passing an empty keep-list is a full wipe of the source, not the\r\n // incremental prune `syncDocs` above uses it for -- there is no fresh scan\r\n // to diff against for a source nothing collects anymore.\r\n let removed = 0;\r\n for (const { source, id } of counts) removed += store.pruneSourceNodes(id, source, []);\r\n const identityPart = otherProjectIds.length > 0 ? `, ${scopeIds.length} project identit${scopeIds.length === 1 ? 'y' : 'ies'}` : '';\r\n out(`${pc.green('pruned')} ${removed} node(s) across ${sources.length} source(s)${identityPart}\\n`);\r\n return 0;\r\n}\r\n\r\nexport async function runSync(opts: SyncOptions): Promise<number> {\r\n const { repo, ws, projectId, config } = await loadContext(opts.cwd);\r\n const log = (line: string) => {\r\n if (!opts.quiet) process.stderr.write(`${line}\\n`);\r\n };\r\n const out = opts.out ?? ((chunk: string) => void process.stdout.write(chunk));\r\n\r\n const store = MemoryStore.open(ws.dbPath);\r\n const started = Date.now();\r\n\r\n try {\r\n store.upsertProject({ id: projectId, root: repo.root, originUrl: repo.originUrl });\r\n\r\n // Cleared *before* reconciliation runs below, deliberately: reconciliation\r\n // writes under `projectId` too, and clearing after it ran would silently\r\n // destroy the very data it just migrated forward -- data that, unlike\r\n // git/diff/docs, a fresh re-ingest cannot reproduce.\r\n if (opts.rebuild) {\r\n const removed = store.clearProject(projectId);\r\n log(`${pc.dim('rebuild')} dropped ${removed} existing node(s)`);\r\n }\r\n\r\n // A repo's own database never holds another repo's data (see\r\n // registry.ts), so any other project id already in it is this same\r\n // repo's prior identity -- almost always its git remote URL changed\r\n // since the last sync (see reconcile.ts for the full story).\r\n const staleProjectIds = store.listOtherProjectIds(projectId);\r\n for (const staleId of staleProjectIds) {\r\n const result = reconcileProjectId(store.raw, staleId, projectId);\r\n const parts = [\r\n result.migrated > 0 ? `${result.migrated} migrated` : null,\r\n result.reassigned > 0 ? `${result.reassigned} reassigned` : null,\r\n result.deduped > 0 ? `${result.deduped} already up to date` : null,\r\n result.skipped > 0 ? `${result.skipped} left behind (not reconstructable)` : null,\r\n ].filter((part): part is string => part !== null);\r\n if (parts.length > 0) {\r\n log(\r\n `${pc.yellow('reconciled')} previous project identity ${pc.dim(staleId)} (remote URL likely changed): ${parts.join(', ')}`,\r\n );\r\n }\r\n }\r\n if (staleProjectIds.length > 0) {\r\n if (opts.rebuild) {\r\n // Reconciliation just salvaged everything recoverable; --rebuild's\r\n // fresh-start intent extends naturally to purging what's deliberately\r\n // left behind (git/diff/doc/pre-hook-shell rows -- see reconcile.ts\r\n // for why those specifically are never migrated).\r\n for (const staleId of staleProjectIds) store.clearProject(staleId);\r\n }\r\n await forgetProjects(staleProjectIds);\r\n // config.json's projectId is otherwise write-once (set at init) and\r\n // would keep reporting the stale id in `nexusmem init`'s \"already\r\n // initialized\" message forever.\r\n if (config.projectId !== projectId) await writeConfig(ws, { ...config, projectId });\r\n }\r\n\r\n // Refreshed on every sync, not only at init: a repo initialized before\r\n // the registry existed, or moved since, is re-pointed by being used.\r\n await recordProject({ projectId, root: repo.root, dbPath: ws.dbPath, originUrl: repo.originUrl });\r\n\r\n const pruneSources = collectPruneSources(opts);\r\n if (pruneSources.length > 0) {\r\n return runPruneSources(store, projectId, staleProjectIds, pruneSources, opts.yes ?? false, out);\r\n }\r\n\r\n const git = await syncGit(store, projectId, opts, repo, config, log);\r\n const diffs = await syncDiffs(store, projectId, opts, repo, config, log);\r\n const shell = await syncShell(store, projectId, opts, repo.root, config, log);\r\n\r\n // Read once, used by two sources. Parsing every transcript twice was\r\n // measurable on a repo with a long history of sessions, and both sources\r\n // want the exact same turns.\r\n const conversationEnabled = opts.conversationOverride ?? config.sources.conversation.enabled;\r\n const turns =\r\n conversationEnabled || config.sources.session.enabled ? await collectClaudeCodeTranscripts(repo.root) : [];\r\n\r\n const conversation = syncConversation(store, projectId, turns, config, log, opts.conversationOverride);\r\n const sessions = await syncSessions(store, projectId, turns, config, log);\r\n const docs = await syncDocs(store, projectId, repo.root, config, log);\r\n\r\n let embedLine = '';\r\n if (!opts.noEmbed) {\r\n // Progress matters now that one pass drains the whole backlog: on a\r\n // first sync of a large repository this is the longest step by far, and\r\n // without a heartbeat it is indistinguishable from a hang.\r\n let lastLogged = 0;\r\n const result = await embedPendingNodes(store, new OllamaEmbeddingProvider(), projectId, {\r\n maxNodes: opts.embedLimit,\r\n onInvalidated: (count) =>\r\n log(`${pc.yellow('vector')} embedding model changed -- dropped ${count} vector(s), re-embedding from scratch`),\r\n onProgress: (attempted, total) => {\r\n if (total < PROGRESS_THRESHOLD || attempted - lastLogged < PROGRESS_EVERY) return;\r\n lastLogged = attempted;\r\n log(` ${pc.dim(`vector: ${attempted}/${total} embedded`)}`);\r\n },\r\n });\r\n\r\n if (result.embedded > 0) {\r\n const skippedPart = result.skipped > 0 ? pc.dim(`, ${result.skipped} skipped`) : '';\r\n const remainingPart = result.remaining > 0 ? pc.yellow(`, ${result.remaining} still pending`) : '';\r\n embedLine = ` ${pc.dim(`vector: ${result.embedded} node(s) embedded`)}${skippedPart}${remainingPart}\\n`;\r\n } else if (result.providerUnavailable) {\r\n log(`${pc.dim('vector')} embedding provider unavailable (is Ollama running with nomic-embed-text pulled?) -- BM25-only for now`);\r\n }\r\n }\r\n\r\n let linkLine = '';\r\n if (opts.linkFailures) {\r\n // After ingest/embedding, not folded into any one source's sync\r\n // function above: correlation reads across shell_command and\r\n // conversation_turn/session_summary nodes together, so it only makes\r\n // sense once whatever this run ingested is already in the store.\r\n const linkStats = correlateFailures(store, projectId);\r\n linkLine = ` ${pc.dim(`chains: ${linkStats.failuresExamined} failure(s) examined, ${linkStats.linkedByRetry} linked by retry, ${linkStats.linkedByDiscussion} by discussion`)}\\n`;\r\n }\r\n\r\n store.markSynced(projectId);\r\n\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n addStats(totals, git.totals);\r\n addStats(totals, diffs.totals);\r\n addStats(totals, shell.totals);\r\n addStats(totals, conversation.totals);\r\n addStats(totals, sessions.totals);\r\n addStats(totals, docs.totals);\r\n\r\n const stats = store.stats(projectId);\r\n const elapsed = ((Date.now() - started) / 1000).toFixed(2);\r\n\r\n const conversationPart = conversationEnabled ? `, ${conversation.seen} conversation exchange(s)` : '';\r\n const sessionPart = config.sources.session.enabled ? `, ${sessions.seen} session summar${sessions.seen === 1 ? 'y' : 'ies'}` : '';\r\n const docsPart = config.sources.docs.enabled ? `, ${docs.seen} doc section(s)` : '';\r\n const diffPart = config.sources.diff.enabled ? `, ${diffs.seen} file diff(s)` : '';\r\n\r\n out(\r\n [\r\n `${pc.green('synced')} ${git.seen} commit(s)${diffPart}, ${shell.seen} shell entr${shell.seen === 1 ? 'y' : 'ies'}${conversationPart}${sessionPart}${docsPart} in ${elapsed}s`,\r\n ` ${pc.green(`+${totals.inserted} new`)} ${pc.yellow(`~${totals.updated} updated`)} ${pc.dim(`=${totals.unchanged} unchanged`)}`,\r\n ` ${pc.dim(`${stats.total} node(s) total across ${stats.distinctFiles} file path(s)`)}`,\r\n '',\r\n ].join('\\n') + embedLine + linkLine,\r\n );\r\n\r\n return 0;\r\n } finally {\r\n store.close();\r\n }\r\n}\r\n","import { truncate } from '../core/text.js';\r\n\r\n/**\r\n * Splits a long assistant reply into topic-sized chunks instead of one node\r\n * per whole exchange.\r\n *\r\n * The acceptance-test failure this fixes was diagnosed against the real\r\n * database, not guessed: a specific technical explanation was present in\r\n * the index but buried (and sometimes truncated) inside a single 4000-char\r\n * node covering several unrelated points. The fix targets how this\r\n * project's own replies are actually written -- long-form prose with\r\n * **bold lead sentences** marking each point, not literal `#` markdown\r\n * headings (those show up in files this assistant writes, like README.md,\r\n * not in its own chat responses). Both are treated as section boundaries;\r\n * plain paragraphs accumulate into the current chunk until it would exceed\r\n * `maxChars`.\r\n */\r\n\r\nexport interface AssistantChunk {\r\n /** The literal heading/bold-lead text that opened this chunk, if any. */\r\n heading: string | null;\r\n text: string;\r\n}\r\n\r\nconst HEADING_LINE = /^#{1,6}\\s+(.+)$/;\r\nconst BOLD_LEAD = /^\\*\\*([^*]+?)\\*\\*/;\r\n\r\n/** Returns the section title if `paragraph` opens a new logical section, else null. */\r\nfunction sectionStart(paragraph: string): string | null {\r\n const firstLine = paragraph.split('\\n')[0] ?? '';\r\n const heading = HEADING_LINE.exec(firstLine);\r\n if (heading) return (heading[1] ?? '').trim();\r\n\r\n const bold = BOLD_LEAD.exec(paragraph);\r\n if (bold) return (bold[1] ?? '').trim();\r\n\r\n return null;\r\n}\r\n\r\nexport function chunkAssistantText(text: string, maxChars: number): AssistantChunk[] {\r\n const paragraphs = text\r\n // Normalize first: every rule below is written against `\\n`, and a CRLF\r\n // file defeats all of them at once. `\\r\\n\\r\\n` holds no two *consecutive*\r\n // `\\n`, so the paragraph split silently returns the whole document as one\r\n // paragraph, headings are never detected, and the file degrades into a few\r\n // coarse size-based chunks. Docs are read straight off a Windows working\r\n // tree where git checks files out as CRLF, so this is the normal case\r\n // there, not an edge one -- it cut this repo's README from 37 sections to 8.\r\n .replace(/\\r\\n?/g, '\\n')\r\n .split(/\\n{2,}/)\r\n .map((p) => p.trim())\r\n .filter(Boolean);\r\n\r\n if (paragraphs.length === 0) return [];\r\n\r\n const chunks: AssistantChunk[] = [];\r\n let buffer: string[] = [];\r\n let bufferHeading: string | null = null;\r\n\r\n const bufferChars = () => buffer.reduce((n, p) => n + p.length, 0) + Math.max(0, buffer.length - 1) * 2;\r\n\r\n const flush = () => {\r\n if (buffer.length === 0) return;\r\n chunks.push({ heading: bufferHeading, text: truncate(buffer.join('\\n\\n'), maxChars) });\r\n buffer = [];\r\n bufferHeading = null;\r\n };\r\n\r\n for (const paragraph of paragraphs) {\r\n const heading = sectionStart(paragraph);\r\n const startsNewSection = heading !== null && buffer.length > 0;\r\n const wouldOverflow = buffer.length > 0 && bufferChars() + 2 + paragraph.length > maxChars;\r\n\r\n if (startsNewSection || wouldOverflow) flush();\r\n if (heading !== null) bufferHeading = heading;\r\n\r\n buffer.push(paragraph);\r\n }\r\n flush();\r\n\r\n return chunks;\r\n}\r\n","/**\r\n * Best-effort secret redaction for collected text before it is ever written\r\n * to the FTS index.\r\n *\r\n * This is a safety net, not a guarantee -- pattern-based redaction cannot\r\n * catch every shape a secret can take. It exists because conversation text\r\n * is the collector most likely to contain something sensitive (a pasted\r\n * credential, a key a user asked for help debugging), but a committed `.env`\r\n * or a hard-coded key makes the code-diff collector a real second candidate,\r\n * which is what the `high-confidence` profile below is for.\r\n */\r\n\r\ninterface Rule {\r\n name: string;\r\n pattern: RegExp;\r\n /**\r\n * The match is a secret by its own shape, with no reliance on the\r\n * surrounding text. Only these rules are safe to run over source code:\r\n * the shape rules match strings nothing else produces, while the\r\n * key/value rule matches ordinary code such as\r\n * `const apiKey = process.env.API_KEY` and would corrupt the very lines\r\n * a diff is indexed for.\r\n */\r\n highConfidence: boolean;\r\n}\r\n\r\nconst RULES: Rule[] = [\r\n {\r\n name: 'private-key-block',\r\n pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,\r\n highConfidence: true,\r\n },\r\n { name: 'aws-access-key', pattern: /\\bAKIA[0-9A-Z]{16}\\b/g, highConfidence: true },\r\n { name: 'github-token', pattern: /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/g, highConfidence: true },\r\n { name: 'slack-token', pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/g, highConfidence: true },\r\n {\r\n name: 'jwt',\r\n pattern: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\b/g,\r\n highConfidence: true,\r\n },\r\n // key/token/secret/password = \"value\" or : value, in code, JSON, env-file or prose form.\r\n {\r\n name: 'key-value-secret',\r\n pattern: /\\b((?:api[_-]?key|secret|password|passwd|token|access[_-]?key)s?)\\s*[:=]\\s*['\"]?[A-Za-z0-9_\\-./+=]{8,}['\"]?/gi,\r\n highConfidence: false,\r\n },\r\n];\r\n\r\nexport interface RedactResult {\r\n text: string;\r\n redactedCount: number;\r\n}\r\n\r\n/**\r\n * `all` runs every rule -- the right trade for prose, where a false positive\r\n * costs a mangled sentence. `high-confidence` runs only the shape rules, for\r\n * text that *is* code and must survive redaction intact.\r\n */\r\nexport type RedactProfile = 'all' | 'high-confidence';\r\n\r\nexport function redact(text: string, profile: RedactProfile = 'all'): RedactResult {\r\n let redactedCount = 0;\r\n let out = text;\r\n\r\n for (const rule of RULES) {\r\n if (profile === 'high-confidence' && !rule.highConfidence) continue;\r\n out = out.replace(rule.pattern, (_match: string, ...rest: unknown[]) => {\r\n redactedCount += 1;\r\n // `String.replace` passes (match, ...groups, offset, wholeString), so for\r\n // a rule with no capture group `rest[0]` is the *offset* -- a number, and\r\n // truthy at any position but the very first. Testing the type rather than\r\n // truthiness is what keeps a match position out of the indexed corpus.\r\n const key = typeof rest[0] === 'string' ? rest[0] : null;\r\n // Keep the key name for key/value matches so the redaction is legible\r\n // (\"apiKey: [redacted]\" reads better than a bare \"[redacted]\").\r\n return key ? `${key}: [redacted]` : '[redacted]';\r\n });\r\n }\r\n\r\n return { text: out, redactedCount };\r\n}\r\n","import { makeNodeId } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport type { FileTouch, MemoryNode } from '../core/types.js';\r\nimport { chunkAssistantText } from '../conversation/chunk.js';\r\nimport { redact } from '../conversation/redact.js';\r\nimport type { RawConversationTurn } from '../conversation/types.js';\r\n\r\nexport interface ConversationCollectorOptions {\r\n /** Default 2500 -- final safety cap on one chunk's assembled Q+A body. */\r\n maxBodyChars?: number;\r\n /**\r\n * Default 900 -- target size for one assistant-reply chunk before it's\r\n * closed out and a new node started. Small enough that a single\r\n * explanatory point stays retrievable on its own, not buried inside\r\n * several unrelated ones in the same long reply.\r\n */\r\n maxChunkChars?: number;\r\n}\r\n\r\nconst DEFAULT_MAX_BODY_CHARS = 2500;\r\nconst DEFAULT_MAX_CHUNK_CHARS = 900;\r\nconst MAX_TITLE_CHARS = 200;\r\nconst MAX_FILES_PER_NODE = 20;\r\n\r\nconst EXPLANATION_MARKERS =\r\n /\\b(because|the reason|design decision|trade-?off|instead of|rationale|so that)\\b/i;\r\nconst TRIVIAL_ACK = /^(ok|okay|thanks?|got it|sounds good|👍|done)\\.?!?$/i;\r\n\r\n/**\r\n * Prior importance of one chunk of a conversation exchange.\r\n *\r\n * Scored per chunk, not per whole exchange -- a genuinely explanatory point\r\n * should outrank a routine one even when they came from the same reply.\r\n * A much noisier signal than a commit type, so scores stay closer to the\r\n * middle. The intuition mirrors `scoreCommit`: text that explains a *why*\r\n * is worth far more to a future query than text that just confirms\r\n * something happened.\r\n */\r\nexport function scoreConversationTurn(userText: string, replyText: string): number {\r\n const text = `${userText}\\n${replyText}`;\r\n let score = 0.3;\r\n\r\n if (EXPLANATION_MARKERS.test(text)) score += 0.25;\r\n if (replyText.length > 400) score += 0.15;\r\n else if (replyText.length < 80) score -= 0.1;\r\n\r\n if (TRIVIAL_ACK.test(userText.trim())) score -= 0.2;\r\n if (userText.trim().endsWith('?')) score += 0.05;\r\n\r\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\r\n}\r\n\r\nconst FILE_PATH = /\\b(?:[\\w.-]+\\/)*[\\w-]+\\.(?:ts|tsx|js|jsx|json|md|py|rs|go|java|c|cpp|h|hpp|css|html|ya?ml|toml|sql|sh|ps1)\\b/g;\r\n\r\n/**\r\n * Heuristic file-mention extraction, not a parsed diff -- lets a\r\n * conversation node join the `node_files` path index like git/shell nodes\r\n * do, so \"why is this file the way it is\" can be answered from either\r\n * direction. False positives (a path-shaped word in prose) are possible and\r\n * acceptable for a best-effort cross-reference.\r\n */\r\nexport function extractMentionedFiles(text: string): FileTouch[] {\r\n const seen = new Set<string>();\r\n const files: FileTouch[] = [];\r\n\r\n for (const match of text.matchAll(FILE_PATH)) {\r\n const path = match[0];\r\n if (seen.has(path)) continue;\r\n seen.add(path);\r\n files.push({ path, insertions: null, deletions: null, binary: false });\r\n if (files.length >= MAX_FILES_PER_NODE) break;\r\n }\r\n\r\n return files;\r\n}\r\n\r\n/**\r\n * Truncating `${userFirstLine}${suffix}` as one string would silently drop\r\n * the suffix whenever the question alone already reached MAX_TITLE_CHARS --\r\n * exactly the case for a long original question, which is also exactly the\r\n * case where a reply is most likely to have been split into many chunks\r\n * that then all render with one indistinguishable title. Truncate the\r\n * question first, leaving guaranteed room for whatever comes after it.\r\n */\r\nfunction withSuffix(base: string, suffix: string): string {\r\n const budget = Math.max(20, MAX_TITLE_CHARS - suffix.length);\r\n return `${truncate(base, budget)}${suffix}`;\r\n}\r\n\r\nfunction chunkTitle(userFirstLine: string, heading: string | null, index: number, count: number): string {\r\n if (heading) return withSuffix(userFirstLine, ` — ${heading}`);\r\n if (count > 1) return withSuffix(userFirstLine, ` (part ${index + 1}/${count})`);\r\n return truncate(userFirstLine, MAX_TITLE_CHARS);\r\n}\r\n\r\n/**\r\n * One exchange becomes one node per chunk of its assistant reply (see\r\n * `chunkAssistantText`), each pairing the *original* question with just\r\n * that chunk -- so every node is independently self-contained and\r\n * searchable, not dependent on a sibling node for context. Short exchanges\r\n * (the common case) produce exactly one chunk, unchanged from before.\r\n */\r\nexport function toMemoryNodes(\r\n turn: RawConversationTurn,\r\n projectId: string,\r\n opts: ConversationCollectorOptions = {},\r\n): MemoryNode[] {\r\n const maxBody = opts.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;\r\n const maxChunk = opts.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;\r\n\r\n const userRedacted = redact(turn.userText);\r\n const userFirstLine = userRedacted.text.split(/\\r?\\n/)[0] ?? userRedacted.text;\r\n\r\n const assistantRedacted = redact(turn.assistantText);\r\n const chunks = chunkAssistantText(assistantRedacted.text, maxChunk);\r\n // Redaction found something but chunking produced nothing to attach it to\r\n // (a reply that was pure whitespace after redaction) -- shouldn't happen\r\n // since callers filter out empty assistantText first, but stay defensive.\r\n if (chunks.length === 0) return [];\r\n\r\n return chunks.map((chunk, index) => {\r\n const body = [`Q: ${userRedacted.text}`, '', `A: ${chunk.text}`].join('\\n');\r\n\r\n return {\r\n id: makeNodeId(projectId, 'conversation_turn', `${turn.naturalKey}:${index}`),\r\n kind: 'conversation_turn',\r\n projectId,\r\n ts: turn.ts,\r\n source: `conversation:${turn.source}`,\r\n title: chunkTitle(userFirstLine, chunk.heading, index, chunks.length),\r\n body: truncate(body, maxBody),\r\n files: extractMentionedFiles(`${userRedacted.text}\\n${chunk.text}`),\r\n signal: scoreConversationTurn(userRedacted.text, chunk.text),\r\n meta: {\r\n cwd: turn.cwd,\r\n source: turn.source,\r\n chunkIndex: index,\r\n chunkCount: chunks.length,\r\n heading: chunk.heading,\r\n // Redaction runs once over the whole reply before chunking (so a\r\n // secret can never straddle a chunk boundary and slip through) --\r\n // this is the turn's total, repeated on every chunk it produced,\r\n // not a per-chunk count.\r\n redactedCount: userRedacted.redactedCount + assistantRedacted.redactedCount,\r\n },\r\n };\r\n });\r\n}\r\n\r\nexport function collectConversationTurns(\r\n turns: readonly RawConversationTurn[],\r\n projectId: string,\r\n opts: ConversationCollectorOptions = {},\r\n): MemoryNode[] {\r\n return turns.filter((t) => t.assistantText.length > 0).flatMap((turn) => toMemoryNodes(turn, projectId, opts));\r\n}\r\n","import type { FileTouch } from '../core/types.js';\n\n/**\n * Pure parsing layer for `git log` output. No I/O here, so it is cheap to\n * unit-test against the ugly real-world cases (renames, binaries, messages\n * containing newlines / tabs / arrows).\n */\n\nexport const RECORD_SEP = '\\x1E';\nexport const UNIT_SEP = '\\x1F';\n\n/**\n * Field order must stay in sync with `parseCommitRecord`.\n *\n * ASCII RS/US are used as delimiters rather than a made-up token: they are\n * control characters that git never emits itself and that essentially never\n * occur in commit messages.\n */\nexport const GIT_LOG_FORMAT =\n '%x1e%H%x1f%h%x1f%P%x1f%an%x1f%ae%x1f%aI%x1f%cI%x1f%s%x1f%B%x1f';\n\nconst FIELD_COUNT = 10; // 9 format fields + the trailing --numstat block\n\nexport interface RawCommit {\n sha: string;\n shortSha: string;\n parents: string[];\n authorName: string;\n authorEmail: string;\n /** ISO-8601 with offset (`%aI`). */\n authoredAt: string;\n committedAt: string;\n subject: string;\n /** Full message including the subject line (`%B`), trimmed. */\n message: string;\n /** Message with the subject line stripped; empty for one-line commits. */\n messageBody: string;\n files: FileTouch[];\n isMerge: boolean;\n}\n\n/**\n * Split a stdout buffer into complete records.\n *\n * Returns the trailing partial record so the caller can carry it into the next\n * chunk. `flush` treats whatever remains as complete.\n */\nexport function splitRecords(buffer: string, flush = false): { records: string[]; rest: string } {\n const parts = buffer.split(RECORD_SEP);\n // parts[0] is whatever preceded the first RS -- empty on a fresh stream.\n const rest = flush ? '' : (parts.pop() ?? '');\n const records = parts.filter((p) => p.trim().length > 0);\n if (flush && rest.trim().length > 0) records.push(rest);\n return { records, rest };\n}\n\nexport function parseCommitRecord(record: string): RawCommit | null {\n // Tolerate a leading separator so callers can pass raw `git log` slices\n // without going through `splitRecords` first.\n let payload = record;\n while (payload.startsWith(RECORD_SEP)) payload = payload.slice(RECORD_SEP.length);\n\n const parts = payload.split(UNIT_SEP);\n if (parts.length < FIELD_COUNT) return null;\n\n const sha = parts[0] ?? '';\n if (!/^[0-9a-f]{7,64}$/i.test(sha)) return null;\n\n // A commit message containing a literal US would add fields; everything\n // between the subject and the final block still belongs to the body.\n const numstatBlock = parts[parts.length - 1] ?? '';\n const message = parts.slice(8, parts.length - 1).join(UNIT_SEP).trim();\n const subject = (parts[7] ?? '').trim();\n const parents = (parts[2] ?? '').trim().split(/\\s+/).filter(Boolean);\n\n return {\n sha,\n shortSha: parts[1] ?? '',\n parents,\n authorName: parts[3] ?? '',\n authorEmail: parts[4] ?? '',\n authoredAt: parts[5] ?? '',\n committedAt: parts[6] ?? '',\n subject,\n message,\n messageBody: stripSubject(message, subject),\n files: parseNumstat(numstatBlock),\n isMerge: parents.length > 1,\n };\n}\n\nfunction stripSubject(message: string, subject: string): string {\n if (!subject || !message.startsWith(subject)) return message;\n return message.slice(subject.length).trim();\n}\n\nconst NUMSTAT_LINE = /^(\\d+|-)\\t(\\d+|-)\\t(.*)$/;\n\nexport function parseNumstat(block: string): FileTouch[] {\n const files: FileTouch[] = [];\n\n for (const line of block.split('\\n')) {\n const m = NUMSTAT_LINE.exec(line.trimEnd());\n if (!m) continue;\n\n const [, addRaw = '', delRaw = '', pathRaw = ''] = m;\n const binary = addRaw === '-' || delRaw === '-';\n const { path, previousPath } = resolveRenamePath(unquoteGitPath(pathRaw));\n\n const touch: FileTouch = {\n path,\n insertions: binary ? null : Number(addRaw),\n deletions: binary ? null : Number(delRaw),\n binary,\n };\n if (previousPath) touch.previousPath = previousPath;\n files.push(touch);\n }\n\n return files;\n}\n\n/**\n * `--numstat` reports renames in two shapes:\n * src/{old => new}/file.ts (shared prefix/suffix factored out)\n * old/file.ts => new/file.ts (no shared parts)\n */\nexport function resolveRenamePath(raw: string): { path: string; previousPath?: string } {\n const braced = /^(.*)\\{(.*?) => (.*?)\\}(.*)$/.exec(raw);\n if (braced) {\n const [, prefix = '', from = '', to = '', suffix = ''] = braced;\n return {\n path: collapseSlashes(prefix + to + suffix),\n previousPath: collapseSlashes(prefix + from + suffix),\n };\n }\n\n const plain = raw.split(' => ');\n if (plain.length === 2) {\n return { path: (plain[1] ?? '').trim(), previousPath: (plain[0] ?? '').trim() };\n }\n\n return { path: raw };\n}\n\nfunction collapseSlashes(p: string): string {\n return p.replace(/\\/{2,}/g, '/').replace(/^\\//, '');\n}\n\n/**\n * git still quotes paths containing `\"`, backslashes or control characters\n * even with `core.quotePath=false`.\n */\nexport function unquoteGitPath(p: string): string {\n if (p.length < 2 || !p.startsWith('\"') || !p.endsWith('\"')) return p;\n const inner = p.slice(1, -1);\n\n // Octal escapes are raw *bytes*, so decode into a byte buffer and let UTF-8\n // decoding reassemble multi-byte characters.\n const bytes: number[] = [];\n\n for (let i = 0; i < inner.length; i += 1) {\n const ch = inner[i] ?? '';\n if (ch !== '\\\\') {\n for (const b of Buffer.from(ch, 'utf8')) bytes.push(b);\n continue;\n }\n\n const esc = inner[i + 1] ?? '';\n const simple: Record<string, number> = { n: 0x0a, t: 0x09, r: 0x0d, a: 0x07, b: 0x08, f: 0x0c, v: 0x0b };\n if (esc in simple) {\n bytes.push(simple[esc] as number);\n i += 1;\n } else if (esc === '\"' || esc === '\\\\') {\n bytes.push(esc.charCodeAt(0));\n i += 1;\n } else if (/[0-7]/.test(esc)) {\n const octal = inner.slice(i + 1, i + 4);\n bytes.push(parseInt(octal, 8) & 0xff);\n i += 3;\n } else {\n bytes.push(0x5c); // lone backslash\n }\n }\n\n return Buffer.from(bytes).toString('utf8');\n}\n","import { GitError, gitStream } from './exec.js';\r\nimport { RECORD_SEP, splitRecords, UNIT_SEP, unquoteGitPath } from './parse.js';\r\n\r\n/**\r\n * Reading and parsing of commit *patches*, as opposed to `log.ts`'s commit\r\n * metadata + `--numstat` summary.\r\n *\r\n * The two are deliberately separate walks rather than one `git log -p\r\n * --numstat` call. Combining them puts the numstat block and the patch in the\r\n * same trailing field, where a diff line such as `-1\\t2\\tfoo` is\r\n * indistinguishable from a real numstat row -- the commit collector would then\r\n * invent file entries out of a diff of any tab-separated file. The cost is one\r\n * extra `git log` process per sync; the benefit is that neither parser has to\r\n * guess which kind of line it is looking at.\r\n */\r\n\r\nexport const GIT_DIFF_LOG_FORMAT = '%x1e%H%x1f%h%x1f%aI%x1f%s%x1f';\r\n\r\n/** 4 format fields + the trailing patch block. */\r\nconst FIELD_COUNT = 5;\r\n\r\nexport type FileDiffStatus = 'added' | 'deleted' | 'renamed' | 'modified';\r\n\r\nexport interface RawFileDiff {\r\n /** Repo-relative path, post-rename. */\r\n path: string;\r\n /** Set only when this file was renamed or copied in this commit. */\r\n previousPath?: string;\r\n status: FileDiffStatus;\r\n /** git printed `Binary files ... differ` (or a binary patch) instead of hunks. */\r\n binary: boolean;\r\n insertions: number;\r\n deletions: number;\r\n hunkCount: number;\r\n /** The unified-diff hunks for this file, without the `diff --git`/`index` preamble. */\r\n patch: string;\r\n}\r\n\r\nexport interface RawCommitDiff {\r\n sha: string;\r\n shortSha: string;\r\n /** ISO-8601 with offset (`%aI`), matching the commit node's `ts`. */\r\n authoredAt: string;\r\n subject: string;\r\n files: RawFileDiff[];\r\n}\r\n\r\nexport interface ReadCommitDiffsOptions {\r\n /** Revision to walk back from. Default `HEAD`. */\r\n rev?: string;\r\n /** Exclusive lower bound -- walks `<afterCommit>..<rev>`. Used for incremental sync. */\r\n afterCommit?: string | null;\r\n /** Any git date expression, e.g. `90.days.ago` or `2025-01-01`. */\r\n since?: string | null;\r\n /** Hard cap on commits walked. Diffs are far bulkier than commit messages, so callers normally set one. */\r\n maxCount?: number | null;\r\n /** Context lines around each hunk. Default 3, git's own default. */\r\n contextLines?: number;\r\n /** Restrict to pathspecs. */\r\n paths?: string[];\r\n}\r\n\r\n/** Errors that just mean \"there is nothing to read\", not \"something broke\". */\r\nconst EMPTY_HISTORY = /does not have any commits yet|unknown revision|bad revision|ambiguous argument/i;\r\n\r\nexport function buildDiffLogArgs(opts: ReadCommitDiffsOptions = {}): string[] {\r\n const { rev = 'HEAD', afterCommit, since, maxCount, contextLines = 3, paths } = opts;\r\n\r\n const args = [\r\n 'log',\r\n `--format=${GIT_DIFF_LOG_FORMAT}`,\r\n '--patch',\r\n '--no-color',\r\n // A merge produces no patch at all unless `-m`/`--cc` is passed, and the\r\n // combined diff those print is a different format from the one parsed\r\n // here. Excluding merges up front keeps the parser honest about what it\r\n // supports; the merge itself is still remembered as a `git_commit` node.\r\n '--no-merges',\r\n '--find-renames',\r\n // Never run a user-configured textconv filter: it would execute an\r\n // arbitrary program from repo config during a sync, and its output is not\r\n // the diff we claim to be indexing.\r\n '--no-textconv',\r\n `--unified=${Math.max(0, contextLines)}`,\r\n ];\r\n\r\n if (maxCount && maxCount > 0) args.push(`--max-count=${maxCount}`);\r\n if (since) args.push(`--since=${since}`);\r\n\r\n args.push(afterCommit ? `${afterCommit}..${rev}` : rev);\r\n\r\n if (paths?.length) args.push('--', ...paths);\r\n\r\n return args;\r\n}\r\n\r\n/** Walk commit patches, yielding one commit's file diffs at a time. */\r\nexport async function* readCommitDiffs(\r\n cwd: string,\r\n opts: ReadCommitDiffsOptions = {},\r\n): AsyncGenerator<RawCommitDiff> {\r\n const args = buildDiffLogArgs(opts);\r\n let buffer = '';\r\n\r\n try {\r\n for await (const chunk of gitStream(cwd, args)) {\r\n buffer += chunk;\r\n const { records, rest } = splitRecords(buffer);\r\n buffer = rest;\r\n for (const record of records) {\r\n const commit = parseCommitDiffRecord(record);\r\n if (commit) yield commit;\r\n }\r\n }\r\n } catch (err) {\r\n if (err instanceof GitError && EMPTY_HISTORY.test(err.stderr)) return;\r\n throw err;\r\n }\r\n\r\n for (const record of splitRecords(buffer, true).records) {\r\n const commit = parseCommitDiffRecord(record);\r\n if (commit) yield commit;\r\n }\r\n}\r\n\r\nexport function parseCommitDiffRecord(record: string): RawCommitDiff | null {\r\n let payload = record;\r\n while (payload.startsWith(RECORD_SEP)) payload = payload.slice(RECORD_SEP.length);\r\n\r\n const parts = payload.split(UNIT_SEP);\r\n if (parts.length < FIELD_COUNT) return null;\r\n\r\n const sha = parts[0] ?? '';\r\n if (!/^[0-9a-f]{7,64}$/i.test(sha)) return null;\r\n\r\n // Same defence as `parseCommitRecord`: a subject containing a literal US\r\n // would add fields, and everything between it and the final block still\r\n // belongs to the subject rather than to the patch.\r\n const patchBlock = parts[parts.length - 1] ?? '';\r\n const subject = parts.slice(3, parts.length - 1).join(UNIT_SEP).trim();\r\n\r\n return {\r\n sha,\r\n shortSha: parts[1] ?? '',\r\n authoredAt: parts[2] ?? '',\r\n subject,\r\n files: parseFileDiffs(patchBlock),\r\n };\r\n}\r\n\r\nconst FILE_HEADER = 'diff --git ';\r\nconst HUNK_HEADER = /^@@ -\\d+(?:,\\d+)? \\+\\d+(?:,\\d+)? @@/;\r\n\r\n/**\r\n * Split one commit's patch into per-file diffs.\r\n *\r\n * Section boundaries are `diff --git` lines at column 0. A hunk body can never\r\n * produce one: every line inside a hunk carries a ` `, `+`, `-` or `\\` prefix,\r\n * so an added line reading `diff --git ...` arrives as `+diff --git ...`.\r\n */\r\nexport function parseFileDiffs(block: string): RawFileDiff[] {\r\n const files: RawFileDiff[] = [];\r\n let section: string[] | null = null;\r\n\r\n const flush = () => {\r\n if (!section) return;\r\n const parsed = parseFileSection(section);\r\n if (parsed) files.push(parsed);\r\n section = null;\r\n };\r\n\r\n for (const raw of block.split('\\n')) {\r\n const line = raw.endsWith('\\r') ? raw.slice(0, -1) : raw;\r\n if (line.startsWith(FILE_HEADER)) {\r\n flush();\r\n section = [line];\r\n } else if (section) {\r\n section.push(line);\r\n }\r\n }\r\n flush();\r\n\r\n return files;\r\n}\r\n\r\nfunction parseFileSection(lines: string[]): RawFileDiff | null {\r\n let status: FileDiffStatus = 'modified';\r\n let binary = false;\r\n let fromPath: string | null = null;\r\n let toPath: string | null = null;\r\n let renamedFrom: string | null = null;\r\n let hunkStart = -1;\r\n\r\n for (let i = 0; i < lines.length; i += 1) {\r\n const line = lines[i] ?? '';\r\n\r\n if (HUNK_HEADER.test(line)) {\r\n hunkStart = i;\r\n break;\r\n }\r\n\r\n if (line.startsWith('new file mode')) status = 'added';\r\n else if (line.startsWith('deleted file mode')) status = 'deleted';\r\n else if (line.startsWith('rename from ')) renamedFrom = unquoteGitPath(line.slice('rename from '.length));\r\n else if (line.startsWith('rename to ')) status = 'renamed';\r\n else if (line.startsWith('copy from ')) renamedFrom = unquoteGitPath(line.slice('copy from '.length));\r\n else if (line.startsWith('Binary files ') || line.startsWith('GIT binary patch')) binary = true;\r\n else if (line.startsWith('--- ')) fromPath = stripDiffPathPrefix(line.slice(4));\r\n else if (line.startsWith('+++ ')) toPath = stripDiffPathPrefix(line.slice(4));\r\n }\r\n\r\n const header = parseDiffGitPaths(lines[0] ?? '');\r\n const path = toPath ?? header.b ?? fromPath ?? header.a;\r\n if (!path) return null;\r\n\r\n const previousPath = renamedFrom ?? (status === 'renamed' ? (fromPath ?? header.a ?? undefined) : undefined);\r\n\r\n // Nothing textual to index: a binary blob, or a metadata-only change (mode\r\n // bits, a pure rename). The commit node already records that the file was\r\n // touched, so dropping these costs no information.\r\n if (binary || hunkStart === -1) {\r\n return {\r\n path,\r\n ...(previousPath && previousPath !== path ? { previousPath } : {}),\r\n status,\r\n binary,\r\n insertions: 0,\r\n deletions: 0,\r\n hunkCount: 0,\r\n patch: '',\r\n };\r\n }\r\n\r\n const hunkLines = lines.slice(hunkStart);\r\n let insertions = 0;\r\n let deletions = 0;\r\n let hunkCount = 0;\r\n\r\n for (const line of hunkLines) {\r\n if (HUNK_HEADER.test(line)) hunkCount += 1;\r\n else if (line.startsWith('+')) insertions += 1;\r\n else if (line.startsWith('-')) deletions += 1;\r\n }\r\n\r\n return {\r\n path,\r\n ...(previousPath && previousPath !== path ? { previousPath } : {}),\r\n status,\r\n binary: false,\r\n insertions,\r\n deletions,\r\n hunkCount,\r\n patch: hunkLines.join('\\n').trimEnd(),\r\n };\r\n}\r\n\r\n/** `--- a/src/foo.ts` → `src/foo.ts`; `--- /dev/null` → `null`. */\r\nfunction stripDiffPathPrefix(raw: string): string | null {\r\n const cleaned = unquoteGitPath(raw.trim());\r\n if (cleaned === '/dev/null') return null;\r\n return cleaned.replace(/^[ab]\\//, '');\r\n}\r\n\r\n/**\r\n * Best-effort paths from a `diff --git a/x b/y` line.\r\n *\r\n * Only used when the `---`/`+++` lines are absent, which happens for binary\r\n * and metadata-only changes -- both of which are dropped anyway, so this is a\r\n * label for a skipped file rather than the identity of an indexed one. An\r\n * unquoted path containing ` b/` is genuinely ambiguous in this format; git\r\n * quotes the awkward cases, and the quoted form is parsed exactly.\r\n */\r\nexport function parseDiffGitPaths(headerLine: string): { a: string | null; b: string | null } {\r\n const rest = headerLine.slice(FILE_HEADER.length);\r\n\r\n if (rest.startsWith('\"')) {\r\n const match = /^(\"(?:[^\"\\\\]|\\\\.)*\")\\s+(\"(?:[^\"\\\\]|\\\\.)*\"|\\S+)$/.exec(rest);\r\n if (match) {\r\n return { a: stripDiffPathPrefix(match[1] ?? ''), b: stripDiffPathPrefix(match[2] ?? '') };\r\n }\r\n }\r\n\r\n const quotedSecond = /^(\\S+)\\s+(\"(?:[^\"\\\\]|\\\\.)*\")$/.exec(rest);\r\n if (quotedSecond) {\r\n return { a: stripDiffPathPrefix(quotedSecond[1] ?? ''), b: stripDiffPathPrefix(quotedSecond[2] ?? '') };\r\n }\r\n\r\n const split = / b\\//.exec(rest);\r\n if (!split || split.index <= 0) return { a: null, b: null };\r\n\r\n return {\r\n a: stripDiffPathPrefix(rest.slice(0, split.index)),\r\n b: stripDiffPathPrefix(rest.slice(split.index + 1)),\r\n };\r\n}\r\n","import { GitError, gitStream } from './exec.js';\nimport { GIT_LOG_FORMAT, parseCommitRecord, splitRecords, type RawCommit } from './parse.js';\n\nexport interface ReadCommitsOptions {\n /** Revision to walk back from. Default `HEAD`. */\n rev?: string;\n /** Exclusive lower bound -- walks `<afterCommit>..<rev>`. Used for incremental sync. */\n afterCommit?: string | null;\n /** Any git date expression, e.g. `90.days.ago` or `2025-01-01`. */\n since?: string | null;\n maxCount?: number | null;\n /** Merge commits carry PR titles, so they are kept by default. */\n includeMerges?: boolean;\n /** Restrict to pathspecs. */\n paths?: string[];\n}\n\n/** Errors that just mean \"there is nothing to read\", not \"something broke\". */\nconst EMPTY_HISTORY = /does not have any commits yet|unknown revision|bad revision|ambiguous argument/i;\n\nexport function buildLogArgs(opts: ReadCommitsOptions = {}): string[] {\n const { rev = 'HEAD', afterCommit, since, maxCount, includeMerges = true, paths } = opts;\n\n const args = ['log', `--format=${GIT_LOG_FORMAT}`, '--numstat', '--no-color'];\n\n if (!includeMerges) args.push('--no-merges');\n if (maxCount && maxCount > 0) args.push(`--max-count=${maxCount}`);\n if (since) args.push(`--since=${since}`);\n\n args.push(afterCommit ? `${afterCommit}..${rev}` : rev);\n\n if (paths?.length) args.push('--', ...paths);\n\n return args;\n}\n\n/**\n * Walk commit history, yielding one parsed commit at a time.\n *\n * The generator is lazy: breaking out of the loop kills the underlying `git`\n * process, so `--limit`-style callers never pay for the full history.\n */\nexport async function* readCommits(cwd: string, opts: ReadCommitsOptions = {}): AsyncGenerator<RawCommit> {\n const args = buildLogArgs(opts);\n let buffer = '';\n\n try {\n for await (const chunk of gitStream(cwd, args)) {\n buffer += chunk;\n const { records, rest } = splitRecords(buffer);\n buffer = rest;\n for (const record of records) {\n const commit = parseCommitRecord(record);\n if (commit) yield commit;\n }\n }\n } catch (err) {\n if (err instanceof GitError && EMPTY_HISTORY.test(err.stderr)) return;\n throw err;\n }\n\n for (const record of splitRecords(buffer, true).records) {\n const commit = parseCommitRecord(record);\n if (commit) yield commit;\n }\n}\n","import { makeNodeId } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport type { FileTouch, MemoryNode } from '../core/types.js';\r\nimport { readCommits, type ReadCommitsOptions } from '../git/log.js';\r\nimport type { RawCommit } from '../git/parse.js';\r\n\r\n/**\r\n * Maps raw git commits onto MemoryNodes.\r\n *\r\n * This is the only place that knows a commit is a commit -- everything\r\n * downstream (storage, search, context packing) sees generic nodes.\r\n */\r\n\r\nexport interface GitCommitCollectorOptions extends ReadCommitsOptions {\r\n /** Keep at most this many files per node, biggest churn first. Default 40. */\r\n maxFilesPerNode?: number;\r\n /** Hard cap on node body size, to bound index and embedding cost. Default 4000. */\r\n maxBodyChars?: number;\r\n}\r\n\r\nconst DEFAULTS = { maxFilesPerNode: 40, maxBodyChars: 4000 } as const;\r\nconst MAX_TITLE_CHARS = 200;\r\n\r\nexport interface ConventionalHeader {\r\n type: string | null;\r\n scope: string | null;\r\n breaking: boolean;\r\n description: string;\r\n}\r\n\r\nconst CONVENTIONAL = /^([a-z]+)(?:\\(([^)]*)\\))?(!)?:\\s*(.+)$/i;\r\n\r\nexport function parseConventionalHeader(subject: string): ConventionalHeader {\r\n const m = CONVENTIONAL.exec(subject.trim());\r\n if (!m) return { type: null, scope: null, breaking: false, description: subject.trim() };\r\n return {\r\n type: (m[1] ?? '').toLowerCase(),\r\n scope: m[2] ?? null,\r\n breaking: Boolean(m[3]),\r\n description: (m[4] ?? '').trim(),\r\n };\r\n}\r\n\r\n/**\r\n * Prior importance by commit type.\r\n *\r\n * The intuition: when an agent asks \"why is this code like this?\", a `fix` or\r\n * `refactor` explains far more than a `chore` or `style`.\r\n *\r\n * Exported because the diff collector scores a file's patch from the same\r\n * commit type. A second copy there would be free to drift, which is exactly\r\n * how the four `signalColor` copies ended up disagreeing.\r\n */\r\nexport const TYPE_WEIGHTS: Record<string, number> = {\r\n fix: 0.8,\r\n feat: 0.8,\r\n revert: 0.78,\r\n perf: 0.7,\r\n refactor: 0.68,\r\n security: 0.85,\r\n test: 0.45,\r\n docs: 0.35,\r\n build: 0.32,\r\n ci: 0.28,\r\n chore: 0.25,\r\n style: 0.2,\r\n};\r\n\r\nconst AUTOMATED = /^(merge (branch|pull request|remote)|bump |update dependenc|\\[bot\\]|revert \"merge)/i;\r\n\r\nexport function scoreCommit(commit: RawCommit): number {\r\n const header = parseConventionalHeader(commit.subject);\r\n\r\n let score = header.type ? (TYPE_WEIGHTS[header.type] ?? 0.5) : 0.5;\r\n\r\n if (header.breaking) score += 0.12;\r\n // A commit that bothered to explain *why* is worth more than a bare subject.\r\n if (commit.messageBody.length > 120) score += 0.1;\r\n if (commit.isMerge) score = Math.min(score, 0.3);\r\n if (AUTOMATED.test(commit.subject)) score -= 0.15;\r\n\r\n const churn = commit.files.reduce((n, f) => n + (f.insertions ?? 0) + (f.deletions ?? 0), 0);\r\n // Very large commits are usually vendoring / generated code, not intent.\r\n if (commit.files.length > 100 || churn > 5000) score *= 0.75;\r\n if (commit.files.length <= 1 && churn <= 3) score -= 0.05;\r\n\r\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\r\n}\r\n\r\nfunction byChurnDesc(a: FileTouch, b: FileTouch): number {\r\n const ca = (a.insertions ?? 0) + (a.deletions ?? 0);\r\n const cb = (b.insertions ?? 0) + (b.deletions ?? 0);\r\n return cb - ca;\r\n}\r\n\r\nfunction renderFileLine(f: FileTouch): string {\r\n const churn = f.binary ? 'binary' : `+${f.insertions ?? 0}/-${f.deletions ?? 0}`;\r\n return f.previousPath ? ` ${f.path} (${churn}, renamed from ${f.previousPath})` : ` ${f.path} (${churn})`;\r\n}\r\n\r\nexport function toMemoryNode(\r\n commit: RawCommit,\r\n projectId: string,\r\n opts: GitCommitCollectorOptions = {},\r\n): MemoryNode {\r\n const maxFiles = opts.maxFilesPerNode ?? DEFAULTS.maxFilesPerNode;\r\n const maxBody = opts.maxBodyChars ?? DEFAULTS.maxBodyChars;\r\n\r\n const header = parseConventionalHeader(commit.subject);\r\n const keptFiles = [...commit.files].sort(byChurnDesc).slice(0, maxFiles);\r\n\r\n const insertions = commit.files.reduce((n, f) => n + (f.insertions ?? 0), 0);\r\n const deletions = commit.files.reduce((n, f) => n + (f.deletions ?? 0), 0);\r\n\r\n const bodyParts = [commit.subject];\r\n if (commit.messageBody) bodyParts.push('', commit.messageBody);\r\n if (keptFiles.length) {\r\n bodyParts.push('', 'Files changed:', ...keptFiles.map(renderFileLine));\r\n if (commit.files.length > keptFiles.length) {\r\n bodyParts.push(` ...and ${commit.files.length - keptFiles.length} more file(s)`);\r\n }\r\n }\r\n\r\n return {\r\n id: makeNodeId(projectId, 'git_commit', commit.sha),\r\n kind: 'git_commit',\r\n projectId,\r\n ts: commit.authoredAt,\r\n source: 'git',\r\n title: truncate(commit.subject || `(no subject) ${commit.shortSha}`, MAX_TITLE_CHARS),\r\n body: truncate(bodyParts.join('\\n'), maxBody),\r\n files: keptFiles,\r\n signal: scoreCommit(commit),\r\n meta: {\r\n sha: commit.sha,\r\n shortSha: commit.shortSha,\r\n parents: commit.parents,\r\n authorName: commit.authorName,\r\n authorEmail: commit.authorEmail,\r\n committedAt: commit.committedAt,\r\n isMerge: commit.isMerge,\r\n filesChanged: commit.files.length,\r\n insertions,\r\n deletions,\r\n conventionalType: header.type,\r\n conventionalScope: header.scope,\r\n breaking: header.breaking,\r\n },\r\n };\r\n}\r\n\r\n/** Stream commits from `cwd`'s repository as MemoryNodes. */\r\nexport async function* collectGitCommits(\r\n cwd: string,\r\n projectId: string,\r\n opts: GitCommitCollectorOptions = {},\r\n): AsyncGenerator<MemoryNode> {\r\n for await (const commit of readCommits(cwd, opts)) {\r\n yield toMemoryNode(commit, projectId, opts);\r\n }\r\n}\r\n","import { redact } from '../conversation/redact.js';\r\nimport { makeNodeId } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport type { MemoryNode } from '../core/types.js';\r\nimport { readCommitDiffs, type RawCommitDiff, type RawFileDiff, type ReadCommitDiffsOptions } from '../git/diff.js';\r\nimport { parseConventionalHeader, TYPE_WEIGHTS } from './git-commits.js';\r\n\r\n/**\r\n * Maps commit patches onto MemoryNodes, one per changed file.\r\n *\r\n * The `git_commit` node answers \"what changed and why did the author say they\r\n * changed it\"; this one answers \"what did the change actually look like\".\r\n * Those are different questions, and the second is the one an agent asks when\r\n * it is about to edit the same lines -- the commit node's `Files changed:`\r\n * list can say `src/git/exec.ts (+41/-6)` without containing a single line of\r\n * the code that makes the answer.\r\n *\r\n * One node per file rather than per commit: a commit that touches six files\r\n * would otherwise pack six unrelated patches into one budgeted summary, and\r\n * retrieval could only ever return all of them or none.\r\n */\r\n\r\nexport const DIFF_SOURCE = 'diff';\r\n\r\nexport interface DiffCollectorOptions extends ReadCommitDiffsOptions {\r\n /** Keep at most this many files per commit, biggest churn first. Default 20. */\r\n maxFilesPerCommit?: number;\r\n /** Hard cap on one node's body, to bound index and embedding cost. Default 2000. */\r\n maxBodyChars?: number;\r\n}\r\n\r\nconst DEFAULTS = { maxFilesPerCommit: 20, maxBodyChars: 2000 } as const;\r\nconst MAX_TITLE_CHARS = 200;\r\n\r\n/**\r\n * Paths whose diff is machine-written.\r\n *\r\n * A lockfile churns on every dependency bump and would otherwise dominate the\r\n * corpus with thousands of lines nobody ever wrote or will ever ask about.\r\n * The commit node still records that the file changed.\r\n */\r\nconst GENERATED_PATHS: RegExp[] = [\r\n /(^|\\/)(node_modules|dist|build|out|coverage|vendor|third_party)\\//,\r\n /(^|\\/)(package-lock\\.json|npm-shrinkwrap\\.json|yarn\\.lock|pnpm-lock\\.yaml|composer\\.lock|Cargo\\.lock|poetry\\.lock|Gemfile\\.lock|go\\.sum)$/,\r\n /\\.(min\\.js|min\\.css|map|snap)$/,\r\n];\r\n\r\nexport function isGeneratedPath(path: string): boolean {\r\n return GENERATED_PATHS.some((re) => re.test(path));\r\n}\r\n\r\nconst TEST_PATHS = /(^|\\/)(tests?|__tests__|spec|e2e)\\/|\\.(test|spec)\\.[cm]?[jt]sx?$/;\r\n\r\n/**\r\n * Prior importance of one file's patch.\r\n *\r\n * Anchored on the commit's own type -- a patch inside a `fix:` is worth more\r\n * than the same patch inside a `chore:` -- then adjusted for what the patch\r\n * itself looks like. The size adjustments both point the same way: a surgical\r\n * change is usually the interesting one, and a thousand-line rewrite is\r\n * usually a move, a reformat or generated output that slipped past the path\r\n * filter.\r\n */\r\nexport function scoreFileDiff(subject: string, file: RawFileDiff): number {\r\n const header = parseConventionalHeader(subject);\r\n let score = header.type ? (TYPE_WEIGHTS[header.type] ?? 0.5) : 0.5;\r\n\r\n if (header.breaking) score += 0.1;\r\n if (TEST_PATHS.test(file.path)) score -= 0.1;\r\n if (file.status === 'added') score += 0.05;\r\n if (file.status === 'deleted') score -= 0.1;\r\n\r\n const churn = file.insertions + file.deletions;\r\n if (churn <= 2) score -= 0.05;\r\n if (churn > 400) score *= 0.75;\r\n\r\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\r\n}\r\n\r\nfunction byChurnDesc(a: RawFileDiff, b: RawFileDiff): number {\r\n return b.insertions + b.deletions - (a.insertions + a.deletions);\r\n}\r\n\r\n/** Files worth indexing: real text hunks, hand-written path. */\r\nexport function indexableFiles(files: readonly RawFileDiff[]): RawFileDiff[] {\r\n return files.filter((f) => !f.binary && f.hunkCount > 0 && !isGeneratedPath(f.path));\r\n}\r\n\r\nconst STATUS_LABEL: Record<RawFileDiff['status'], string> = {\r\n added: 'added',\r\n deleted: 'deleted',\r\n renamed: 'renamed',\r\n modified: 'modified',\r\n};\r\n\r\nfunction fileTitle(shortSha: string, subject: string, file: RawFileDiff): string {\r\n return truncate(`${file.path} @ ${shortSha} — ${subject}`, MAX_TITLE_CHARS);\r\n}\r\n\r\nexport function toMemoryNodes(\r\n commit: RawCommitDiff,\r\n projectId: string,\r\n opts: DiffCollectorOptions = {},\r\n): MemoryNode[] {\r\n const maxFiles = opts.maxFilesPerCommit ?? DEFAULTS.maxFilesPerCommit;\r\n const maxBody = opts.maxBodyChars ?? DEFAULTS.maxBodyChars;\r\n\r\n const kept = indexableFiles(commit.files).sort(byChurnDesc).slice(0, maxFiles);\r\n\r\n return kept.map((file) => {\r\n const churn = `+${file.insertions}/-${file.deletions}`;\r\n const renamePart = file.previousPath ? `, renamed from ${file.previousPath}` : '';\r\n const head = [\r\n `${commit.subject} (${commit.shortSha})`,\r\n `${STATUS_LABEL[file.status]} ${file.path} (${churn}, ${file.hunkCount} hunk${file.hunkCount === 1 ? '' : 's'}${renamePart})`,\r\n '',\r\n ].join('\\n');\r\n\r\n // High-confidence rules only: this body is source code, and the key/value\r\n // rule would rewrite ordinary lines like `apiKey = config.apiKey` into a\r\n // redaction marker. See redact.ts.\r\n const { text: patch } = redact(file.patch, 'high-confidence');\r\n\r\n return {\r\n id: makeNodeId(projectId, 'code_diff', `${commit.sha}:${file.path}`),\r\n kind: 'code_diff',\r\n projectId,\r\n ts: commit.authoredAt,\r\n source: DIFF_SOURCE,\r\n title: fileTitle(commit.shortSha, commit.subject, file),\r\n body: truncate(head + patch, maxBody),\r\n files: [\r\n {\r\n path: file.path,\r\n ...(file.previousPath ? { previousPath: file.previousPath } : {}),\r\n insertions: file.insertions,\r\n deletions: file.deletions,\r\n binary: false,\r\n },\r\n ],\r\n signal: scoreFileDiff(commit.subject, file),\r\n meta: {\r\n sha: commit.sha,\r\n shortSha: commit.shortSha,\r\n path: file.path,\r\n status: file.status,\r\n hunkCount: file.hunkCount,\r\n insertions: file.insertions,\r\n deletions: file.deletions,\r\n subject: commit.subject,\r\n },\r\n };\r\n });\r\n}\r\n\r\n/** Stream file-level diff nodes from `cwd`'s repository. */\r\nexport async function* collectCommitDiffs(\r\n cwd: string,\r\n projectId: string,\r\n opts: DiffCollectorOptions = {},\r\n): AsyncGenerator<MemoryNode> {\r\n for await (const commit of readCommitDiffs(cwd, opts)) {\r\n for (const node of toMemoryNodes(commit, projectId, opts)) yield node;\r\n }\r\n}\r\n","import { chunkAssistantText } from '../conversation/chunk.js';\nimport { makeNodeId } from '../core/ids.js';\nimport { truncate } from '../core/text.js';\nimport type { MemoryNode } from '../core/types.js';\nimport type { RawDocFile } from '../docs/types.js';\n\n/**\n * Maps a repo's tracked `.md` files onto MemoryNodes, one per section.\n *\n * The gap this closes was found by dogfooding the live MCP server (see\n * README.md's Phase 3 row): a design-rationale question answered from\n * README.md prose came back empty, because git/shell/conversation are the\n * only sources that were ever read. Chunking reuses `chunkAssistantText`\n * from the conversation collector rather than inventing a second heading\n * splitter -- it already treats literal `#`..`######` lines as section\n * boundaries, which is exactly what a real markdown file is made of (the\n * bold-lead-paragraph case it also handles just never triggers here).\n */\n\nexport interface DocsCollectorOptions {\n /** Default 2000 -- final safety cap on one section's body. */\n maxBodyChars?: number;\n /** Default 1200 -- target size for one section chunk before it's split further. */\n maxChunkChars?: number;\n}\n\nconst DEFAULT_MAX_BODY_CHARS = 2000;\nconst DEFAULT_MAX_CHUNK_CHARS = 1200;\nconst MAX_TITLE_CHARS = 200;\n\nconst EXPLANATION_MARKERS = /\\b(because|the reason|design decision|trade-?off|instead of|rationale|why)\\b/i;\n\n/**\n * Prior importance of one doc section.\n *\n * Deliberately close to the middle, like a conversation chunk's score: a\n * doc file mixes genuine design rationale with routine scaffolding (a table\n * of contents entry, a install-step list), and only the source text itself\n * tells them apart.\n */\nexport function scoreDocSection(path: string, heading: string | null, text: string): number {\n let score = 0.45;\n\n if (EXPLANATION_MARKERS.test(text)) score += 0.25;\n if (/(^|\\/)readme\\.md$/i.test(path)) score += 0.1;\n if (heading === null) score -= 0.1; // preamble text with no section of its own\n if (text.length < 80) score -= 0.15;\n\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\n}\n\nfunction slugify(heading: string | null, index: number): string {\n if (heading === null) return `_preamble-${index}`;\n const slug = heading\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return slug || `_section-${index}`;\n}\n\nfunction sectionTitle(path: string, heading: string | null, index: number, count: number): string {\n if (heading) return truncate(`${path} — ${heading}`, MAX_TITLE_CHARS);\n if (count > 1) return truncate(`${path} (part ${index + 1}/${count})`, MAX_TITLE_CHARS);\n return truncate(path, MAX_TITLE_CHARS);\n}\n\nexport function toMemoryNodes(file: RawDocFile, projectId: string, opts: DocsCollectorOptions = {}): MemoryNode[] {\n const maxBody = opts.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;\n const maxChunk = opts.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;\n\n const chunks = chunkAssistantText(file.content, maxChunk);\n if (chunks.length === 0) return [];\n\n // Stable ids keyed by heading text, not raw index -- a section added\n // earlier in the file must not silently reshuffle every node after it.\n // Duplicate headings (e.g. two \"Why\" sections) fall back to an occurrence\n // count so they still get distinct, deterministic keys.\n const seenSlugs = new Map<string, number>();\n\n return chunks.map((chunk, index) => {\n const baseSlug = slugify(chunk.heading, index);\n const occurrence = seenSlugs.get(baseSlug) ?? 0;\n seenSlugs.set(baseSlug, occurrence + 1);\n const naturalKey = occurrence === 0 ? `${file.path}#${baseSlug}` : `${file.path}#${baseSlug}:${occurrence}`;\n\n return {\n id: makeNodeId(projectId, 'doc_section', naturalKey),\n kind: 'doc_section',\n projectId,\n ts: file.ts,\n source: 'docs',\n title: sectionTitle(file.path, chunk.heading, index, chunks.length),\n body: truncate(chunk.text, maxBody),\n files: [{ path: file.path, insertions: null, deletions: null, binary: false }],\n signal: scoreDocSection(file.path, chunk.heading, chunk.text),\n meta: {\n path: file.path,\n heading: chunk.heading,\n chunkIndex: index,\n chunkCount: chunks.length,\n },\n };\n });\n}\n\nexport function collectDocFiles(files: readonly RawDocFile[], projectId: string, opts: DocsCollectorOptions = {}): MemoryNode[] {\n return files.flatMap((file) => toMemoryNodes(file, projectId, opts));\n}\n","import { sha256Hex } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport { scoreConversationTurn } from '../collectors/conversation.js';\r\nimport { redact } from '../conversation/redact.js';\r\nimport type { RawConversationTurn } from '../conversation/types.js';\r\n\r\n/** One working session's worth of exchanges, in the order they happened. */\r\nexport interface SessionGroup {\r\n sessionKey: string;\r\n source: string;\r\n cwd: string | null;\r\n turns: RawConversationTurn[];\r\n startedAt: string;\r\n endedAt: string;\r\n}\r\n\r\nexport interface SessionPrompt {\r\n prompt: string;\r\n /**\r\n * Identity of exactly what was sent to the model.\r\n *\r\n * Hashing the finished prompt rather than the raw session means a change\r\n * to the template, the truncation limits, or the turn-selection rule all\r\n * invalidate the cached summary, and nothing else does. With a\r\n * temperature-0 model, an unchanged hash provably implies an unchanged\r\n * summary, which is what makes skipping the work safe rather than merely\r\n * cheap.\r\n */\r\n hash: string;\r\n /** Turns that actually made it into the prompt, after any budget trimming. */\r\n includedTurns: number;\r\n}\r\n\r\nexport const MAX_USER_CHARS = 400;\r\nexport const MAX_REPLY_CHARS = 700;\r\nexport const DEFAULT_MAX_PROMPT_CHARS = 12_000;\r\n\r\n/**\r\n * Split a flat list of exchanges into sessions, ordered oldest first.\r\n *\r\n * Grouping is by the session id the reader recorded, never by time gap: two\r\n * sessions on the same repository can overlap in wall-clock time, and a long\r\n * pause inside one session is just lunch, not a boundary.\r\n */\r\nexport function groupTurnsIntoSessions(turns: readonly RawConversationTurn[]): SessionGroup[] {\r\n const groups = new Map<string, SessionGroup>();\r\n\r\n for (const turn of turns) {\r\n const existing = groups.get(turn.sessionKey);\r\n if (existing) {\r\n existing.turns.push(turn);\r\n if (turn.ts < existing.startedAt) existing.startedAt = turn.ts;\r\n if (turn.ts > existing.endedAt) existing.endedAt = turn.ts;\r\n existing.cwd ??= turn.cwd;\r\n continue;\r\n }\r\n\r\n groups.set(turn.sessionKey, {\r\n sessionKey: turn.sessionKey,\r\n source: turn.source,\r\n cwd: turn.cwd,\r\n turns: [turn],\r\n startedAt: turn.ts,\r\n endedAt: turn.ts,\r\n });\r\n }\r\n\r\n for (const group of groups.values()) {\r\n group.turns.sort((a, b) => a.ts.localeCompare(b.ts));\r\n }\r\n\r\n return [...groups.values()].sort((a, b) => a.startedAt.localeCompare(b.startedAt));\r\n}\r\n\r\n/**\r\n * Sessions quiet for long enough to be worth summarizing.\r\n *\r\n * A session still being typed into would be summarized on one sync and\r\n * re-summarized on the next, burning a model call each time to produce a\r\n * summary that was already out of date when it was written. Waiting for the\r\n * session to settle costs nothing -- the exchanges are already indexed\r\n * individually by the conversation collector.\r\n */\r\nexport function selectSettledSessions(\r\n sessions: readonly SessionGroup[],\r\n settleMinutes: number,\r\n now: Date = new Date(),\r\n): SessionGroup[] {\r\n const cutoff = now.getTime() - settleMinutes * 60_000;\r\n return sessions.filter((s) => {\r\n const ended = Date.parse(s.endedAt);\r\n // An unparseable timestamp is treated as settled rather than skipped\r\n // forever: the alternative silently drops a whole session from memory.\r\n return Number.isNaN(ended) || ended <= cutoff;\r\n });\r\n}\r\n\r\n/** Exported so a test can size a prompt budget against it without hard-coding a length. */\r\nexport const SESSION_INSTRUCTIONS = `You are summarizing one working session between a developer and an AI coding assistant, so that a future assistant can recall what happened without re-reading the transcript.\r\n\r\nYour first line MUST begin with \"TITLE: \" and nothing else. Start your reply with those six characters.\r\n\r\nWrite your answer in exactly this shape:\r\nTITLE: <under 15 words, naming the specific work, e.g. \"Raised the Node floor to 22 after a CI failure\">\r\n- <a decision that was made, and why>\r\n- <a problem that was diagnosed, and its cause>\r\n- <what was left unfinished or explicitly deferred>\r\n\r\nRules:\r\n- Answer in English, whatever language the transcript is in. This summary is stored in a keyword index that cannot segment languages without spaces between words.\r\n- The title must name this session specifically. \"Summary of the session\" or \"Project update\" are wrong.\r\n- Prefer reasons over narration. \"Chose X over Y because Z\" is worth more than \"worked on X\".\r\n- Only state what the transcript supports. Do not guess or invent.\r\n- Between 3 and 6 bullets. No preamble, no closing remarks, no markdown bold.`;\r\n\r\n/**\r\n * Build the prompt for one session.\r\n *\r\n * Every exchange is redacted before it reaches the model. The model is local,\r\n * so this is not about exfiltration -- it is that the model's output is\r\n * stored and searchable, and a secret quoted into a summary would be indexed\r\n * in plain text just like any other node body.\r\n */\r\nexport function buildSessionPrompt(session: SessionGroup, maxPromptChars = DEFAULT_MAX_PROMPT_CHARS): SessionPrompt {\r\n const rendered = session.turns.map((turn) => {\r\n const user = redact(turn.userText).text;\r\n const reply = redact(turn.assistantText).text;\r\n return {\r\n ts: turn.ts,\r\n text: [`[${turn.ts}] developer: ${truncate(user, MAX_USER_CHARS)}`, `assistant: ${truncate(reply, MAX_REPLY_CHARS)}`].join(\r\n '\\n',\r\n ),\r\n signal: scoreConversationTurn(user, reply),\r\n };\r\n });\r\n\r\n // Over budget, keep the highest-signal exchanges and put them back in\r\n // order. Dropping the tail instead would lose the end of the session,\r\n // which is where conclusions live; dropping the head would lose the task.\r\n // Selecting by the same score the conversation collector already uses\r\n // keeps \"what counts as important\" defined in exactly one place.\r\n const budget = maxPromptChars - SESSION_INSTRUCTIONS.length;\r\n const kept: typeof rendered = [];\r\n let used = 0;\r\n\r\n for (const turn of [...rendered].sort((a, b) => b.signal - a.signal)) {\r\n if (used + turn.text.length > budget) continue;\r\n kept.push(turn);\r\n used += turn.text.length;\r\n }\r\n kept.sort((a, b) => a.ts.localeCompare(b.ts));\r\n\r\n const body = kept.map((t) => t.text).join('\\n\\n');\r\n const prompt = `${SESSION_INSTRUCTIONS}\\n\\n---\\n\\n${body}\\n\\n---\\n\\nSummary:`;\r\n\r\n return { prompt, hash: sha256Hex(prompt), includedTurns: kept.length };\r\n}\r\n\r\nexport interface ParsedSummary {\r\n title: string;\r\n body: string;\r\n}\r\n\r\n/**\r\n * Title to use when the model's own is unusable: the first line of the\r\n * question that opened the session.\r\n *\r\n * The same convention `conversation.ts` uses for a turn's title, and for the\r\n * same reason -- it is the human's own words, so it is always specific to\r\n * this session even when it is not elegant.\r\n */\r\nexport function sessionFallbackTitle(session: SessionGroup): string {\r\n const opening = session.turns[0];\r\n if (!opening) return 'Working session';\r\n const line = redact(opening.userText).text.split(/\\r?\\n/)[0]?.trim();\r\n return line && line.length > 0 ? line : 'Working session';\r\n}\r\n\r\nconst MAX_TITLE_CHARS = 200;\r\n\r\n/** Titles that name no particular session, and so tell a future reader nothing. */\r\nconst GENERIC_TITLE = /^(a |the )?(session |conversation |project |work )?(summary|update|overview|recap|status)\\b/i;\r\n\r\n/**\r\n * Titles where the model describes the role it was asked to play instead of\r\n * naming what happened -- \"Role: Lead Systems Engineer for NexusMem\" tells a\r\n * future reader nothing a `GENERIC_TITLE` check would catch, because it names\r\n * no summary-ish word at all.\r\n *\r\n * Bilingual on purpose, not Thai-only: `SESSION_INSTRUCTIONS` asks for\r\n * English, but the 3B model does not reliably comply (that is also why Thai\r\n * titles exist in the index at all), so an English-only guard would still\r\n * miss the same shape in English. Anchored at the start and matched against\r\n * multi-character Thai words rather than single letters, so a compound word\r\n * that happens to start with the same syllables -- \"คุณภาพ\" (quality) begins\r\n * with \"คุณ\" (you) -- is not caught; see the test suite's anti-false-positive\r\n * case using a real title from this project's own history.\r\n */\r\nconst ROLE_PREAMBLE_TITLE =\r\n /^(role\\s*[::]|you\\s*(?:'re|are)\\s+(acting as|serving as|playing the role of|a\\b)|i\\s*(?:'m|am)\\s+(acting as|a\\b)|acting as\\b|บทบาท\\s*[::]|ในฐานะ|คุณ(กำลัง)?(ทำหน้าที่เป็น|เป็น|รับบทบาทเป็น)|(ผม|ฉัน)(กำลัง)?(ทำหน้าที่เป็น|เป็น))/i;\r\n\r\n/**\r\n * Strip the decoration a small model adds even when told not to.\r\n * `**Chose X:**` and `- Chose X` both need to become `Chose X`.\r\n */\r\nfunction cleanTitle(line: string): string {\r\n return line\r\n .replace(/^[-*#>\\s]+/, '')\r\n .replace(/\\*+/g, '')\r\n .replace(/\\s*:\\s*$/, '')\r\n .trim();\r\n}\r\n\r\n/**\r\n * Pull a title and body out of whatever the model returned.\r\n *\r\n * The `fallbackTitle` is not a formality -- it is what the title becomes\r\n * whenever the model's own first line cannot be trusted. Dogfooding a 3B\r\n * model over 14 real sessions produced nine unusable titles: conversational\r\n * preambles in the transcript's language, single bullets carried over with\r\n * their markdown, and a bare \"Summary of the Session\". An earlier version\r\n * accepted any first line as a title, which is how all nine reached the\r\n * index. A summary is still never rejected for bad formatting -- only its\r\n * title is replaced, and the model's full text is kept as the body.\r\n */\r\nexport function parseSummary(raw: string, fallbackTitle: string): ParsedSummary | null {\r\n const text = redact(raw).text.trim();\r\n if (text.length === 0) return null;\r\n\r\n const lines = text.split(/\\r?\\n/);\r\n const firstIndex = lines.findIndex((line) => line.trim().length > 0);\r\n if (firstIndex === -1) return null;\r\n\r\n const first = lines[firstIndex]!.trim();\r\n const labelled = /^TITLE:\\s*(.+)$/i.exec(first);\r\n const fallback = truncate(cleanTitle(fallbackTitle) || 'Working session', MAX_TITLE_CHARS);\r\n\r\n // No TITLE line at all means the model wrote prose from the first\r\n // character. Its opening line is then an introduction, not a name for the\r\n // session, so it belongs in the body and nowhere else.\r\n if (!labelled) return { title: fallback, body: text };\r\n\r\n const candidate = cleanTitle(labelled[1]!);\r\n const rest = lines\r\n .slice(firstIndex + 1)\r\n .join('\\n')\r\n .trim();\r\n\r\n return {\r\n title:\r\n candidate.length > 0 && !GENERIC_TITLE.test(candidate) && !ROLE_PREAMBLE_TITLE.test(candidate)\r\n ? truncate(candidate, MAX_TITLE_CHARS)\r\n : fallback,\r\n // A model that emitted only a title still gets a usable node: the title\r\n // doubles as the body rather than storing an empty one.\r\n body: rest.length > 0 ? rest : candidate,\r\n };\r\n}\r\n","import { makeNodeId } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport type { MemoryNode } from '../core/types.js';\r\nimport type { RawConversationTurn } from '../conversation/types.js';\r\nimport type { SummarizationProvider } from '../slm/provider.js';\r\nimport {\r\n buildSessionPrompt,\r\n groupTurnsIntoSessions,\r\n parseSummary,\r\n selectSettledSessions,\r\n sessionFallbackTitle,\r\n type SessionGroup,\r\n} from '../slm/summarize.js';\r\nimport { extractMentionedFiles } from './conversation.js';\r\n\r\nexport const SESSION_SOURCE_PREFIX = 'session';\r\n\r\nexport interface SessionCollectorOptions {\r\n /** Minutes of quiet before a session is considered finished. Default 30. */\r\n settleMinutes?: number;\r\n /** Character budget for one prompt. Default 12000. */\r\n maxPromptChars?: number;\r\n /** Final cap on a stored summary body. Default 2500. */\r\n maxBodyChars?: number;\r\n /** Sessions summarized in one sync. Default 10 -- each is a model call measured in seconds. */\r\n maxSessions?: number;\r\n /** Injected for testing. */\r\n now?: Date;\r\n /**\r\n * Returns the content hash already stored for a session, or null.\r\n *\r\n * The collector asks rather than reads so it stays free of the store: the\r\n * sync layer owns database access, and this stays a pure\r\n * transcripts-plus-model function that a test can drive with a Map.\r\n */\r\n knownHash?: (sessionKey: string) => string | null;\r\n onProgress?: (done: number, total: number) => void;\r\n}\r\n\r\nconst DEFAULT_SETTLE_MINUTES = 30;\r\nconst DEFAULT_MAX_BODY_CHARS = 2500;\r\nconst DEFAULT_MAX_SESSIONS = 10;\r\n\r\nexport interface SessionCollectorResult {\r\n nodes: MemoryNode[];\r\n /** Sessions already summarized at the same content hash, so not re-sent to the model. */\r\n cached: number;\r\n /** Sessions still being worked in, so not eligible yet. */\r\n unsettled: number;\r\n /**\r\n * Eligible sessions left for a later sync because `maxSessions` was\r\n * reached. Distinct from `unsettled`: these are ready and merely queued,\r\n * and without the distinction a first sync of a long-lived repository\r\n * reports a number well below the session count with nothing to explain\r\n * the gap.\r\n */\r\n deferred: number;\r\n /** Sessions the model failed to summarize. */\r\n failed: number;\r\n /** True if the model produced nothing at all -- almost always \"no chat model pulled\". */\r\n providerUnavailable: boolean;\r\n}\r\n\r\n/**\r\n * Signal for a session summary.\r\n *\r\n * Above a typical conversation turn (0.3-0.7) because a summary is the\r\n * distilled form of many turns, and a query that matches it is better served\r\n * by the overview than by one exchange out of forty. Held below the top of\r\n * the commit range so a summary can never outrank the commit that a question\r\n * is literally about. Longer sessions score higher: more exchanges distilled\r\n * into the same budget is a denser node, not a longer one.\r\n */\r\nexport function scoreSession(turnCount: number): number {\r\n const score = 0.6 + Math.min(0.25, turnCount / 100);\r\n return Number(score.toFixed(3));\r\n}\r\n\r\nfunction toNode(\r\n session: SessionGroup,\r\n projectId: string,\r\n summary: { title: string; body: string },\r\n meta: { hash: string; model: string; includedTurns: number },\r\n maxBodyChars: number,\r\n): MemoryNode {\r\n const header = `Session of ${session.startedAt.slice(0, 10)} — ${session.turns.length} exchange(s)`;\r\n const body = `${header}\\n\\n${summary.body}`;\r\n\r\n return {\r\n id: makeNodeId(projectId, 'session_summary', session.sessionKey),\r\n kind: 'session_summary',\r\n projectId,\r\n // The session's end, not its start: a summary describes a finished piece\r\n // of work, and recency ranking should treat it as being as fresh as the\r\n // last thing that happened in it.\r\n ts: session.endedAt,\r\n source: `${SESSION_SOURCE_PREFIX}:${session.source}`,\r\n title: truncate(summary.title, 200),\r\n body: truncate(body, maxBodyChars),\r\n // Drawn from the whole session rather than only the summary: the model\r\n // mentions few paths, but `node_files` is what lets \"why is this file\r\n // like this\" reach the session that explains it.\r\n files: extractMentionedFiles(session.turns.map((t) => `${t.userText}\\n${t.assistantText}`).join('\\n')),\r\n signal: scoreSession(session.turns.length),\r\n meta: {\r\n sessionKey: session.sessionKey,\r\n source: session.source,\r\n turnCount: session.turns.length,\r\n summarizedTurns: meta.includedTurns,\r\n startedAt: session.startedAt,\r\n endedAt: session.endedAt,\r\n cwd: session.cwd,\r\n model: meta.model,\r\n contentHash: meta.hash,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Summarize each finished session into a single node.\r\n *\r\n * This is the one collector that costs real compute, so three things bound\r\n * it: only settled sessions are eligible, a session whose prompt hashes to\r\n * what is already stored is skipped entirely, and no more than\r\n * `maxSessions` reach the model in one sync. The rest wait for the next run.\r\n */\r\nexport async function collectSessionSummaries(\r\n turns: readonly RawConversationTurn[],\r\n projectId: string,\r\n provider: SummarizationProvider,\r\n opts: SessionCollectorOptions = {},\r\n): Promise<SessionCollectorResult> {\r\n const maxBodyChars = opts.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;\r\n const maxSessions = opts.maxSessions ?? DEFAULT_MAX_SESSIONS;\r\n\r\n const all = groupTurnsIntoSessions(turns);\r\n const settled = selectSettledSessions(all, opts.settleMinutes ?? DEFAULT_SETTLE_MINUTES, opts.now);\r\n\r\n const nodes: MemoryNode[] = [];\r\n let cached = 0;\r\n let failed = 0;\r\n let attempted = 0;\r\n\r\n // Newest first: if the cap bites, the sessions a developer is most likely\r\n // to ask about are the ones that got summarized.\r\n const candidates = [...settled].sort((a, b) => b.endedAt.localeCompare(a.endedAt));\r\n const pending: Array<{ session: SessionGroup; prompt: ReturnType<typeof buildSessionPrompt> }> = [];\r\n\r\n for (const session of candidates) {\r\n const prompt = buildSessionPrompt(session, opts.maxPromptChars);\r\n if (opts.knownHash?.(session.sessionKey) === prompt.hash) {\r\n cached += 1;\r\n continue;\r\n }\r\n pending.push({ session, prompt });\r\n }\r\n\r\n for (const { session, prompt } of pending.slice(0, maxSessions)) {\r\n const raw = await provider.complete(prompt.prompt);\r\n attempted += 1;\r\n\r\n const summary = raw === null ? null : parseSummary(raw, sessionFallbackTitle(session));\r\n if (!summary) {\r\n failed += 1;\r\n // Nothing was stored, so the hash was never recorded and the next sync\r\n // retries this session -- a model that was merely busy gets another go.\r\n continue;\r\n }\r\n\r\n nodes.push(\r\n toNode(\r\n session,\r\n projectId,\r\n summary,\r\n { hash: prompt.hash, model: provider.identity, includedTurns: prompt.includedTurns },\r\n maxBodyChars,\r\n ),\r\n );\r\n opts.onProgress?.(nodes.length, Math.min(pending.length, maxSessions));\r\n }\r\n\r\n return {\r\n nodes,\r\n cached,\r\n unsettled: all.length - settled.length,\r\n deferred: Math.max(0, pending.length - maxSessions),\r\n failed,\r\n providerUnavailable: attempted > 0 && nodes.length === 0,\r\n };\r\n}\r\n","import { makeNodeId } from '../core/ids.js';\nimport { truncate } from '../core/text.js';\nimport type { MemoryNode } from '../core/types.js';\nimport type { RawShellEntry } from '../shell/types.js';\n\nexport interface ShellCollectorOptions {\n /** Default 1000 -- commands are short; no need for the 4000-char body cap commits use. */\n maxBodyChars?: number;\n}\n\nconst DEFAULT_MAX_BODY_CHARS = 1000;\nconst MAX_TITLE_CHARS = 200;\n\nconst NOISE = /^(cd|ls|dir|pwd|clear|cls|exit|history|whoami|date|type|cat|more|less|ll|la)\\b/i;\nconst BUILD_TEST =\n /^(npm|pnpm|yarn)\\s+(run\\s+)?(test|build|lint|typecheck|tsc)\\b|^(pytest|go\\s+test|cargo\\s+(test|build)|mvn\\s+test|gradle\\s+test|dotnet\\s+(test|build))\\b/i;\nconst INSTALL = /^(npm|pnpm|yarn)\\s+(install|add|remove|uninstall|ci)\\b|^pip\\s+install\\b|^(cargo\\s+add|go\\s+get|composer\\s+require)\\b/i;\nconst GIT_CMD = /^git\\s+/i;\nconst GIT_DESTRUCTIVE = /^git\\s+(push\\s+.*--force|reset\\s+--hard|clean\\s+-[a-z]*f|branch\\s+-d)\\b/i;\nconst RISKY = /(rm\\s+-rf|remove-item\\s+.*-recurse|del\\s+\\/s|drop\\s+(table|database)|--force\\b|truncate\\s+table)/i;\n\n/**\n * Prior importance by command shape.\n *\n * Kept deliberately coarse: this is a much weaker signal than a commit's\n * conventional-commit type, so scores cluster closer to the middle. `git`\n * commands score low by default because the git collector already captures\n * that history with far richer detail (diff, files, message) -- the shell\n * trace of `git commit -m \"...\"` would just be redundant noise next to it.\n */\nexport function scoreShellCommand(entry: RawShellEntry): number {\n const cmd = entry.command.trim();\n\n let score: number;\n if (NOISE.test(cmd)) score = 0.1;\n else if (RISKY.test(cmd) || GIT_DESTRUCTIVE.test(cmd)) score = 0.75;\n else if (INSTALL.test(cmd)) score = 0.6;\n else if (BUILD_TEST.test(cmd)) score = 0.55;\n else if (GIT_CMD.test(cmd)) score = 0.2;\n else score = 0.35;\n\n // Exit code is only known from the hook; an unknown code leaves score untouched.\n if (entry.exitCode !== null) {\n score += entry.exitCode !== 0 ? 0.25 : 0.05;\n }\n\n if (cmd.length > 60) score += 0.05;\n else if (cmd.length <= 3) score -= 0.05;\n\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\n}\n\nfunction renderBody(entry: RawShellEntry, maxChars: number): string {\n const parts = [`$ ${entry.command}`];\n const metaLine: string[] = [];\n if (entry.cwd) metaLine.push(`cwd: ${entry.cwd}`);\n if (entry.exitCode !== null) metaLine.push(`exit: ${entry.exitCode}`);\n if (entry.durationMs !== null) metaLine.push(`duration: ${entry.durationMs}ms`);\n if (metaLine.length) parts.push('', metaLine.join(' '));\n return truncate(parts.join('\\n'), maxChars);\n}\n\nexport function toMemoryNode(entry: RawShellEntry, projectId: string, opts: ShellCollectorOptions = {}): MemoryNode {\n const maxBody = opts.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;\n const titleLine = entry.command.split(/\\r?\\n/)[0] ?? entry.command;\n\n return {\n id: makeNodeId(projectId, 'shell_command', entry.naturalKey),\n kind: 'shell_command',\n projectId,\n ts: entry.ts,\n source: `shell:${entry.shell}`,\n title: truncate(titleLine, MAX_TITLE_CHARS),\n body: renderBody(entry, maxBody),\n files: [],\n signal: scoreShellCommand(entry),\n meta: {\n command: entry.command,\n cwd: entry.cwd,\n exitCode: entry.exitCode,\n durationMs: entry.durationMs,\n tsApprox: entry.tsApprox,\n shell: entry.shell,\n },\n };\n}\n\nexport function collectShellHistory(\n entries: readonly RawShellEntry[],\n projectId: string,\n opts: ShellCollectorOptions = {},\n): MemoryNode[] {\n return entries.map((entry) => toMemoryNode(entry, projectId, opts));\n}\n","import { readFile } from 'node:fs/promises';\r\nimport { basename } from 'node:path';\r\nimport { listTranscriptFiles } from './paths.js';\r\nimport type { RawConversationTurn } from './types.js';\r\n\r\n/**\r\n * Parses Claude Code's local session transcript format.\r\n *\r\n * This format is internal and undocumented -- it is not a published API,\r\n * just what was observed on disk at `~/.claude/projects/<slug>/*.jsonl`\r\n * (one JSON object per line; `type: 'user' | 'assistant' | ...` records\r\n * threaded by `parentUuid`/`uuid`, message content shaped like the\r\n * Anthropic Messages API). Treat every assumption here as liable to break\r\n * on a Claude Code update: parse defensively, skip what doesn't match\r\n * rather than throw, and never let a shape change break `sync` for\r\n * git/shell.\r\n */\r\n\r\ninterface ContentBlock {\r\n type?: string;\r\n text?: string;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface TranscriptLine {\r\n type?: string;\r\n uuid?: string;\r\n parentUuid?: string | null;\r\n isSidechain?: boolean;\r\n timestamp?: string;\r\n cwd?: string;\r\n message?: {\r\n role?: string;\r\n content?: string | ContentBlock[];\r\n };\r\n}\r\n\r\nfunction parseLine(raw: string): TranscriptLine | null {\r\n const trimmed = raw.trim();\r\n if (!trimmed) return null;\r\n try {\r\n return JSON.parse(trimmed) as TranscriptLine;\r\n } catch {\r\n return null; // a torn write or a record shape we don't recognise -- skip, don't fail sync\r\n }\r\n}\r\n\r\n/** A real human message, not a tool-result being fed back into the model. */\r\nfunction extractUserText(line: TranscriptLine): string | null {\r\n const content = line.message?.content;\r\n if (typeof content === 'string') return content.trim() || null;\r\n\r\n if (Array.isArray(content)) {\r\n if (content.some((b) => b.type === 'tool_result')) return null;\r\n const text = content\r\n .filter((b) => b.type === 'text' && typeof b.text === 'string')\r\n .map((b) => b.text)\r\n .join('\\n')\r\n .trim();\r\n return text || null;\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/** The assistant's prose reply. Tool calls and internal `thinking` are deliberately excluded. */\r\nfunction extractAssistantText(line: TranscriptLine): string {\r\n const content = line.message?.content;\r\n if (!Array.isArray(content)) return '';\r\n return content\r\n .filter((b) => b.type === 'text' && typeof b.text === 'string')\r\n .map((b) => b.text)\r\n .join('\\n')\r\n .trim();\r\n}\r\n\r\nexport interface ParseTranscriptOptions {\r\n source?: string;\r\n /**\r\n * Identifies the session these lines came from. Claude Code names each\r\n * transcript file after its session id, so the caller passes the file's\r\n * basename; nothing inside the records is relied on for this.\r\n */\r\n sessionId?: string;\r\n}\r\n\r\n/**\r\n * Group a transcript into exchanges: one real human message plus every bit\r\n * of assistant prose that follows it, up to the next human message.\r\n *\r\n * Tool calls in between are not part of the exchange's *text* -- they are\r\n * execution detail, already captured with far more structure by the git and\r\n * shell collectors when they matter. Sidechain records (sub-agent work\r\n * spawned via a Task-like tool) are excluded; they are not the primary\r\n * human/assistant dialogue this collector is after.\r\n */\r\nexport function parseClaudeCodeTranscript(raw: string, opts: ParseTranscriptOptions = {}): RawConversationTurn[] {\r\n const source = opts.source ?? 'claude-code';\r\n const sessionKey = `${source}:${opts.sessionId ?? 'unknown'}`;\r\n const turns: RawConversationTurn[] = [];\r\n\r\n let current: { uuid: string; userText: string; ts: string; cwd: string | null; assistantParts: string[] } | null = null;\r\n\r\n const flush = () => {\r\n if (!current) return;\r\n const assistantText = current.assistantParts.join('\\n\\n').trim();\r\n turns.push({\r\n naturalKey: `claude-code:${current.uuid}`,\r\n userText: current.userText,\r\n assistantText,\r\n ts: current.ts,\r\n cwd: current.cwd,\r\n source,\r\n sessionKey,\r\n });\r\n current = null;\r\n };\r\n\r\n for (const rawLine of raw.split(/\\r?\\n/)) {\r\n const line = parseLine(rawLine);\r\n if (!line || line.isSidechain) continue;\r\n\r\n if (line.type === 'user') {\r\n const userText = extractUserText(line);\r\n if (userText === null) continue; // a tool-result record, not a human message\r\n\r\n flush();\r\n current = {\r\n uuid: line.uuid ?? `noid-${turns.length}`,\r\n userText,\r\n ts: line.timestamp ?? new Date(0).toISOString(),\r\n cwd: line.cwd ?? null,\r\n assistantParts: [],\r\n };\r\n continue;\r\n }\r\n\r\n if (line.type === 'assistant' && current) {\r\n const text = extractAssistantText(line);\r\n if (text) current.assistantParts.push(text);\r\n }\r\n }\r\n\r\n flush();\r\n return turns;\r\n}\r\n\r\n/**\r\n * Read every transcript recorded for this repo and parse them all.\r\n *\r\n * Re-reads full files rather than tracking a per-file cursor: exchange\r\n * grouping needs to see a user turn's eventual assistant reply to close it\r\n * out, and a naive line-offset cursor can't guarantee it lands between two\r\n * exchanges rather than inside one. Content-addressed ids (the source's own\r\n * `uuid`) make re-processing a no-op cost at the store layer, same trade\r\n * the shell scrape fallback makes -- simpler and always correct beats\r\n * incremental and occasionally wrong.\r\n */\r\nexport async function collectClaudeCodeTranscripts(repoRoot: string): Promise<RawConversationTurn[]> {\r\n const files = await listTranscriptFiles(repoRoot);\r\n const turns: RawConversationTurn[] = [];\r\n\r\n for (const file of files) {\r\n const raw = await readFile(file, 'utf8');\r\n turns.push(...parseClaudeCodeTranscript(raw, { sessionId: basename(file, '.jsonl') }));\r\n }\r\n\r\n return turns;\r\n}\r\n","import { existsSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Claude Code's own slugging rule for a project path, reverse-engineered\n * from what is actually on disk (undocumented -- see claude-code-reader.ts).\n * `D:\\ai-projects\\NexusMem` -> `D--ai-projects-NexusMem`: every path\n * separator and drive-letter colon becomes `-`; everything else, including\n * hyphens already in folder names, passes through untouched.\n */\nexport function claudeProjectSlug(repoRoot: string): string {\n return repoRoot.replace(/[\\\\/:]/g, '-');\n}\n\nexport function claudeProjectTranscriptDir(repoRoot: string): string {\n return join(homedir(), '.claude', 'projects', claudeProjectSlug(repoRoot));\n}\n\n/** Every session transcript recorded for this repo, oldest-looking name first is not guaranteed -- callers should sort if order matters. */\nexport async function listTranscriptFiles(repoRoot: string): Promise<string[]> {\n const dir = claudeProjectTranscriptDir(repoRoot);\n if (!existsSync(dir)) return [];\n\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile() && e.name.endsWith('.jsonl')).map((e) => join(dir, e.name));\n}\n","import { readFile, stat } from 'node:fs/promises';\r\nimport { join } from 'node:path';\r\nimport { git } from '../git/exec.js';\r\nimport type { RawDocFile } from './types.js';\r\n\r\n/**\r\n * `.md` pathspecs to index by default.\r\n *\r\n * A bare `*.md` is not anchored to the repo root -- git's default (non-glob)\r\n * pathspec matching runs per path component, so it already reaches\r\n * `docs/phase-2-spec.md` as well as the top-level `README.md` (confirmed\r\n * against this repo). One pattern covers both.\r\n */\r\nconst DEFAULT_PATHSPECS = ['*.md'];\r\n\r\nexport interface ListDocFilesOptions {\r\n include?: string[];\r\n}\r\n\r\n/**\r\n * Tracked-only by design: `git ls-files` already excludes `node_modules`,\r\n * `.git` and the self-ignoring `.nexusmem/` workspace (see\r\n * config/workspace.ts) for free, the same way the git collector gets commit\r\n * history without hand-rolled ignore logic.\r\n */\r\nexport async function listDocFiles(repoRoot: string, opts: ListDocFilesOptions = {}): Promise<string[]> {\r\n const pathspecs = opts.include ?? DEFAULT_PATHSPECS;\r\n const out = await git(repoRoot, ['ls-files', '--', ...pathspecs]);\r\n return out\r\n .split('\\n')\r\n .map((line) => line.trim())\r\n .filter(Boolean);\r\n}\r\n\r\nexport interface DocScan {\r\n files: RawDocFile[];\r\n /**\r\n * Tracked paths that could not be read this run.\r\n *\r\n * Reported rather than silently dropped because \"produced no sections\" and\r\n * \"was never looked at\" mean opposite things to a caller that prunes: nodes\r\n * from a file in here must be kept, or one unreadable file would wipe every\r\n * section it had ever contributed.\r\n */\r\n unreadable: string[];\r\n}\r\n\r\nexport async function readDocFiles(repoRoot: string, opts: ListDocFilesOptions = {}): Promise<DocScan> {\r\n const paths = await listDocFiles(repoRoot, opts);\r\n const files: RawDocFile[] = [];\r\n const unreadable: string[] = [];\r\n\r\n for (const relPath of paths) {\r\n const path = relPath.replace(/\\\\/g, '/');\r\n const absPath = join(repoRoot, relPath);\r\n let content: string;\r\n let mtime: Date;\r\n try {\r\n [content, { mtime }] = await Promise.all([readFile(absPath, 'utf8'), stat(absPath)]);\r\n } catch {\r\n // Tracked in git but missing on disk (deleted-but-not-staged, a\r\n // worktree quirk) -- skip rather than fail the whole sync over it.\r\n unreadable.push(path);\r\n continue;\r\n }\r\n\r\n files.push({ path, content, ts: mtime.toISOString() });\r\n }\r\n\r\n return { files, unreadable };\r\n}\r\n","import { existsSync } from 'node:fs';\nimport { readFile, stat } from 'node:fs/promises';\nimport { sha256Hex } from '../core/ids.js';\nimport { readHookLog, type HookLogEntry } from './hook-log.js';\nimport { parseBashHistory } from './parse-bash.js';\nimport { parsePsReadLineHistory } from './parse-psreadline.js';\nimport { parseZshHistory } from './parse-zsh.js';\nimport { bashHistoryPath, hookLogPath, psReadLineHistoryPath, zshHistoryPath } from './paths.js';\nimport type { RawShellEntry } from './types.js';\n\nexport interface ShellSourceResult {\n /** The `shell:<name>` suffix used for both MemoryNode.source and the sync_state key. */\n name: string;\n entries: RawShellEntry[];\n /** Hook source only: how far into the log this read reached, to persist as the next cursor. */\n cursorAfter?: string;\n}\n\nexport interface CollectShellHistoryOptions {\n /** How many lines to keep from scrape-based (non-hook) sources. Default 300. */\n tailLines?: number;\n /** Repo root, for scoping hook-log entries to this project by cwd. */\n repoRoot?: string;\n /** Previous cursor for the hook log (a line count), or null to read from the start. */\n hookCursor?: string | null;\n /**\n * Once the hook is installed, its PowerShell coverage is authoritative --\n * the raw PSReadLine file duplicates the same commands with worse data\n * (no cwd, no exit code) and is skipped. Bash/zsh scraping is unaffected;\n * the hook only covers PowerShell. Default true.\n */\n preferHook?: boolean;\n}\n\nfunction isUnderRoot(cwd: string, root: string): boolean {\n const norm = (p: string) => p.replace(/\\\\/g, '/').replace(/\\/+$/, '').toLowerCase();\n const c = norm(cwd);\n const r = norm(root);\n return c === r || c.startsWith(`${r}/`);\n}\n\nfunction hookEntryToRaw(e: HookLogEntry): RawShellEntry {\n return {\n naturalKey: `pwsh-hook:${e.ts}:${sha256Hex(e.command).slice(0, 12)}`,\n command: e.command,\n ts: e.ts,\n tsApprox: false,\n exitCode: e.exitCode,\n cwd: e.cwd,\n durationMs: e.durationMs,\n shell: 'pwsh-hook',\n };\n}\n\nasync function tryReadScrapeSource(\n path: string,\n parse: (raw: string, mtimeMs: number, opts: { tailLines?: number }) => RawShellEntry[],\n tailLines: number,\n): Promise<RawShellEntry[] | null> {\n if (!existsSync(path)) return null;\n const [raw, stats] = await Promise.all([readFile(path, 'utf8'), stat(path)]);\n return parse(raw, stats.mtimeMs, { tailLines });\n}\n\n/** Enumerate and read every shell-history source available on this machine. */\nexport async function collectAvailableShellHistory(opts: CollectShellHistoryOptions = {}): Promise<ShellSourceResult[]> {\n const results: ShellSourceResult[] = [];\n const tailLines = opts.tailLines ?? 300;\n const preferHook = opts.preferHook ?? true;\n\n const hookPath = hookLogPath();\n const hookExists = existsSync(hookPath);\n\n if (hookExists) {\n const fromLine = Number(opts.hookCursor ?? '0') || 0;\n const { entries, totalLines } = await readHookLog(hookPath, fromLine);\n const scoped = opts.repoRoot ? entries.filter((e) => isUnderRoot(e.cwd, opts.repoRoot!)) : entries;\n results.push({ name: 'pwsh-hook', entries: scoped.map(hookEntryToRaw), cursorAfter: String(totalLines) });\n }\n\n const skipPwshScrape = preferHook && hookExists;\n if (!skipPwshScrape && process.platform === 'win32') {\n const entries = await tryReadScrapeSource(psReadLineHistoryPath(), parsePsReadLineHistory, tailLines);\n if (entries) results.push({ name: 'pwsh', entries });\n }\n\n const bashEntries = await tryReadScrapeSource(bashHistoryPath(), parseBashHistory, tailLines);\n if (bashEntries) results.push({ name: 'bash', entries: bashEntries });\n\n const zshEntries = await tryReadScrapeSource(zshHistoryPath(), parseZshHistory, tailLines);\n if (zshEntries) results.push({ name: 'zsh', entries: zshEntries });\n\n return results;\n}\n","import { appendFile, mkdir, readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\n\n/**\n * One line of the opt-in hook log: a JSONL file the installed shell hook\n * appends to on every command. This is the high-quality tier -- exact\n * timestamp, cwd and exit code, none of which the scrape-based fallbacks can\n * offer.\n */\nexport interface HookLogEntry {\n ts: string;\n cwd: string;\n exitCode: number | null;\n durationMs: number | null;\n command: string;\n}\n\n/** A malformed line (typically a torn write from a crash mid-append) is skipped, not fatal. */\nexport function parseHookLogLine(line: string): HookLogEntry | null {\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n let obj: unknown;\n try {\n obj = JSON.parse(trimmed);\n } catch {\n return null;\n }\n if (typeof obj !== 'object' || obj === null) return null;\n\n const o = obj as Record<string, unknown>;\n if (typeof o.ts !== 'string' || typeof o.cwd !== 'string' || typeof o.command !== 'string') return null;\n\n return {\n ts: o.ts,\n cwd: o.cwd,\n exitCode: typeof o.exitCode === 'number' ? o.exitCode : null,\n durationMs: typeof o.durationMs === 'number' ? o.durationMs : null,\n command: o.command,\n };\n}\n\nexport interface ReadHookLogResult {\n entries: HookLogEntry[];\n /** Total lines currently in the file -- the caller's next cursor. */\n totalLines: number;\n}\n\n/**\n * Read lines appended since `fromLine`.\n *\n * `fromLine` beyond the file's current length means the file was rotated or\n * cleared out from under a stale cursor -- treated the same way a stale git\n * cursor is: fall back to reading everything, rather than silently skipping\n * history that is actually new.\n */\nexport async function readHookLog(path: string, fromLine: number): Promise<ReadHookLogResult> {\n let raw: string;\n try {\n raw = await readFile(path, 'utf8');\n } catch {\n return { entries: [], totalLines: fromLine };\n }\n\n const lines = raw.split(/\\r?\\n/).filter((l) => l.length > 0);\n const slice = fromLine > 0 && fromLine <= lines.length ? lines.slice(fromLine) : lines;\n const entries = slice.map(parseHookLogLine).filter((e): e is HookLogEntry => e !== null);\n\n return { entries, totalLines: lines.length };\n}\n\n/** Append one entry. Exposed for tests; the real writer is the installed PowerShell hook. */\nexport async function appendHookLogEntry(path: string, entry: HookLogEntry): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await appendFile(path, `${JSON.stringify(entry)}\\n`, 'utf8');\n}\n","import { sha256Hex } from '../core/ids.js';\nimport type { RawShellEntry } from './types.js';\nimport type { ScrapeOptions } from './parse-psreadline.js';\n\n/** A `#<epoch>` comment line precedes the command when `HISTTIMEFORMAT` is set. */\nconst EPOCH_COMMENT = /^#(\\d{9,10})$/;\n\n/**\n * Parse `.bash_history`.\n *\n * Real timestamps are used when present (`HISTTIMEFORMAT` is set); otherwise\n * falls back to the same backward-from-mtime approximation as PSReadLine.\n */\nexport function parseBashHistory(raw: string, mtimeMs: number, opts: ScrapeOptions = {}): RawShellEntry[] {\n const lines = raw.split(/\\r?\\n/);\n\n const prelim: Array<{ command: string; ts: string | null }> = [];\n let pendingEpoch: number | null = null;\n\n for (const line of lines) {\n if (line.trim().length === 0) continue;\n\n const m = EPOCH_COMMENT.exec(line.trim());\n if (m) {\n pendingEpoch = Number(m[1]);\n continue;\n }\n\n prelim.push({ command: line, ts: pendingEpoch !== null ? new Date(pendingEpoch * 1000).toISOString() : null });\n pendingEpoch = null;\n }\n\n const tail = opts.tailLines ? prelim.slice(-opts.tailLines) : prelim;\n const startIndex = prelim.length - tail.length;\n\n return tail.map((p, i) => {\n const fromEnd = tail.length - 1 - i;\n const approx = p.ts === null;\n return {\n naturalKey: `bash:${startIndex + i}:${sha256Hex(p.command).slice(0, 12)}`,\n command: p.command,\n ts: p.ts ?? new Date(mtimeMs - fromEnd * 1000).toISOString(),\n tsApprox: approx,\n exitCode: null,\n cwd: null,\n durationMs: null,\n shell: 'bash',\n };\n });\n}\n","import { sha256Hex } from '../core/ids.js';\nimport type { RawShellEntry } from './types.js';\n\nexport interface ScrapeOptions {\n /** Keep only the last N lines. Unbounded by default. */\n tailLines?: number;\n}\n\n/**\n * Parse PowerShell's `ConsoleHost_history.txt`.\n *\n * One command per line, no metadata at all -- not a timestamp, not an exit\n * code, not a cwd. Multi-line entries (a function typed at the prompt) are\n * not reconstructed; each physical line is treated as its own command. Good\n * enough for the common one-liner case, which is nearly all shell history.\n */\nexport function parsePsReadLineHistory(raw: string, mtimeMs: number, opts: ScrapeOptions = {}): RawShellEntry[] {\n const allLines = raw.split(/\\r?\\n/).filter((l) => l.trim().length > 0);\n const tail = opts.tailLines ? allLines.slice(-opts.tailLines) : allLines;\n const startIndex = allLines.length - tail.length;\n\n return tail.map((command, i) => {\n // No per-line timestamp exists, so space entries backward from the\n // file's mtime, one synthetic second apart -- preserves order and keeps\n // recency ranking roughly sane without ever being presented as exact.\n const fromEnd = tail.length - 1 - i;\n return {\n naturalKey: `pwsh:${startIndex + i}:${sha256Hex(command).slice(0, 12)}`,\n command,\n ts: new Date(mtimeMs - fromEnd * 1000).toISOString(),\n tsApprox: true,\n exitCode: null,\n cwd: null,\n durationMs: null,\n shell: 'pwsh',\n };\n });\n}\n","import { sha256Hex } from '../core/ids.js';\nimport type { RawShellEntry } from './types.js';\nimport type { ScrapeOptions } from './parse-psreadline.js';\n\n/** `EXTENDED_HISTORY` format: `: <epoch>:<duration>;<command>`. */\nconst EXTENDED_PREFIX = /^: (\\d+):(\\d+);(.*)$/;\n\n/**\n * Parse `.zsh_history`.\n *\n * Handles the (very common, oh-my-zsh-default) extended-history format with\n * real epoch timestamps, and its backslash-continuation convention for\n * commands typed across multiple physical lines. Falls back to the same\n * backward-from-mtime approximation as PSReadLine when extended history is\n * off.\n */\nexport function parseZshHistory(raw: string, mtimeMs: number, opts: ScrapeOptions = {}): RawShellEntry[] {\n const rawLines = raw.split(/\\r?\\n/);\n const prelim: Array<{ command: string; ts: string | null; durationMs: number | null }> = [];\n\n let i = 0;\n while (i < rawLines.length) {\n const line = rawLines[i] ?? '';\n if (line.trim().length === 0) {\n i += 1;\n continue;\n }\n\n const m = EXTENDED_PREFIX.exec(line);\n let epoch: number | null = null;\n let duration: number | null = null;\n let cmd: string;\n\n if (m) {\n epoch = Number(m[1]);\n duration = Number(m[2]);\n cmd = m[3] ?? '';\n } else {\n cmd = line;\n }\n\n // A trailing backslash means the command continues on the next physical line.\n while (cmd.endsWith('\\\\') && i + 1 < rawLines.length) {\n i += 1;\n cmd = `${cmd.slice(0, -1)}\\n${rawLines[i]}`;\n }\n\n prelim.push({ command: cmd, ts: epoch !== null ? new Date(epoch * 1000).toISOString() : null, durationMs: duration });\n i += 1;\n }\n\n const tail = opts.tailLines ? prelim.slice(-opts.tailLines) : prelim;\n const startIndex = prelim.length - tail.length;\n\n return tail.map((p, idx) => {\n const fromEnd = tail.length - 1 - idx;\n const approx = p.ts === null;\n return {\n naturalKey: `zsh:${startIndex + idx}:${sha256Hex(p.command).slice(0, 12)}`,\n command: p.command,\n ts: p.ts ?? new Date(mtimeMs - fromEnd * 1000).toISOString(),\n tsApprox: approx,\n exitCode: null,\n cwd: null,\n durationMs: p.durationMs,\n shell: 'zsh',\n };\n });\n}\n","import type { Database as DB } from 'better-sqlite3';\nimport { makeNodeId, sha256Hex } from '../core/ids.js';\nimport type { NodeKind } from '../core/types.js';\n\ninterface StoredNodeRow {\n id: string;\n kind: string;\n project_id: string;\n ts: string;\n ts_epoch: number;\n source: string;\n title: string;\n body: string;\n signal: number;\n meta: string;\n created_at: number;\n}\n\ninterface StoredFileRow {\n path: string;\n previous_path: string | null;\n insertions: number | null;\n deletions: number | null;\n is_binary: number;\n}\n\nexport interface ProjectIdReconcileResult {\n oldProjectId: string;\n /** Rows re-inserted under a freshly recomputed id -- genuinely new content. */\n migrated: number;\n /** Rows reassigned to the new project id in place, keeping their existing id. */\n reassigned: number;\n /** Rows dropped because an equivalent row already exists under the new id. */\n deduped: number;\n /** Rows left untouched under the old id -- their natural key can't be reconstructed. */\n skipped: number;\n}\n\nfunction recomputeByNaturalKey(\n db: DB,\n oldProjectId: string,\n newProjectId: string,\n kind: NodeKind,\n source: string | null,\n computeNaturalKey: (row: StoredNodeRow, meta: Record<string, unknown>) => string | null,\n): { migrated: number; deduped: number; skipped: number } {\n const rows = (\n source\n ? db.prepare('SELECT * FROM nodes WHERE project_id = ? AND kind = ? AND source = ?').all(oldProjectId, kind, source)\n : db.prepare('SELECT * FROM nodes WHERE project_id = ? AND kind = ?').all(oldProjectId, kind)\n ) as StoredNodeRow[];\n\n const nodeExists = db.prepare('SELECT 1 FROM nodes WHERE id = ?');\n const insertNode = db.prepare(\n `INSERT INTO nodes (id, kind, project_id, ts, ts_epoch, source, title, body, signal, meta, created_at)\n VALUES (@id, @kind, @projectId, @ts, @tsEpoch, @source, @title, @body, @signal, @meta, @createdAt)`,\n );\n const readFiles = db.prepare('SELECT path, previous_path, insertions, deletions, is_binary FROM node_files WHERE node_id = ?');\n const insertFile = db.prepare(\n `INSERT INTO node_files (node_id, path, previous_path, insertions, deletions, is_binary)\n VALUES (@nodeId, @path, @previousPath, @insertions, @deletions, @isBinary)`,\n );\n const dropEmbedding = db.prepare('DELETE FROM nodes_vec WHERE rowid = (SELECT rowid FROM nodes WHERE id = ?)');\n const deleteNode = db.prepare('DELETE FROM nodes WHERE id = ?');\n\n let migrated = 0;\n let deduped = 0;\n let skipped = 0;\n\n for (const row of rows) {\n let meta: Record<string, unknown>;\n try {\n meta = JSON.parse(row.meta) as Record<string, unknown>;\n } catch {\n skipped += 1;\n continue;\n }\n\n const naturalKey = computeNaturalKey(row, meta);\n if (naturalKey === null) {\n skipped += 1;\n continue;\n }\n\n const newId = makeNodeId(newProjectId, kind, naturalKey);\n\n if (nodeExists.get(newId)) {\n deduped += 1;\n } else {\n insertNode.run({\n id: newId,\n kind: row.kind,\n projectId: newProjectId,\n ts: row.ts,\n tsEpoch: row.ts_epoch,\n source: row.source,\n title: row.title,\n body: row.body,\n signal: row.signal,\n meta: row.meta,\n createdAt: row.created_at,\n });\n for (const file of readFiles.all(row.id) as StoredFileRow[]) {\n insertFile.run({\n nodeId: newId,\n path: file.path,\n previousPath: file.previous_path,\n insertions: file.insertions,\n deletions: file.deletions,\n isBinary: file.is_binary,\n });\n }\n migrated += 1;\n }\n\n dropEmbedding.run(row.id);\n deleteNode.run(row.id); // cascades node_files; nodes_fts cleans itself via its own AFTER DELETE trigger\n }\n\n return { migrated, deduped, skipped };\n}\n\n/**\n * Bring nodes stranded under a previous project id forward to the current one.\n *\n * `makeProjectId` (core/project.ts) is derived from the repo's git remote URL\n * on purpose, so the same repo re-cloned to a new path or machine keeps\n * sharing memory. The case that design doesn't cover is the mirror one: the\n * path stays put but the remote URL changes (a GitHub rename, an org\n * transfer) -- which silently mints a *different* id and strands every node\n * synced under the old one, invisible to every future `status`/`query`/MCP\n * call even though it is still sitting in the same `memory.db` file. Found\n * live 2026-08-15 after this repo's own GitHub account was renamed: 903\n * nodes went dark this way.\n *\n * Only kinds whose original natural key survives in what's already stored\n * are recomputed and re-inserted (`session_summary` via `meta.sessionKey`;\n * hook-sourced `shell_command` via `ts` + `meta.command`, matching\n * `shell/detect.ts`'s `pwsh-hook:${ts}:${sha256(command)}` scheme).\n * `conversation_turn`'s natural key embeds a transcript UUID that is never\n * persisted on the node, so it can't be recomputed -- those rows are instead\n * reassigned to the new project id in place, keeping their existing id.\n *\n * Deliberately NOT migrated:\n * - `git_commit` / `code_diff`: git history is immutable, so a normal `sync`\n * already re-derives every commit and diff under the new id. A stale copy\n * under the old id carries no information a fresh sync doesn't already\n * have, so replaying the id scheme (just the commit sha) buys nothing.\n * - `doc_section`: current file content is always fully rescanned, so a\n * fresh sync already reproduces every section still present in the repo.\n * Migrating would mean replaying `docs.ts`'s slug+occurrence scheme for\n * sections that (empirically, checked live) always turned out to already\n * be present under the new id anyway.\n * - Pre-hook shell scrape sources (`shell:pwsh`, `shell:bash`, `shell:zsh`):\n * their natural key includes a scrape-time list position that was never\n * stored, so it cannot be reconstructed. This is also already-known dead\n * noise the project intends to prune separately -- see nexusmem-constraints\n * in the maintainer's notes -- so leaving it under the now-inert old id is\n * no different in effect from pruning it.\n */\nexport function reconcileProjectId(db: DB, oldProjectId: string, newProjectId: string): ProjectIdReconcileResult {\n return db.transaction((): ProjectIdReconcileResult => {\n const sessions = recomputeByNaturalKey(db, oldProjectId, newProjectId, 'session_summary', null, (_row, meta) =>\n typeof meta.sessionKey === 'string' ? meta.sessionKey : null,\n );\n\n const hookShell = recomputeByNaturalKey(\n db,\n oldProjectId,\n newProjectId,\n 'shell_command',\n 'shell:pwsh-hook',\n (row, meta) =>\n typeof meta.command === 'string' ? `pwsh-hook:${row.ts}:${sha256Hex(meta.command).slice(0, 12)}` : null,\n );\n\n const reassigned = db\n .prepare(`UPDATE nodes SET project_id = ? WHERE project_id = ? AND kind = 'conversation_turn'`)\n .run(newProjectId, oldProjectId).changes;\n\n return {\n oldProjectId,\n migrated: sessions.migrated + hookShell.migrated,\n reassigned,\n deduped: sessions.deduped + hookShell.deduped,\n skipped: sessions.skipped + hookShell.skipped,\n };\n })();\n}\n","import type { MemoryStore } from '../store/store.js';\r\nimport type { EmbeddingProvider } from './embed.js';\r\n\r\n/** `meta` key holding the identity of whatever produced the vectors currently in `nodes_vec`. */\r\nexport const EMBEDDING_IDENTITY_KEY = 'embedding.identity';\r\n\r\nexport interface EmbedPendingResult {\r\n embedded: number;\r\n skipped: number;\r\n /** True if the provider never produced a single vector -- likely means Ollama isn't reachable at all. */\r\n providerUnavailable: boolean;\r\n /** Vectors discarded because the provider that made them is no longer the one in use. */\r\n invalidated: number;\r\n /**\r\n * Nodes still without a vector when the pass ended -- whether because\r\n * `maxNodes` capped it, the provider gave up, or individual texts failed.\r\n * Measured, not inferred, so the CLI never has to guess whether the\r\n * backlog was actually cleared.\r\n */\r\n remaining: number;\r\n}\r\n\r\nexport interface EmbedPendingOptions {\r\n /** Texts per provider request. Default 32. */\r\n batchSize?: number;\r\n /** Rows read from SQLite per page. Default 500. */\r\n pageSize?: number;\r\n /**\r\n * Hard cap on nodes attempted in one pass. Default: none -- a single sync\r\n * drains the whole backlog, which is the point of batching.\r\n */\r\n maxNodes?: number;\r\n /** Consecutive all-failed requests tolerated before giving up. Default 3. */\r\n failureTolerance?: number;\r\n /** Called after each request with cumulative attempts and the backlog size measured at the start. */\r\n onProgress?: (attempted: number, total: number) => void;\r\n /**\r\n * Called before any embedding when a provider change forced the existing\r\n * vectors to be dropped.\r\n *\r\n * A callback rather than just the returned count because the re-embed that\r\n * follows is the longest part of a sync: reporting it afterwards means the\r\n * user watches an unexplained progress bar and only learns the reason once\r\n * it has finished. Observed exactly that way while dogfooding this change.\r\n */\r\n onInvalidated?: (count: number) => void;\r\n}\r\n\r\nconst DEFAULT_BATCH_SIZE = 32;\r\nconst DEFAULT_PAGE_SIZE = 500;\r\nconst DEFAULT_FAILURE_TOLERANCE = 3;\r\n\r\n/**\r\n * Bring the stored vectors and the current provider back into agreement.\r\n *\r\n * Any mismatch invalidates the whole corpus rather than part of it: vectors\r\n * from two providers occupy different spaces, `nodes_vec` records no\r\n * per-row provenance, and a KNN over the mixture returns confident\r\n * nonsense. A corpus with no recorded identity counts as a mismatch -- it\r\n * predates this key, so it was built by the unnormalised `/api/embeddings`\r\n * endpoint (see embed.ts) and is not comparable with anything produced now.\r\n *\r\n * The cost is honest and bounded: nodes are untouched, so a re-embed\r\n * rebuilds what was dropped. It is reported, never silent.\r\n */\r\nfunction reconcileProviderIdentity(store: MemoryStore, provider: EmbeddingProvider): number {\r\n if (store.getMeta(EMBEDDING_IDENTITY_KEY) === provider.identity) return 0;\r\n\r\n const invalidated = store.dropAllEmbeddings();\r\n // Written after the drop, so a crash in between merely repeats a no-op\r\n // drop on the next run rather than leaving stale vectors under a new name.\r\n store.setMeta(EMBEDDING_IDENTITY_KEY, provider.identity);\r\n return invalidated;\r\n}\r\n\r\n/** One request's worth of embeddings, positionally aligned, whether or not the provider can batch. */\r\nasync function embedTexts(provider: EmbeddingProvider, texts: readonly string[]): Promise<(Float32Array | null)[]> {\r\n if (provider.embedBatch) return provider.embedBatch(texts);\r\n\r\n const out: (Float32Array | null)[] = [];\r\n for (const text of texts) out.push(await provider.embed(text));\r\n return out;\r\n}\r\n\r\nfunction chunk<T>(items: readonly T[], size: number): T[][] {\r\n const out: T[][] = [];\r\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\r\n return out;\r\n}\r\n\r\n/**\r\n * Embed every node for a project that doesn't have a vector yet.\r\n *\r\n * A node with no embedding just doesn't participate in vector search --\r\n * BM25 still finds it. This is additive, not a gate: sync always succeeds\r\n * whether or not an embedding provider is available.\r\n *\r\n * Drains the entire backlog by default. It used to stop after 200 nodes,\r\n * which meant a large repository needed several `sync` runs before vector\r\n * search covered it, with nothing in the output saying so. Two things make\r\n * one pass safe to leave uncapped:\r\n *\r\n * - **Paging is monotonic in rowid**, so a node the provider failed on is\r\n * passed over rather than retried forever (see `findNodesNeedingEmbedding`).\r\n * - **A dead provider is detected in seconds, not in timeouts × corpus.**\r\n * `failureTolerance` consecutive all-failed requests end the pass, so an\r\n * Ollama that isn't running costs three requests, not ten thousand.\r\n */\r\nexport async function embedPendingNodes(\r\n store: MemoryStore,\r\n provider: EmbeddingProvider,\r\n projectId: string,\r\n opts: EmbedPendingOptions = {},\r\n): Promise<EmbedPendingResult> {\r\n const batchSize = opts.batchSize ?? DEFAULT_BATCH_SIZE;\r\n const pageSize = opts.pageSize ?? DEFAULT_PAGE_SIZE;\r\n const failureTolerance = opts.failureTolerance ?? DEFAULT_FAILURE_TOLERANCE;\r\n const maxNodes = opts.maxNodes ?? Number.POSITIVE_INFINITY;\r\n\r\n const invalidated = reconcileProviderIdentity(store, provider);\r\n if (invalidated > 0) opts.onInvalidated?.(invalidated);\r\n\r\n const total = Math.min(store.countNodesNeedingEmbedding(projectId), maxNodes);\r\n\r\n let embedded = 0;\r\n let skipped = 0;\r\n let attempted = 0;\r\n let consecutiveFailedRequests = 0;\r\n let cursor = 0;\r\n\r\n outer: while (attempted < maxNodes) {\r\n const page = store.findNodesNeedingEmbedding(projectId, Math.min(pageSize, maxNodes - attempted), cursor);\r\n if (page.length === 0) break;\r\n // Advance before writing anything: the cursor is a position in the walk,\r\n // not a record of success, and must move past failures too.\r\n cursor = page[page.length - 1]!.rowid;\r\n\r\n for (const group of chunk(page, batchSize)) {\r\n const vectors = await embedTexts(\r\n provider,\r\n group.map((node) => `${node.title}\\n${node.body}`),\r\n );\r\n\r\n let embeddedHere = 0;\r\n for (const [index, node] of group.entries()) {\r\n const vector = vectors[index];\r\n if (vector && vector.length === provider.dimension) {\r\n store.upsertEmbedding(node.rowid, vector);\r\n embedded += 1;\r\n embeddedHere += 1;\r\n } else {\r\n skipped += 1;\r\n }\r\n }\r\n\r\n attempted += group.length;\r\n consecutiveFailedRequests = embeddedHere === 0 ? consecutiveFailedRequests + 1 : 0;\r\n opts.onProgress?.(attempted, total);\r\n\r\n if (consecutiveFailedRequests >= failureTolerance) break outer;\r\n }\r\n }\r\n\r\n return {\r\n embedded,\r\n skipped,\r\n // Unchanged meaning: nothing came back at all. Reached far sooner now --\r\n // `failureTolerance` requests instead of the whole first page.\r\n providerUnavailable: attempted > 0 && embedded === 0,\r\n invalidated,\r\n remaining: store.countNodesNeedingEmbedding(projectId),\r\n };\r\n}\r\n","import { makeProjectId } from '../core/project.js';\nimport { readRepoInfo, type RepoInfo } from '../git/repo.js';\nimport { readConfig, resolveWorkspace, type NexusConfig, type Workspace } from '../config/workspace.js';\n\nexport interface CliContext {\n repo: RepoInfo;\n ws: Workspace;\n projectId: string;\n config: NexusConfig;\n}\n\n/**\n * Resolve the repo, its workspace and its config.\n *\n * The project id is always recomputed from the repo rather than trusted from\n * config, so that moving or re-cloning a repo cannot silently split its memory\n * across two namespaces.\n */\nexport async function loadContext(cwd: string): Promise<CliContext> {\n const repo = await readRepoInfo(cwd);\n const ws = resolveWorkspace(repo.root);\n const config = await readConfig(ws);\n return { repo, ws, projectId: makeProjectId({ root: repo.root, originUrl: repo.originUrl }), config };\n}\n","import pc from 'picocolors';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport { renderContextBlock } from '../../retrieval/pack.js';\r\nimport { runCrossProjectQuery, runHybridQuery } from '../../retrieval/query-pipeline.js';\r\nimport { openAllProjectSources } from '../../retrieval/sources.js';\r\nimport { MemoryStore } from '../../store/store.js';\r\nimport { OllamaEmbeddingProvider } from '../../vector/embed.js';\r\nimport { loadContext } from '../context.js';\r\n\r\nexport interface QueryOptions {\r\n cwd: string;\r\n query: string;\r\n /** Token budget for the packed context that gets printed to stdout. */\r\n budget: number;\r\n /** How many FTS/vector candidates to rank/pack from, before the budget is applied. */\r\n candidates: number;\r\n halfLifeDays?: number;\r\n /** Skip embedding the query and vector search entirely -- BM25-only, same as before hybrid retrieval existed. */\r\n noVector?: boolean;\r\n /** Search every registered repository, not just this one. */\r\n allProjects?: boolean;\r\n json: boolean;\r\n}\r\n\r\nexport async function runQuery(opts: QueryOptions): Promise<number> {\r\n const { repo, ws, projectId } = await loadContext(opts.cwd);\r\n\r\n // Exactly one of these owns the database handles: cross-project mode opens\r\n // this repo's database as one source among several, so opening it twice\r\n // would leave a second connection for the same file with nothing to do.\r\n const opened = opts.allProjects\r\n ? await openAllProjectSources({ projectId, root: repo.root, dbPath: ws.dbPath })\r\n : null;\r\n let store: MemoryStore | null = null;\r\n\r\n try {\r\n const queryOpts = {\r\n budget: opts.budget,\r\n candidates: opts.candidates,\r\n halfLifeDays: opts.halfLifeDays,\r\n embeddingProvider: opts.noVector ? null : new OllamaEmbeddingProvider(),\r\n };\r\n\r\n let result;\r\n if (opened) {\r\n result = await runCrossProjectQuery(opened.sources, opts.query, queryOpts);\r\n } else {\r\n store = MemoryStore.open(ws.dbPath);\r\n result = await runHybridQuery(store, projectId, opts.query, queryOpts);\r\n }\r\n const { bm25Count, vectorCount, hits, packed } = result;\r\n\r\n if (opened && !opts.json) {\r\n const searched = opened.sources.map((s) => s.label).join(', ');\r\n process.stderr.write(`${pc.dim('scope ')} ${opened.sources.length} project(s): ${searched}\\n`);\r\n for (const { entry } of opened.unreadable) {\r\n process.stderr.write(`${pc.yellow('unreadable')} ${entry.root} -- skipped\\n`);\r\n }\r\n if (opened.missing.length > 0) {\r\n process.stderr.write(\r\n `${pc.dim('skipped')} ${opened.missing.length} registered project(s) whose database is not on disk` +\r\n ` ${pc.dim('(nexusmem projects --prune to forget them)')}\\n`,\r\n );\r\n }\r\n }\r\n\r\n const matched = hits.length;\r\n\r\n // Packer efficiency: the packed context against the summed raw bodies of\r\n // *the same candidate set*. It measures ranking + budgeted packing\r\n // against its own input, which is what makes it useful for tuning.\r\n //\r\n // Deliberately NOT called \"token saved\": the baseline is hypothetical --\r\n // without NexusMem you'd never have sent these candidate bodies at all,\r\n // so this says nothing about a session's actual token bill. End-to-end\r\n // saving is measured against what the agent would otherwise have read,\r\n // and is a separate number entirely (README § Benchmarks).\r\n //\r\n // Can go negative: for a handful of small matches, fixed per-node\r\n // formatting overhead can outweigh what little there was to trim. The\r\n // efficiency comes from dropping low-score matches entirely once\r\n // candidates exceed the budget, and from truncating large bodies --\r\n // neither has much to work with on a tiny, already-terse result set.\r\n const rawTokens = hits.reduce((n, h) => n + approxTokens(h.body), 0);\r\n const packerEfficiency = rawTokens > 0 ? 1 - packed.tokensUsed / rawTokens : 0;\r\n\r\n if (opts.json) {\r\n process.stdout.write(\r\n `${JSON.stringify(\r\n {\r\n query: opts.query,\r\n matched,\r\n bm25Matched: bm25Count,\r\n vectorMatched: vectorCount,\r\n packed: packed.nodes,\r\n tokensUsed: packed.tokensUsed,\r\n tokensBudget: packed.tokensBudget,\r\n droppedForBudget: packed.droppedForBudget,\r\n droppedForDiversity: packed.droppedForDiversity,\r\n },\r\n null,\r\n 2,\r\n )}\\n`,\r\n );\r\n return 0;\r\n }\r\n\r\n if (matched === 0) {\r\n process.stderr.write(`${pc.yellow('no matches')} for \"${opts.query}\"\\n`);\r\n return 0;\r\n }\r\n\r\n process.stderr.write(\r\n [\r\n `${pc.dim('matched')} ${bm25Count} bm25${vectorCount > 0 ? ` + ${vectorCount} vector` : ''}, packed ${pc.bold(String(packed.nodes.length))} into budget`,\r\n `${pc.dim('tokens ')} ${packed.tokensUsed}/${packed.tokensBudget}` +\r\n (packed.droppedForBudget ? pc.dim(` (${packed.droppedForBudget} dropped for budget)`) : '') +\r\n (packed.droppedForDiversity ? pc.dim(` (${packed.droppedForDiversity} dropped for diversity)`) : ''),\r\n rawTokens > 0\r\n ? `${pc.dim('vs raw ')} ${rawTokens} tokens if these same matches were sent unpacked ${packerEfficiency >= 0 ? pc.green(`(${(packerEfficiency * 100).toFixed(0)}% packer efficiency)`) : pc.yellow(`(${(-packerEfficiency * 100).toFixed(0)}% larger -- overhead dominates on small result sets)`)}`\r\n : '',\r\n '',\r\n ]\r\n .filter(Boolean)\r\n .join('\\n'),\r\n );\r\n\r\n process.stdout.write(`${renderContextBlock(opts.query, packed)}\\n`);\r\n return 0;\r\n } finally {\r\n opened?.close();\r\n store?.close();\r\n }\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectConversationTurns } from '../../collectors/conversation.js';\r\nimport { collectClaudeCodeTranscripts } from '../../conversation/claude-code-reader.js';\r\nimport { claudeProjectTranscriptDir, listTranscriptFiles } from '../../conversation/paths.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { CONVERSATION_SIGNAL_BANDS, formatSignal } from '../format.js';\r\n\r\nexport interface ScanConversationOptions {\r\n cwd: string;\r\n minSignal: number;\r\n json: boolean;\r\n}\r\n\r\nexport async function runScanConversation(opts: ScanConversationOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const files = await listTranscriptFiles(repo.root);\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n files.length\r\n ? `${pc.dim('transcripts')} ${files.length} file(s) in ${claudeProjectTranscriptDir(repo.root)}\\n\\n`\r\n : `${pc.yellow('no transcripts found')} at ${claudeProjectTranscriptDir(repo.root)}\\n`,\r\n );\r\n }\r\n\r\n const turns = await collectClaudeCodeTranscripts(repo.root);\r\n const nodes = collectConversationTurns(turns, projectId).filter((n) => n.signal >= opts.minSignal);\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n for (const node of nodes) process.stdout.write(`${formatNode(node)}\\n`);\r\n\r\n const redactedTotal = nodes.reduce((n, x) => n + (Number(x.meta.redactedCount) || 0), 0);\r\n const approxTotal = nodes.reduce((n, x) => n + approxTokens(x.body), 0);\r\n process.stderr.write(\r\n `\\n${pc.bold(String(nodes.length))} of ${turns.length} exchange(s) above threshold ${pc.dim(`~${approxTotal.toLocaleString()} tokens if sent raw`)}` +\r\n (redactedTotal > 0 ? ` ${pc.yellow(`${redactedTotal} secret-like value(s) redacted`)}` : '') +\r\n '\\n',\r\n );\r\n\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n return [formatSignal(node.signal, CONVERSATION_SIGNAL_BANDS), node.ts.slice(0, 16).replace('T', ' '), node.title].join(' ');\r\n}\r\n","import pc from 'picocolors';\r\n\r\n/**\r\n * Shared rendering for the `scan-*` commands.\r\n *\r\n * These commands all print one line per candidate node, led by its signal, so\r\n * a user can eyeball what a collector would ingest before committing to a\r\n * sync. The signal column previously existed as four near-identical private\r\n * copies, which had already drifted: three graded high signal green while\r\n * `scan-shell` graded it red, so the same column meant opposite things\r\n * depending on which command produced it.\r\n */\r\n\r\n/**\r\n * Cutoffs for the three signal bands, per source.\r\n *\r\n * These stay per-source on purpose. Collectors do not score on a shared\r\n * scale -- a commit's conventional-commit type is much stronger evidence than\r\n * a command's shape, so `scoreShellCommand` clusters near the middle while\r\n * git commits use the full range. One global cutoff would paint every shell\r\n * entry the same color and say nothing.\r\n */\r\nexport interface SignalBands {\r\n /** At or above this, the signal is high for this source. */\r\n high: number;\r\n /** At or above this (but below `high`), middling. */\r\n medium: number;\r\n}\r\n\r\nexport const GIT_SIGNAL_BANDS: SignalBands = { high: 0.7, medium: 0.45 };\r\nexport const SHELL_SIGNAL_BANDS: SignalBands = { high: 0.6, medium: 0.4 };\r\nexport const CONVERSATION_SIGNAL_BANDS: SignalBands = { high: 0.55, medium: 0.35 };\r\nexport const DOCS_SIGNAL_BANDS: SignalBands = { high: 0.55, medium: 0.35 };\r\n/** Same cutoffs as git: a diff's score is anchored on its commit's type, so it lives on the same scale. */\r\nexport const DIFF_SIGNAL_BANDS: SignalBands = { high: 0.7, medium: 0.45 };\r\n\r\nexport type SignalBand = 'high' | 'medium' | 'low';\r\n\r\n/**\r\n * Which band a signal falls in, given its source's cutoffs.\r\n *\r\n * Split out from the coloring so the threshold logic is assertable: under a\r\n * non-TTY test runner picocolors emits no escape codes, which would make a\r\n * test of the rendered string blind to exactly the kind of divergence this\r\n * module exists to prevent.\r\n */\r\nexport function signalBand(signal: number, bands: SignalBands): SignalBand {\r\n if (signal >= bands.high) return 'high';\r\n if (signal >= bands.medium) return 'medium';\r\n return 'low';\r\n}\r\n\r\n/**\r\n * The one place a band becomes a color.\r\n *\r\n * Being a single map is the actual fix for the drift: a per-source palette is\r\n * now unrepresentable rather than merely discouraged, so \"high is green\" holds\r\n * for every command by construction. Thresholds vary by source; the color\r\n * language does not.\r\n */\r\nconst BAND_COLOR: Record<SignalBand, (s: string) => string> = {\r\n high: pc.green,\r\n medium: pc.yellow,\r\n low: pc.dim,\r\n};\r\n\r\n/** A node's signal as a fixed-width, color-graded figure. */\r\nexport function formatSignal(signal: number, bands: SignalBands): string {\r\n return BAND_COLOR[signalBand(signal, bands)](signal.toFixed(2));\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectCommitDiffs } from '../../collectors/diffs.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { DIFF_SIGNAL_BANDS, formatSignal } from '../format.js';\r\nimport { summarize } from './scan-git.js';\r\n\r\nexport interface ScanDiffOptions {\r\n cwd: string;\r\n since?: string;\r\n /** Commits to walk, not nodes to emit -- one commit yields one node per changed file. */\r\n limit?: number;\r\n json: boolean;\r\n minSignal: number;\r\n}\r\n\r\nconst DEFAULT_SCAN_COMMITS = 50;\r\n\r\nexport async function runScanDiff(opts: ScanDiffOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n [\r\n `${pc.dim('repo ')} ${repo.root}`,\r\n `${pc.dim('branch ')} ${repo.branch ?? pc.yellow('(detached)')}`,\r\n `${pc.dim('project')} ${pc.cyan(projectId)}`,\r\n '',\r\n ].join('\\n'),\r\n );\r\n }\r\n\r\n const nodes: MemoryNode[] = [];\r\n\r\n for await (const node of collectCommitDiffs(repo.root, projectId, {\r\n since: opts.since ?? null,\r\n maxCount: opts.limit ?? DEFAULT_SCAN_COMMITS,\r\n })) {\r\n if (node.signal < opts.minSignal) continue;\r\n nodes.push(node);\r\n if (!opts.json) process.stdout.write(`${formatNode(node)}\\n`);\r\n }\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n // The same summary the other scan commands print, from the same helper --\r\n // \"~N tokens if sent raw\" has to mean one thing across all of them.\r\n process.stderr.write(`\\n${summarize(nodes)}\\n`);\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n const sha = String(node.meta.shortSha ?? '').padEnd(9);\r\n const churn = `+${node.meta.insertions ?? 0}/-${node.meta.deletions ?? 0}`;\r\n return [\r\n formatSignal(node.signal, DIFF_SIGNAL_BANDS),\r\n pc.dim(node.ts.slice(0, 10)),\r\n pc.magenta(sha),\r\n String(node.meta.path ?? ''),\r\n pc.dim(`(${churn}, ${node.meta.hunkCount ?? 0} hunk(s))`),\r\n ].join(' ');\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectGitCommits } from '../../collectors/git-commits.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { formatSignal, GIT_SIGNAL_BANDS } from '../format.js';\r\n\r\nexport interface ScanGitOptions {\r\n cwd: string;\r\n since?: string;\r\n limit?: number;\r\n merges: boolean;\r\n json: boolean;\r\n minSignal: number;\r\n}\r\n\r\nexport async function runScanGit(opts: ScanGitOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n [\r\n `${pc.dim('repo ')} ${repo.root}`,\r\n `${pc.dim('branch ')} ${repo.branch ?? pc.yellow('(detached)')}`,\r\n `${pc.dim('origin ')} ${repo.originUrl ?? pc.dim('(none)')}`,\r\n `${pc.dim('project')} ${pc.cyan(projectId)}`,\r\n '',\r\n ].join('\\n'),\r\n );\r\n }\r\n\r\n const nodes: MemoryNode[] = [];\r\n const collectOpts = {\r\n since: opts.since ?? null,\r\n maxCount: opts.limit ?? null,\r\n includeMerges: opts.merges,\r\n };\r\n\r\n for await (const node of collectGitCommits(repo.root, projectId, collectOpts)) {\r\n if (node.signal < opts.minSignal) continue;\r\n nodes.push(node);\r\n if (!opts.json) process.stdout.write(`${formatNode(node)}\\n`);\r\n }\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n process.stderr.write(`\\n${summarize(nodes)}\\n`);\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n const sha = String(node.meta.shortSha ?? '').padEnd(9);\r\n const date = node.ts.slice(0, 10);\r\n const files = Number(node.meta.filesChanged ?? 0);\r\n const churn = `+${node.meta.insertions ?? 0}/-${node.meta.deletions ?? 0}`;\r\n return [\r\n formatSignal(node.signal, GIT_SIGNAL_BANDS),\r\n pc.dim(date),\r\n pc.magenta(sha),\r\n node.title,\r\n pc.dim(`(${files} file${files === 1 ? '' : 's'}, ${churn})`),\r\n ].join(' ');\r\n}\r\n\r\n/** Exported for tests: the token total it reports must match every other scan command's. */\r\nexport function summarize(nodes: MemoryNode[]): string {\r\n if (nodes.length === 0) return pc.yellow('no commits matched');\r\n\r\n const timestamps = nodes.map((n) => n.ts).sort();\r\n const avgSignal = nodes.reduce((n, x) => n + x.signal, 0) / nodes.length;\r\n // The shared helper, not a local re-derivation: every scan command prints\r\n // this same \"tokens if sent raw\" figure, so they must all count it alike.\r\n const totalTokens = nodes.reduce((n, x) => n + approxTokens(x.body), 0);\r\n\r\n const fileHits = new Map<string, number>();\r\n for (const node of nodes) {\r\n for (const f of node.files) fileHits.set(f.path, (fileHits.get(f.path) ?? 0) + 1);\r\n }\r\n const hottest = [...fileHits.entries()]\r\n .sort((a, b) => b[1] - a[1])\r\n .slice(0, 5)\r\n .map(([path, count]) => ` ${String(count).padStart(3)}x ${path}`);\r\n\r\n return [\r\n `${pc.bold(String(nodes.length))} nodes ${pc.dim(`${timestamps[0]?.slice(0, 10)} .. ${timestamps.at(-1)?.slice(0, 10)}`)}`,\r\n ` avg signal ${avgSignal.toFixed(3)} ~${totalTokens.toLocaleString()} tokens if sent raw`,\r\n hottest.length ? ` hottest files:\\n${hottest.join('\\n')}` : '',\r\n ]\r\n .filter(Boolean)\r\n .join('\\n');\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectDocFiles } from '../../collectors/docs.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readDocFiles } from '../../docs/read.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { DOCS_SIGNAL_BANDS, formatSignal } from '../format.js';\r\n\r\nexport interface ScanDocsOptions {\r\n cwd: string;\r\n minSignal: number;\r\n json: boolean;\r\n}\r\n\r\nexport async function runScanDocs(opts: ScanDocsOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const { files, unreadable } = await readDocFiles(repo.root);\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n files.length\r\n ? `${pc.dim('tracked .md files')} ${files.map((f) => f.path).join(', ')}\\n\\n`\r\n : `${pc.yellow('no tracked .md files found')}\\n`,\r\n );\r\n if (unreadable.length > 0) {\r\n process.stderr.write(`${pc.yellow('unreadable')} ${unreadable.join(', ')}\\n\\n`);\r\n }\r\n }\r\n\r\n const nodes = collectDocFiles(files, projectId).filter((n) => n.signal >= opts.minSignal);\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n for (const node of nodes) process.stdout.write(`${formatNode(node)}\\n`);\r\n\r\n const approxTotal = nodes.reduce((n, x) => n + approxTokens(x.body), 0);\r\n process.stderr.write(\r\n `\\n${pc.bold(String(nodes.length))} section(s) from ${files.length} file(s) above threshold ${pc.dim(`~${approxTotal.toLocaleString()} tokens if sent raw`)}\\n`,\r\n );\r\n\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n return [formatSignal(node.signal, DOCS_SIGNAL_BANDS), node.title].join(' ');\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectSessionSummaries } from '../../collectors/sessions.js';\r\nimport { collectClaudeCodeTranscripts } from '../../conversation/claude-code-reader.js';\r\nimport { claudeProjectTranscriptDir } from '../../conversation/paths.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { DEFAULT_SLM_MODEL, OllamaChatProvider } from '../../slm/provider.js';\r\nimport { buildSessionPrompt, groupTurnsIntoSessions, selectSettledSessions } from '../../slm/summarize.js';\r\n\r\nexport interface ScanSessionOptions {\r\n cwd: string;\r\n model: string;\r\n settleMinutes: number;\r\n maxSessions: number;\r\n /** Show what would be sent to the model, and send nothing. */\r\n dryRun: boolean;\r\n json: boolean;\r\n}\r\n\r\n/**\r\n * Preview session summarization without writing anything to the database.\r\n *\r\n * `--dry-run` is the interesting mode: it prints the prompt each session\r\n * would produce, which is the only way to see what the model is actually\r\n * being shown after redaction and budget trimming.\r\n */\r\nexport async function runScanSession(opts: ScanSessionOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const turns = await collectClaudeCodeTranscripts(repo.root);\r\n if (turns.length === 0) {\r\n process.stderr.write(`${pc.yellow('no transcripts found')} at ${claudeProjectTranscriptDir(repo.root)}\\n`);\r\n return 0;\r\n }\r\n\r\n const sessions = groupTurnsIntoSessions(turns);\r\n const settled = selectSettledSessions(sessions, opts.settleMinutes);\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n `${pc.dim('sessions')} ${sessions.length} found, ${settled.length} settled (quiet ${opts.settleMinutes}m+)\\n\\n`,\r\n );\r\n }\r\n\r\n if (opts.dryRun) {\r\n const previews = settled.slice(0, opts.maxSessions).map((session) => {\r\n const { prompt, hash, includedTurns } = buildSessionPrompt(session);\r\n return {\r\n sessionKey: session.sessionKey,\r\n startedAt: session.startedAt,\r\n endedAt: session.endedAt,\r\n turns: session.turns.length,\r\n includedTurns,\r\n hash,\r\n promptChars: prompt.length,\r\n prompt,\r\n };\r\n });\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(previews, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n for (const preview of previews) {\r\n process.stdout.write(\r\n `${pc.bold(preview.sessionKey)} ${preview.startedAt.slice(0, 16).replace('T', ' ')} ` +\r\n `${preview.includedTurns}/${preview.turns} turn(s), ${preview.promptChars} chars\\n${preview.prompt}\\n\\n`,\r\n );\r\n }\r\n return 0;\r\n }\r\n\r\n const result = await collectSessionSummaries(turns, projectId, new OllamaChatProvider({ model: opts.model }), {\r\n settleMinutes: opts.settleMinutes,\r\n maxSessions: opts.maxSessions,\r\n onProgress: (done, total) => {\r\n if (!opts.json) process.stderr.write(` ${pc.dim(`summarizing ${done}/${total}`)}\\n`);\r\n },\r\n });\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(result.nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n for (const node of result.nodes) {\r\n process.stdout.write(`${pc.bold(node.title)}\\n${pc.dim(node.ts.slice(0, 16).replace('T', ' '))}\\n${node.body}\\n\\n`);\r\n }\r\n\r\n if (result.providerUnavailable) {\r\n process.stderr.write(\r\n `${pc.yellow('model unavailable')} -- is Ollama running with \\`${opts.model}\\` pulled? (\\`ollama pull ${opts.model}\\`)\\n`,\r\n );\r\n return 0;\r\n }\r\n\r\n process.stderr.write(\r\n `${pc.bold(String(result.nodes.length))} summarized` +\r\n (result.failed > 0 ? `, ${pc.yellow(`${result.failed} failed`)}` : '') +\r\n ` ${pc.dim(`(model ${opts.model})`)}\\n`,\r\n );\r\n return 0;\r\n}\r\n\r\nexport const SCAN_SESSION_DEFAULT_MODEL = DEFAULT_SLM_MODEL;\r\n","import pc from 'picocolors';\r\nimport { collectShellHistory } from '../../collectors/shell-history.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { collectAvailableShellHistory } from '../../shell/detect.js';\r\nimport { formatSignal, SHELL_SIGNAL_BANDS } from '../format.js';\r\n\r\nexport interface ScanShellOptions {\r\n cwd: string;\r\n tailLines: number;\r\n minSignal: number;\r\n json: boolean;\r\n}\r\n\r\nexport async function runScanShell(opts: ScanShellOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const results = await collectAvailableShellHistory({ tailLines: opts.tailLines, repoRoot: repo.root });\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n results.length\r\n ? `${pc.dim('sources found')} ${results.map((r) => r.name).join(', ')}\\n\\n`\r\n : `${pc.yellow('no shell history source found on this machine')}\\n`,\r\n );\r\n }\r\n\r\n const allNodes: MemoryNode[] = [];\r\n for (const result of results) {\r\n const nodes = collectShellHistory(result.entries, projectId).filter((n) => n.signal >= opts.minSignal);\r\n allNodes.push(...nodes);\r\n\r\n if (!opts.json) {\r\n process.stdout.write(`${pc.bold(`shell:${result.name}`)} ${pc.dim(`(${nodes.length} of ${result.entries.length} above threshold)`)}\\n`);\r\n for (const node of nodes) process.stdout.write(`${formatNode(node)}\\n`);\r\n process.stdout.write('\\n');\r\n }\r\n }\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(allNodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n const approxTotal = allNodes.reduce((n, x) => n + approxTokens(x.body), 0);\r\n process.stderr.write(`${pc.bold(String(allNodes.length))} node(s) total ${pc.dim(`~${approxTotal.toLocaleString()} tokens if sent raw`)}\\n`);\r\n\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n const approx = node.meta.tsApprox ? pc.dim('~') : ' ';\r\n const exit = node.meta.exitCode;\r\n // Red is reserved for the failure itself. The signal column grades\r\n // importance, not danger, and reads green-for-high like every other\r\n // `scan-*` command -- see cli/format.ts.\r\n const exitLabel = typeof exit === 'number' && exit !== 0 ? pc.red(`exit ${exit}`) : '';\r\n return [formatSignal(node.signal, SHELL_SIGNAL_BANDS), approx + node.ts.slice(0, 16).replace('T', ' '), node.title, exitLabel]\r\n .filter(Boolean)\r\n .join(' ');\r\n}\r\n","import { statSync } from 'node:fs';\nimport pc from 'picocolors';\nimport { MemoryStore } from '../../store/store.js';\nimport { currentSchemaVersion, LATEST_SCHEMA_VERSION } from '../../store/schema.js';\nimport { loadContext } from '../context.js';\n\nexport interface StatusOptions {\n cwd: string;\n}\n\nfunction humanBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nfunction fileSize(path: string): number {\n try {\n return statSync(path).size;\n } catch {\n return 0;\n }\n}\n\nexport async function runStatus(opts: StatusOptions): Promise<number> {\n const { repo, ws, projectId } = await loadContext(opts.cwd);\n const store = MemoryStore.open(ws.dbPath);\n\n try {\n const stats = store.stats(projectId);\n const sources = store.listSyncState(projectId);\n const gitCursor = sources.find((s) => s.source === 'git')?.cursor ?? null;\n const schema = currentSchemaVersion(store.raw);\n\n // WAL content counts towards what is actually on disk.\n const dbBytes = fileSize(ws.dbPath) + fileSize(`${ws.dbPath}-wal`);\n\n const kinds = Object.entries(stats.byKind)\n .sort((a, b) => b[1] - a[1])\n .map(([kind, n]) => ` ${String(n).padStart(6)} ${kind}`);\n\n process.stdout.write(\n [\n `${pc.dim('repo ')} ${repo.root}`,\n `${pc.dim('branch ')} ${repo.branch ?? pc.yellow('(detached)')}`,\n `${pc.dim('project ')} ${pc.cyan(projectId)}`,\n `${pc.dim('schema ')} v${schema}${schema === LATEST_SCHEMA_VERSION ? '' : pc.yellow(` (latest is v${LATEST_SCHEMA_VERSION})`)}`,\n `${pc.dim('database')} ${ws.dbPath} ${pc.dim(`(${humanBytes(dbBytes)})`)}`,\n '',\n `${pc.bold(String(stats.total))} node(s)${stats.total ? ` ${pc.dim(`${stats.oldest?.slice(0, 10)} .. ${stats.newest?.slice(0, 10)}`)}` : ''}`,\n ...kinds,\n stats.total ? ` ${pc.dim(`${stats.distinctFiles} distinct file path(s)`)}` : '',\n '',\n sources.length ? pc.dim('sources') : pc.yellow('no sources synced yet'),\n ...sources.map((s) => {\n const when = s.lastRunAt ? new Date(s.lastRunAt).toISOString().slice(0, 16).replace('T', ' ') : 'never';\n const cursorLabel = s.source === 'git' ? (s.cursor?.slice(0, 7) ?? '-') : (s.cursor ?? '-');\n return ` ${s.source.padEnd(14)} ${pc.dim(`last run ${when}`)} ${pc.dim(`cursor ${cursorLabel}`)}`;\n }),\n gitCursor && gitCursor !== repo.head ? `${pc.yellow('git behind HEAD')} — run ${pc.bold('nexusmem sync')}` : '',\n '',\n ]\n .filter((line) => line !== '')\n .join('\\n')\n .concat('\\n'),\n );\n\n return 0;\n } finally {\n store.close();\n }\n}\n"],"mappings":"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","names":["pc","resolve","chunk","code","signal","mkdir","readFile","writeFile","homedir","join","join","join","homedir","readFile","mkdir","writeFile","pc","existsSync","mkdir","readFile","writeFile","join","z","z","join","readFile","existsSync","mkdir","writeFile","dirname","dirname","chunk","pc","pc","pc","z","basename","DEFAULT_BASE_URL","DEFAULT_TIMEOUT_MS","pc","chunk","FIELD_COUNT","chunk","EMPTY_HISTORY","chunk","MAX_TITLE_CHARS","DEFAULTS","MAX_TITLE_CHARS","byChurnDesc","toMemoryNodes","DEFAULT_MAX_BODY_CHARS","DEFAULT_MAX_CHUNK_CHARS","MAX_TITLE_CHARS","EXPLANATION_MARKERS","toMemoryNodes","chunk","MAX_TITLE_CHARS","DEFAULT_MAX_BODY_CHARS","DEFAULT_MAX_BODY_CHARS","MAX_TITLE_CHARS","toMemoryNode","readFile","basename","existsSync","homedir","join","readFile","basename","readFile","join","join","readFile","existsSync","readFile","stat","mkdir","readFile","dirname","existsSync","readFile","stat","pc","chunk","git","basename","chunk","z","pc","pc","pc","pc","pc","pc","pc","pc","formatNode","summarize","pc","formatNode","summarize","pc","pc","formatNode","pc","pc","pc","pc","formatNode","pc","pc","pc"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/config/workspace.ts","../../src/slm/provider.ts","../../src/core/version.ts","../../src/git/exec.ts","../../src/git/repo.ts","../../src/hooks/install.ts","../../src/shell/paths.ts","../../src/config/paths.ts","../../src/hooks/powershell.ts","../../src/cli/commands/hook.ts","../../src/cli/commands/init.ts","../../src/config/registry.ts","../../src/core/ids.ts","../../src/core/project.ts","../../src/store/store.ts","../../src/store/fts.ts","../../src/store/schema.ts","../../src/cli/commands/projects.ts","../../src/mcp/server.ts","../../src/mcp/tools.ts","../../src/core/text.ts","../../src/retrieval/pack.ts","../../src/correlate/failure-fix.ts","../../src/retrieval/fuse.ts","../../src/retrieval/rank.ts","../../src/retrieval/query-pipeline.ts","../../src/retrieval/sources.ts","../../src/vector/embed.ts","../../src/cli/commands/sync.ts","../../src/conversation/chunk.ts","../../src/conversation/redact.ts","../../src/collectors/conversation.ts","../../src/git/parse.ts","../../src/git/diff.ts","../../src/git/log.ts","../../src/collectors/git-commits.ts","../../src/collectors/diffs.ts","../../src/collectors/docs.ts","../../src/slm/summarize.ts","../../src/collectors/sessions.ts","../../src/collectors/shell-history.ts","../../src/conversation/claude-code-reader.ts","../../src/conversation/paths.ts","../../src/docs/read.ts","../../src/shell/detect.ts","../../src/shell/hook-log.ts","../../src/shell/parse-bash.ts","../../src/shell/parse-psreadline.ts","../../src/shell/parse-zsh.ts","../../src/store/reconcile.ts","../../src/vector/sync.ts","../../src/cli/context.ts","../../src/cli/commands/query.ts","../../src/cli/commands/scan-conversation.ts","../../src/cli/format.ts","../../src/cli/commands/scan-diff.ts","../../src/cli/commands/scan-git.ts","../../src/cli/commands/scan-docs.ts","../../src/cli/commands/scan-session.ts","../../src/cli/commands/scan-shell.ts","../../src/cli/commands/status.ts"],"sourcesContent":["import { Command } from 'commander';\r\nimport pc from 'picocolors';\r\nimport { ConfigError } from '../config/workspace.js';\r\nimport { readOwnVersion } from '../core/version.js';\r\nimport { GitCrashError, GitSpawnError } from '../git/exec.js';\r\nimport { NotAGitRepositoryError } from '../git/repo.js';\r\nimport { ProfileNotFoundError } from '../hooks/install.js';\r\nimport { runHookInstall, runHookRemove, runHookStatus } from './commands/hook.js';\r\nimport { runInit } from './commands/init.js';\r\nimport { runProjects } from './commands/projects.js';\r\nimport { runMcpServer } from '../mcp/server.js';\r\nimport { runQuery } from './commands/query.js';\r\nimport { runScanConversation } from './commands/scan-conversation.js';\r\nimport { runScanDiff } from './commands/scan-diff.js';\r\nimport { runScanDocs } from './commands/scan-docs.js';\r\nimport { runScanGit } from './commands/scan-git.js';\r\nimport { runScanSession, SCAN_SESSION_DEFAULT_MODEL } from './commands/scan-session.js';\r\nimport { runScanShell } from './commands/scan-shell.js';\r\nimport { runStatus } from './commands/status.js';\r\nimport { runSync } from './commands/sync.js';\r\n\r\n/** Failures the user can act on, reported without a stack trace. */\r\nfunction isExpected(err: unknown): err is Error {\r\n return (\r\n err instanceof NotAGitRepositoryError ||\r\n // \"git isn't installed\" and \"the spawn failed, try again\" are both things\r\n // the user fixes, not stack traces they debug.\r\n err instanceof GitSpawnError ||\r\n // Survived every retry, so git is genuinely unstable on this machine\r\n // (antivirus, a bad install). Actionable, and not our stack to print.\r\n err instanceof GitCrashError ||\r\n err instanceof ConfigError ||\r\n err instanceof ProfileNotFoundError\r\n );\r\n}\r\n\r\n/** Wrap a command so expected failures exit 1 with a clean message. */\r\nfunction guard(run: () => Promise<number>): () => Promise<void> {\r\n return async () => {\r\n try {\r\n process.exitCode = await run();\r\n } catch (err) {\r\n if (isExpected(err)) {\r\n process.stderr.write(`${pc.red('error')} ${err.message}\\n`);\r\n process.exitCode = 1;\r\n return;\r\n }\r\n throw err;\r\n }\r\n };\r\n}\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('nexusmem')\r\n .description('NexusMem — local-first persistent memory for AI coding agents')\r\n .version(readOwnVersion());\r\n\r\nprogram\r\n .command('init')\r\n .description('Create the .nexusmem workspace and database for this repository')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--force', 'overwrite an existing config (the database is kept)', false)\r\n .option('--hook', 'also install the opt-in PowerShell hook (cwd + exit code + timestamp)', false)\r\n .option('--enable-conversation', 'opt in to the conversation-transcript source (off by default -- see docs/phase-2-spec.md)', false)\r\n .action((options) =>\r\n guard(() =>\r\n runInit({ cwd: options.cwd, force: options.force, hook: options.hook, enableConversation: options.enableConversation }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('sync')\r\n .description('Ingest new history into the local database')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--full', 'ignore the stored cursor and re-walk all history', false)\r\n .option('--rebuild', 'drop this project\\'s nodes and re-ingest from scratch', false)\r\n .option('--since <date>', 'override the configured git cutoff, e.g. 1.year.ago')\r\n .option('--shell-lines <count>', 'override the configured shell tail-window size', (v) => Number.parseInt(v, 10))\r\n .option('--conversation', 'force the conversation source on for this run, without persisting it to config', false)\r\n .option('--no-embed', 'skip the vector-embedding pass for this run')\r\n .option('--embed-limit <count>', 'stop embedding after this many nodes (default: embed everything pending)', (v) =>\r\n Number.parseInt(v, 10),\r\n )\r\n .option('--prune-source <name>', 'delete every node from this exact source (e.g. shell:pwsh) instead of syncing -- dry-run unless --yes is also given')\r\n .option(\r\n '--prune-stale-shell',\r\n 'shortcut for --prune-source on shell:pwsh, shell:bash and shell:zsh at once -- the dead pre-hook scrape sources -- dry-run unless --yes is also given',\r\n false,\r\n )\r\n .option('--yes', 'confirm an irreversible --prune-source/--prune-stale-shell delete', false)\r\n .option(\r\n '--link-failures',\r\n 'opt-in (experimental): after ingest, link failed shell commands to whatever later resolved them',\r\n false,\r\n )\r\n .option('-q, --quiet', 'only print the final summary', false)\r\n .action((options) =>\r\n guard(() =>\r\n runSync({\r\n cwd: options.cwd,\r\n full: options.full,\r\n rebuild: options.rebuild,\r\n since: options.since,\r\n shellTailLines: options.shellLines,\r\n conversationOverride: options.conversation ? true : undefined,\r\n noEmbed: !options.embed,\r\n embedLimit: options.embedLimit,\r\n pruneSource: options.pruneSource,\r\n pruneStaleShell: options.pruneStaleShell,\r\n yes: options.yes,\r\n linkFailures: options.linkFailures,\r\n quiet: options.quiet,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('hook')\r\n .description('Manage the opt-in PowerShell hook that logs cwd + exit code + timestamp')\r\n .addCommand(\r\n new Command('install')\r\n .description('Install (or update) the hook in your PowerShell profile')\r\n .option('--profile <path>', 'override the auto-detected $PROFILE path')\r\n .action((options) => guard(() => runHookInstall({ profile: options.profile }))()),\r\n )\r\n .addCommand(\r\n new Command('remove')\r\n .description('Remove the hook block from your PowerShell profile')\r\n .option('--profile <path>', 'override the auto-detected $PROFILE path')\r\n .action((options) => guard(() => runHookRemove({ profile: options.profile }))()),\r\n )\r\n .addCommand(\r\n new Command('status')\r\n .description('Show whether the hook is installed')\r\n .option('--profile <path>', 'override the auto-detected $PROFILE path')\r\n .action((options) => guard(() => runHookStatus({ profile: options.profile }))()),\r\n );\r\n\r\nprogram\r\n .command('status')\r\n .description('Show what is currently remembered for this repository')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .action((options) => guard(() => runStatus({ cwd: options.cwd }))());\r\n\r\nprogram\r\n .command('query')\r\n .description('Search remembered history and print a token-budgeted context block')\r\n .argument('<text>', 'free-text query')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('-b, --budget <tokens>', 'max tokens in the packed context', (v) => Number.parseInt(v, 10), 2000)\r\n .option('-n, --candidates <count>', 'how many search hits to rank before packing', (v) => Number.parseInt(v, 10), 30)\r\n .option('--half-life <days>', 'days for a node\\'s recency weight to halve', (v) => Number.parseFloat(v))\r\n .option('--no-vector', 'BM25 only -- skip embedding the query and vector search')\r\n .option('-a, --all-projects', 'search every registered repository, not just this one', false)\r\n .option('--json', 'emit the packed result as JSON on stdout', false)\r\n .action((text: string, options) =>\r\n guard(() =>\r\n runQuery({\r\n cwd: options.cwd,\r\n query: text,\r\n budget: options.budget,\r\n candidates: options.candidates,\r\n halfLifeDays: options.halfLife,\r\n noVector: !options.vector,\r\n allProjects: options.allProjects,\r\n json: options.json,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('projects')\r\n .description('List the repositories `query --all-projects` would search')\r\n .option('--prune', 'forget registered projects whose database is no longer on disk', false)\r\n .option('--json', 'emit the registry as JSON on stdout', false)\r\n .action((options) => guard(() => runProjects({ prune: options.prune, json: options.json }))());\r\n\r\nprogram\r\n .command('scan-git')\r\n .description('Preview the MemoryNodes git history would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--since <date>', 'only commits newer than this git date expression, e.g. 90.days.ago')\r\n .option('-n, --limit <count>', 'stop after N commits', (v) => Number.parseInt(v, 10))\r\n .option('--no-merges', 'skip merge commits')\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() =>\r\n runScanGit({\r\n cwd: options.cwd,\r\n since: options.since,\r\n limit: options.limit,\r\n merges: options.merges,\r\n json: options.json,\r\n minSignal: options.minSignal,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('scan-diff')\r\n .description('Preview the MemoryNodes commit patches would produce, one per changed file (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--since <date>', 'only commits newer than this git date expression, e.g. 90.days.ago')\r\n .option('-n, --limit <count>', 'stop after N commits (not N nodes)', (v) => Number.parseInt(v, 10))\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() =>\r\n runScanDiff({\r\n cwd: options.cwd,\r\n since: options.since,\r\n limit: options.limit,\r\n json: options.json,\r\n minSignal: options.minSignal,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('scan-shell')\r\n .description('Preview the MemoryNodes shell history would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('-n, --tail-lines <count>', 'lines kept from each scrape-based source', (v) => Number.parseInt(v, 10), 300)\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() =>\r\n runScanShell({ cwd: options.cwd, tailLines: options.tailLines, minSignal: options.minSignal, json: options.json }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('scan-conversation')\r\n .description('Preview the MemoryNodes the conversation transcript would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() => runScanConversation({ cwd: options.cwd, minSignal: options.minSignal, json: options.json }))(),\r\n );\r\n\r\nprogram\r\n .command('scan-session')\r\n .description('Preview the session summaries a local model would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--model <name>', 'Ollama model to summarize with', SCAN_SESSION_DEFAULT_MODEL)\r\n .option('--settle-minutes <count>', 'minutes of quiet before a session counts as finished', (v) => Number.parseInt(v, 10), 30)\r\n .option('-n, --max-sessions <count>', 'how many sessions to summarize', (v) => Number.parseInt(v, 10), 3)\r\n .option('--dry-run', 'print the prompts instead of calling the model', false)\r\n .option('--json', 'emit as JSON on stdout', false)\r\n .action((options) =>\r\n guard(() =>\r\n runScanSession({\r\n cwd: options.cwd,\r\n model: options.model,\r\n settleMinutes: options.settleMinutes,\r\n maxSessions: options.maxSessions,\r\n dryRun: options.dryRun,\r\n json: options.json,\r\n }),\r\n )(),\r\n );\r\n\r\nprogram\r\n .command('scan-docs')\r\n .description('Preview the MemoryNodes tracked .md files would produce (writes nothing)')\r\n .option('-C, --cwd <path>', 'repository path', process.cwd())\r\n .option('--min-signal <score>', 'drop nodes below this signal', (v) => Number.parseFloat(v), 0)\r\n .option('--json', 'emit MemoryNodes as JSON on stdout', false)\r\n .action((options) => guard(() => runScanDocs({ cwd: options.cwd, minSignal: options.minSignal, json: options.json }))());\r\n\r\nprogram\r\n .command('mcp')\r\n .description('Start the MCP server (stdio transport) for Claude Desktop, Cursor, Windsurf, etc.')\r\n .action(() => guard(() => runMcpServer().then(() => 0))());\r\n\r\nprogram.parseAsync(process.argv).catch((err: unknown) => {\r\n const message = err instanceof Error ? err.message : String(err);\r\n process.stderr.write(`${pc.red('error')} ${message}\\n`);\r\n process.exitCode = 1;\r\n});\r\n","import { existsSync } from 'node:fs';\r\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\r\nimport { join } from 'node:path';\r\nimport { z } from 'zod';\r\nimport { DEFAULT_SLM_MODEL } from '../slm/provider.js';\r\n\r\n/** Everything NexusMem stores lives under this directory in the repo root. */\r\nexport const WORKSPACE_DIR = '.nexusmem';\r\n\r\nexport interface Workspace {\r\n /** Repository root. */\r\n root: string;\r\n /** `<root>/.nexusmem` */\r\n dir: string;\r\n dbPath: string;\r\n configPath: string;\r\n}\r\n\r\nexport function resolveWorkspace(repoRoot: string): Workspace {\r\n const dir = join(repoRoot, WORKSPACE_DIR);\r\n return {\r\n root: repoRoot,\r\n dir,\r\n dbPath: join(dir, 'memory.db'),\r\n configPath: join(dir, 'config.json'),\r\n };\r\n}\r\n\r\nexport function isInitialized(ws: Workspace): boolean {\r\n return existsSync(ws.configPath);\r\n}\r\n\r\nexport const ConfigSchema = z.object({\r\n version: z.literal(1),\r\n projectId: z.string().min(1),\r\n sources: z\r\n .object({\r\n git: z\r\n .object({\r\n enabled: z.boolean().default(true),\r\n /** Git date expression bounding how far back to ingest; null = all history. */\r\n since: z.string().nullable().default(null),\r\n includeMerges: z.boolean().default(true),\r\n })\r\n .default({ enabled: true, since: null, includeMerges: true }),\r\n shell: z\r\n .object({\r\n enabled: z.boolean().default(true),\r\n /** Lines kept from scrape-based (no-hook) history files each sync. */\r\n tailLines: z.number().int().positive().default(300),\r\n })\r\n .default({ enabled: true, tailLines: 300 }),\r\n /**\r\n * Opt-in, unlike git/shell: conversation transcripts are the source\r\n * most likely to contain something sensitive (a pasted credential,\r\n * confidential discussion), so this must be a deliberate choice, not\r\n * an automatic default. See docs/phase-2-spec.md.\r\n */\r\n conversation: z\r\n .object({\r\n enabled: z.boolean().default(false),\r\n })\r\n .default({ enabled: false }),\r\n /**\r\n * One distilled node per finished working session, written by a local\r\n * small language model.\r\n *\r\n * Opt-in for the same reason as `conversation` -- it reads the same\r\n * transcripts -- and additionally because it is the only source that\r\n * costs real compute. Independent of `conversation.enabled`: summaries\r\n * without the raw exchanges is a legitimate, and much smaller, way to\r\n * remember a session.\r\n */\r\n session: z\r\n .object({\r\n enabled: z.boolean().default(false),\r\n /** Ollama model tag. Must be pulled locally; nothing is downloaded automatically. */\r\n model: z.string().default(DEFAULT_SLM_MODEL),\r\n /** Minutes of quiet before a session counts as finished and can be summarized. */\r\n settleMinutes: z.number().int().nonnegative().default(30),\r\n /** Sessions summarized per sync. Each is a model call measured in seconds. */\r\n maxSessions: z.number().int().positive().default(10),\r\n maxPromptChars: z.number().int().positive().default(12_000),\r\n })\r\n .default({\r\n enabled: false,\r\n model: DEFAULT_SLM_MODEL,\r\n settleMinutes: 30,\r\n maxSessions: 10,\r\n maxPromptChars: 12_000,\r\n }),\r\n /** Tracked `.md` files -- README, architecture docs. On by default like git/shell: no secrets risk, just project prose. */\r\n docs: z\r\n .object({\r\n enabled: z.boolean().default(true),\r\n /** git pathspecs passed to `git ls-files`. */\r\n include: z.array(z.string()).default(['*.md']),\r\n })\r\n .default({ enabled: true, include: ['*.md'] }),\r\n /**\r\n * The patch text of each commit, one node per changed file.\r\n *\r\n * Bounded by `maxCommits` rather than by `git.since`, because patches\r\n * are an order of magnitude bulkier than commit messages: an unbounded\r\n * first sync of a long-lived repository would spend most of its time\r\n * and database on code nobody will ask about. Later syncs walk only\r\n * `cursor..HEAD`, so the cap effectively applies to the first run.\r\n */\r\n diff: z\r\n .object({\r\n enabled: z.boolean().default(true),\r\n maxCommits: z.number().int().positive().default(200),\r\n maxFilesPerCommit: z.number().int().positive().default(20),\r\n contextLines: z.number().int().nonnegative().default(3),\r\n })\r\n .default({ enabled: true, maxCommits: 200, maxFilesPerCommit: 20, contextLines: 3 }),\r\n })\r\n .default({\r\n git: { enabled: true, since: null, includeMerges: true },\r\n shell: { enabled: true, tailLines: 300 },\r\n conversation: { enabled: false },\r\n session: {\r\n enabled: false,\r\n model: DEFAULT_SLM_MODEL,\r\n settleMinutes: 30,\r\n maxSessions: 10,\r\n maxPromptChars: 12_000,\r\n },\r\n docs: { enabled: true, include: ['*.md'] },\r\n diff: { enabled: true, maxCommits: 200, maxFilesPerCommit: 20, contextLines: 3 },\r\n }),\r\n limits: z\r\n .object({\r\n maxFilesPerNode: z.number().int().positive().default(40),\r\n maxBodyChars: z.number().int().positive().default(4000),\r\n })\r\n .default({ maxFilesPerNode: 40, maxBodyChars: 4000 }),\r\n});\r\n\r\nexport type NexusConfig = z.infer<typeof ConfigSchema>;\r\n\r\nexport function defaultConfig(projectId: string): NexusConfig {\r\n return ConfigSchema.parse({ version: 1, projectId });\r\n}\r\n\r\nexport class ConfigError extends Error {\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'ConfigError';\r\n }\r\n}\r\n\r\nexport async function readConfig(ws: Workspace): Promise<NexusConfig> {\r\n let raw: string;\r\n try {\r\n raw = await readFile(ws.configPath, 'utf8');\r\n } catch {\r\n throw new ConfigError(`Not initialized: ${ws.configPath} not found. Run \\`nexusmem init\\` first.`);\r\n }\r\n\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(raw);\r\n } catch (err) {\r\n throw new ConfigError(`${ws.configPath} is not valid JSON: ${(err as Error).message}`);\r\n }\r\n\r\n const result = ConfigSchema.safeParse(parsed);\r\n if (!result.success) {\r\n const issues = result.error.issues.map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`).join('\\n');\r\n throw new ConfigError(`${ws.configPath} is invalid:\\n${issues}`);\r\n }\r\n return result.data;\r\n}\r\n\r\nexport async function writeConfig(ws: Workspace, config: NexusConfig): Promise<void> {\r\n await mkdir(ws.dir, { recursive: true });\r\n await writeFile(ws.configPath, `${JSON.stringify(config, null, 2)}\\n`, 'utf8');\r\n}\r\n\r\n/**\r\n * Make the workspace ignore itself.\r\n *\r\n * A self-ignoring directory means `init` never has to edit the user's own\r\n * .gitignore -- one less surprising write into a repo we do not own.\r\n */\r\nexport async function writeWorkspaceGitignore(ws: Workspace): Promise<void> {\r\n await mkdir(ws.dir, { recursive: true });\r\n await writeFile(join(ws.dir, '.gitignore'), '# Machine-local derived data.\\n*\\n', 'utf8');\r\n}\r\n","/**\r\n * Small-language-model abstraction, used only for session summarization.\r\n *\r\n * Mirrors the embedding provider's contract on purpose: `complete` returns\r\n * `null` rather than throwing for every failure -- server down, model not\r\n * pulled, timeout, malformed response -- so a machine without a chat model\r\n * loses summaries and nothing else. Everything NexusMem does apart from this\r\n * one collector must keep working with no SLM present at all.\r\n *\r\n * Local-only by construction. There is no API-key option and no hosted\r\n * fallback: the input is verbatim conversation transcript, and the whole\r\n * argument for summarizing it at all is that the text never leaves the\r\n * machine.\r\n */\r\nexport interface SummarizationProvider {\r\n /** Stable name for the model behind this provider, recorded on the nodes it produces. */\r\n readonly identity: string;\r\n complete(prompt: string): Promise<string | null>;\r\n}\r\n\r\nexport interface OllamaChatProviderOptions {\r\n baseUrl?: string;\r\n model?: string;\r\n /** Milliseconds before giving up on one completion. Default 120s. */\r\n timeoutMs?: number;\r\n /** Upper bound on generated tokens. Default 400 -- a summary, not an essay. */\r\n maxTokens?: number;\r\n}\r\n\r\nconst DEFAULT_BASE_URL = 'http://127.0.0.1:11434';\r\n/**\r\n * Chosen for a 12GB VRAM budget shared with the embedding model: ~1.9GB on\r\n * disk, and reliable enough at holding a fixed output shape that the parser\r\n * in summarize.ts does not need to be clever.\r\n */\r\nexport const DEFAULT_SLM_MODEL = 'qwen2.5:3b';\r\nconst DEFAULT_TIMEOUT_MS = 120_000;\r\nconst DEFAULT_MAX_TOKENS = 400;\r\n\r\nexport class OllamaChatProvider implements SummarizationProvider {\r\n readonly identity: string;\r\n private readonly baseUrl: string;\r\n private readonly model: string;\r\n private readonly timeoutMs: number;\r\n private readonly maxTokens: number;\r\n\r\n constructor(opts: OllamaChatProviderOptions = {}) {\r\n this.baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;\r\n this.model = opts.model ?? DEFAULT_SLM_MODEL;\r\n this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n this.maxTokens = opts.maxTokens ?? DEFAULT_MAX_TOKENS;\r\n this.identity = `ollama:${this.model}`;\r\n }\r\n\r\n async complete(prompt: string): Promise<string | null> {\r\n const controller = new AbortController();\r\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\r\n\r\n try {\r\n const res = await fetch(`${this.baseUrl}/api/generate`, {\r\n method: 'POST',\r\n headers: { 'content-type': 'application/json' },\r\n body: JSON.stringify({\r\n model: this.model,\r\n prompt,\r\n stream: false,\r\n options: {\r\n // Deterministic: the same session must summarize to the same text\r\n // across syncs, or the content hash that suppresses re-work would\r\n // never match and every sync would rewrite every summary node.\r\n temperature: 0,\r\n seed: 1,\r\n num_predict: this.maxTokens,\r\n },\r\n }),\r\n signal: controller.signal,\r\n });\r\n\r\n if (!res.ok) return null;\r\n\r\n const data = (await res.json()) as { response?: unknown };\r\n if (typeof data.response !== 'string') return null;\r\n\r\n const text = data.response.trim();\r\n return text.length > 0 ? text : null;\r\n } catch {\r\n return null;\r\n } finally {\r\n clearTimeout(timeout);\r\n }\r\n }\r\n}\r\n\r\n/** Deterministic, network-free provider for tests. */\r\nexport class FakeSummarizationProvider implements SummarizationProvider {\r\n readonly identity = 'fake-slm';\r\n readonly prompts: string[] = [];\r\n\r\n constructor(private readonly reply: (prompt: string) => string | null = () => 'A fake summary.') {}\r\n\r\n async complete(prompt: string): Promise<string | null> {\r\n this.prompts.push(prompt);\r\n return this.reply(prompt);\r\n }\r\n}\r\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Read straight from package.json rather than a literal, so a reported\n * version cannot drift from what was actually published -- which is exactly\n * what happened to the literals this replaces: 0.1.1 shipped with the CLI's\n * `--version` and the MCP server's `initialize` response both still reporting\n * 0.1.0, because the release only bumped package.json.\n *\n * `../../package.json` resolves correctly from both the source location\n * (`src/<subdir>/*.ts`, dev via tsx) and the built location\n * (`dist/cli/index.js`, published), since tsup bundles every entry to the\n * same two-levels-deep location either way. npm always includes package.json\n * in a published tarball regardless of the `files` field, so this is safe to\n * rely on post-publish too.\n */\nexport function readOwnVersion(): string {\n const pkgPath = fileURLToPath(new URL('../../package.json', import.meta.url));\n return (JSON.parse(readFileSync(pkgPath, 'utf8')) as { version: string }).version;\n}\n","import { spawn } from 'node:child_process';\r\n\r\nexport class GitError extends Error {\r\n constructor(\r\n message: string,\r\n readonly args: string[],\r\n readonly exitCode: number,\r\n readonly stderr: string,\r\n ) {\r\n super(message);\r\n this.name = 'GitError';\r\n }\r\n}\r\n\r\n/**\r\n * `git` could not be started at all -- distinct from git starting and then\r\n * exiting non-zero (`GitError`).\r\n *\r\n * Worth its own type because the two have nothing in common diagnostically:\r\n * a `GitError` is git's own verdict about the repository, while this means we\r\n * never got git's opinion. Collapsing them is what previously let a failed\r\n * process spawn be reported as \"not a git repository\", pointing the user at\r\n * their repo when the repo was fine.\r\n */\r\nexport class GitSpawnError extends Error {\r\n constructor(\r\n message: string,\r\n readonly args: string[],\r\n /** libuv errno string (`ENOENT`, `EPERM`, ...), or `undefined` if the platform gave none. */\r\n readonly code: string | undefined,\r\n /** Whether retrying the identical command could plausibly succeed. */\r\n readonly transient: boolean,\r\n override readonly cause: unknown,\r\n ) {\r\n super(message);\r\n this.name = 'GitSpawnError';\r\n }\r\n}\r\n\r\n/**\r\n * `git` started, then died without reaching an exit of its own -- a segfault\r\n * or an access violation, not a verdict.\r\n *\r\n * Distinct from `GitError` for the same reason `GitSpawnError` is: a\r\n * `GitError` carries git's opinion about the repository and must be shown to\r\n * the user as such, while this means git never formed one. Reported as a\r\n * `GitError` it reads as \"git rev-parse exited with code 3221225477\", which\r\n * sends the reader looking for a git problem that does not exist.\r\n */\r\nexport class GitCrashError extends Error {\r\n constructor(\r\n message: string,\r\n readonly args: string[],\r\n /** `0xC0000005` on Windows, or the POSIX signal name. */\r\n readonly status: string,\r\n readonly exitCode: number,\r\n readonly signal: NodeJS.Signals | null,\r\n ) {\r\n super(message);\r\n this.name = 'GitCrashError';\r\n }\r\n}\r\n\r\n/**\r\n * A Windows process torn down by an unhandled exception exits with its\r\n * NTSTATUS value, and every failure NTSTATUS sits at or above this bound.\r\n * git's own exit codes are small (1, 2, 128, 129), so the boundary separates\r\n * \"the OS killed git\" from \"git ran and disagreed\" without guesswork.\r\n *\r\n * Observed here as 0xC0000005 (STATUS_ACCESS_VIOLATION) from `git init` and\r\n * `git commit` in test fixtures, at roughly two runs in five on this machine.\r\n */\r\nconst NTSTATUS_FAILURE_BASE = 0xc0000000;\r\n\r\n/**\r\n * Ctrl+C arrives as an NTSTATUS too, and is the one that must not be retried:\r\n * it is the user's instruction to stop, not a fault.\r\n */\r\nconst STATUS_CONTROL_C_EXIT = 0xc000013a;\r\n\r\n/** POSIX counterpart -- the process died on a signal instead of returning a code. */\r\nconst FATAL_SIGNALS = new Set<string>(['SIGSEGV', 'SIGBUS', 'SIGABRT', 'SIGILL', 'SIGFPE']);\r\n\r\n/** A short label for how git died, or `null` if it exited normally (however unhappily). */\r\nfunction crashStatus(code: number, signal: NodeJS.Signals | null): string | null {\r\n if (signal) return FATAL_SIGNALS.has(signal) ? signal : null;\r\n if (code >= NTSTATUS_FAILURE_BASE && code !== STATUS_CONTROL_C_EXIT) {\r\n return `0x${code.toString(16).toUpperCase()}`;\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Spawn failures that are worth retrying rather than reporting as a broken\r\n * setup.\r\n *\r\n * Windows is the reason this list exists: under process-creation pressure\r\n * (antivirus scanning a new image, handle exhaustion) `uv_spawn` intermittently\r\n * returns `EPERM` or `EAGAIN` for a binary that is present and runnable, and\r\n * succeeds on an immediate retry. `ENOENT` is deliberately absent -- git being\r\n * missing, or the cwd not existing, does not fix itself.\r\n */\r\nconst TRANSIENT_SPAWN_CODES = new Set(['EAGAIN', 'EPERM', 'EACCES', 'EMFILE', 'ENFILE', 'ENOMEM', 'EBUSY', 'ETXTBSY']);\r\n\r\nfunction toSpawnError(err: unknown, cwd: string, args: string[]): GitSpawnError {\r\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\r\n\r\n if (code === 'ENOENT') {\r\n // Ambiguous by design in libuv: the missing thing is either the binary or\r\n // the cwd, and the error carries nothing that tells them apart.\r\n return new GitSpawnError(\r\n `Could not run git: either git is not on PATH, or the directory does not exist: ${cwd}`,\r\n args,\r\n code,\r\n false,\r\n err,\r\n );\r\n }\r\n\r\n const transient = code !== undefined && TRANSIENT_SPAWN_CODES.has(code);\r\n const suffix = transient ? ' This is usually transient on Windows -- retrying the same command often succeeds.' : '';\r\n\r\n return new GitSpawnError(\r\n `Could not start git (${code ?? 'unknown spawn failure'}) in ${cwd}.${suffix}`,\r\n args,\r\n code,\r\n transient,\r\n err,\r\n );\r\n}\r\n\r\n/**\r\n * Flags applied to every invocation.\r\n *\r\n * - `core.quotePath=false` keeps non-ASCII paths readable instead of `\\303\\251`.\r\n * - `--no-pager` / `core.pager=` stops git from ever trying to spawn `less`.\r\n */\r\nconst BASE_ARGS = ['-c', 'core.quotePath=false', '-c', 'core.pager=', '--no-pager'];\r\n\r\n/**\r\n * Backoff between spawn retries, in milliseconds. Length sets the retry count.\r\n *\r\n * The failures this covers are short-lived contention (an antivirus scanner\r\n * holding a new image, momentary handle exhaustion), so the useful waits are\r\n * tens to low hundreds of milliseconds. A worst-case run adds ~600ms before\r\n * giving up, and only on a path that was going to fail outright before.\r\n */\r\nconst RETRY_DELAYS_MS = [50, 150, 400];\r\n\r\nconst realSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\r\n\r\nexport interface GitExecOptions {\r\n /** Injectable for deterministic tests; defaults to `child_process.spawn`. */\r\n spawn?: typeof spawn;\r\n /** Injectable for deterministic tests; defaults to a real timer. */\r\n sleep?: (ms: number) => Promise<void>;\r\n}\r\n\r\n/**\r\n * Stream `git <args>` stdout as UTF-8 chunks, retrying the two failures that\r\n * are the environment's fault rather than git's: a transient failure to start\r\n * (`GitSpawnError`), and git being killed mid-run (`GitCrashError`).\r\n *\r\n * A non-zero exit is never retried. That is git's own answer, and running the\r\n * same command again will get the same one.\r\n *\r\n * The two retryable cases differ in where they can strike. A spawn failure is\r\n * necessarily before any output exists; a crash can happen part-way through a\r\n * long `git log`. The `produced` guard is what makes retrying safe in both:\r\n * once a single chunk has reached the consumer, re-running would replay output\r\n * into a parser that has already consumed it, so from that point the error\r\n * propagates instead.\r\n *\r\n * Retrying is also safe at the command level here because every git invocation\r\n * in this codebase reads (`log`, `rev-parse`, `merge-base`, `ls-files`,\r\n * `remote get-url`). Nothing writes, so a half-finished attempt leaves no lock\r\n * file or partial state for the next one to trip over. A future write command\r\n * would need that reasoning revisited.\r\n */\r\nexport async function* gitStream(cwd: string, args: string[], opts: GitExecOptions = {}): AsyncGenerator<string> {\r\n const sleep = opts.sleep ?? realSleep;\r\n\r\n for (let attempt = 0; ; attempt += 1) {\r\n let produced = false;\r\n try {\r\n for await (const chunk of runGitOnce(cwd, args, opts)) {\r\n produced = true;\r\n yield chunk;\r\n }\r\n return;\r\n } catch (err) {\r\n const retryable = (err instanceof GitSpawnError && err.transient) || err instanceof GitCrashError;\r\n if (produced || !retryable || attempt >= RETRY_DELAYS_MS.length) throw err;\r\n await sleep(RETRY_DELAYS_MS[attempt]!);\r\n }\r\n }\r\n}\r\n\r\nasync function* runGitOnce(cwd: string, args: string[], opts: GitExecOptions): AsyncGenerator<string> {\r\n const fullArgs = [...BASE_ARGS, ...args];\r\n const child = (opts.spawn ?? spawn)('git', fullArgs, { cwd, windowsHide: true });\r\n\r\n child.stdout.setEncoding('utf8');\r\n child.stderr.setEncoding('utf8');\r\n\r\n let stderr = '';\r\n child.stderr.on('data', (chunk: string) => {\r\n // Bounded, so a pathological repo cannot blow up memory via stderr.\r\n if (stderr.length < 64 * 1024) stderr += chunk;\r\n });\r\n\r\n const exited = new Promise<{ code: number; signal: NodeJS.Signals | null }>((resolve, reject) => {\r\n child.once('error', (err) => reject(toSpawnError(err, cwd, fullArgs)));\r\n child.once('close', (code, signal) => resolve({ code: code ?? 0, signal: signal ?? null }));\r\n });\r\n // A spawn failure rejects `exited` before the stdout loop below has a\r\n // consumer for it; without this the rejection is unhandled for a tick even\r\n // though we do await it further down.\r\n exited.catch(() => {});\r\n\r\n try {\r\n for await (const chunk of child.stdout) {\r\n yield chunk as string;\r\n }\r\n } finally {\r\n // Consumer broke out early (e.g. hit --limit): don't leave git running.\r\n if (child.exitCode === null) child.kill();\r\n }\r\n\r\n const { code, signal } = await exited;\r\n\r\n const crash = crashStatus(code, signal);\r\n if (crash) {\r\n throw new GitCrashError(\r\n `git ${args.join(' ')} was killed before it could answer (${crash}) in ${cwd}.` +\r\n ' This is an environment fault, not a problem with the repository.',\r\n fullArgs,\r\n crash,\r\n code,\r\n signal,\r\n );\r\n }\r\n\r\n if (code !== 0) {\r\n const trimmed = stderr.trim();\r\n // Lead with git's own first line: now that callers no longer rewrite every\r\n // failure into \"not a git repository\", this message is what the user sees\r\n // for the cases that aren't specifically handled.\r\n const detail = trimmed.split('\\n')[0];\r\n throw new GitError(\r\n `git ${args.join(' ')} exited with code ${code}${detail ? `: ${detail}` : ''}`,\r\n fullArgs,\r\n code,\r\n trimmed,\r\n );\r\n }\r\n}\r\n\r\n/** Buffered variant, for commands with small, bounded output. */\r\nexport async function git(cwd: string, args: string[], opts: GitExecOptions = {}): Promise<string> {\r\n let out = '';\r\n for await (const chunk of gitStream(cwd, args, opts)) out += chunk;\r\n return out;\r\n}\r\n\r\n/** Buffered variant that returns `null` instead of throwing (e.g. no origin remote). */\r\nexport async function gitOrNull(cwd: string, args: string[], opts: GitExecOptions = {}): Promise<string | null> {\r\n try {\r\n return await git(cwd, args, opts);\r\n } catch (err) {\r\n if (err instanceof GitError) return null;\r\n throw err;\r\n }\r\n}\r\n","import { resolve } from 'node:path';\r\nimport { git, gitOrNull, GitError } from './exec.js';\r\n\r\nexport class NotAGitRepositoryError extends Error {\r\n constructor(readonly cwd: string) {\r\n super(`Not a git repository: ${cwd}`);\r\n this.name = 'NotAGitRepositoryError';\r\n }\r\n}\r\n\r\n/**\r\n * git's own wording when the path simply isn't inside a work tree, e.g.\r\n * `fatal: not a git repository (or any of the parent directories): .git`.\r\n *\r\n * Matching on it is what keeps this error meaning exactly one thing. Every\r\n * other non-zero exit from `rev-parse` -- dubious ownership, a corrupt object\r\n * store, an unreadable config -- is a different problem with a different fix,\r\n * and is now surfaced with git's own message instead of being relabelled.\r\n */\r\nconst NOT_A_REPO = /not a git repository/i;\r\n\r\nexport interface RepoInfo {\r\n /** Absolute, platform-native path to the work tree root. */\r\n root: string;\r\n /** `null` on a detached HEAD. */\r\n branch: string | null;\r\n /** `null` in a repo with no commits yet. */\r\n head: string | null;\r\n originUrl: string | null;\r\n}\r\n\r\n/**\r\n * Whether `ancestor` is reachable from `descendant`.\r\n *\r\n * Used to validate a stored sync cursor: after a rebase, amend or branch\r\n * switch the old HEAD may no longer be in history, and `cursor..HEAD` would\r\n * then silently skip commits. Falling back to a full resync is the safe move.\r\n */\r\nexport async function isAncestor(cwd: string, ancestor: string, descendant: string): Promise<boolean> {\r\n try {\r\n await git(cwd, ['merge-base', '--is-ancestor', ancestor, descendant]);\r\n return true;\r\n } catch (err) {\r\n if (err instanceof GitError) return false;\r\n throw err;\r\n }\r\n}\r\n\r\nexport async function readRepoInfo(cwd: string): Promise<RepoInfo> {\r\n let rootRaw: string;\r\n try {\r\n rootRaw = await git(cwd, ['rev-parse', '--show-toplevel']);\r\n } catch (err) {\r\n if (err instanceof GitError && NOT_A_REPO.test(err.stderr)) {\r\n throw new NotAGitRepositoryError(cwd);\r\n }\r\n // Anything else -- a `GitSpawnError` (git missing, or a transient Windows\r\n // spawn failure), or git failing for some reason of its own -- keeps its\r\n // own type and message. Reporting those as \"not a git repository\" sent\r\n // the reader to inspect a repository that was never the problem.\r\n throw err;\r\n }\r\n\r\n // git always prints forward slashes; resolve() gives us a native path back.\r\n const root = resolve(rootRaw.trim());\r\n\r\n const [branchRaw, headRaw, originRaw] = await Promise.all([\r\n gitOrNull(root, ['rev-parse', '--abbrev-ref', 'HEAD']),\r\n gitOrNull(root, ['rev-parse', 'HEAD']),\r\n gitOrNull(root, ['remote', 'get-url', 'origin']),\r\n ]);\r\n\r\n const branch = branchRaw?.trim() ?? null;\r\n\r\n return {\r\n root,\r\n branch: branch && branch !== 'HEAD' ? branch : null,\r\n head: headRaw?.trim() || null,\r\n originUrl: originRaw?.trim() || null,\r\n };\r\n}\r\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { hookLogPath, resolvePowerShellProfilePath } from '../shell/paths.js';\nimport { isHookInstalled, stripHookSnippet, upsertHookSnippet } from './powershell.js';\n\nexport interface HookTarget {\n profilePath: string;\n logPath: string;\n}\n\nexport class ProfileNotFoundError extends Error {\n constructor() {\n super('Could not resolve a PowerShell profile path (tried `powershell -Command $PROFILE`). Pass --profile explicitly.');\n this.name = 'ProfileNotFoundError';\n }\n}\n\nexport async function resolveHookTarget(profileOverride?: string, logPathOverride?: string): Promise<HookTarget> {\n const profilePath = profileOverride ?? (await resolvePowerShellProfilePath());\n if (!profilePath) throw new ProfileNotFoundError();\n return { profilePath, logPath: logPathOverride ?? hookLogPath() };\n}\n\nasync function readProfile(path: string): Promise<string> {\n try {\n return await readFile(path, 'utf8');\n } catch {\n return '';\n }\n}\n\nexport async function installHook(target: HookTarget): Promise<{ changed: boolean; alreadyInstalled: boolean }> {\n const current = await readProfile(target.profilePath);\n const alreadyInstalled = isHookInstalled(current);\n const next = upsertHookSnippet(current, target.logPath);\n\n if (next === current) return { changed: false, alreadyInstalled };\n\n await mkdir(dirname(target.profilePath), { recursive: true });\n await writeFile(target.profilePath, next, 'utf8');\n return { changed: true, alreadyInstalled };\n}\n\nexport async function removeHook(target: HookTarget): Promise<{ changed: boolean }> {\n const current = await readProfile(target.profilePath);\n if (!isHookInstalled(current)) return { changed: false };\n\n await writeFile(target.profilePath, stripHookSnippet(current), 'utf8');\n return { changed: true };\n}\n\nexport async function hookStatus(target: HookTarget): Promise<{ installed: boolean }> {\n const current = await readProfile(target.profilePath);\n return { installed: isHookInstalled(current) };\n}\n","import { execFile } from 'node:child_process';\r\nimport { homedir } from 'node:os';\r\nimport { join } from 'node:path';\r\nimport { promisify } from 'node:util';\r\nimport { globalWorkspaceDir } from '../config/paths.js';\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nexport function psReadLineHistoryPath(): string {\r\n const appData = process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming');\r\n return join(appData, 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt');\r\n}\r\n\r\nexport function bashHistoryPath(): string {\r\n return process.env.HISTFILE_BASH ?? join(homedir(), '.bash_history');\r\n}\r\n\r\nexport function zshHistoryPath(): string {\r\n return process.env.HISTFILE ?? join(homedir(), '.zsh_history');\r\n}\r\n\r\n/**\r\n * The hook log lives in the user-scoped directory rather than under any one\r\n * repo's `.nexusmem/`, because a shell session moves between projects -- the\r\n * log is one growing stream shared across every repo, filtered to each repo's\r\n * cwd at read time.\r\n */\r\nexport function hookLogPath(): string {\r\n return join(globalWorkspaceDir(), 'shell-history.jsonl');\r\n}\r\n\r\n/**\r\n * Resolve `$PROFILE` by asking PowerShell itself.\r\n *\r\n * The exact path depends on host (Windows PowerShell vs. PowerShell 7) and\r\n * is not worth hardcoding when the shell will just tell us.\r\n */\r\nexport async function resolvePowerShellProfilePath(exe: 'pwsh' | 'powershell' = 'powershell'): Promise<string | null> {\r\n try {\r\n const { stdout } = await execFileAsync(exe, ['-NoLogo', '-NoProfile', '-Command', '$PROFILE'], {\r\n windowsHide: true,\r\n });\r\n const path = stdout.trim();\r\n return path.length > 0 ? path : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n","import { homedir } from 'node:os';\r\nimport { join } from 'node:path';\r\n\r\n/**\r\n * The user-scoped (not repo-scoped) NexusMem directory.\r\n *\r\n * Lived in `shell/paths.ts` while the hook log was the only thing in it. The\r\n * project registry is the second, and it has nothing to do with shells, so\r\n * the location moved somewhere neither feature owns.\r\n *\r\n * `NEXUSMEM_HOME` overrides it. That is not a convenience flag: without it a\r\n * test of anything user-scoped would read and write the developer's real home\r\n * directory, which is exactly the kind of test this project does not have.\r\n */\r\nexport function globalWorkspaceDir(): string {\r\n return process.env.NEXUSMEM_HOME ?? join(homedir(), '.nexusmem');\r\n}\r\n","/**\n * Generates and manages the block NexusMem inserts into a PowerShell profile\n * to log every command with its real timestamp, cwd and exit code.\n *\n * The block wraps the existing `prompt` function rather than replacing it,\n * so an already-customized prompt (oh-my-posh, posh-git, ...) keeps\n * rendering exactly as before -- logging piggybacks on the fact that\n * `prompt` runs once per command, it does not own the prompt's appearance.\n */\n\nconst MARK_START = '# >>> nexusmem shell hook >>>';\nconst MARK_END = '# <<< nexusmem shell hook <<<';\n\n/**\n * PowerShell single-quoted strings have exactly one escape rule (a literal\n * `'` doubles to `''`) and no backslash processing at all -- unlike a JSON\n * or JS string. `JSON.stringify` would leave a Windows path's backslashes\n * doubled in the resulting PowerShell literal, since JSON escaping and\n * PowerShell escaping are different rules applied to the same character.\n */\nfunction toPowerShellLiteral(s: string): string {\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nexport function renderHookSnippet(logPath: string): string {\n return [\n MARK_START,\n 'if (Test-Path Function:\\\\prompt) { $function:__ssd_original_prompt = $function:prompt }',\n '$global:__ssd_last_history_id = -1',\n `$global:__ssd_log_path = ${toPowerShellLiteral(logPath)}`,\n 'function global:prompt {',\n ' $__ssd_h = Get-History -Count 1 -ErrorAction SilentlyContinue',\n ' if ($__ssd_h -and $__ssd_h.Id -ne $global:__ssd_last_history_id) {',\n ' $global:__ssd_last_history_id = $__ssd_h.Id',\n ' try {',\n ' $__ssd_entry = [ordered]@{',\n ' ts = (Get-Date).ToString(\"o\")',\n ' cwd = (Get-Location).Path',\n ' exitCode = $LASTEXITCODE',\n ' durationMs = [int](($__ssd_h.EndExecutionTime - $__ssd_h.StartExecutionTime).TotalMilliseconds)',\n ' command = $__ssd_h.CommandLine',\n ' }',\n ' $__ssd_dir = Split-Path -Parent $global:__ssd_log_path',\n ' if (-not (Test-Path $__ssd_dir)) { New-Item -ItemType Directory -Force -Path $__ssd_dir | Out-Null }',\n ' Add-Content -LiteralPath $global:__ssd_log_path -Value ($__ssd_entry | ConvertTo-Json -Compress) -Encoding utf8',\n ' } catch {}',\n ' }',\n ' if (Test-Path Function:\\\\__ssd_original_prompt) { & $function:__ssd_original_prompt }',\n \" else { \\\"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) \\\" }\",\n '}',\n MARK_END,\n '',\n ].join('\\n');\n}\n\nexport function isHookInstalled(profileContent: string): boolean {\n return profileContent.includes(MARK_START);\n}\n\nexport function stripHookSnippet(profileContent: string): string {\n const startIdx = profileContent.indexOf(MARK_START);\n const endIdx = profileContent.indexOf(MARK_END);\n if (startIdx === -1 || endIdx === -1) return profileContent;\n\n const afterBlock = profileContent.slice(endIdx + MARK_END.length).replace(/^\\r?\\n/, '');\n return profileContent.slice(0, startIdx) + afterBlock;\n}\n\n/** Idempotent: strips any existing block first, so re-running with a new log path updates cleanly. */\nexport function upsertHookSnippet(profileContent: string, logPath: string): string {\n const stripped = stripHookSnippet(profileContent).replace(/\\s+$/, '');\n const prefix = stripped.length > 0 ? `${stripped}\\n\\n` : '';\n return `${prefix}${renderHookSnippet(logPath)}`;\n}\n","import pc from 'picocolors';\nimport { hookStatus, installHook, removeHook, resolveHookTarget } from '../../hooks/install.js';\n\nexport interface HookOptions {\n profile?: string;\n logPath?: string;\n}\n\nexport async function runHookInstall(opts: HookOptions): Promise<number> {\n const target = await resolveHookTarget(opts.profile, opts.logPath);\n const result = await installHook(target);\n\n process.stdout.write(\n [\n result.changed\n ? `${pc.green(result.alreadyInstalled ? 'updated' : 'installed')} shell hook`\n : `${pc.dim('already up to date')}`,\n ` profile ${target.profilePath}`,\n ` log ${target.logPath}`,\n '',\n `New commands in any PowerShell session using this profile will now log their timestamp, cwd and exit code.`,\n `Open a new PowerShell window (or run \\`. $PROFILE\\`) for it to take effect.`,\n `Run ${pc.bold('nexusmem hook remove')} to undo this.`,\n '',\n ].join('\\n'),\n );\n\n return 0;\n}\n\nexport async function runHookRemove(opts: HookOptions): Promise<number> {\n const target = await resolveHookTarget(opts.profile, opts.logPath);\n const result = await removeHook(target);\n\n process.stdout.write(\n result.changed\n ? `${pc.green('removed')} shell hook from ${target.profilePath}\\n`\n : `${pc.dim('nothing to remove')} — no hook block found in ${target.profilePath}\\n`,\n );\n\n return 0;\n}\n\nexport async function runHookStatus(opts: HookOptions): Promise<number> {\n const target = await resolveHookTarget(opts.profile, opts.logPath);\n const result = await hookStatus(target);\n\n process.stdout.write(\n [\n `${pc.dim('profile')} ${target.profilePath}`,\n `${pc.dim('log ')} ${target.logPath}`,\n `${pc.dim('status ')} ${result.installed ? pc.green('installed') : pc.yellow('not installed')}`,\n '',\n ].join('\\n'),\n );\n\n return 0;\n}\n","import { relative } from 'node:path';\r\nimport pc from 'picocolors';\r\nimport {\r\n defaultConfig,\r\n isInitialized,\r\n readConfig,\r\n resolveWorkspace,\r\n writeConfig,\r\n writeWorkspaceGitignore,\r\n} from '../../config/workspace.js';\r\nimport { recordProject } from '../../config/registry.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { installHook, ProfileNotFoundError, resolveHookTarget } from '../../hooks/install.js';\r\nimport { MemoryStore } from '../../store/store.js';\r\nimport { LATEST_SCHEMA_VERSION } from '../../store/schema.js';\r\n\r\nexport interface InitOptions {\r\n cwd: string;\r\n force: boolean;\r\n /** Also install the opt-in PowerShell hook that logs cwd + exit code + timestamp. */\r\n hook: boolean;\r\n /** Persist `sources.conversation.enabled = true` in config.json. */\r\n enableConversation: boolean;\r\n /**\r\n * Where the result summary goes. Defaults to real stdout for the CLI.\r\n *\r\n * Callers that are not a terminal pass their own sink. The MCP server is\r\n * the reason this exists rather than a capture wrapper: there, `stdout` is\r\n * the JSON-RPC transport, so borrowing it for human-readable output is not\r\n * a formatting choice but a protocol hazard.\r\n */\r\n out?: (chunk: string) => void;\r\n}\r\n\r\nexport async function runInit(opts: InitOptions): Promise<number> {\r\n const out = opts.out ?? ((chunk: string) => void process.stdout.write(chunk));\r\n const repo = await readRepoInfo(opts.cwd);\r\n const ws = resolveWorkspace(repo.root);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const already = isInitialized(ws);\r\n if (already && !opts.force) {\r\n const existing = await readConfig(ws);\r\n process.stderr.write(\r\n `${pc.yellow('already initialized')} ${relative(process.cwd(), ws.configPath) || ws.configPath}\\n` +\r\n ` project ${pc.cyan(existing.projectId)}\\n` +\r\n ` use ${pc.bold('--force')} to reset the config (the database is kept)\\n`,\r\n );\r\n return 0;\r\n }\r\n\r\n await writeWorkspaceGitignore(ws);\r\n const config = defaultConfig(projectId);\r\n if (opts.enableConversation) config.sources.conversation.enabled = true;\r\n await writeConfig(ws, config);\r\n\r\n // Creating the database here means `init` surfaces native-module or\r\n // filesystem problems immediately, rather than halfway through a long sync.\r\n const store = MemoryStore.open(ws.dbPath);\r\n try {\r\n store.upsertProject({ id: projectId, root: repo.root, originUrl: repo.originUrl });\r\n } finally {\r\n store.close();\r\n }\r\n\r\n // Cross-project recall can only find a database it has been told about --\r\n // nothing else on the machine points at `<repo>/.nexusmem/`.\r\n await recordProject({ projectId, root: repo.root, dbPath: ws.dbPath, originUrl: repo.originUrl });\r\n\r\n const lines = [\r\n `${pc.green('initialized')} ${ws.dir}`,\r\n ` project ${pc.cyan(projectId)}`,\r\n ` repo ${repo.root}`,\r\n ` branch ${repo.branch ?? pc.yellow('(detached)')}`,\r\n ` schema v${LATEST_SCHEMA_VERSION}`,\r\n ];\r\n\r\n if (opts.enableConversation) {\r\n lines.push(` ${pc.yellow('conversation source enabled')} -- transcripts will be redacted-but-indexed on sync`);\r\n }\r\n\r\n if (opts.hook) {\r\n try {\r\n const target = await resolveHookTarget();\r\n const result = await installHook(target);\r\n lines.push(\r\n '',\r\n `${pc.green(result.changed ? 'installed' : 'already installed')} shell hook`,\r\n ` profile ${target.profilePath}`,\r\n ` log ${target.logPath}`,\r\n ` open a new PowerShell window (or run \\`. $PROFILE\\`) for it to take effect`,\r\n );\r\n } catch (err) {\r\n if (err instanceof ProfileNotFoundError) {\r\n lines.push('', `${pc.yellow('hook not installed')} ${err.message}`);\r\n } else {\r\n throw err;\r\n }\r\n }\r\n }\r\n\r\n lines.push('', `Next: ${pc.bold('nexusmem sync')}`, '');\r\n out(lines.join('\\n'));\r\n\r\n return 0;\r\n}\r\n","import { existsSync } from 'node:fs';\r\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\r\nimport { join } from 'node:path';\r\nimport { z } from 'zod';\r\nimport { globalWorkspaceDir } from './paths.js';\r\n\r\n/**\r\n * The list of repositories NexusMem has been run in on this machine.\r\n *\r\n * Cross-project recall needs it because every database is repo-scoped: a\r\n * query run inside one repo has no way of knowing another repo's memory\r\n * exists, since `.nexusmem/memory.db` lives under a directory it never looks\r\n * at. The alternative -- one shared global database -- was rejected for\r\n * giving up the property that deleting `<repo>/.nexusmem/` removes that\r\n * repo's memory and nothing else.\r\n *\r\n * The registry is therefore an *index*, never a source of truth. Every entry\r\n * is a pointer that may already be wrong (repo deleted, directory moved), so\r\n * reads verify the database is still on disk instead of trusting the file.\r\n */\r\n\r\nconst ENTRY_SCHEMA = z.object({\r\n projectId: z.string().min(1),\r\n root: z.string().min(1),\r\n dbPath: z.string().min(1),\r\n originUrl: z.string().nullable().default(null),\r\n /** Epoch ms of the last `init`/`sync` that recorded this entry. */\r\n lastSeenAt: z.number().int().nonnegative(),\r\n});\r\n\r\nconst REGISTRY_SCHEMA = z.object({\r\n version: z.literal(1),\r\n projects: z.array(ENTRY_SCHEMA).default([]),\r\n});\r\n\r\nexport type RegistryEntry = z.infer<typeof ENTRY_SCHEMA>;\r\n\r\nexport function registryPath(): string {\r\n return join(globalWorkspaceDir(), 'projects.json');\r\n}\r\n\r\n/**\r\n * Every recorded entry, most recently seen first.\r\n *\r\n * A missing, unreadable or corrupt file reads as an empty registry rather\r\n * than an error: this is a derived index that `sync` rebuilds by being run,\r\n * and failing a query because a cache file was truncated would be the wrong\r\n * trade in both directions.\r\n */\r\nexport async function readRegistry(): Promise<RegistryEntry[]> {\r\n let raw: string;\r\n try {\r\n raw = await readFile(registryPath(), 'utf8');\r\n } catch {\r\n return [];\r\n }\r\n\r\n try {\r\n const parsed = REGISTRY_SCHEMA.safeParse(JSON.parse(raw));\r\n if (!parsed.success) return [];\r\n return [...parsed.data.projects].sort((a, b) => b.lastSeenAt - a.lastSeenAt);\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\nexport interface LiveRegistry {\r\n /** Entries whose database is still on disk. */\r\n entries: RegistryEntry[];\r\n /** Entries whose database has since disappeared. Reported, never silently dropped from the file. */\r\n missing: RegistryEntry[];\r\n}\r\n\r\n/**\r\n * The registry split into what can still be searched and what cannot.\r\n *\r\n * Stale entries are *not* rewritten out of the file here. A database can be\r\n * temporarily unreachable -- an unmounted drive, a network share, a repo on a\r\n * USB disk -- and a query is the wrong moment to decide a project is gone\r\n * forever. `nexusmem projects --prune` is the explicit way to forget one.\r\n */\r\nexport async function readLiveRegistry(): Promise<LiveRegistry> {\r\n const all = await readRegistry();\r\n const entries: RegistryEntry[] = [];\r\n const missing: RegistryEntry[] = [];\r\n\r\n for (const entry of all) {\r\n (existsSync(entry.dbPath) ? entries : missing).push(entry);\r\n }\r\n\r\n return { entries, missing };\r\n}\r\n\r\nexport interface RecordProjectInput {\r\n projectId: string;\r\n root: string;\r\n dbPath: string;\r\n originUrl: string | null;\r\n}\r\n\r\n/**\r\n * Add or refresh one project's entry.\r\n *\r\n * Keyed by `projectId`, so re-cloning a repository to a new path moves the\r\n * entry rather than adding a second one -- the same identity rule the store\r\n * itself uses. Written to a temporary file and renamed, so a crash or a\r\n * second process mid-write cannot leave a half-written registry behind; two\r\n * concurrent syncs can still race, and the later writer simply wins.\r\n */\r\nexport async function recordProject(input: RecordProjectInput): Promise<RegistryEntry[]> {\r\n const existing = await readRegistry();\r\n const entry: RegistryEntry = { ...input, lastSeenAt: Date.now() };\r\n const projects = [entry, ...existing.filter((e) => e.projectId !== input.projectId)];\r\n\r\n await writeRegistry(projects);\r\n return projects;\r\n}\r\n\r\n/** Drop entries by project id. Returns how many were removed. */\r\nexport async function forgetProjects(projectIds: readonly string[]): Promise<number> {\r\n const existing = await readRegistry();\r\n const drop = new Set(projectIds);\r\n const kept = existing.filter((e) => !drop.has(e.projectId));\r\n\r\n if (kept.length === existing.length) return 0;\r\n\r\n await writeRegistry(kept);\r\n return existing.length - kept.length;\r\n}\r\n\r\nasync function writeRegistry(projects: readonly RegistryEntry[]): Promise<void> {\r\n const path = registryPath();\r\n const tmp = `${path}.${process.pid}.tmp`;\r\n\r\n await mkdir(globalWorkspaceDir(), { recursive: true });\r\n await writeFile(tmp, `${JSON.stringify({ version: 1, projects }, null, 2)}\\n`, 'utf8');\r\n await rename(tmp, path);\r\n}\r\n","import { createHash } from 'node:crypto';\nimport type { NodeKind } from './types.js';\n\n/** NUL cannot appear in any of our key components, so it is a safe joiner. */\nconst KEY_SEP = '\\u0000';\n\nexport function sha256Hex(input: string): string {\n return createHash('sha256').update(input, 'utf8').digest('hex');\n}\n\n/**\n * Content-addressed node id.\n *\n * `naturalKey` must be whatever uniquely identifies the event at its source\n * (a commit sha, a `timestamp:command` pair, ...). Re-running `sync` then\n * re-derives the exact same id, which makes ingestion idempotent without\n * relying on a cursor being correct.\n */\nexport function makeNodeId(projectId: string, kind: NodeKind, naturalKey: string): string {\n return sha256Hex([projectId, kind, naturalKey].join(KEY_SEP)).slice(0, 24);\n}\n","import { sha256Hex } from './ids.js';\n\n/**\n * Normalise a git remote URL so that the same repo yields the same id whether\n * it was cloned over ssh, https, with or without a `.git` suffix.\n *\n * git@github.com:acme/Repo.git -> github.com/acme/repo\n * https://github.com/acme/repo -> github.com/acme/repo\n */\nexport function normalizeGitUrl(url: string): string {\n let s = url.trim();\n\n // scp-like syntax: [user@]host:path\n const scp = /^(?:[^@/]+@)?([^/:]+):(.+)$/.exec(s);\n if (scp && !s.includes('://')) {\n s = `${scp[1]}/${scp[2]}`;\n } else {\n s = s.replace(/^[a-z+]+:\\/\\//i, '').replace(/^[^@/]+@/, '');\n }\n\n return s\n .replace(/\\/+$/, '')\n .replace(/\\.git$/i, '')\n .replace(/\\/+$/, '')\n .replace(/\\/{2,}/g, '/')\n .toLowerCase();\n}\n\nexport interface ProjectIdentity {\n /** Absolute path to the repo root on this machine. */\n root: string;\n originUrl?: string | null;\n}\n\n/**\n * Stable identity for a project.\n *\n * Prefers the origin URL so that the same repo checked out twice (or on two\n * machines) shares a memory namespace; falls back to the absolute path for\n * repos with no remote.\n */\nexport function makeProjectId({ root, originUrl }: ProjectIdentity): string {\n const basis = originUrl ? `remote:${normalizeGitUrl(originUrl)}` : `path:${root.replace(/\\\\/g, '/').toLowerCase()}`;\n return sha256Hex(basis).slice(0, 16);\n}\n","import Database from 'better-sqlite3';\r\nimport { mkdirSync } from 'node:fs';\r\nimport { dirname } from 'node:path';\r\nimport * as sqliteVec from 'sqlite-vec';\r\nimport type { MemoryNode, NodeKind } from '../core/types.js';\r\nimport { toMatchQuery } from './fts.js';\r\nimport { migrate } from './schema.js';\r\n\r\n/** Enough of a node's content to pack it, without the `node_files`/`meta` join a full `MemoryNode` carries. */\r\nexport interface LinkedNode {\r\n id: string;\r\n kind: NodeKind;\r\n projectId: string;\r\n ts: string;\r\n title: string;\r\n body: string;\r\n signal: number;\r\n}\r\n\r\nexport interface IngestStats {\r\n inserted: number;\r\n updated: number;\r\n unchanged: number;\r\n}\r\n\r\nexport interface ProjectRecord {\r\n id: string;\r\n root: string;\r\n originUrl: string | null;\r\n}\r\n\r\nexport interface StoreStats {\r\n total: number;\r\n byKind: Record<string, number>;\r\n oldest: string | null;\r\n newest: string | null;\r\n distinctFiles: number;\r\n}\r\n\r\nexport interface SearchHit {\r\n id: string;\r\n kind: NodeKind;\r\n ts: string;\r\n title: string;\r\n body: string;\r\n signal: number;\r\n /** bm25 score; lower is a better lexical match. */\r\n rank: number;\r\n /**\r\n * Human-readable name of the project this hit came from.\r\n *\r\n * Never set by the store, which is always querying one project and has\r\n * nothing to disambiguate. The cross-project pipeline attaches it so a\r\n * packed context block can say which repository each line is from.\r\n */\r\n project?: string;\r\n}\r\n\r\ninterface NodeRow {\r\n id: string;\r\n kind: NodeKind;\r\n ts: string;\r\n title: string;\r\n body: string;\r\n signal: number;\r\n rank: number;\r\n}\r\n\r\nexport interface VectorHit {\r\n id: string;\r\n kind: NodeKind;\r\n ts: string;\r\n title: string;\r\n body: string;\r\n signal: number;\r\n /** Euclidean distance from the query vector; lower is closer. */\r\n distance: number;\r\n}\r\n\r\n/** Enough of a node to list it, without the `node_files`/`meta`/body a full `MemoryNode` carries. */\r\nexport interface RecentNode {\r\n id: string;\r\n kind: NodeKind;\r\n ts: string;\r\n source: string;\r\n title: string;\r\n signal: number;\r\n}\r\n\r\nexport interface EmbeddableNode {\r\n rowid: number;\r\n id: string;\r\n title: string;\r\n body: string;\r\n}\r\n\r\nfunction epochOf(ts: string): number {\r\n const parsed = Date.parse(ts);\r\n return Number.isNaN(parsed) ? Date.now() : parsed;\r\n}\r\n\r\nexport class MemoryStore {\r\n private constructor(private readonly db: Database.Database) {}\r\n\r\n static open(dbPath: string): MemoryStore {\r\n mkdirSync(dirname(dbPath), { recursive: true });\r\n const db = new Database(dbPath);\r\n\r\n // WAL lets a long `sync` write while an agent reads via `query`.\r\n db.pragma('journal_mode = WAL');\r\n // NORMAL is the right durability trade for a rebuildable derived index:\r\n // worst case after a crash we re-run sync, which is idempotent anyway.\r\n db.pragma('synchronous = NORMAL');\r\n db.pragma('foreign_keys = ON');\r\n\r\n // Must load before migrate(): the nodes_vec migration's CREATE VIRTUAL\r\n // TABLE ... USING vec0 needs the module registered first.\r\n sqliteVec.load(db);\r\n\r\n migrate(db);\r\n return new MemoryStore(db);\r\n }\r\n\r\n close(): void {\r\n this.db.close();\r\n }\r\n\r\n upsertProject(project: ProjectRecord): void {\r\n this.db\r\n .prepare(\r\n `INSERT INTO projects (id, root, origin_url, created_at)\r\n VALUES (@id, @root, @originUrl, @now)\r\n ON CONFLICT(id) DO UPDATE SET root = excluded.root, origin_url = excluded.origin_url`,\r\n )\r\n .run({ ...project, now: Date.now() });\r\n }\r\n\r\n markSynced(projectId: string): void {\r\n this.db.prepare('UPDATE projects SET last_synced_at = ? WHERE id = ?').run(Date.now(), projectId);\r\n }\r\n\r\n /**\r\n * Every other project id ever recorded in THIS repo's own database.\r\n *\r\n * A repo's `.nexusmem/memory.db` is never shared with another repo (each\r\n * gets its own, gitignored), so any id here besides `currentProjectId` is\r\n * evidence of a prior identity for this same repo -- typically its git\r\n * remote URL changed since the last sync. See `reconcileProjectId` in\r\n * `store/reconcile.ts`.\r\n */\r\n listOtherProjectIds(currentProjectId: string): string[] {\r\n return (this.db.prepare('SELECT id FROM projects WHERE id != ?').all(currentProjectId) as Array<{ id: string }>).map(\r\n (r) => r.id,\r\n );\r\n }\r\n\r\n /**\r\n * Write a batch of nodes in one transaction.\r\n *\r\n * Ids are content-addressed, so re-ingesting the same event is a no-op --\r\n * a node is only rewritten when the derived content actually changed (which\r\n * happens when scoring or body composition is improved between releases).\r\n */\r\n upsertNodes(nodes: readonly MemoryNode[]): IngestStats {\r\n const exists = this.db.prepare('SELECT body, signal, title FROM nodes WHERE id = ?');\r\n // vec0 has no triggers to keep itself in sync (see schema.ts) -- when a\r\n // node's indexed text actually changes, its old embedding is stale and\r\n // must be dropped so the embedding pass in vector/embed.ts re-embeds it.\r\n const dropStaleEmbedding = this.db.prepare(\r\n 'DELETE FROM nodes_vec WHERE rowid = (SELECT rowid FROM nodes WHERE id = ?)',\r\n );\r\n const insertNode = this.db.prepare(\r\n `INSERT INTO nodes (id, kind, project_id, ts, ts_epoch, source, title, body, signal, meta, created_at)\r\n VALUES (@id, @kind, @projectId, @ts, @tsEpoch, @source, @title, @body, @signal, @meta, @now)\r\n ON CONFLICT(id) DO UPDATE SET\r\n ts = excluded.ts, ts_epoch = excluded.ts_epoch, source = excluded.source,\r\n title = excluded.title, body = excluded.body, signal = excluded.signal, meta = excluded.meta`,\r\n );\r\n const clearFiles = this.db.prepare('DELETE FROM node_files WHERE node_id = ?');\r\n const insertFile = this.db.prepare(\r\n `INSERT INTO node_files (node_id, path, previous_path, insertions, deletions, is_binary)\r\n VALUES (@nodeId, @path, @previousPath, @insertions, @deletions, @isBinary)\r\n ON CONFLICT(node_id, path) DO UPDATE SET\r\n previous_path = excluded.previous_path, insertions = excluded.insertions,\r\n deletions = excluded.deletions, is_binary = excluded.is_binary`,\r\n );\r\n\r\n const stats: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n const run = this.db.transaction((batch: readonly MemoryNode[]) => {\r\n const now = Date.now();\r\n\r\n for (const node of batch) {\r\n const prior = exists.get(node.id) as { body: string; signal: number; title: string } | undefined;\r\n\r\n if (prior) {\r\n if (prior.body === node.body && prior.signal === node.signal && prior.title === node.title) {\r\n stats.unchanged += 1;\r\n continue;\r\n }\r\n stats.updated += 1;\r\n dropStaleEmbedding.run(node.id);\r\n } else {\r\n stats.inserted += 1;\r\n }\r\n\r\n insertNode.run({\r\n id: node.id,\r\n kind: node.kind,\r\n projectId: node.projectId,\r\n ts: node.ts,\r\n tsEpoch: epochOf(node.ts),\r\n source: node.source,\r\n title: node.title,\r\n body: node.body,\r\n signal: node.signal,\r\n meta: JSON.stringify(node.meta),\r\n now,\r\n });\r\n\r\n clearFiles.run(node.id);\r\n for (const file of node.files) {\r\n insertFile.run({\r\n nodeId: node.id,\r\n path: file.path,\r\n previousPath: file.previousPath ?? null,\r\n insertions: file.insertions,\r\n deletions: file.deletions,\r\n isBinary: file.binary ? 1 : 0,\r\n });\r\n }\r\n }\r\n });\r\n\r\n run(nodes);\r\n return stats;\r\n }\r\n\r\n /**\r\n * The stored `meta` blob for one node, or null if it has never been\r\n * written. Used by the session summarizer to recognise work it has\r\n * already done without re-reading the node's whole body.\r\n */\r\n getNodeMeta(id: string): Record<string, unknown> | null {\r\n const row = this.db.prepare('SELECT meta FROM nodes WHERE id = ?').get(id) as { meta: string } | undefined;\r\n if (!row) return null;\r\n try {\r\n return JSON.parse(row.meta) as Record<string, unknown>;\r\n } catch {\r\n return null; // a meta blob we cannot read is treated as absent, never as a reason to fail a sync\r\n }\r\n }\r\n\r\n getSyncCursor(projectId: string, source: string): string | null {\r\n const row = this.db\r\n .prepare('SELECT cursor FROM sync_state WHERE project_id = ? AND source = ?')\r\n .get(projectId, source) as { cursor: string | null } | undefined;\r\n return row?.cursor ?? null;\r\n }\r\n\r\n setSyncCursor(projectId: string, source: string, cursor: string | null): void {\r\n this.db\r\n .prepare(\r\n `INSERT INTO sync_state (project_id, source, cursor, last_run_at)\r\n VALUES (?, ?, ?, ?)\r\n ON CONFLICT(project_id, source) DO UPDATE SET cursor = excluded.cursor, last_run_at = excluded.last_run_at`,\r\n )\r\n .run(projectId, source, cursor, Date.now());\r\n }\r\n\r\n /** Every source that has ever synced for this project, most recently run first. */\r\n listSyncState(projectId: string): Array<{ source: string; cursor: string | null; lastRunAt: number | null }> {\r\n return this.db\r\n .prepare('SELECT source, cursor, last_run_at AS lastRunAt FROM sync_state WHERE project_id = ? ORDER BY last_run_at DESC')\r\n .all(projectId) as Array<{ source: string; cursor: string | null; lastRunAt: number | null }>;\r\n }\r\n\r\n /** Drop every node for a project. Used by `sync --rebuild`. */\r\n clearProject(projectId: string): number {\r\n // nodes_vec has no FK/trigger relationship to nodes (see schema.ts) --\r\n // clean it up explicitly, before the rows it points at disappear.\r\n this.db\r\n .prepare('DELETE FROM nodes_vec WHERE rowid IN (SELECT rowid FROM nodes WHERE project_id = ?)')\r\n .run(projectId);\r\n const info = this.db.prepare('DELETE FROM nodes WHERE project_id = ?').run(projectId);\r\n this.db.prepare('DELETE FROM sync_state WHERE project_id = ?').run(projectId);\r\n return info.changes;\r\n }\r\n\r\n /**\r\n * Record a directed relationship between two existing nodes -- e.g. a\r\n * failed `shell_command` and whatever node later resolved it\r\n * (`relation = 'resolved_by'`). A relation, not a new content node: the\r\n * correlation *is* the relationship, and duplicating either side's content\r\n * into a third node would just be another independently-ranked candidate.\r\n *\r\n * Idempotent by design (`INSERT OR IGNORE` against the table's own primary\r\n * key) so re-running a correlation pass over already-linked nodes is a\r\n * no-op, not a duplicate-row error.\r\n */\r\n linkNodes(fromNodeId: string, toNodeId: string, relation: string): void {\r\n this.db\r\n .prepare('INSERT OR IGNORE INTO node_links (from_node_id, to_node_id, relation, created_at) VALUES (?, ?, ?, ?)')\r\n .run(fromNodeId, toNodeId, relation, Date.now());\r\n }\r\n\r\n /** Ids linked from `fromNodeId` under one relation, most recently linked first. Empty if none exist. */\r\n getLinkedNodeIds(fromNodeId: string, relation: string): string[] {\r\n return (\r\n this.db\r\n .prepare('SELECT to_node_id FROM node_links WHERE from_node_id = ? AND relation = ? ORDER BY created_at DESC')\r\n .all(fromNodeId, relation) as Array<{ to_node_id: string }>\r\n ).map((row) => row.to_node_id);\r\n }\r\n\r\n /**\r\n * Hydrate full content for a set of node ids, e.g. to pack a linked\r\n * resolution alongside the failure node that points at it. Order is not\r\n * guaranteed to match `ids`; ids with no matching row are silently omitted\r\n * rather than erroring. `node_links` has `ON DELETE CASCADE` on both\r\n * columns, so an individual node delete (e.g. `reconcile.ts` migrating a\r\n * node to a freshly-computed id) removes any link pointing at the old id\r\n * along with it -- correct as a safety default, though note that reconcile\r\n * does not currently re-create the link under the migrated node's new id;\r\n * that gap is not addressed here.\r\n */\r\n getNodesByIds(ids: readonly string[]): LinkedNode[] {\r\n if (ids.length === 0) return [];\r\n return this.db\r\n .prepare(\r\n `SELECT id, kind, project_id AS projectId, ts, title, body, signal\r\n FROM nodes WHERE id IN (SELECT value FROM json_each(?))`,\r\n )\r\n .all(JSON.stringify(ids)) as LinkedNode[];\r\n }\r\n\r\n /**\r\n * The most recently-remembered nodes for a project, newest event first --\r\n * chronology, not relevance. No `body`: a listing (e.g. a sidebar) needs\r\n * the title and enough metadata to label each row, not the full text.\r\n * `idx_nodes_project_ts` already exists for exactly this access pattern.\r\n */\r\n listRecentNodes(projectId: string, limit = 20): RecentNode[] {\r\n return this.db\r\n .prepare(\r\n `SELECT id, kind, ts, source, title, signal\r\n FROM nodes\r\n WHERE project_id = ?\r\n ORDER BY ts_epoch DESC\r\n LIMIT ?`,\r\n )\r\n .all(projectId, limit) as RecentNode[];\r\n }\r\n\r\n /** How many nodes of one source exist for a project. Used to preview a `pruneSourceNodes` wipe before running it. */\r\n countSourceNodes(projectId: string, source: string): number {\r\n const row = this.db\r\n .prepare('SELECT COUNT(*) AS count FROM nodes WHERE project_id = ? AND source = ?')\r\n .get(projectId, source) as { count: number };\r\n return row.count;\r\n }\r\n\r\n /**\r\n * Delete the nodes of one source that its latest full scan did not produce.\r\n *\r\n * Needed by any source whose node ids are derived from content that can be\r\n * *edited in place* rather than only appended to. A `doc_section` id comes\r\n * from `path + heading slug`, so renaming a markdown heading mints a new node\r\n * and strands the old one: `sync` reports `+1 new`, and the corpus then holds\r\n * two contradictory versions of the same section, both of which come back for\r\n * the same query. Git and shell nodes describe events that already happened\r\n * and are never restated, so they have nothing to prune.\r\n *\r\n * Scoping is the whole safety story here, and it is deliberately narrow:\r\n *\r\n * - `project_id` -- never reaches another repository's memory.\r\n * - `source` -- an exact match on the collector's own key, so pruning `docs`\r\n * cannot touch `conversation:claude-code`, `shell:pwsh` or `git` nodes even\r\n * though they share the table.\r\n * - `keepIds` -- everything this scan produced.\r\n * - `keepPaths` -- files the scan could not read. Their nodes are kept\r\n * because an unreadable file is not evidence that its sections are gone.\r\n *\r\n * Callers must pass the ids from a *complete* scan of the source. A partial\r\n * or filtered scan would read as \"these nodes no longer exist\" and delete\r\n * real history.\r\n */\r\n pruneSourceNodes(\r\n projectId: string,\r\n source: string,\r\n keepIds: readonly string[],\r\n opts: { keepPaths?: readonly string[] } = {},\r\n ): number {\r\n // json_each keeps this one statement regardless of how many sections a\r\n // repo has, instead of an id list that grows into SQLite's parameter cap.\r\n const scope = `project_id = @projectId AND source = @source\r\n AND id NOT IN (SELECT value FROM json_each(@keepIds))\r\n AND id NOT IN (SELECT node_id FROM node_files WHERE path IN (SELECT value FROM json_each(@keepPaths)))`;\r\n\r\n const params = {\r\n projectId,\r\n source,\r\n keepIds: JSON.stringify(keepIds),\r\n keepPaths: JSON.stringify(opts.keepPaths ?? []),\r\n };\r\n\r\n return this.db.transaction(() => {\r\n // Same ordering constraint as clearProject: nodes_vec is not reachable by\r\n // FK or trigger, so its rows must go while their rowids still resolve.\r\n // nodes_fts *is* trigger-backed (schema.ts) and cleans itself up on\r\n // DELETE, and node_files cascades.\r\n this.db.prepare(`DELETE FROM nodes_vec WHERE rowid IN (SELECT rowid FROM nodes WHERE ${scope})`).run(params);\r\n return this.db.prepare(`DELETE FROM nodes WHERE ${scope}`).run(params).changes;\r\n })();\r\n }\r\n\r\n /**\r\n * Nodes for this project that have no embedding yet (new, or invalidated\r\n * by a content change).\r\n *\r\n * `afterRowid` makes paging monotonic: the pass walks rowids strictly\r\n * upward instead of re-reading \"the first N still pending\". That matters\r\n * because a node the provider *failed* on stays pending -- an offset-free\r\n * loop would fetch the same failures forever, which is exactly the shape\r\n * of an infinite sync.\r\n */\r\n findNodesNeedingEmbedding(projectId: string, limit = 200, afterRowid = 0): EmbeddableNode[] {\r\n return this.db\r\n .prepare(\r\n `SELECT n.rowid AS rowid, n.id AS id, n.title AS title, n.body AS body\r\n FROM nodes n\r\n LEFT JOIN nodes_vec v ON v.rowid = n.rowid\r\n WHERE n.project_id = ? AND v.rowid IS NULL AND n.rowid > ?\r\n ORDER BY n.rowid\r\n LIMIT ?`,\r\n )\r\n .all(projectId, afterRowid, limit) as EmbeddableNode[];\r\n }\r\n\r\n /** How many of this project's nodes still need a vector. For progress reporting. */\r\n countNodesNeedingEmbedding(projectId: string): number {\r\n const row = this.db\r\n .prepare(\r\n `SELECT COUNT(*) AS n\r\n FROM nodes n\r\n LEFT JOIN nodes_vec v ON v.rowid = n.rowid\r\n WHERE n.project_id = ? AND v.rowid IS NULL`,\r\n )\r\n .get(projectId) as { n: number };\r\n return row.n;\r\n }\r\n\r\n upsertEmbedding(rowid: number, embedding: Float32Array): void {\r\n this.db\r\n .prepare('INSERT OR REPLACE INTO nodes_vec (rowid, embedding) VALUES (?, ?)')\r\n .run(BigInt(rowid), embedding);\r\n }\r\n\r\n /**\r\n * Drop every vector in this database, across all projects.\r\n *\r\n * Whole-database on purpose: `nodes_vec` is shared and holds no\r\n * provenance, so once the vectors in it stopped being comparable there is\r\n * no subset that is still trustworthy. Nodes are untouched, so the next\r\n * embedding pass simply rebuilds them.\r\n */\r\n dropAllEmbeddings(): number {\r\n return this.db.prepare('DELETE FROM nodes_vec').run().changes;\r\n }\r\n\r\n getMeta(key: string): string | null {\r\n const row = this.db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\r\n return row?.value ?? null;\r\n }\r\n\r\n setMeta(key: string, value: string): void {\r\n this.db\r\n .prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value')\r\n .run(key, value);\r\n }\r\n\r\n /**\r\n * Nearest-neighbour search over the corpus.\r\n *\r\n * `nodes_vec` has no `project_id` column of its own (embeddings are\r\n * generic; project scoping lives on `nodes`), so this over-fetches `k`\r\n * before joining and filtering, then caps to `limit`. Simple and correct;\r\n * not the efficient way to do this at a scale this project isn't at yet.\r\n */\r\n vectorSearch(projectId: string, embedding: Float32Array, limit = 20): VectorHit[] {\r\n const overfetch = Math.max(limit * 8, 50);\r\n return this.db\r\n .prepare(\r\n `SELECT n.id, n.kind, n.ts, n.title, n.body, n.signal, v.distance AS distance\r\n FROM nodes_vec v\r\n JOIN nodes n ON n.rowid = v.rowid\r\n WHERE v.embedding MATCH ? AND k = ? AND n.project_id = ?\r\n ORDER BY v.distance\r\n LIMIT ?`,\r\n )\r\n .all(embedding, overfetch, projectId, limit) as VectorHit[];\r\n }\r\n\r\n stats(projectId: string): StoreStats {\r\n const kinds = this.db\r\n .prepare('SELECT kind, COUNT(*) AS n FROM nodes WHERE project_id = ? GROUP BY kind')\r\n .all(projectId) as Array<{ kind: string; n: number }>;\r\n\r\n const range = this.db\r\n .prepare('SELECT MIN(ts) AS oldest, MAX(ts) AS newest FROM nodes WHERE project_id = ?')\r\n .get(projectId) as { oldest: string | null; newest: string | null };\r\n\r\n const files = this.db\r\n .prepare(\r\n `SELECT COUNT(DISTINCT f.path) AS n\r\n FROM node_files f JOIN nodes n ON n.id = f.node_id\r\n WHERE n.project_id = ?`,\r\n )\r\n .get(projectId) as { n: number };\r\n\r\n return {\r\n total: kinds.reduce((sum, k) => sum + k.n, 0),\r\n byKind: Object.fromEntries(kinds.map((k) => [k.kind, k.n])),\r\n oldest: range.oldest,\r\n newest: range.newest,\r\n distinctFiles: files.n,\r\n };\r\n }\r\n\r\n /**\r\n * Lexical search over the corpus.\r\n *\r\n * Title is weighted 10x body: a commit subject that names the thing you asked\r\n * about is far stronger evidence than the same word buried in a file list.\r\n * Ranking by `relevance x signal` happens a layer up, in retrieval.\r\n */\r\n search(projectId: string, query: string, limit = 20): SearchHit[] {\r\n const match = toMatchQuery(query);\r\n if (!match) return [];\r\n\r\n const rows = this.db\r\n .prepare(\r\n `SELECT n.id, n.kind, n.ts, n.title, n.body, n.signal,\r\n bm25(nodes_fts, 10.0, 1.0) AS rank\r\n FROM nodes_fts\r\n JOIN nodes n ON n.rowid = nodes_fts.rowid\r\n WHERE nodes_fts MATCH ? AND n.project_id = ?\r\n ORDER BY rank\r\n LIMIT ?`,\r\n )\r\n .all(match, projectId, limit) as NodeRow[];\r\n\r\n return rows;\r\n }\r\n\r\n /** Escape hatch for tests and future modules. */\r\n get raw(): Database.Database {\r\n return this.db;\r\n }\r\n}\r\n","/**\r\n * FTS5 has its own query language (`AND`, `OR`, `NEAR`, `*`, `^`, `:` column\r\n * filters). User input must never reach it raw -- a stray `\"` or a bare `OR`\r\n * turns a search into a syntax error.\r\n */\r\n\r\n/** Characters FTS5 treats as syntax rather than text. */\r\nconst FTS_SYNTAX = /[\"'()*:^{}[\\]-]/g;\r\n\r\n/**\r\n * Tokens too generic to carry search signal on their own. bm25 rewards rare\r\n * terms, so a token that is common in *meaning* but happens to be rare in\r\n * *this* corpus gets an inflated score purely from scarcity, not relevance --\r\n * verified live: a query ending in \"...PSID sender id\" pulled an unrelated\r\n * `winget install --id ...` shell command into the results, because \"id\"\r\n * alone prefix-matched \"--id\" with nothing else to weigh it down.\r\n *\r\n * Deliberately not a general English stopword list -- a real signal token\r\n * lost is worse than a little noise kept, so this only excludes what\r\n * dogfooding has actually shown to be a problem. Short technical terms like\r\n * \"ai\"/\"ui\"/\"db\" are left alone.\r\n */\r\nconst LOW_SIGNAL_TOKENS = new Set(['id']);\r\n\r\n/**\r\n * Exported for `failure-fix.ts`'s discussion-bridge heuristic, which layers\r\n * its own corpus-relative document-frequency filter on top of this\r\n * tokenization (see `filterBoilerplateTokens` there) -- the hardcoded\r\n * `LOW_SIGNAL_TOKENS` set here catches known-generic terms in general search,\r\n * but a term like a project's own tool name is only generic *in that\r\n * project's own corpus*, which requires a DB query this pure function\r\n * deliberately does not make.\r\n */\r\nexport function significantTokens(input: string): string[] {\r\n const tokens = input\r\n .replace(FTS_SYNTAX, ' ')\r\n .split(/\\s+/)\r\n .map((t) => t.trim())\r\n .filter((t) => t.length > 0);\r\n\r\n if (tokens.length === 0) return [];\r\n\r\n // Drop low-signal tokens, but never down to zero: a query that is only\r\n // \"id\" must still search for something rather than matching nothing.\r\n const signal = tokens.filter((t) => !LOW_SIGNAL_TOKENS.has(t.toLowerCase()));\r\n return signal.length > 0 ? signal : tokens;\r\n}\r\n\r\n/**\r\n * Turn free-form user text into a safe FTS5 MATCH expression.\r\n *\r\n * Each token becomes a quoted prefix term, and tokens are OR-ed so that a\r\n * multi-word question still finds partially matching nodes -- bm25 ranking\r\n * then rewards the nodes that matched more of them.\r\n */\r\nexport function toMatchQuery(input: string): string | null {\r\n const kept = significantTokens(input);\r\n if (kept.length === 0) return null;\r\n\r\n return kept.map((t) => `\"${t}\"*`).join(' OR ');\r\n}\r\n\r\n/**\r\n * Same tokenization as `toMatchQuery`, but AND-ed rather than OR-ed --\r\n * every significant token must appear for a match. Built for\r\n * `failure-fix.ts`'s discussion-bridge heuristic, which found (dogfooding\r\n * against this repo's real history) that a single shared generic token was\r\n * enough to link a failure to a wholly unrelated discussion, e.g. an\r\n * \"npm whoami\" failure matched to a summary that merely mentions \"npm\" in\r\n * passing. Requiring every token cuts recall -- a discussion that paraphrases\r\n * the command instead of naming it will not match -- but a false positive\r\n * here silently attaches the wrong \"fix\" to a real failure, which is worse\r\n * than finding none.\r\n */\r\nexport function toStrictMatchQuery(input: string): string | null {\r\n const kept = significantTokens(input);\r\n if (kept.length === 0) return null;\r\n\r\n return kept.map((t) => `\"${t}\"*`).join(' AND ');\r\n}\r\n","import type { Database } from 'better-sqlite3';\n\n/**\n * Schema migrations, applied in order and tracked via `PRAGMA user_version`.\n *\n * Migrations are append-only: never edit a shipped migration, add a new one.\n */\n\nconst V1 = `\nCREATE TABLE meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\nCREATE TABLE projects (\n id TEXT PRIMARY KEY,\n root TEXT NOT NULL,\n origin_url TEXT,\n created_at INTEGER NOT NULL,\n last_synced_at INTEGER\n);\n\nCREATE TABLE nodes (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n project_id TEXT NOT NULL,\n -- Human-readable ISO-8601 with offset, kept verbatim from the source event.\n ts TEXT NOT NULL,\n -- Same instant as epoch ms, so range scans and ordering never parse strings.\n ts_epoch INTEGER NOT NULL,\n source TEXT NOT NULL,\n title TEXT NOT NULL,\n body TEXT NOT NULL,\n signal REAL NOT NULL,\n meta TEXT NOT NULL DEFAULT '{}',\n created_at INTEGER NOT NULL\n);\n\nCREATE INDEX idx_nodes_project_ts ON nodes (project_id, ts_epoch DESC);\nCREATE INDEX idx_nodes_project_kind ON nodes (project_id, kind, ts_epoch DESC);\n\nCREATE TABLE node_files (\n node_id TEXT NOT NULL REFERENCES nodes (id) ON DELETE CASCADE,\n path TEXT NOT NULL,\n previous_path TEXT,\n insertions INTEGER,\n deletions INTEGER,\n is_binary INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (node_id, path)\n);\n\n-- Path-scoped recall (\"what happened to src/store/db.ts?\") is a first-class\n-- query, so it gets its own index rather than a scan over node_files.\nCREATE INDEX idx_node_files_path ON node_files (path);\n\n-- External-content FTS: the index stores no copy of the text, it points back\n-- at nodes.rowid. Halves the on-disk footprint of the searchable corpus.\nCREATE VIRTUAL TABLE nodes_fts USING fts5 (\n title,\n body,\n content = 'nodes',\n content_rowid = 'rowid',\n tokenize = 'unicode61 remove_diacritics 2'\n);\n\nCREATE TRIGGER nodes_fts_ai AFTER INSERT ON nodes BEGIN\n INSERT INTO nodes_fts (rowid, title, body) VALUES (new.rowid, new.title, new.body);\nEND;\n\nCREATE TRIGGER nodes_fts_ad AFTER DELETE ON nodes BEGIN\n INSERT INTO nodes_fts (nodes_fts, rowid, title, body) VALUES ('delete', old.rowid, old.title, old.body);\nEND;\n\nCREATE TRIGGER nodes_fts_au AFTER UPDATE ON nodes BEGIN\n INSERT INTO nodes_fts (nodes_fts, rowid, title, body) VALUES ('delete', old.rowid, old.title, old.body);\n INSERT INTO nodes_fts (rowid, title, body) VALUES (new.rowid, new.title, new.body);\nEND;\n\nCREATE TABLE sync_state (\n project_id TEXT NOT NULL,\n -- Collector identity, e.g. 'git' or 'shell:pwsh'.\n source TEXT NOT NULL,\n -- Opaque to the store; for git this is the HEAD sha at last successful sync.\n cursor TEXT,\n last_run_at INTEGER,\n PRIMARY KEY (project_id, source)\n);\n`;\n\n/**\n * nomic-embed-text produces 768-dimensional vectors (confirmed against a\n * live Ollama call, not assumed). `vec0` fixes a table's dimension at\n * creation time, so switching embedding models later means a new\n * migration and a re-embed, not an in-place change to this one.\n */\nexport const EMBEDDING_DIM = 768;\n\nconst V2 = `\n-- Unlike nodes_fts, this is NOT trigger-populated: computing an embedding\n-- means an async call to an external model, which a synchronous SQL trigger\n-- cannot make. Rows are written explicitly by the embedding pass in\n-- vector/embed.ts, keyed by the same rowid nodes_fts already uses.\nCREATE VIRTUAL TABLE nodes_vec USING vec0 (\n embedding float[${EMBEDDING_DIM}]\n);\n`;\n\nconst V3 = `\n-- A relation between two existing nodes, not a new content node -- the\n-- \"failure -> fix\" correlation is the relationship itself, and duplicating\n-- either side's content into a third node would just be another\n-- independently-ranked candidate instead of the link the feature needs.\n-- One physical table, multiple relation kinds; 'resolved_by' is the first.\nCREATE TABLE node_links (\n from_node_id TEXT NOT NULL REFERENCES nodes (id) ON DELETE CASCADE,\n to_node_id TEXT NOT NULL REFERENCES nodes (id) ON DELETE CASCADE,\n relation TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (from_node_id, to_node_id, relation)\n);\n\n-- Packing a failure node needs its resolutions; nothing needs the reverse\n-- direction yet, so only the forward lookup gets an index.\nCREATE INDEX idx_node_links_from ON node_links (from_node_id);\n`;\n\ninterface Migration {\n version: number;\n up: (db: Database) => void;\n}\n\nconst MIGRATIONS: Migration[] = [\n { version: 1, up: (db) => db.exec(V1) },\n { version: 2, up: (db) => db.exec(V2) },\n { version: 3, up: (db) => db.exec(V3) },\n];\n\nexport const LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;\n\nexport function currentSchemaVersion(db: Database): number {\n return Number(db.pragma('user_version', { simple: true }) ?? 0);\n}\n\nexport function migrate(db: Database): { from: number; to: number } {\n const from = currentSchemaVersion(db);\n\n for (const migration of MIGRATIONS) {\n if (migration.version <= from) continue;\n db.transaction(() => {\n migration.up(db);\n db.pragma(`user_version = ${migration.version}`);\n })();\n }\n\n return { from, to: currentSchemaVersion(db) };\n}\n","import pc from 'picocolors';\r\nimport { forgetProjects, readLiveRegistry, registryPath } from '../../config/registry.js';\r\nimport { MemoryStore } from '../../store/store.js';\r\n\r\nexport interface ProjectsOptions {\r\n /** Forget registered projects whose database is no longer on disk. */\r\n prune: boolean;\r\n json: boolean;\r\n}\r\n\r\n/**\r\n * What `query --all-projects` would search, and what it would skip.\r\n *\r\n * Cross-project recall reads from databases outside the current repository,\r\n * which is exactly the kind of thing a user should be able to inspect before\r\n * trusting it. This is that inspection.\r\n */\r\nexport async function runProjects(opts: ProjectsOptions): Promise<number> {\r\n const { entries, missing } = await readLiveRegistry();\r\n\r\n const rows = entries.map((entry) => {\r\n let nodes: number | null = null;\r\n try {\r\n const store = MemoryStore.open(entry.dbPath);\r\n try {\r\n nodes = store.stats(entry.projectId).total;\r\n } finally {\r\n store.close();\r\n }\r\n } catch {\r\n // Present but unopenable (corrupt file, a lock we cannot take, a\r\n // native module mismatch). Reported as unknown rather than as zero,\r\n // which would read as \"synced and empty\".\r\n nodes = null;\r\n }\r\n return { ...entry, nodes };\r\n });\r\n\r\n if (opts.prune) {\r\n const removed = await forgetProjects(missing.map((entry) => entry.projectId));\r\n process.stderr.write(`${pc.yellow('pruned')} ${removed} project(s) whose database is gone\\n`);\r\n }\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify({ registry: registryPath(), projects: rows, missing }, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n process.stderr.write(`${pc.dim('registry')} ${registryPath()}\\n\\n`);\r\n\r\n if (rows.length === 0) {\r\n process.stderr.write(`${pc.yellow('no projects registered')} -- run ${pc.bold('nexusmem sync')} in a repository\\n`);\r\n return 0;\r\n }\r\n\r\n for (const row of rows) {\r\n const seen = new Date(row.lastSeenAt).toISOString().slice(0, 16).replace('T', ' ');\r\n const count = row.nodes === null ? pc.yellow('unreadable') : `${row.nodes} node(s)`;\r\n process.stdout.write(`${pc.cyan(row.projectId.slice(0, 8))} ${row.root}\\n ${pc.dim(`${count}, last seen ${seen}`)}\\n`);\r\n }\r\n\r\n if (!opts.prune && missing.length > 0) {\r\n process.stderr.write(\r\n `\\n${pc.yellow(`${missing.length} registered project(s) have no database on disk`)}` +\r\n ` ${pc.dim('-- run with --prune to forget them')}\\n`,\r\n );\r\n for (const entry of missing) process.stderr.write(` ${pc.dim(entry.root)}\\n`);\r\n }\r\n\r\n return 0;\r\n}\r\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\r\nimport { z } from 'zod';\r\nimport { readOwnVersion } from '../core/version.js';\r\nimport { getStatus, listRecentMemory, searchMemory, syncProject } from './tools.js';\r\n\r\n/**\r\n * Local-only, stdio-transport MCP server exposing NexusMem's memory to any\r\n * MCP-capable client (Claude Desktop, Cursor, Windsurf, ...). No auth layer:\r\n * a client that can spawn this process already has the same filesystem\r\n * access the process itself would use.\r\n */\r\nexport function createServer(): McpServer {\r\n const server = new McpServer({ name: 'nexusmem', version: readOwnVersion() });\r\n\r\n server.registerTool(\r\n 'search_memory',\r\n {\r\n title: 'Search remembered project history',\r\n description:\r\n 'Search a NexusMem-tracked repository\\'s remembered history: git commits, code diffs (the patch of each changed file), shell commands, tracked markdown docs, and (if enabled) conversation transcripts and per-session summaries. Returns a token-budgeted, ranked context block -- not raw search results.',\r\n inputSchema: {\r\n projectRoot: z.string().describe('Absolute path to the repository root'),\r\n query: z.string().describe('Free-text question or search terms'),\r\n budget: z.number().int().positive().optional().describe('Max tokens in the returned context block. Default 2000.'),\r\n allProjects: z\r\n .boolean()\r\n .optional()\r\n .describe(\r\n 'Search every repository NexusMem has been run in on this machine, not just projectRoot. Use when the answer may live in a different project (a pattern solved elsewhere, a tool that failed the same way before). Each result is tagged with its repository.',\r\n ),\r\n },\r\n },\r\n async ({ projectRoot, query, budget, allProjects }) => {\r\n const result = await searchMemory({ projectRoot, query, budget, allProjects });\r\n // The packed context block goes in BOTH fields: clients differ on which\r\n // one they surface to the model, and a client that prefers\r\n // structuredContent would otherwise see only the match stats -- the\r\n // block itself (the tool's entire value) silently dropped.\r\n return {\r\n content: [{ type: 'text', text: result.text }],\r\n structuredContent: {\r\n text: result.text,\r\n matched: result.matched,\r\n bm25Matched: result.bm25Matched,\r\n vectorMatched: result.vectorMatched,\r\n tokensUsed: result.tokensUsed,\r\n tokensBudget: result.tokensBudget,\r\n projectsSearched: result.projectsSearched,\r\n } as Record<string, unknown>,\r\n };\r\n },\r\n );\r\n\r\n server.registerTool(\r\n 'sync_project',\r\n {\r\n title: 'Sync remembered history',\r\n description:\r\n 'Ingest new git, diff, shell, docs and (if enabled) conversation history for a NexusMem-tracked repository into its local database. ' +\r\n 'Pass pruneSource or pruneStaleShell instead to delete a dead source\\'s nodes (e.g. the pre-hook shell scrape) rather than syncing -- ' +\r\n 'dry-run unless yes is also true, since this is an irreversible full wipe of that source.',\r\n inputSchema: {\r\n projectRoot: z.string().describe('Absolute path to the repository root'),\r\n pruneSource: z.string().optional().describe('Delete every node from this exact source (e.g. \"shell:pwsh\") instead of syncing'),\r\n pruneStaleShell: z\r\n .boolean()\r\n .optional()\r\n .describe('Shortcut for pruneSource on shell:pwsh, shell:bash and shell:zsh at once -- the dead pre-hook scrape sources'),\r\n yes: z.boolean().optional().describe('Confirms the delete. Without it, pruneSource/pruneStaleShell only report the matching count.'),\r\n },\r\n },\r\n async ({ projectRoot, pruneSource, pruneStaleShell, yes }) => {\r\n const result = await syncProject({ projectRoot, pruneSource, pruneStaleShell, yes });\r\n return { content: [{ type: 'text', text: result.summary }] };\r\n },\r\n );\r\n\r\n server.registerTool(\r\n 'get_status',\r\n {\r\n title: 'Show what is remembered',\r\n description: 'Report how many nodes NexusMem currently remembers for a repository, broken down by kind and source.',\r\n inputSchema: {\r\n projectRoot: z.string().describe('Absolute path to the repository root'),\r\n },\r\n },\r\n async ({ projectRoot }) => {\r\n const result = await getStatus({ projectRoot });\r\n return {\r\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\r\n structuredContent: result as unknown as Record<string, unknown>,\r\n };\r\n },\r\n );\r\n\r\n server.registerTool(\r\n 'list_recent_memory',\r\n {\r\n title: 'List recently remembered items',\r\n description:\r\n 'List the most recently remembered items for a NexusMem-tracked repository -- git commits, code diffs, shell commands, tracked docs, and (if enabled) conversation transcripts and session summaries -- newest first. Chronological, not relevance-ranked: use search_memory instead for a specific question.',\r\n inputSchema: {\r\n projectRoot: z.string().describe('Absolute path to the repository root'),\r\n limit: z.number().int().positive().optional().describe('Max items to return, newest first. Default 20.'),\r\n },\r\n },\r\n async ({ projectRoot, limit }) => {\r\n const result = await listRecentMemory({ projectRoot, limit });\r\n return {\r\n content: [{ type: 'text', text: JSON.stringify(result.items, null, 2) }],\r\n structuredContent: { items: result.items } as unknown as Record<string, unknown>,\r\n };\r\n },\r\n );\r\n\r\n return server;\r\n}\r\n\r\nexport async function runMcpServer(): Promise<void> {\r\n const server = createServer();\r\n const transport = new StdioServerTransport();\r\n await server.connect(transport);\r\n}\r\n","import { basename } from 'node:path';\r\nimport { makeProjectId } from '../core/project.js';\r\nimport { readRepoInfo } from '../git/repo.js';\r\nimport { renderContextBlock } from '../retrieval/pack.js';\r\nimport { runCrossProjectQuery, runHybridQuery } from '../retrieval/query-pipeline.js';\r\nimport { openAllProjectSources } from '../retrieval/sources.js';\r\nimport { resolveWorkspace } from '../config/workspace.js';\r\nimport { MemoryStore, type RecentNode } from '../store/store.js';\r\nimport { OllamaEmbeddingProvider } from '../vector/embed.js';\r\nimport { runInit } from '../cli/commands/init.js';\r\nimport { runSync, type SyncOptions } from '../cli/commands/sync.js';\r\n\r\n/**\r\n * Thin MCP-facing wrappers over the same CLI logic (`runSync`, the hybrid\r\n * query pipeline, `MemoryStore.stats`) -- no new business logic here. Every\r\n * tool takes an explicit `projectRoot` because, unlike a terminal command,\r\n * an MCP tool call carries no implicit shell cwd.\r\n */\r\n\r\nexport interface SearchMemoryInput {\r\n projectRoot: string;\r\n query: string;\r\n budget?: number;\r\n candidates?: number;\r\n /** BM25 only -- skip embedding the query and vector search. Mainly for tests; real callers want hybrid retrieval. */\r\n noVector?: boolean;\r\n /** Search every repository NexusMem has been run in on this machine, not just `projectRoot`. */\r\n allProjects?: boolean;\r\n}\r\n\r\nexport interface SearchMemoryOutput {\r\n text: string;\r\n matched: number;\r\n bm25Matched: number;\r\n vectorMatched: number;\r\n tokensUsed: number;\r\n tokensBudget: number;\r\n /** Names of the repositories actually searched -- one entry unless `allProjects` was set. */\r\n projectsSearched: string[];\r\n}\r\n\r\nexport async function searchMemory(input: SearchMemoryInput): Promise<SearchMemoryOutput> {\r\n const repo = await readRepoInfo(input.projectRoot);\r\n const ws = resolveWorkspace(repo.root);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n const budget = input.budget ?? 2000;\r\n const candidates = input.candidates ?? 30;\r\n const queryOpts = {\r\n budget,\r\n candidates,\r\n embeddingProvider: input.noVector ? null : new OllamaEmbeddingProvider(),\r\n };\r\n\r\n if (input.allProjects) {\r\n const opened = await openAllProjectSources({ projectId, root: repo.root, dbPath: ws.dbPath });\r\n try {\r\n const { bm25Count, vectorCount, hits, packed } = await runCrossProjectQuery(opened.sources, input.query, queryOpts);\r\n return {\r\n text: renderContextBlock(input.query, packed),\r\n matched: hits.length,\r\n bm25Matched: bm25Count,\r\n vectorMatched: vectorCount,\r\n tokensUsed: packed.tokensUsed,\r\n tokensBudget: packed.tokensBudget,\r\n projectsSearched: opened.sources.map((s) => s.label),\r\n };\r\n } finally {\r\n opened.close();\r\n }\r\n }\r\n\r\n const store = MemoryStore.open(ws.dbPath);\r\n try {\r\n const { bm25Count, vectorCount, hits, packed } = await runHybridQuery(store, projectId, input.query, queryOpts);\r\n\r\n return {\r\n text: renderContextBlock(input.query, packed),\r\n matched: hits.length,\r\n bm25Matched: bm25Count,\r\n vectorMatched: vectorCount,\r\n tokensUsed: packed.tokensUsed,\r\n tokensBudget: packed.tokensBudget,\r\n projectsSearched: [basename(repo.root) || repo.root],\r\n };\r\n } finally {\r\n store.close();\r\n }\r\n}\r\n\r\nexport interface SyncProjectInput {\r\n projectRoot: string;\r\n /** Skip the embedding pass for this sync. Mainly for tests; real callers want vectors kept fresh. */\r\n noEmbed?: boolean;\r\n /** Delete every node from this exact source (e.g. `shell:pwsh`) instead of syncing. Dry-run unless `yes` is also set. */\r\n pruneSource?: string;\r\n /** Shortcut for `pruneSource` on shell:pwsh, shell:bash and shell:zsh at once -- the dead pre-hook scrape sources. */\r\n pruneStaleShell?: boolean;\r\n /** Confirms an irreversible `pruneSource`/`pruneStaleShell` delete. Without it, the matching count is returned and nothing is removed. */\r\n yes?: boolean;\r\n}\r\n\r\nexport interface SyncProjectOutput {\r\n summary: string;\r\n}\r\n\r\n/**\r\n * Collects `runInit`/`runSync`'s summary through an explicit sink rather than\r\n * by reassigning `process.stdout.write`, which is what this used to do.\r\n *\r\n * That mattered more than it looked: under the stdio transport, `stdout` *is*\r\n * the JSON-RPC channel. `StdioServerTransport` holds the stream object and\r\n * resolves `.write` at send time, so a patched `write` also intercepts\r\n * protocol traffic -- a response emitted while a sync was running would land\r\n * in the capture buffer, and the patch's unconditional `return true` would\r\n * report it as delivered. Overlapping syncs compounded it: the second call\r\n * saved the first call's patch as \"the original\" and restored that instead.\r\n *\r\n * Passing a sink keeps the single shared implementation (the reason for the\r\n * original trade) without borrowing a global that something else owns.\r\n *\r\n * Always runs `init` first: an MCP client has no reason to know this tool\r\n * needs a separate init step, and `runInit` is already a safe no-op (just a\r\n * printed notice) when the project is initialized already.\r\n */\r\nexport async function syncProject(input: SyncProjectInput): Promise<SyncProjectOutput> {\r\n const chunks: string[] = [];\r\n const out = (chunk: string) => {\r\n chunks.push(chunk);\r\n };\r\n\r\n await runInit({ cwd: input.projectRoot, force: false, hook: false, enableConversation: false, out });\r\n\r\n const opts: SyncOptions = {\r\n cwd: input.projectRoot,\r\n full: false,\r\n rebuild: false,\r\n quiet: true,\r\n noEmbed: input.noEmbed,\r\n pruneSource: input.pruneSource,\r\n pruneStaleShell: input.pruneStaleShell,\r\n yes: input.yes,\r\n out,\r\n };\r\n await runSync(opts);\r\n\r\n return { summary: stripAnsi(chunks.join('').trim()) };\r\n}\r\n\r\n/**\r\n * Strips ANSI SGR color codes (`\\x1b[...m`) from CLI-formatted text before\r\n * it leaves the MCP boundary.\r\n *\r\n * `runInit`/`runSync` format their `out` stream with picocolors for\r\n * terminal display -- correct there. But picocolors' own source\r\n * (`node_modules/picocolors/picocolors.js`) treats `platform === 'win32'`\r\n * as sufficient evidence of color support on its own; it never checks\r\n * `process.stdout.isTTY`. This process's stdout is the MCP JSON-RPC\r\n * channel, piped, never a terminal, on every platform -- so on Windows the\r\n * summary came out colorized regardless. Confirmed live, not just reasoned\r\n * about: a real MCP client (the VS Code extension's Output channel)\r\n * rendered the raw escape codes as literal text.\r\n */\r\nfunction stripAnsi(text: string): string {\r\n return text.replace(/\\x1b\\[[0-9;]*m/g, '');\r\n}\r\n\r\nexport interface ListRecentMemoryInput {\r\n projectRoot: string;\r\n /** Max items to return, newest first. Default 20. */\r\n limit?: number;\r\n}\r\n\r\nexport interface ListRecentMemoryOutput {\r\n items: RecentNode[];\r\n}\r\n\r\n/**\r\n * Chronology, not relevance -- \"what has this project's memory recorded\r\n * lately\" rather than \"what answers this question\" (that's `searchMemory`).\r\n * Built for the VS Code extension's sidebar view, which lists rather than\r\n * searches.\r\n */\r\nexport async function listRecentMemory(input: ListRecentMemoryInput): Promise<ListRecentMemoryOutput> {\r\n const repo = await readRepoInfo(input.projectRoot);\r\n const ws = resolveWorkspace(repo.root);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const store = MemoryStore.open(ws.dbPath);\r\n try {\r\n return { items: store.listRecentNodes(projectId, input.limit) };\r\n } finally {\r\n store.close();\r\n }\r\n}\r\n\r\nexport interface GetStatusInput {\r\n projectRoot: string;\r\n}\r\n\r\nexport interface GetStatusOutput {\r\n total: number;\r\n byKind: Record<string, number>;\r\n sources: Array<{ source: string; cursor: string | null; lastRunAt: number | null }>;\r\n}\r\n\r\nexport async function getStatus(input: GetStatusInput): Promise<GetStatusOutput> {\r\n const repo = await readRepoInfo(input.projectRoot);\r\n const ws = resolveWorkspace(repo.root);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const store = MemoryStore.open(ws.dbPath);\r\n try {\r\n const stats = store.stats(projectId);\r\n return { total: stats.total, byKind: stats.byKind, sources: store.listSyncState(projectId) };\r\n } finally {\r\n store.close();\r\n }\r\n}\r\n","export function truncate(s: string, max: number): string {\n return s.length <= max ? s : `${s.slice(0, max - 1).trimEnd()}…`;\n}\n\n/** Rough heuristic for English-dominant text: ~4 chars per token. */\nexport function approxTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n","import { approxTokens, truncate } from '../core/text.js';\r\nimport type { RankedHit } from './rank.js';\r\n\r\nexport interface PackedNode {\r\n id: string;\r\n kind: string;\r\n ts: string;\r\n title: string;\r\n signal: number;\r\n score: number;\r\n summary: string;\r\n tokens: number;\r\n /** Set only for a cross-project query, where a line's repository is not implied by context. */\r\n project?: string;\r\n}\r\n\r\nexport interface PackResult {\r\n nodes: PackedNode[];\r\n tokensUsed: number;\r\n tokensBudget: number;\r\n consideredNodes: number;\r\n droppedForBudget: number;\r\n droppedForDiversity: number;\r\n}\r\n\r\nconst DEFAULT_SUMMARY_CHARS = 320;\r\n/** Formatting overhead per node (date prefix, bullet, line breaks) counted as tokens. */\r\nconst NODE_OVERHEAD_TOKENS = 8;\r\n\r\nconst CONVERSATION_ANSWER_MARKER = '\\n\\nA: ';\r\n\r\n/**\r\n * At most this many hits from the same original document may appear in one\r\n * packed result.\r\n *\r\n * `conversation_turn` and `doc_section` both chunk one long reply or file\r\n * into several nodes that share the *same* `ts` (see\r\n * `collectors/conversation.ts` and `collectors/docs.ts`) -- there is no\r\n * per-chunk parent id available at this layer, but the shared timestamp\r\n * already identifies \"pieces of the same original text\" in practice, since\r\n * two unrelated exchanges or files are never indexed in the same\r\n * millisecond. Verified live: a query for \"token\" returned 9 of its top 12\r\n * hits as different chunks of one heavily-sectioned conversation reply,\r\n * crowding out every other node -- including the actually relevant one.\r\n * Kept at 2 rather than 1 so a reply that is genuinely relevant in two\r\n * places still shows both, without letting either dominate.\r\n */\r\nconst MAX_PER_FAMILY = 2;\r\nconst CHUNKED_KINDS = new Set(['conversation_turn', 'doc_section']);\r\n\r\n/** Start of a hunk, at the beginning of a line. */\r\nconst HUNK_BOUNDARY = '\\n@@ ';\r\n\r\n/**\r\n * Words carried by almost every question, and therefore by almost every hunk.\r\n *\r\n * Only used to choose between hunks of one node -- BM25 has its own view of\r\n * term weight and is untouched by this list.\r\n */\r\nconst STOPWORDS = new Set([\r\n 'the', 'and', 'for', 'are', 'was', 'were', 'that', 'this', 'with', 'from', 'into', 'not', 'but',\r\n 'what', 'why', 'how', 'when', 'where', 'which', 'who', 'does', 'did', 'has', 'have', 'had', 'can',\r\n 'could', 'would', 'should', 'all', 'any', 'every', 'each', 'its', 'our', 'you', 'your', 'about',\r\n]);\r\n\r\nfunction queryTerms(query: string): string[] {\r\n const words = query.toLowerCase().match(/[a-z0-9_]{3,}/g) ?? [];\r\n return [...new Set(words.filter((w) => !STOPWORDS.has(w)).map(singularize))];\r\n}\r\n\r\n/**\r\n * Word pieces of a line of code.\r\n *\r\n * Splitting on case transitions as well as punctuation is what lets a natural\r\n * question meet an identifier: `RETRY_DELAYS_MS` and `SpawnOptions` become\r\n * `retry delays ms` and `spawn options`, so \"retry delays\" and \"spawn\" find\r\n * them. A plain `\\b` word match finds neither, because `_` is a word character\r\n * and a camel hump is not a boundary at all.\r\n */\r\nfunction codeTokens(text: string): Set<string> {\r\n const spaced = text.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();\r\n return new Set((spaced.match(/[a-z0-9]{2,}/g) ?? []).map(singularize));\r\n}\r\n\r\n/** Crude, deliberately: enough to let \"spawns\" meet `spawn`, and nothing more. */\r\nfunction singularize(word: string): string {\r\n return word.length > 3 && word.endsWith('s') && !word.endsWith('ss') ? word.slice(0, -1) : word;\r\n}\r\n\r\n/**\r\n * The part of a patch worth spending the budget on.\r\n *\r\n * A summary is a few hundred characters and a real patch is thousands, so\r\n * taking the head means showing whichever hunk happens to sit at the top of\r\n * the file -- an import block, or the licence comment. Dogfooding this on\r\n * `src/git/exec.ts` returned the right file for \"what flags are passed to\r\n * every git invocation\" and then showed a class definition seventy lines above\r\n * the answer.\r\n *\r\n * Term overlap is a crude relevance measure, but it is measured against the\r\n * *hunks of one already-retrieved node*, where ranking has done its job and\r\n * the only question left is which few lines to show.\r\n */\r\nfunction pickHunk(patch: string, query: string): string {\r\n const first = patch.indexOf('@@ ');\r\n if (first === -1) return patch;\r\n\r\n const hunks: string[] = [];\r\n let rest = patch.slice(first);\r\n for (;;) {\r\n const next = rest.indexOf(HUNK_BOUNDARY, 1);\r\n if (next === -1) {\r\n hunks.push(rest);\r\n break;\r\n }\r\n hunks.push(rest.slice(0, next));\r\n rest = rest.slice(next + 1);\r\n }\r\n\r\n const terms = queryTerms(query);\r\n if (terms.length === 0) return hunks[0] ?? patch;\r\n\r\n let best = hunks[0] ?? patch;\r\n let bestScore = 0;\r\n for (const hunk of hunks) {\r\n const tokens = codeTokens(hunk);\r\n // Whole tokens, not substrings: a query about git must not score every\r\n // hunk in a repository alike because the letters appear inside some\r\n // longer identifier.\r\n const score = terms.reduce((n, term) => n + (tokens.has(term) ? 1 : 0), 0);\r\n if (score > bestScore) {\r\n best = hunk;\r\n bestScore = score;\r\n }\r\n }\r\n return focusHunk(best, terms);\r\n}\r\n\r\n/**\r\n * Drop leading context so the summary starts at the change.\r\n *\r\n * A hunk opens with up to three unchanged lines, and a 320-character summary\r\n * is about six lines: taken from the top, a hunk can spend its entire budget\r\n * on code that did not change and stop one line short of the one that did.\r\n * The hunk header is kept -- it names the enclosing function, which is how a\r\n * reader locates the excerpt.\r\n */\r\nfunction focusHunk(hunk: string, terms: string[]): string {\r\n const lines = hunk.split('\\n');\r\n const header = lines[0] ?? '';\r\n const body = lines.slice(1);\r\n const isChange = (line: string) => line.startsWith('+') || line.startsWith('-');\r\n\r\n let idx = terms.length\r\n ? body.findIndex((line) => {\r\n if (!isChange(line)) return false;\r\n const tokens = codeTokens(line);\r\n return terms.some((term) => tokens.has(term));\r\n })\r\n : -1;\r\n if (idx === -1) idx = body.findIndex(isChange);\r\n if (idx <= 1) return hunk;\r\n\r\n // One line of context above the change, so it does not read as free-floating.\r\n return [header, ...body.slice(idx - 1)].join('\\n');\r\n}\r\n\r\nfunction summarize(hit: RankedHit, maxChars: number, query: string): string {\r\n // Conversation nodes always shape their body as \"Q: <question>\\n\\nA:\r\n // <answer>\" (collectors/conversation.ts repeats the full original question\r\n // in every chunk so each node is self-contained). The answer is the part\r\n // worth showing -- truncating from the start of the body would otherwise\r\n // spend the whole summary on a long question and never reach it.\r\n const answerIdx = hit.body.indexOf(CONVERSATION_ANSWER_MARKER);\r\n if (answerIdx !== -1) {\r\n const answer = hit.body.slice(answerIdx + CONVERSATION_ANSWER_MARKER.length).trim();\r\n if (answer) return truncate(answer, maxChars);\r\n }\r\n\r\n // A diff body is \"<subject>\\n<file line>\\n\\n<hunks>\": keep the two header\r\n // lines, which say what changed and by how much, then spend the rest of the\r\n // budget on the hunk that matches the question.\r\n if (hit.kind === 'code_diff') {\r\n const patchStart = hit.body.indexOf(HUNK_BOUNDARY);\r\n if (patchStart !== -1) {\r\n const head = hit.body.slice(0, patchStart).trim();\r\n const hunk = pickHunk(hit.body.slice(patchStart + 1), query);\r\n return truncate(`${head}\\n${hunk}`, maxChars);\r\n }\r\n }\r\n\r\n // Body already leads with the title; skip straight to whatever follows it\r\n // so the summary doesn't repeat text that's already shown as the heading.\r\n const rest = hit.body.startsWith(hit.title) ? hit.body.slice(hit.title.length).trim() : hit.body;\r\n return truncate(rest || hit.title, maxChars);\r\n}\r\n\r\n/**\r\n * Greedily fill a token budget with the highest-scoring hits.\r\n *\r\n * Nodes are tried strictly in score order. One that doesn't fit is skipped,\r\n * not a stopping point -- a later, smaller, lower-priority node may still\r\n * fit the remaining budget. This is best-effort packing, not knapsack-\r\n * optimal, but it keeps \"why is node X included\" answerable by score alone.\r\n */\r\nexport function packContext(\r\n ranked: readonly RankedHit[],\r\n tokensBudget: number,\r\n opts: { summaryChars?: number; query?: string } = {},\r\n): PackResult {\r\n const summaryChars = opts.summaryChars ?? DEFAULT_SUMMARY_CHARS;\r\n const query = opts.query ?? '';\r\n const nodes: PackedNode[] = [];\r\n let tokensUsed = 0;\r\n let droppedForBudget = 0;\r\n let droppedForDiversity = 0;\r\n const familyCounts = new Map<string, number>();\r\n\r\n for (const hit of ranked) {\r\n const familyKey = CHUNKED_KINDS.has(hit.kind) ? `${hit.kind}:${hit.ts}` : null;\r\n if (familyKey && (familyCounts.get(familyKey) ?? 0) >= MAX_PER_FAMILY) {\r\n droppedForDiversity += 1;\r\n continue;\r\n }\r\n\r\n const summary = summarize(hit, summaryChars, query);\r\n const tokens = approxTokens(hit.title) + approxTokens(summary) + NODE_OVERHEAD_TOKENS;\r\n\r\n if (tokensUsed + tokens > tokensBudget) {\r\n droppedForBudget += 1;\r\n continue;\r\n }\r\n\r\n nodes.push({\r\n id: hit.id,\r\n kind: hit.kind,\r\n ts: hit.ts,\r\n title: hit.title,\r\n signal: hit.signal,\r\n score: hit.score,\r\n summary,\r\n tokens,\r\n ...(hit.project ? { project: hit.project } : {}),\r\n });\r\n tokensUsed += tokens;\r\n // Only a hit that actually made it in spends its family's slot -- one\r\n // skipped for budget must not block a smaller sibling later in the list.\r\n if (familyKey) familyCounts.set(familyKey, (familyCounts.get(familyKey) ?? 0) + 1);\r\n }\r\n\r\n return { nodes, tokensUsed, tokensBudget, consideredNodes: ranked.length, droppedForBudget, droppedForDiversity };\r\n}\r\n\r\n/** Render a packed result as plain text, ready to paste into an agent's context. */\r\nexport function renderContextBlock(query: string, result: PackResult): string {\r\n if (result.nodes.length === 0) return `No remembered context matched \"${query}\".`;\r\n\r\n const lines = [`Relevant history for: ${query}`, ''];\r\n for (const node of result.nodes) {\r\n // The project tag is the whole point of a cross-project answer: without\r\n // it the reader cannot tell which repository a line describes, and two\r\n // repositories' conventions read as one contradictory history.\r\n const project = node.project ? `[${node.project}] ` : '';\r\n lines.push(`- ${node.ts.slice(0, 10)} ${project}${node.title}`);\r\n if (node.summary && node.summary !== node.title) {\r\n // A patch is the one body whose line structure *is* the content:\r\n // flattened onto one line, `- return a;` and `+ return b;` become an\r\n // unreadable run of tokens. Every other kind is prose, where collapsing\r\n // whitespace keeps one node to one line.\r\n if (node.kind === 'code_diff') {\r\n for (const line of node.summary.split('\\n')) lines.push(` ${line}`);\r\n } else {\r\n lines.push(` ${node.summary.replace(/\\n+/g, ' ')}`);\r\n }\r\n }\r\n }\r\n return lines.join('\\n');\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport { significantTokens } from '../store/fts.js';\r\nimport type { MemoryStore } from '../store/store.js';\r\n\r\n/**\r\n * Links a failed `shell_command` node to whatever later resolved it --\r\n * Phase 7's \"failure -> fix chain\" building block. Two independent,\r\n * deliberately narrow heuristics; a failure can be linked by either, both,\r\n * or neither. Both are unvalidated until dogfooded against a real corpus\r\n * (see ROADMAP.local.md's Phase 7 entry) -- this is a first pass sized for\r\n * that validation, not a claim that either heuristic is correct yet.\r\n *\r\n * - **Same-command retry.** A later `shell_command` in the same project and\r\n * `cwd`, the *exact* normalized command text (trim + collapse whitespace +\r\n * lowercase), `exitCode === 0`, within `retryWindowMs`. High precision by\r\n * construction, low recall: a fix that changes the command itself (a typo\r\n * correction, an added flag) is invisible to an exact-text match. Not\r\n * attempted here -- fuzzy matching is a stretch goal, not this pass's job.\r\n * - **Conversation bridge.** The best FTS match (AND of every significant,\r\n * non-boilerplate token in the failing command) among\r\n * `conversation_turn`/`session_summary` nodes in the following\r\n * `discussionWindowMs`. Originally used an OR-of-tokens match and was\r\n * dogfooded against this repo's real history 2026-08-15: roughly half\r\n * the links were wrong, and the confirmed false positives were all driven\r\n * by a single shared generic token (e.g. an \"npm whoami\" failure linked to\r\n * an unrelated summary that just happens to mention \"npm\"). Tightened to\r\n * AND -- still loose in the other direction, since a discussion that\r\n * paraphrases the command instead of naming its words will not match, but\r\n * an unvalidated false positive is worse than a missed true positive here.\r\n * Does not chain further to whatever commit that conversation might cite;\r\n * linking failure -> discussion is the whole claim this heuristic makes.\r\n *\r\n * Re-dogfooded at larger scale 2026-08-16 against a second real project\r\n * (`villa-bot`, previously unseen by this heuristic): the AND fix held on\r\n * this repo's own 5 links (still 5/5 correct) but missed a new false-\r\n * positive class the small original sample never surfaced -- a command\r\n * made entirely of the tool's own boilerplate words (`nexusmem sync`)\r\n * AND-matched an unrelated turn that just happened to show the same\r\n * command as generic advice. bm25 score could not separate this from a\r\n * true positive (measured: the false positive scored -9.685, *stronger*\r\n * than two real true positives at -5.899/-6.559) -- bm25 rewards rarity\r\n * *within whatever corpus it's run against*, and in villa-bot's smaller\r\n * corpus those words hadn't accumulated enough occurrences to be\r\n * recognized as boilerplate, even though the same words measure 33-39%\r\n * document frequency in this repo's own (more self-referential) history.\r\n * `filterBoilerplateTokens` below adds that corpus-relative check as a\r\n * second filtering pass. Note honestly: at villa-bot's actual measured\r\n * frequency for those words (9.3%/4.6%, comfortably under the threshold),\r\n * this pass does *not* retroactively catch that specific instance -- it\r\n * was a low-frequency AND-coincidence, not corpus saturation. What it does\r\n * protect against is the class the numbers actually support: a command\r\n * whose words are truly ubiquitous in a project's own history (like this\r\n * repo's own name/verbs), which the villa-bot corpus wasn't saturated with\r\n * yet but plausibly will be over time, and which this repo's corpus\r\n * already is.\r\n */\r\n\r\nexport interface CorrelateOptions {\r\n /** How long after a failure a same-command retry may count as its resolution. Default 24h. */\r\n retryWindowMs?: number;\r\n /** How long after a failure a conversation may count as discussing it. Default 24h. */\r\n discussionWindowMs?: number;\r\n}\r\n\r\nexport interface CorrelateStats {\r\n failuresExamined: number;\r\n linkedByRetry: number;\r\n linkedByDiscussion: number;\r\n}\r\n\r\nconst DEFAULT_RETRY_WINDOW_MS = 24 * 60 * 60 * 1000;\r\nconst DEFAULT_DISCUSSION_WINDOW_MS = 24 * 60 * 60 * 1000;\r\n\r\n/**\r\n * One relation string per heuristic, not a shared `resolved_by` -- dogfooding\r\n * against this repo's real history (2026-08-15) found the retry heuristic\r\n * correct on every manually-checked link, but the discussion heuristic wrong\r\n * on roughly half. A consumer (e.g. `pack.ts`) needs to trust one and ignore\r\n * the other; a single relation string could not express that distinction\r\n * without also tagging every row, which the relation string already does\r\n * for free.\r\n */\r\nexport const RESOLVED_BY_RETRY = 'resolved_by:retry';\r\nexport const RESOLVED_BY_DISCUSSION = 'resolved_by:discussion';\r\n\r\ninterface FailureRow {\r\n id: string;\r\n ts_epoch: number;\r\n command: string | null;\r\n cwd: string | null;\r\n}\r\n\r\nfunction normalizeCommand(command: string): string {\r\n return command.trim().replace(/\\s+/g, ' ').toLowerCase();\r\n}\r\n\r\n/**\r\n * A token appearing in more than this fraction of a project's own\r\n * `conversation_turn`/`session_summary` nodes is treated as corpus-relative\r\n * boilerplate for the discussion-bridge heuristic -- picked from real\r\n * measured numbers, not guessed: the known \"id\" false positive measures\r\n * 22-40% document frequency across two real corpora checked, and this repo's\r\n * own name/verbs (\"nexusmem\"/\"sync\") measure 33-39% in this repo's own\r\n * history, while genuinely distinguishing terms from the same real links\r\n * (\"whoami\", \"wsl\", \"publish\") all measure under 2%. 0.2 sits clearly below\r\n * the boilerplate cluster and clearly above the signal cluster in every\r\n * real measurement taken so far.\r\n */\r\nconst MAX_TOKEN_DOC_FREQUENCY = 0.2;\r\n\r\n/**\r\n * Below this many discussable nodes, frequency is not a meaningful signal --\r\n * with a handful of nodes total, any word can trivially hit 20%+ just by\r\n * appearing once or twice, which would suppress real links on a young\r\n * project purely for lack of data rather than because the word is actually\r\n * boilerplate. 10 is a floor, not a tuned value: below it, skip the filter\r\n * entirely and fall back to whatever `significantTokens` already decided.\r\n */\r\nconst MIN_CORPUS_FOR_FREQUENCY_FILTER = 10;\r\n\r\n/**\r\n * Drops tokens that are boilerplate *in this specific project's own\r\n * history*, unlike `LOW_SIGNAL_TOKENS` in `fts.ts` which is a fixed list for\r\n * general search. Deliberately does NOT fall back to the unfiltered token\r\n * list when every token is boilerplate (the pattern `significantTokens`\r\n * itself uses) -- for this heuristic specifically, a command built entirely\r\n * of words that saturate the project's own corpus (e.g. this repo's own\r\n * name/verbs, \"nexusmem sync\") has no word left that could distinguish a\r\n * real discussion of *this* failure from generic chatter, and this\r\n * heuristic's whole design already prefers a missed link over a false one.\r\n */\r\nfunction filterBoilerplateTokens(db: Database.Database, projectId: string, tokens: string[]): string[] {\r\n if (tokens.length === 0) return tokens;\r\n\r\n const total = (\r\n db\r\n .prepare(`SELECT COUNT(*) AS c FROM nodes WHERE project_id = ? AND kind IN ('conversation_turn', 'session_summary')`)\r\n .get(projectId) as { c: number }\r\n ).c;\r\n if (total < MIN_CORPUS_FOR_FREQUENCY_FILTER) return tokens;\r\n\r\n const countMatching = db.prepare(\r\n `SELECT COUNT(*) AS c FROM nodes_fts JOIN nodes n ON n.rowid = nodes_fts.rowid\r\n WHERE nodes_fts MATCH ? AND n.project_id = ? AND n.kind IN ('conversation_turn', 'session_summary')`,\r\n );\r\n\r\n return tokens.filter((t) => {\r\n const matching = (countMatching.get(`\"${t}\"*`, projectId) as { c: number }).c;\r\n return matching / total <= MAX_TOKEN_DOC_FREQUENCY;\r\n });\r\n}\r\n\r\nexport function correlateFailures(store: MemoryStore, projectId: string, opts: CorrelateOptions = {}): CorrelateStats {\r\n const retryWindowMs = opts.retryWindowMs ?? DEFAULT_RETRY_WINDOW_MS;\r\n const discussionWindowMs = opts.discussionWindowMs ?? DEFAULT_DISCUSSION_WINDOW_MS;\r\n\r\n const db = store.raw;\r\n\r\n const failures = db\r\n .prepare(\r\n `SELECT id, ts_epoch, json_extract(meta, '$.command') AS command, json_extract(meta, '$.cwd') AS cwd\r\n FROM nodes\r\n WHERE project_id = ? AND kind = 'shell_command'\r\n AND json_extract(meta, '$.exitCode') IS NOT NULL\r\n AND json_extract(meta, '$.exitCode') != 0`,\r\n )\r\n .all(projectId) as FailureRow[];\r\n\r\n const findRetry = db.prepare(\r\n `SELECT id FROM nodes\r\n WHERE project_id = ? AND kind = 'shell_command'\r\n AND json_extract(meta, '$.exitCode') = 0\r\n AND ts_epoch > ? AND ts_epoch <= ?\r\n AND lower(trim(json_extract(meta, '$.command'))) = ?\r\n AND (json_extract(meta, '$.cwd') IS ? OR json_extract(meta, '$.cwd') = ?)\r\n ORDER BY ts_epoch ASC LIMIT 1`,\r\n );\r\n\r\n const findDiscussion = db.prepare(\r\n `SELECT n.id FROM nodes_fts\r\n JOIN nodes n ON n.rowid = nodes_fts.rowid\r\n WHERE nodes_fts MATCH ? AND n.project_id = ? AND n.kind IN ('conversation_turn', 'session_summary')\r\n AND n.ts_epoch > ? AND n.ts_epoch <= ?\r\n ORDER BY bm25(nodes_fts, 10.0, 1.0)\r\n LIMIT 1`,\r\n );\r\n\r\n let linkedByRetry = 0;\r\n let linkedByDiscussion = 0;\r\n\r\n for (const failure of failures) {\r\n if (!failure.command) continue;\r\n\r\n const retry = findRetry.get(\r\n projectId,\r\n failure.ts_epoch,\r\n failure.ts_epoch + retryWindowMs,\r\n normalizeCommand(failure.command),\r\n failure.cwd,\r\n failure.cwd,\r\n ) as { id: string } | undefined;\r\n if (retry) {\r\n store.linkNodes(failure.id, retry.id, RESOLVED_BY_RETRY);\r\n linkedByRetry += 1;\r\n }\r\n\r\n const tokens = filterBoilerplateTokens(db, projectId, significantTokens(failure.command));\r\n const match = tokens.length > 0 ? tokens.map((t) => `\"${t}\"*`).join(' AND ') : null;\r\n if (match) {\r\n const discussion = findDiscussion.get(match, projectId, failure.ts_epoch, failure.ts_epoch + discussionWindowMs) as\r\n | { id: string }\r\n | undefined;\r\n if (discussion) {\r\n store.linkNodes(failure.id, discussion.id, RESOLVED_BY_DISCUSSION);\r\n linkedByDiscussion += 1;\r\n }\r\n }\r\n }\r\n\r\n return { failuresExamined: failures.length, linkedByRetry, linkedByDiscussion };\r\n}\r\n\r\nexport interface ChainStats {\r\n /** Every failed `shell_command` node this project has ever recorded, linked or not. */\r\n failuresTotal: number;\r\n /** Distinct failures with at least one `resolved_by:retry` link. */\r\n resolvedByRetry: number;\r\n /** Distinct failures with at least one `resolved_by:discussion` link. */\r\n resolvedByDiscussion: number;\r\n /** Distinct failures resolved by either heuristic -- the headline \"chains built\" number. */\r\n resolvedTotal: number;\r\n}\r\n\r\n/**\r\n * Read-only summary of what `correlateFailures` has built so far, for\r\n * `nexusmem status` -- the failure->fix chain feature is this project's one\r\n * genuinely hard-to-copy capability (see ROADMAP.local.md's Phase 9), and\r\n * before this it was invisible to anyone who didn't already know to query\r\n * `node_links` directly. Counts what already exists; running `sync\r\n * --link-failures` is what grows these numbers, not this function.\r\n */\r\nexport function getChainStats(store: MemoryStore, projectId: string): ChainStats {\r\n const db = store.raw;\r\n\r\n const failuresTotal = (\r\n db\r\n .prepare(\r\n `SELECT COUNT(*) AS c FROM nodes\r\n WHERE project_id = ? AND kind = 'shell_command'\r\n AND json_extract(meta, '$.exitCode') IS NOT NULL AND json_extract(meta, '$.exitCode') != 0`,\r\n )\r\n .get(projectId) as { c: number }\r\n ).c;\r\n\r\n const countDistinctLinked = (relations: string[]): number =>\r\n (\r\n db\r\n .prepare(\r\n `SELECT COUNT(DISTINCT nl.from_node_id) AS c\r\n FROM node_links nl JOIN nodes n ON n.id = nl.from_node_id\r\n WHERE n.project_id = ? AND nl.relation IN (${relations.map(() => '?').join(', ')})`,\r\n )\r\n .get(projectId, ...relations) as { c: number }\r\n ).c;\r\n\r\n return {\r\n failuresTotal,\r\n resolvedByRetry: countDistinctLinked([RESOLVED_BY_RETRY]),\r\n resolvedByDiscussion: countDistinctLinked([RESOLVED_BY_DISCUSSION]),\r\n resolvedTotal: countDistinctLinked([RESOLVED_BY_RETRY, RESOLVED_BY_DISCUSSION]),\r\n };\r\n}\r\n","import type { SearchHit, VectorHit } from '../store/store.js';\n\n/**\n * Reciprocal Rank Fusion: combine several best-first ranked lists into one\n * relevance score per item, using only each item's *position* in each list,\n * never the raw scores.\n *\n * This is what makes fusing BM25 (a cost, lower is better) with vector\n * distance (also lower is better, but on a completely different, unbounded\n * scale) safe without any manual normalization between them -- position is\n * the only thing the two scales agree on.\n */\n\n/** Standard RRF constant. Large enough that rank 1 doesn't overwhelmingly dominate rank 2. */\nconst RRF_K = 60;\n\nexport interface RankedItem {\n id: string;\n}\n\n/**\n * `lists` are each assumed already sorted best-first. An id absent from a\n * list simply contributes nothing from it -- appearing in multiple lists\n * compounds, which is the intended behavior: a node both BM25 *and* vector\n * search agree on should outrank one only one of them found.\n */\nexport function reciprocalRankFusion(lists: readonly (readonly RankedItem[])[]): Map<string, number> {\n const scores = new Map<string, number>();\n\n for (const list of lists) {\n list.forEach((item, index) => {\n const contribution = 1 / (RRF_K + index + 1);\n scores.set(item.id, (scores.get(item.id) ?? 0) + contribution);\n });\n }\n\n return scores;\n}\n\n/**\n * Union two hit sets by id for a combined ranking pass.\n *\n * A vector-only match (found by semantic similarity, sharing no keywords\n * with the query at all) has no real BM25 rank -- it gets a placeholder\n * `rank` of 0, which is never read: once `relevanceScores` (from\n * `reciprocalRankFusion`) is passed to `rankHits`, the BM25-derived\n * relevance path is bypassed entirely for every hit in the set, fused or not.\n */\nexport function mergeSearchAndVectorHits(bm25Hits: readonly SearchHit[], vectorHits: readonly VectorHit[]): SearchHit[] {\n const byId = new Map<string, SearchHit>();\n\n for (const hit of bm25Hits) byId.set(hit.id, hit);\n\n for (const hit of vectorHits) {\n if (byId.has(hit.id)) continue;\n byId.set(hit.id, { id: hit.id, kind: hit.kind, ts: hit.ts, title: hit.title, body: hit.body, signal: hit.signal, rank: 0 });\n }\n\n return [...byId.values()];\n}\n","import type { SearchHit } from '../store/store.js';\r\n\r\nexport interface RankedHit extends SearchHit {\r\n /** 0..1, normalized from bm25 within this result set. 1 = best lexical match. */\r\n relevance: number;\r\n /** 0..1, structural importance rescaled so it can never zero out relevance. */\r\n signalWeight: number;\r\n /** 0..1, decays with age but never below the floor. */\r\n recencyFactor: number;\r\n ageDays: number;\r\n /**\r\n * `relevance * signalWeight**SIGNAL_EXPONENT * recencyFactor**RECENCY_EXPONENT`.\r\n * Sort key, best first. The reported `signalWeight`/`recencyFactor` are the\r\n * raw factors, not the exponentiated ones, so they stay readable as \"how\r\n * important\" and \"how fresh\" independent of how much weight ranking gives them.\r\n */\r\n score: number;\r\n}\r\n\r\nexport interface RankOptions {\r\n /** Days for the recency factor to halve. Default 30. */\r\n halfLifeDays?: number;\r\n /** Injectable for deterministic tests; defaults to the real clock. */\r\n now?: Date;\r\n /**\r\n * Pre-fused relevance (e.g. from `reciprocalRankFusion` over BM25 +\r\n * vector search), keyed by node id, higher-is-better. When provided, this\r\n * replaces the BM25-only `relevance` derivation entirely -- vector search\r\n * changes what counts as relevant, not the rest of the ranking formula.\r\n */\r\n relevanceScores?: ReadonlyMap<string, number>;\r\n}\r\n\r\n/**\r\n * Floors on each factor.\r\n *\r\n * Every factor lives in [floor, 1] rather than [0, 1]. Multiplying three\r\n * [0,1] terms lets any single dimension crush the other two to zero -- an\r\n * old-but-perfect match would lose to a recent-but-mediocre one purely on\r\n * age. Floors keep the combination a *reordering* within each dimension\r\n * instead of an on/off gate.\r\n */\r\nconst RELEVANCE_FLOOR = 0.15;\r\nconst SIGNAL_FLOOR = 0.2;\r\nconst RECENCY_FLOOR = 0.3;\r\nconst DEFAULT_HALF_LIFE_DAYS = 30;\r\nconst MS_PER_DAY = 86_400_000;\r\n\r\n/**\r\n * How far the *priors*, together, may overturn the *query*.\r\n *\r\n * `relevance` is the only factor derived from what was asked; `signal` and\r\n * `recency` are query-independent priors that hold before any query exists.\r\n * Multiplying all three as equals let the priors win outright: signal spans\r\n * 5x (0.2 -> 1) and recency 3.33x (0.3 -> 1), while relevance spans 6.7x, so a\r\n * well-scored recent commit could outrank a document that matched the question\r\n * far better. Observed on a real query: a `fix:` commit (signal .9) took rank 1\r\n * from the top-fused doc section (signal .55) on a 44% signal edge against a\r\n * 15% relevance deficit.\r\n *\r\n * Exponents bound that instead of banning it. Priors still order\r\n * equally-relevant hits exactly as before (the transform is monotonic); they\r\n * simply cannot overturn a large relevance gap.\r\n *\r\n * **The budget is shared, not per-prior.** Capping each prior at\r\n * `MAX_PRIOR_OVERTURN` separately caps neither the pair: the score multiplies\r\n * them, so two priors each worth 2x are worth 4x together. That is not a corner\r\n * case -- it describes every commit made during an active working day, both\r\n * fresh and high-signal at once, so the failure concentrated on exactly the days\r\n * with the most worth remembering. Found by dogfooding: a query about the\r\n * PowerShell hook returned two unrelated same-day `fix:` commits at ranks 3 and\r\n * 4 while the section that answered it sat at rank 6.\r\n *\r\n * So `MAX_PRIOR_OVERTURN` is the budget for all priors *jointly*, split evenly\r\n * between them (`sqrt(2)` each), and each prior is then raised to the power that\r\n * makes its entire range worth exactly its share -- solving\r\n * `span^exponent = PER_PRIOR_OVERTURN`. Adding a third prior re-divides the same\r\n * budget rather than enlarging it, which is the property that was missing.\r\n */\r\nconst MAX_PRIOR_OVERTURN = 2;\r\n/** signal and recency. Update when a query-independent factor joins the score. */\r\nconst PRIOR_COUNT = 2;\r\nconst PER_PRIOR_OVERTURN = MAX_PRIOR_OVERTURN ** (1 / PRIOR_COUNT);\r\nconst SIGNAL_EXPONENT = Math.log(PER_PRIOR_OVERTURN) / Math.log(1 / SIGNAL_FLOOR);\r\nconst RECENCY_EXPONENT = Math.log(PER_PRIOR_OVERTURN) / Math.log(1 / RECENCY_FLOOR);\r\n\r\n/**\r\n * bm25() in SQLite is a *cost*: smaller (more negative) is a better match.\r\n * Min-max normalize within this result set so the scale is comparable across\r\n * queries, then rescale into [RELEVANCE_FLOOR, 1].\r\n */\r\nfunction normalizeRelevance(hits: readonly SearchHit[]): number[] {\r\n const costs = hits.map((h) => h.rank);\r\n const min = Math.min(...costs);\r\n const max = Math.max(...costs);\r\n\r\n if (min === max) return hits.map(() => 1);\r\n\r\n return costs.map((cost) => {\r\n const normalized = (max - cost) / (max - min); // best cost -> 1, worst -> 0\r\n return RELEVANCE_FLOOR + (1 - RELEVANCE_FLOOR) * normalized;\r\n });\r\n}\r\n\r\n/**\r\n * Same rescale-into-[floor,1] treatment as `normalizeRelevance`, but for an\r\n * externally supplied score where *higher* is better (RRF's convention),\r\n * unlike bm25's cost convention.\r\n */\r\nfunction normalizeExternalRelevance(hits: readonly SearchHit[], scores: ReadonlyMap<string, number>): number[] {\r\n const values = hits.map((h) => scores.get(h.id) ?? 0);\r\n const min = Math.min(...values);\r\n const max = Math.max(...values);\r\n\r\n if (min === max) return hits.map(() => 1);\r\n\r\n return values.map((v) => {\r\n const normalized = (v - min) / (max - min); // best (highest) -> 1\r\n return RELEVANCE_FLOOR + (1 - RELEVANCE_FLOOR) * normalized;\r\n });\r\n}\r\n\r\nfunction ageDaysOf(ts: string, now: Date): number {\r\n const parsed = Date.parse(ts);\r\n if (Number.isNaN(parsed)) return 0;\r\n return Math.max(0, (now.getTime() - parsed) / MS_PER_DAY);\r\n}\r\n\r\n/**\r\n * Combine lexical match quality, structural importance and recency into one\r\n * score, and return hits sorted best-first.\r\n *\r\n * This is the layer the whole \"signal at ingest time\" design pays off in:\r\n * `signalWeight` is what keeps a `fix:` commit ahead of an equally-relevant\r\n * `chore:` one, without a query-time re-analysis of either.\r\n */\r\nexport function rankHits(hits: readonly SearchHit[], opts: RankOptions = {}): RankedHit[] {\r\n if (hits.length === 0) return [];\r\n\r\n const halfLife = opts.halfLifeDays ?? DEFAULT_HALF_LIFE_DAYS;\r\n const now = opts.now ?? new Date();\r\n const relevances = opts.relevanceScores ? normalizeExternalRelevance(hits, opts.relevanceScores) : normalizeRelevance(hits);\r\n\r\n const ranked = hits.map((hit, i) => {\r\n const relevance = relevances[i] ?? RELEVANCE_FLOOR;\r\n const signalWeight = SIGNAL_FLOOR + (1 - SIGNAL_FLOOR) * hit.signal;\r\n const ageDays = ageDaysOf(hit.ts, now);\r\n const recencyFactor = RECENCY_FLOOR + (1 - RECENCY_FLOOR) * 2 ** (-ageDays / halfLife);\r\n\r\n const score = relevance * signalWeight ** SIGNAL_EXPONENT * recencyFactor ** RECENCY_EXPONENT;\r\n\r\n return { ...hit, relevance, signalWeight, ageDays, recencyFactor, score };\r\n });\r\n\r\n return ranked.sort((a, b) => b.score - a.score);\r\n}\r\n","import { RESOLVED_BY_DISCUSSION, RESOLVED_BY_RETRY } from '../correlate/failure-fix.js';\r\nimport type { MemoryStore, SearchHit, VectorHit } from '../store/store.js';\r\nimport type { EmbeddingProvider } from '../vector/embed.js';\r\nimport { mergeSearchAndVectorHits, reciprocalRankFusion } from './fuse.js';\r\nimport { packContext, type PackResult } from './pack.js';\r\nimport { rankHits, type RankedHit } from './rank.js';\r\n\r\n/** Both chain relations are surfaced -- see the doc comment on `pullLinkedResolutions` for why. */\r\nconst SURFACED_RELATIONS = [RESOLVED_BY_RETRY, RESOLVED_BY_DISCUSSION] as const;\r\n\r\n/**\r\n * Pull a `shell_command` failure's linked resolution(s) into the ranked\r\n * list, immediately after the failure, so they survive `packContext`'s\r\n * budget/diversity cuts alongside it instead of needing to earn their own\r\n * place on query relevance alone. That is the whole point of Phase 7's chain\r\n * feature: a resolution the query never mentioned should still ride along\r\n * with the failure it resolves.\r\n *\r\n * Both `RESOLVED_BY_RETRY` and `RESOLVED_BY_DISCUSSION` are surfaced.\r\n * Discussion links were excluded through Phase 7 -- dogfooding against this\r\n * repo's real history found roughly half of them wrong, driven by a single\r\n * shared generic token. `toStrictMatchQuery` (an AND of every significant\r\n * token) fixed that at the source: re-dogfooded against the same real\r\n * corpus, all 5 resulting links (3 distinct failure/discussion pairs) were\r\n * verified correct by hand, full body read, not just the summary. Given a\r\n * false discussion link is now no more likely than a false retry link, there\r\n * is no remaining reason to treat them differently at this layer.\r\n *\r\n * The pulled-in node inherits its failure's relevance/signalWeight/\r\n * recencyFactor/score wholesale rather than computing its own: it has no\r\n * independent relevance to this query, and is included *because* it resolves\r\n * a hit that already matched, not because it matched on its own. A\r\n * resolution already present in `ranked` on its own merits is left\r\n * untouched, never duplicated.\r\n *\r\n * `resolveStore` maps a hit back to the `MemoryStore` its links live in --\r\n * always the same store for `runHybridQuery`, but per-source for\r\n * `runCrossProjectQuery`, since links are only ever recorded within one\r\n * project's own database (node ids are project-scoped, so a link cannot\r\n * cross databases in the first place).\r\n */\r\nfunction pullLinkedResolutions(\r\n resolveStore: (hit: RankedHit) => MemoryStore | undefined,\r\n ranked: readonly RankedHit[],\r\n): RankedHit[] {\r\n const present = new Set(ranked.map((hit) => hit.id));\r\n const withLinks: RankedHit[] = [];\r\n\r\n for (const hit of ranked) {\r\n withLinks.push(hit);\r\n if (hit.kind !== 'shell_command') continue;\r\n\r\n const store = resolveStore(hit);\r\n if (!store) continue;\r\n\r\n for (const relation of SURFACED_RELATIONS) {\r\n for (const linkedId of store.getLinkedNodeIds(hit.id, relation)) {\r\n if (present.has(linkedId)) continue;\r\n const [resolution] = store.getNodesByIds([linkedId]);\r\n if (!resolution) continue;\r\n\r\n present.add(linkedId);\r\n withLinks.push({\r\n id: resolution.id,\r\n kind: resolution.kind,\r\n ts: resolution.ts,\r\n title: resolution.title,\r\n body: resolution.body,\r\n signal: resolution.signal,\r\n rank: 0, // no bm25/vector rank of its own -- never read again past this point\r\n relevance: hit.relevance,\r\n signalWeight: hit.signalWeight,\r\n recencyFactor: hit.recencyFactor,\r\n ageDays: hit.ageDays,\r\n score: hit.score,\r\n ...(hit.project ? { project: hit.project } : {}),\r\n });\r\n }\r\n }\r\n }\r\n\r\n return withLinks;\r\n}\r\n\r\nexport interface HybridQueryOptions {\r\n budget: number;\r\n candidates: number;\r\n halfLifeDays?: number;\r\n /** `null`/omitted skips vector search entirely -- BM25-only, same behavior as before hybrid retrieval existed. */\r\n embeddingProvider?: EmbeddingProvider | null;\r\n}\r\n\r\nexport interface HybridQueryResult {\r\n bm25Count: number;\r\n vectorCount: number;\r\n /** The full candidate set that was ranked (bm25 ∪ vector, deduped) -- for callers that need the raw, unpacked bodies (e.g. a \"tokens if sent unpacked\" comparison). */\r\n hits: SearchHit[];\r\n packed: PackResult;\r\n}\r\n\r\n/** One repository's memory, as an input to a cross-project query. */\r\nexport interface QuerySource {\r\n store: MemoryStore;\r\n projectId: string;\r\n /** Shown to the user next to each hit; must be unique across the sources of one query. */\r\n label: string;\r\n}\r\n\r\nexport interface CrossProjectQueryResult extends HybridQueryResult {\r\n /** Per-source match counts, for reporting which repositories actually contributed. */\r\n perProject: Array<{ label: string; bm25: number; vector: number }>;\r\n}\r\n\r\n/**\r\n * Search several repositories at once and rank the results together.\r\n *\r\n * Node ids are `sha256(projectId + kind + naturalKey)`, so hits from\r\n * different databases cannot collide and the union needs no renaming.\r\n *\r\n * The one thing that does *not* survive the union is BM25's scale. A bm25()\r\n * cost is computed against its own corpus statistics, so -8.1 in a\r\n * ten-thousand-node repository and -8.1 in a fifty-node one are not the same\r\n * quality of match, and min-max normalizing them together silently invents a\r\n * comparison. RRF is therefore always applied here -- even with vector search\r\n * off, where a single-project query would normalize raw bm25 directly --\r\n * because each project's list is only ever compared against itself, by\r\n * position.\r\n *\r\n * Its known bias, stated rather than hidden: a project whose best match is\r\n * mediocre still contributes a rank-1 item, and rank 1 pays the same in every\r\n * list. Cross-project recall therefore favours breadth. `signal`, recency and\r\n * the budget are what keep that in check.\r\n */\r\nexport async function runCrossProjectQuery(\r\n sources: readonly QuerySource[],\r\n query: string,\r\n opts: HybridQueryOptions,\r\n): Promise<CrossProjectQueryResult> {\r\n // One embedding for every source: the query is the same, and this is the\r\n // only network call on the path.\r\n const queryVector = opts.embeddingProvider ? await opts.embeddingProvider.embed(query) : null;\r\n\r\n const lists: SearchHit[][] = [];\r\n const hits: SearchHit[] = [];\r\n const perProject: CrossProjectQueryResult['perProject'] = [];\r\n let bm25Count = 0;\r\n let vectorCount = 0;\r\n\r\n for (const source of sources) {\r\n const label = (hit: SearchHit): SearchHit => ({ ...hit, project: source.label });\r\n\r\n const bm25Hits = source.store.search(source.projectId, query, opts.candidates).map(label);\r\n const vectorHits = queryVector\r\n ? source.store.vectorSearch(source.projectId, queryVector, opts.candidates)\r\n : [];\r\n\r\n bm25Count += bm25Hits.length;\r\n vectorCount += vectorHits.length;\r\n perProject.push({ label: source.label, bm25: bm25Hits.length, vector: vectorHits.length });\r\n\r\n if (bm25Hits.length > 0) lists.push(bm25Hits);\r\n if (vectorHits.length > 0) {\r\n lists.push(vectorHits.map((hit) => ({ ...hit, rank: 0, project: source.label })));\r\n }\r\n\r\n hits.push(...mergeSearchAndVectorHits(bm25Hits, vectorHits).map(label));\r\n }\r\n\r\n const storeByLabel = new Map(sources.map((source) => [source.label, source.store]));\r\n const relevanceScores = reciprocalRankFusion(lists);\r\n const ranked = pullLinkedResolutions(\r\n (hit) => (hit.project ? storeByLabel.get(hit.project) : undefined),\r\n rankHits(hits, { halfLifeDays: opts.halfLifeDays, relevanceScores }),\r\n );\r\n const packed = packContext(ranked, opts.budget, { query });\r\n\r\n return { bm25Count, vectorCount, hits, packed, perProject };\r\n}\r\n\r\n/**\r\n * The one retrieval pipeline both `nexusmem query` and the MCP `search_memory`\r\n * tool run -- BM25 search, optional vector search, RRF fusion when both\r\n * fired, then rank and pack. Kept in one place so the CLI and the MCP server\r\n * can never quietly drift into answering the same query differently.\r\n */\r\nexport async function runHybridQuery(\r\n store: MemoryStore,\r\n projectId: string,\r\n query: string,\r\n opts: HybridQueryOptions,\r\n): Promise<HybridQueryResult> {\r\n const bm25Hits = store.search(projectId, query, opts.candidates);\r\n\r\n let vectorHits: VectorHit[] = [];\r\n if (opts.embeddingProvider) {\r\n const queryVector = await opts.embeddingProvider.embed(query);\r\n if (queryVector) vectorHits = store.vectorSearch(projectId, queryVector, opts.candidates);\r\n }\r\n\r\n const hits = vectorHits.length > 0 ? mergeSearchAndVectorHits(bm25Hits, vectorHits) : bm25Hits;\r\n const relevanceScores = vectorHits.length > 0 ? reciprocalRankFusion([bm25Hits, vectorHits]) : undefined;\r\n\r\n const ranked = pullLinkedResolutions(\r\n () => store,\r\n rankHits(hits, { halfLifeDays: opts.halfLifeDays, relevanceScores }),\r\n );\r\n // The query reaches the packer as well as the searcher: for a diff node it\r\n // decides which hunk of an already-retrieved patch is worth the budget.\r\n const packed = packContext(ranked, opts.budget, { query });\r\n\r\n return { bm25Count: bm25Hits.length, vectorCount: vectorHits.length, hits, packed };\r\n}\r\n","import { basename } from 'node:path';\r\nimport { readLiveRegistry, type RegistryEntry } from '../config/registry.js';\r\nimport { MemoryStore } from '../store/store.js';\r\nimport type { QuerySource } from './query-pipeline.js';\r\n\r\n/**\r\n * Turning the registry into a set of open databases to search.\r\n *\r\n * Every failure mode here is non-fatal by design: a cross-project query that\r\n * refuses to answer because one of six repositories is on an unplugged drive\r\n * would be worse than one that answers from five and says so.\r\n */\r\n\r\nexport interface OpenedSources {\r\n sources: QuerySource[];\r\n /** Registered projects whose database file is not currently on disk. */\r\n missing: RegistryEntry[];\r\n /** Registered projects whose database exists but could not be opened. */\r\n unreadable: Array<{ entry: RegistryEntry; reason: string }>;\r\n close(): void;\r\n}\r\n\r\nexport interface CurrentProject {\r\n projectId: string;\r\n root: string;\r\n dbPath: string;\r\n}\r\n\r\n/**\r\n * Open the current repository plus every other registered one.\r\n *\r\n * The current project is included even when the registry has never heard of\r\n * it -- a repo initialized before the registry existed would otherwise be\r\n * missing from its own query.\r\n */\r\nexport async function openAllProjectSources(current: CurrentProject): Promise<OpenedSources> {\r\n const { entries, missing } = await readLiveRegistry();\r\n\r\n const wanted: CurrentProject[] = [current];\r\n for (const entry of entries) {\r\n if (entry.projectId === current.projectId) continue;\r\n wanted.push({ projectId: entry.projectId, root: entry.root, dbPath: entry.dbPath });\r\n }\r\n\r\n const labels = labelProjects(wanted);\r\n const sources: QuerySource[] = [];\r\n const unreadable: OpenedSources['unreadable'] = [];\r\n\r\n wanted.forEach((project, index) => {\r\n try {\r\n sources.push({\r\n store: MemoryStore.open(project.dbPath),\r\n projectId: project.projectId,\r\n label: labels[index] ?? project.projectId.slice(0, 8),\r\n });\r\n } catch (err) {\r\n // Never the current project's database: `loadContext` has already\r\n // opened that one by the time this runs.\r\n const entry = entries.find((e) => e.projectId === project.projectId);\r\n if (entry) unreadable.push({ entry, reason: (err as Error).message });\r\n }\r\n });\r\n\r\n return {\r\n sources,\r\n missing,\r\n unreadable,\r\n close: () => {\r\n for (const source of sources) source.store.close();\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Short, unique names for a set of projects.\r\n *\r\n * A directory basename is what a person calls their repo, so it is the right\r\n * label right up until two of them are called `api`. Collisions get the head\r\n * of the project id appended rather than the full path: the label is a\r\n * disambiguator in a context block, not an address.\r\n */\r\nexport function labelProjects(projects: readonly CurrentProject[]): string[] {\r\n const counts = new Map<string, number>();\r\n for (const project of projects) {\r\n const name = basename(project.root) || project.root;\r\n counts.set(name, (counts.get(name) ?? 0) + 1);\r\n }\r\n\r\n return projects.map((project) => {\r\n const name = basename(project.root) || project.root;\r\n return (counts.get(name) ?? 0) > 1 ? `${name}#${project.projectId.slice(0, 6)}` : name;\r\n });\r\n}\r\n","/**\r\n * Embedding provider abstraction.\r\n *\r\n * The real implementation calls a local Ollama server; tests and any\r\n * environment without Ollama running use a fake. `embed` returns `null`\r\n * (never throws) on any failure -- connection refused, model not pulled,\r\n * malformed response -- so a missing embedding provider degrades the whole\r\n * system to BM25-only search, not a broken `sync`.\r\n */\r\nexport interface EmbeddingProvider {\r\n readonly dimension: number;\r\n /**\r\n * Stable name for \"what produced these vectors\".\r\n *\r\n * Persisted alongside the corpus so a provider swap can be *detected*\r\n * rather than silently mixed in. Two vectors from different models --\r\n * or, less obviously, from two endpoints of the same model that differ\r\n * in normalisation -- are not comparable, and `nodes_vec` stores no\r\n * per-row provenance to tell them apart after the fact. Must change\r\n * whenever the produced vectors change meaning.\r\n */\r\n readonly identity: string;\r\n embed(text: string): Promise<Float32Array | null>;\r\n /**\r\n * Embed several texts in one round trip, positionally aligned with the\r\n * input. Optional: a provider without it is driven one call at a time.\r\n * A failure is per-request, so a rejected batch yields all-`null`.\r\n */\r\n embedBatch?(texts: readonly string[]): Promise<(Float32Array | null)[]>;\r\n}\r\n\r\nexport interface OllamaEmbeddingProviderOptions {\r\n baseUrl?: string;\r\n model?: string;\r\n dimension?: number;\r\n /**\r\n * Milliseconds allowed *per text*. A request's real budget is this times\r\n * the number of texts in it, capped by `maxTimeoutMs` -- a batch of 32\r\n * legitimately takes longer than a single embed and must not be aborted\r\n * for being a batch. Default 10s.\r\n */\r\n timeoutMs?: number;\r\n /** Upper bound on any single request's timeout, however large the batch. Default 120s. */\r\n maxTimeoutMs?: number;\r\n}\r\n\r\nconst DEFAULT_BASE_URL = 'http://127.0.0.1:11434';\r\nconst DEFAULT_MODEL = 'nomic-embed-text';\r\n/** Confirmed against a live Ollama call -- see store/schema.ts's EMBEDDING_DIM. */\r\nconst DEFAULT_DIMENSION = 768;\r\nconst DEFAULT_TIMEOUT_MS = 10_000;\r\nconst DEFAULT_MAX_TIMEOUT_MS = 120_000;\r\n\r\n/**\r\n * Ollama's batch embedding endpoint.\r\n *\r\n * Deliberately NOT the older `/api/embeddings`, and the difference is not\r\n * only that this one takes an array: **`/api/embed` returns L2-normalised\r\n * vectors and `/api/embeddings` does not** (measured on this machine against\r\n * nomic-embed-text: norm 1.0 vs 20.7 for the same input). `nodes_vec` ranks\r\n * by Euclidean distance, so a corpus holding both is not merely\r\n * inconsistent -- every normalised vector sits ~20x nearer the origin than\r\n * every unnormalised one, and the two groups separate by scale instead of by\r\n * meaning. Hence `EMBEDDING_IDENTITY` below names the endpoint, and the\r\n * embedding pass re-embeds from scratch when it changes.\r\n */\r\nconst EMBED_PATH = '/api/embed';\r\n\r\nexport class OllamaEmbeddingProvider implements EmbeddingProvider {\r\n readonly dimension: number;\r\n readonly identity: string;\r\n private readonly baseUrl: string;\r\n private readonly model: string;\r\n private readonly timeoutMs: number;\r\n private readonly maxTimeoutMs: number;\r\n\r\n constructor(opts: OllamaEmbeddingProviderOptions = {}) {\r\n this.baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;\r\n this.model = opts.model ?? DEFAULT_MODEL;\r\n this.dimension = opts.dimension ?? DEFAULT_DIMENSION;\r\n this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n this.maxTimeoutMs = opts.maxTimeoutMs ?? DEFAULT_MAX_TIMEOUT_MS;\r\n // `baseUrl` is deliberately absent: pointing at a different host running\r\n // the same model is not a different embedding space, so it must not\r\n // trigger a re-embed.\r\n this.identity = `ollama${EMBED_PATH}:${this.model}:${this.dimension}`;\r\n }\r\n\r\n async embed(text: string): Promise<Float32Array | null> {\r\n const [only] = await this.embedBatch([text]);\r\n return only ?? null;\r\n }\r\n\r\n async embedBatch(texts: readonly string[]): Promise<(Float32Array | null)[]> {\r\n if (texts.length === 0) return [];\r\n\r\n const controller = new AbortController();\r\n const budget = Math.min(this.timeoutMs * texts.length, this.maxTimeoutMs);\r\n const timeout = setTimeout(() => controller.abort(), budget);\r\n\r\n try {\r\n const res = await fetch(`${this.baseUrl}${EMBED_PATH}`, {\r\n method: 'POST',\r\n headers: { 'content-type': 'application/json' },\r\n body: JSON.stringify({ model: this.model, input: texts }),\r\n signal: controller.signal,\r\n });\r\n\r\n if (!res.ok) return texts.map(() => null);\r\n\r\n const data = (await res.json()) as { embeddings?: unknown };\r\n if (!Array.isArray(data.embeddings)) return texts.map(() => null);\r\n\r\n // Positional alignment with `texts` is the contract callers rely on to\r\n // know which node each vector belongs to. Ollama returns one row per\r\n // input, but a short or long array would silently shift that mapping\r\n // and mislabel every embedding after the gap -- refuse instead.\r\n if (data.embeddings.length !== texts.length) return texts.map(() => null);\r\n\r\n return data.embeddings.map((row) =>\r\n Array.isArray(row) && row.length === this.dimension ? new Float32Array(row as number[]) : null,\r\n );\r\n } catch {\r\n return texts.map(() => null); // Ollama not running, model not pulled, network hiccup -- all degrade the same way\r\n } finally {\r\n clearTimeout(timeout);\r\n }\r\n }\r\n}\r\n\r\n/** Deterministic, network-free provider for tests. */\r\nexport class FakeEmbeddingProvider implements EmbeddingProvider {\r\n readonly identity: string;\r\n\r\n constructor(readonly dimension = 8) {\r\n this.identity = `fake:${dimension}`;\r\n }\r\n\r\n async embed(text: string): Promise<Float32Array | null> {\r\n // A cheap hash-based vector: stable per input, distinct enough across\r\n // different inputs to exercise real KNN ordering in tests.\r\n const v = new Float32Array(this.dimension);\r\n for (let i = 0; i < text.length; i += 1) {\r\n const idx = i % this.dimension;\r\n v[idx] = (v[idx] ?? 0) + text.charCodeAt(i);\r\n }\r\n return v;\r\n }\r\n}\r\n","import pc from 'picocolors';\r\nimport { correlateFailures } from '../../correlate/failure-fix.js';\r\nimport { collectConversationTurns } from '../../collectors/conversation.js';\r\nimport { collectCommitDiffs, DIFF_SOURCE } from '../../collectors/diffs.js';\r\nimport { collectDocFiles } from '../../collectors/docs.js';\r\nimport { collectGitCommits } from '../../collectors/git-commits.js';\r\nimport { collectSessionSummaries } from '../../collectors/sessions.js';\r\nimport { collectShellHistory } from '../../collectors/shell-history.js';\r\nimport { forgetProjects, recordProject } from '../../config/registry.js';\r\nimport { writeConfig } from '../../config/workspace.js';\r\nimport { collectClaudeCodeTranscripts } from '../../conversation/claude-code-reader.js';\r\nimport type { RawConversationTurn } from '../../conversation/types.js';\r\nimport { makeNodeId } from '../../core/ids.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readDocFiles } from '../../docs/read.js';\r\nimport { isAncestor } from '../../git/repo.js';\r\nimport { collectAvailableShellHistory } from '../../shell/detect.js';\r\nimport { OllamaChatProvider } from '../../slm/provider.js';\r\nimport { reconcileProjectId } from '../../store/reconcile.js';\r\nimport { MemoryStore, type IngestStats } from '../../store/store.js';\r\nimport { OllamaEmbeddingProvider } from '../../vector/embed.js';\r\nimport { embedPendingNodes } from '../../vector/sync.js';\r\nimport { loadContext } from '../context.js';\r\n\r\nexport interface SyncOptions {\r\n cwd: string;\r\n /** Ignore the stored cursor and re-walk all history (still deduplicated). */\r\n full: boolean;\r\n /** Delete this project's nodes first, then re-ingest from scratch. */\r\n rebuild: boolean;\r\n /** Overrides `sources.git.since` from config for this run. */\r\n since?: string;\r\n /** Overrides `sources.shell.tailLines` from config for this run. */\r\n shellTailLines?: number;\r\n /** Forces the (opt-in) conversation source on for this run without persisting it to config. */\r\n conversationOverride?: boolean;\r\n /** Skip the embedding pass entirely -- useful when Ollama isn't running and you don't want to wait out its timeout. */\r\n noEmbed?: boolean;\r\n /** Stop the embedding pass after this many nodes. Unset means drain the backlog. */\r\n embedLimit?: number;\r\n /** Wipe every node of this exact source (e.g. `shell:pwsh`) instead of syncing. Dry-run unless `yes` is also set. */\r\n pruneSource?: string;\r\n /** Shortcut for `pruneSource` on all three dead pre-hook shell-scrape sources at once. Combines with `pruneSource` if both are set. */\r\n pruneStaleShell?: boolean;\r\n /** Confirms an irreversible `pruneSource`/`pruneStaleShell` delete. Without it, the matching count is printed and nothing is removed. */\r\n yes?: boolean;\r\n /**\r\n * Opt-in (Phase 7): after ingest, run `correlateFailures` to link failed\r\n * shell commands to whatever later resolved them. Off by default -- both\r\n * heuristics are new and unvalidated, matching how session summarization\r\n * shipped opt-in first before being trusted as a default.\r\n */\r\n linkFailures?: boolean;\r\n quiet: boolean;\r\n /**\r\n * Where the final summary goes. Defaults to real stdout for the CLI.\r\n *\r\n * Progress lines keep going to stderr regardless (see `log` below); this is\r\n * only the result. The MCP server passes its own sink because there `stdout`\r\n * carries the JSON-RPC transport -- see `InitOptions.out`.\r\n */\r\n out?: (chunk: string) => void;\r\n}\r\n\r\n/**\r\n * Rows per transaction.\r\n *\r\n * Large enough that per-transaction overhead disappears, small enough that a\r\n * huge repository does not hold every node in memory before the first write.\r\n */\r\nconst BATCH_SIZE = 500;\r\n\r\n/** Below this backlog the embedding pass finishes fast enough that progress lines are just noise. */\r\nconst PROGRESS_THRESHOLD = 200;\r\nconst PROGRESS_EVERY = 100;\r\n\r\nconst GIT_SOURCE = 'git';\r\n\r\nfunction addStats(into: IngestStats, from: IngestStats): void {\r\n into.inserted += from.inserted;\r\n into.updated += from.updated;\r\n into.unchanged += from.unchanged;\r\n}\r\n\r\nasync function syncGit(\r\n store: MemoryStore,\r\n projectId: string,\r\n opts: SyncOptions,\r\n repo: Awaited<ReturnType<typeof loadContext>>['repo'],\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n if (!repo.head) {\r\n log(`${pc.yellow('git')} skipped -- repository has no commits yet`);\r\n return { totals, seen: 0 };\r\n }\r\n if (!config.sources.git.enabled) {\r\n log(`${pc.dim('git')} disabled in config`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n let cursor = opts.full || opts.rebuild ? null : store.getSyncCursor(projectId, GIT_SOURCE);\r\n\r\n if (cursor && !(await isAncestor(repo.root, cursor, repo.head))) {\r\n log(`${pc.yellow('git cursor stale')} ${cursor.slice(0, 7)} is not an ancestor of HEAD — falling back to a full walk`);\r\n cursor = null;\r\n }\r\n\r\n if (cursor === repo.head) {\r\n log(`${pc.green('git up to date')} at ${repo.head.slice(0, 7)}`);\r\n store.setSyncCursor(projectId, GIT_SOURCE, repo.head);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n log(\r\n `${pc.dim('git syncing')} ${repo.branch ?? 'HEAD'} ${cursor ? `${cursor.slice(0, 7)}..${repo.head.slice(0, 7)}` : '(full history)'}`,\r\n );\r\n\r\n let batch: MemoryNode[] = [];\r\n let seen = 0;\r\n\r\n const flush = () => {\r\n if (batch.length === 0) return;\r\n addStats(totals, store.upsertNodes(batch));\r\n batch = [];\r\n log(` ${pc.dim(`${seen} commits read, ${totals.inserted} new`)}`);\r\n };\r\n\r\n const nodes = collectGitCommits(repo.root, projectId, {\r\n afterCommit: cursor,\r\n since: opts.since ?? config.sources.git.since,\r\n includeMerges: config.sources.git.includeMerges,\r\n maxFilesPerNode: config.limits.maxFilesPerNode,\r\n maxBodyChars: config.limits.maxBodyChars,\r\n });\r\n\r\n for await (const node of nodes) {\r\n batch.push(node);\r\n seen += 1;\r\n if (batch.length >= BATCH_SIZE) flush();\r\n }\r\n flush();\r\n\r\n // Only advance the cursor once the walk completed without throwing -- a\r\n // crash mid-sync leaves the old cursor, and the next run redoes the range\r\n // (harmlessly, because ingestion is idempotent).\r\n store.setSyncCursor(projectId, GIT_SOURCE, repo.head);\r\n return { totals, seen };\r\n}\r\n\r\nasync function syncDiffs(\r\n store: MemoryStore,\r\n projectId: string,\r\n opts: SyncOptions,\r\n repo: Awaited<ReturnType<typeof loadContext>>['repo'],\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n if (!repo.head) return { totals, seen: 0 };\r\n if (!config.sources.diff.enabled) {\r\n log(`${pc.dim('diff')} disabled in config`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n // Its own cursor, not git's: the two sources walk the same history but are\r\n // enabled independently, so a repository that had diffs turned on later must\r\n // not inherit git's \"already up to date\" position and skip everything.\r\n let cursor = opts.full || opts.rebuild ? null : store.getSyncCursor(projectId, DIFF_SOURCE);\r\n\r\n if (cursor && !(await isAncestor(repo.root, cursor, repo.head))) {\r\n log(`${pc.yellow('diff cursor stale')} ${cursor.slice(0, 7)} is not an ancestor of HEAD — falling back to a bounded walk`);\r\n cursor = null;\r\n }\r\n\r\n if (cursor === repo.head) {\r\n store.setSyncCursor(projectId, DIFF_SOURCE, repo.head);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n let batch: MemoryNode[] = [];\r\n let seen = 0;\r\n\r\n const flush = () => {\r\n if (batch.length === 0) return;\r\n addStats(totals, store.upsertNodes(batch));\r\n batch = [];\r\n };\r\n\r\n const nodes = collectCommitDiffs(repo.root, projectId, {\r\n afterCommit: cursor,\r\n since: opts.since ?? config.sources.git.since,\r\n maxCount: config.sources.diff.maxCommits,\r\n maxFilesPerCommit: config.sources.diff.maxFilesPerCommit,\r\n contextLines: config.sources.diff.contextLines,\r\n maxBodyChars: config.limits.maxBodyChars,\r\n });\r\n\r\n for await (const node of nodes) {\r\n batch.push(node);\r\n seen += 1;\r\n if (batch.length >= BATCH_SIZE) flush();\r\n }\r\n flush();\r\n\r\n // Same rule as git: advance only after a walk that completed, so a crash\r\n // mid-sync redoes the range instead of silently skipping it.\r\n store.setSyncCursor(projectId, DIFF_SOURCE, repo.head);\r\n log(` ${pc.dim(`${DIFF_SOURCE}: ${seen} file diff(s) read`)}`);\r\n\r\n return { totals, seen };\r\n}\r\n\r\nasync function syncShell(\r\n store: MemoryStore,\r\n projectId: string,\r\n opts: SyncOptions,\r\n repoRoot: string,\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n if (!config.sources.shell.enabled) {\r\n log(`${pc.dim('shell')} disabled in config`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n const results = await collectAvailableShellHistory({\r\n tailLines: opts.shellTailLines ?? config.sources.shell.tailLines,\r\n repoRoot,\r\n hookCursor: store.getSyncCursor(projectId, 'shell:pwsh-hook'),\r\n });\r\n\r\n if (results.length === 0) {\r\n log(`${pc.dim('shell')} no history source found on this machine`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n let seen = 0;\r\n for (const result of results) {\r\n const sourceKey = `shell:${result.name}`;\r\n const nodes = collectShellHistory(result.entries, projectId, { maxBodyChars: config.limits.maxBodyChars });\r\n seen += nodes.length;\r\n\r\n if (nodes.length > 0) {\r\n addStats(totals, store.upsertNodes(nodes));\r\n }\r\n\r\n // Hook source is a real append-only log: advance a walk-forward cursor.\r\n // Scrape sources re-read their tail window every run (bounded, cheap,\r\n // and self-deduplicating via content-addressed ids) so their \"cursor\" is\r\n // informational only, for `status` to show a last-synced marker.\r\n store.setSyncCursor(projectId, sourceKey, result.cursorAfter ?? `scanned:${result.entries.length}`);\r\n log(` ${pc.dim(`${sourceKey}: ${nodes.length} entr${nodes.length === 1 ? 'y' : 'ies'} read`)}`);\r\n }\r\n\r\n return { totals, seen };\r\n}\r\n\r\nconst CONVERSATION_SOURCE = 'conversation:claude-code';\r\n\r\nfunction syncConversation(\r\n store: MemoryStore,\r\n projectId: string,\r\n turns: readonly RawConversationTurn[],\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n forceEnabled: boolean | undefined,\r\n): { totals: IngestStats; seen: number } {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n const enabled = forceEnabled ?? config.sources.conversation.enabled;\r\n\r\n if (!enabled) {\r\n // Opt-in and silent by default -- this source is off for almost every\r\n // sync, and it would be noise to announce that on every single run.\r\n return { totals, seen: 0 };\r\n }\r\n\r\n if (turns.length === 0) {\r\n log(`${pc.dim('conversation')} no transcripts found`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n const nodes = collectConversationTurns(turns, projectId, { maxBodyChars: config.limits.maxBodyChars });\r\n if (nodes.length > 0) addStats(totals, store.upsertNodes(nodes));\r\n\r\n // Re-read in full each sync (see claude-code-reader.ts) -- the cursor here\r\n // is informational only, matching the shell scrape sources.\r\n store.setSyncCursor(projectId, CONVERSATION_SOURCE, `scanned:${nodes.length}`);\r\n log(` ${pc.dim(`${CONVERSATION_SOURCE}: ${nodes.length} of ${turns.length} exchange(s) kept`)}`);\r\n\r\n return { totals, seen: nodes.length };\r\n}\r\n\r\nconst SESSION_SOURCE = 'session:claude-code';\r\n\r\nasync function syncSessions(\r\n store: MemoryStore,\r\n projectId: string,\r\n turns: readonly RawConversationTurn[],\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n const settings = config.sources.session;\r\n\r\n // Opt-in and silent when off, same as the conversation source.\r\n if (!settings.enabled) return { totals, seen: 0 };\r\n\r\n if (turns.length === 0) {\r\n log(`${pc.dim('session')} no transcripts found`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n const result = await collectSessionSummaries(turns, projectId, new OllamaChatProvider({ model: settings.model }), {\r\n settleMinutes: settings.settleMinutes,\r\n maxSessions: settings.maxSessions,\r\n maxPromptChars: settings.maxPromptChars,\r\n maxBodyChars: config.limits.maxBodyChars,\r\n knownHash: (sessionKey) => {\r\n const meta = store.getNodeMeta(makeNodeId(projectId, 'session_summary', sessionKey));\r\n return typeof meta?.contentHash === 'string' ? meta.contentHash : null;\r\n },\r\n onProgress: (done, total) => log(` ${pc.dim(`session: summarizing ${done}/${total}`)}`),\r\n });\r\n\r\n if (result.nodes.length > 0) addStats(totals, store.upsertNodes(result.nodes));\r\n\r\n if (result.providerUnavailable) {\r\n log(\r\n `${pc.dim('session')} summarization model unavailable (is Ollama running with \\`${settings.model}\\` pulled?) -- skipped`,\r\n );\r\n } else {\r\n const parts = [`${result.nodes.length} summarized`];\r\n if (result.cached > 0) parts.push(`${result.cached} unchanged`);\r\n if (result.deferred > 0) parts.push(`${result.deferred} queued for the next sync`);\r\n if (result.unsettled > 0) parts.push(`${result.unsettled} still active`);\r\n if (result.failed > 0) parts.push(`${result.failed} failed`);\r\n log(` ${pc.dim(`${SESSION_SOURCE}: ${parts.join(', ')}`)}`);\r\n }\r\n\r\n // Informational only, like the other full-rescan sources.\r\n store.setSyncCursor(projectId, SESSION_SOURCE, `scanned:${result.nodes.length}`);\r\n\r\n return { totals, seen: result.nodes.length };\r\n}\r\n\r\nconst DOCS_SOURCE = 'docs';\r\n\r\nasync function syncDocs(\r\n store: MemoryStore,\r\n projectId: string,\r\n repoRoot: string,\r\n config: Awaited<ReturnType<typeof loadContext>>['config'],\r\n log: (line: string) => void,\r\n): Promise<{ totals: IngestStats; seen: number }> {\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n\r\n if (!config.sources.docs.enabled) {\r\n log(`${pc.dim('docs')} disabled in config`);\r\n return { totals, seen: 0 };\r\n }\r\n\r\n const { files, unreadable } = await readDocFiles(repoRoot, { include: config.sources.docs.include });\r\n\r\n const nodes = collectDocFiles(files, projectId, { maxBodyChars: config.limits.maxBodyChars });\r\n if (nodes.length > 0) addStats(totals, store.upsertNodes(nodes));\r\n\r\n // Prune *after* the upsert, so a renamed heading's replacement is already in\r\n // place and only the stranded original is left to remove.\r\n //\r\n // This scan is always a complete one -- every tracked .md file, re-read in\r\n // full -- which is what makes the delete safe: anything of this source not in\r\n // `nodes` genuinely no longer exists in the repository. An empty scan is a\r\n // legitimate outcome (every .md file deleted) and prunes accordingly; files\r\n // that could not be read are excluded rather than treated as gone.\r\n const pruned = store.pruneSourceNodes(\r\n projectId,\r\n DOCS_SOURCE,\r\n nodes.map((node) => node.id),\r\n { keepPaths: unreadable },\r\n );\r\n\r\n // Re-read in full each sync, the same trade the conversation source makes:\r\n // content-addressed ids make it idempotent, and a doc file has no cheap\r\n // append-only cursor to walk incrementally.\r\n store.setSyncCursor(projectId, DOCS_SOURCE, `scanned:${nodes.length}`);\r\n\r\n if (files.length === 0 && unreadable.length === 0) {\r\n log(`${pc.dim('docs')} no tracked .md files found`);\r\n } else {\r\n const prunedPart = pruned > 0 ? `, ${pc.yellow(`${pruned} stale removed`)}` : '';\r\n const skippedPart = unreadable.length > 0 ? `, ${unreadable.length} unreadable (kept)` : '';\r\n log(` ${pc.dim(`${DOCS_SOURCE}: ${nodes.length} section(s) from ${files.length} file(s)`)}${prunedPart}${pc.dim(skippedPart)}`);\r\n }\r\n\r\n return { totals, seen: nodes.length };\r\n}\r\n\r\n/**\r\n * The three sources `collectAvailableShellHistory` produced before the\r\n * PowerShell hook existed. Nothing has written to them since the hook took\r\n * over (it always returns `pwsh-hook` results once installed -- see the\r\n * `hookCursor`-driven branch in `syncShell` below), so on a machine with the\r\n * hook installed these are pure dead weight with no live collector to diff\r\n * against, unlike `docs`.\r\n */\r\nconst STALE_SHELL_SOURCES = ['shell:pwsh', 'shell:bash', 'shell:zsh'] as const;\r\n\r\n/** Resolves `--prune-source`/`--prune-stale-shell` into a deduplicated list of exact source strings. */\r\nfunction collectPruneSources(opts: SyncOptions): string[] {\r\n const sources = new Set<string>();\r\n if (opts.pruneStaleShell) {\r\n for (const source of STALE_SHELL_SOURCES) sources.add(source);\r\n }\r\n if (opts.pruneSource?.trim()) sources.add(opts.pruneSource.trim());\r\n return [...sources];\r\n}\r\n\r\n/**\r\n * Handles `--prune-source`/`--prune-stale-shell` as a standalone maintenance\r\n * action -- it never falls through into the rest of `runSync`'s ingest\r\n * pipeline, so a single invocation either inspects/deletes the named\r\n * source(s) or does a normal sync, never both in one run.\r\n *\r\n * Dry-run by default: without `--yes` this only counts and prints, matching\r\n * the user's explicit call that a scoped, easy-to-typo delete needs a shown\r\n * number before anything irreversible happens -- unlike `--rebuild`, which\r\n * has no such gate because its own name already states the full-project\r\n * scope.\r\n *\r\n * Sweeps `otherProjectIds` (this same sync's own `listOtherProjectIds`\r\n * result) in addition to the live `projectId`, not just the live id alone.\r\n * Found live, 2026-08-15: this repo's own dead `shell:pwsh` rows were\r\n * invisible to a live-id-only prune because they were left stranded under\r\n * the pre-rename project id by [[nexusmem-project-id-fragmentation]]'s\r\n * reconcile step (deliberately -- see `reconcile.ts`'s doc comment, which\r\n * already called this \"no different in effect from pruning it\" without\r\n * anything actually able to reach it). `reconcile.ts` already treats every\r\n * id `listOtherProjectIds` returns as a prior identity of this same repo,\r\n * never another repository's data (`db` is one file per repo) -- this reuses\r\n * that exact invariant rather than inventing a new one.\r\n */\r\nfunction runPruneSources(\r\n store: MemoryStore,\r\n projectId: string,\r\n otherProjectIds: readonly string[],\r\n sources: readonly string[],\r\n yes: boolean,\r\n out: (chunk: string) => void,\r\n): number {\r\n const scopeIds = [projectId, ...otherProjectIds];\r\n const counts = sources.flatMap((source) => scopeIds.map((id) => ({ source, id, count: store.countSourceNodes(id, source) })));\r\n const total = counts.reduce((sum, c) => sum + c.count, 0);\r\n\r\n if (total === 0) {\r\n out(`${pc.dim('prune-source')} no node(s) match ${sources.join(', ')} -- nothing to do\\n`);\r\n return 0;\r\n }\r\n\r\n const describe = (c: { source: string; id: string; count: number }) =>\r\n ` ${pc.dim(c.source)}${c.id !== projectId ? pc.dim(` (prior identity ${c.id.slice(0, 8)})`) : ''}: ${c.count} node(s)`;\r\n\r\n if (!yes) {\r\n const lines = counts.filter((c) => c.count > 0).map(describe);\r\n out(\r\n [`${pc.yellow('would remove')} ${total} node(s):`, ...lines, pc.dim('re-run with --yes to actually delete these -- this cannot be undone'), ''].join(\r\n '\\n',\r\n ),\r\n );\r\n return 0;\r\n }\r\n\r\n // Passing an empty keep-list is a full wipe of the source, not the\r\n // incremental prune `syncDocs` above uses it for -- there is no fresh scan\r\n // to diff against for a source nothing collects anymore.\r\n let removed = 0;\r\n for (const { source, id } of counts) removed += store.pruneSourceNodes(id, source, []);\r\n const identityPart = otherProjectIds.length > 0 ? `, ${scopeIds.length} project identit${scopeIds.length === 1 ? 'y' : 'ies'}` : '';\r\n out(`${pc.green('pruned')} ${removed} node(s) across ${sources.length} source(s)${identityPart}\\n`);\r\n return 0;\r\n}\r\n\r\nexport async function runSync(opts: SyncOptions): Promise<number> {\r\n const { repo, ws, projectId, config } = await loadContext(opts.cwd);\r\n const log = (line: string) => {\r\n if (!opts.quiet) process.stderr.write(`${line}\\n`);\r\n };\r\n const out = opts.out ?? ((chunk: string) => void process.stdout.write(chunk));\r\n\r\n const store = MemoryStore.open(ws.dbPath);\r\n const started = Date.now();\r\n\r\n try {\r\n store.upsertProject({ id: projectId, root: repo.root, originUrl: repo.originUrl });\r\n\r\n // Cleared *before* reconciliation runs below, deliberately: reconciliation\r\n // writes under `projectId` too, and clearing after it ran would silently\r\n // destroy the very data it just migrated forward -- data that, unlike\r\n // git/diff/docs, a fresh re-ingest cannot reproduce.\r\n if (opts.rebuild) {\r\n const removed = store.clearProject(projectId);\r\n log(`${pc.dim('rebuild')} dropped ${removed} existing node(s)`);\r\n }\r\n\r\n // A repo's own database never holds another repo's data (see\r\n // registry.ts), so any other project id already in it is this same\r\n // repo's prior identity -- almost always its git remote URL changed\r\n // since the last sync (see reconcile.ts for the full story).\r\n const staleProjectIds = store.listOtherProjectIds(projectId);\r\n for (const staleId of staleProjectIds) {\r\n const result = reconcileProjectId(store.raw, staleId, projectId);\r\n const parts = [\r\n result.migrated > 0 ? `${result.migrated} migrated` : null,\r\n result.reassigned > 0 ? `${result.reassigned} reassigned` : null,\r\n result.deduped > 0 ? `${result.deduped} already up to date` : null,\r\n result.skipped > 0 ? `${result.skipped} left behind (not reconstructable)` : null,\r\n ].filter((part): part is string => part !== null);\r\n if (parts.length > 0) {\r\n log(\r\n `${pc.yellow('reconciled')} previous project identity ${pc.dim(staleId)} (remote URL likely changed): ${parts.join(', ')}`,\r\n );\r\n }\r\n }\r\n if (staleProjectIds.length > 0) {\r\n if (opts.rebuild) {\r\n // Reconciliation just salvaged everything recoverable; --rebuild's\r\n // fresh-start intent extends naturally to purging what's deliberately\r\n // left behind (git/diff/doc/pre-hook-shell rows -- see reconcile.ts\r\n // for why those specifically are never migrated).\r\n for (const staleId of staleProjectIds) store.clearProject(staleId);\r\n }\r\n await forgetProjects(staleProjectIds);\r\n // config.json's projectId is otherwise write-once (set at init) and\r\n // would keep reporting the stale id in `nexusmem init`'s \"already\r\n // initialized\" message forever.\r\n if (config.projectId !== projectId) await writeConfig(ws, { ...config, projectId });\r\n }\r\n\r\n // Refreshed on every sync, not only at init: a repo initialized before\r\n // the registry existed, or moved since, is re-pointed by being used.\r\n await recordProject({ projectId, root: repo.root, dbPath: ws.dbPath, originUrl: repo.originUrl });\r\n\r\n const pruneSources = collectPruneSources(opts);\r\n if (pruneSources.length > 0) {\r\n return runPruneSources(store, projectId, staleProjectIds, pruneSources, opts.yes ?? false, out);\r\n }\r\n\r\n const git = await syncGit(store, projectId, opts, repo, config, log);\r\n const diffs = await syncDiffs(store, projectId, opts, repo, config, log);\r\n const shell = await syncShell(store, projectId, opts, repo.root, config, log);\r\n\r\n // Read once, used by two sources. Parsing every transcript twice was\r\n // measurable on a repo with a long history of sessions, and both sources\r\n // want the exact same turns.\r\n const conversationEnabled = opts.conversationOverride ?? config.sources.conversation.enabled;\r\n const turns =\r\n conversationEnabled || config.sources.session.enabled ? await collectClaudeCodeTranscripts(repo.root) : [];\r\n\r\n const conversation = syncConversation(store, projectId, turns, config, log, opts.conversationOverride);\r\n const sessions = await syncSessions(store, projectId, turns, config, log);\r\n const docs = await syncDocs(store, projectId, repo.root, config, log);\r\n\r\n let embedLine = '';\r\n if (!opts.noEmbed) {\r\n // Progress matters now that one pass drains the whole backlog: on a\r\n // first sync of a large repository this is the longest step by far, and\r\n // without a heartbeat it is indistinguishable from a hang.\r\n let lastLogged = 0;\r\n const result = await embedPendingNodes(store, new OllamaEmbeddingProvider(), projectId, {\r\n maxNodes: opts.embedLimit,\r\n onInvalidated: (count) =>\r\n log(`${pc.yellow('vector')} embedding model changed -- dropped ${count} vector(s), re-embedding from scratch`),\r\n onProgress: (attempted, total) => {\r\n if (total < PROGRESS_THRESHOLD || attempted - lastLogged < PROGRESS_EVERY) return;\r\n lastLogged = attempted;\r\n log(` ${pc.dim(`vector: ${attempted}/${total} embedded`)}`);\r\n },\r\n });\r\n\r\n if (result.embedded > 0) {\r\n const skippedPart = result.skipped > 0 ? pc.dim(`, ${result.skipped} skipped`) : '';\r\n const remainingPart = result.remaining > 0 ? pc.yellow(`, ${result.remaining} still pending`) : '';\r\n embedLine = ` ${pc.dim(`vector: ${result.embedded} node(s) embedded`)}${skippedPart}${remainingPart}\\n`;\r\n } else if (result.providerUnavailable) {\r\n log(`${pc.dim('vector')} embedding provider unavailable (is Ollama running with nomic-embed-text pulled?) -- BM25-only for now`);\r\n }\r\n }\r\n\r\n let linkLine = '';\r\n if (opts.linkFailures) {\r\n // After ingest/embedding, not folded into any one source's sync\r\n // function above: correlation reads across shell_command and\r\n // conversation_turn/session_summary nodes together, so it only makes\r\n // sense once whatever this run ingested is already in the store.\r\n const linkStats = correlateFailures(store, projectId);\r\n linkLine = ` ${pc.dim(`chains: ${linkStats.failuresExamined} failure(s) examined, ${linkStats.linkedByRetry} linked by retry, ${linkStats.linkedByDiscussion} by discussion`)}\\n`;\r\n }\r\n\r\n store.markSynced(projectId);\r\n\r\n const totals: IngestStats = { inserted: 0, updated: 0, unchanged: 0 };\r\n addStats(totals, git.totals);\r\n addStats(totals, diffs.totals);\r\n addStats(totals, shell.totals);\r\n addStats(totals, conversation.totals);\r\n addStats(totals, sessions.totals);\r\n addStats(totals, docs.totals);\r\n\r\n const stats = store.stats(projectId);\r\n const elapsed = ((Date.now() - started) / 1000).toFixed(2);\r\n\r\n const conversationPart = conversationEnabled ? `, ${conversation.seen} conversation exchange(s)` : '';\r\n const sessionPart = config.sources.session.enabled ? `, ${sessions.seen} session summar${sessions.seen === 1 ? 'y' : 'ies'}` : '';\r\n const docsPart = config.sources.docs.enabled ? `, ${docs.seen} doc section(s)` : '';\r\n const diffPart = config.sources.diff.enabled ? `, ${diffs.seen} file diff(s)` : '';\r\n\r\n out(\r\n [\r\n `${pc.green('synced')} ${git.seen} commit(s)${diffPart}, ${shell.seen} shell entr${shell.seen === 1 ? 'y' : 'ies'}${conversationPart}${sessionPart}${docsPart} in ${elapsed}s`,\r\n ` ${pc.green(`+${totals.inserted} new`)} ${pc.yellow(`~${totals.updated} updated`)} ${pc.dim(`=${totals.unchanged} unchanged`)}`,\r\n ` ${pc.dim(`${stats.total} node(s) total across ${stats.distinctFiles} file path(s)`)}`,\r\n '',\r\n ].join('\\n') + embedLine + linkLine,\r\n );\r\n\r\n return 0;\r\n } finally {\r\n store.close();\r\n }\r\n}\r\n","import { truncate } from '../core/text.js';\r\n\r\n/**\r\n * Splits a long assistant reply into topic-sized chunks instead of one node\r\n * per whole exchange.\r\n *\r\n * The acceptance-test failure this fixes was diagnosed against the real\r\n * database, not guessed: a specific technical explanation was present in\r\n * the index but buried (and sometimes truncated) inside a single 4000-char\r\n * node covering several unrelated points. The fix targets how this\r\n * project's own replies are actually written -- long-form prose with\r\n * **bold lead sentences** marking each point, not literal `#` markdown\r\n * headings (those show up in files this assistant writes, like README.md,\r\n * not in its own chat responses). Both are treated as section boundaries;\r\n * plain paragraphs accumulate into the current chunk until it would exceed\r\n * `maxChars`.\r\n */\r\n\r\nexport interface AssistantChunk {\r\n /** The literal heading/bold-lead text that opened this chunk, if any. */\r\n heading: string | null;\r\n text: string;\r\n}\r\n\r\nconst HEADING_LINE = /^#{1,6}\\s+(.+)$/;\r\nconst BOLD_LEAD = /^\\*\\*([^*]+?)\\*\\*/;\r\n\r\n/** Returns the section title if `paragraph` opens a new logical section, else null. */\r\nfunction sectionStart(paragraph: string): string | null {\r\n const firstLine = paragraph.split('\\n')[0] ?? '';\r\n const heading = HEADING_LINE.exec(firstLine);\r\n if (heading) return (heading[1] ?? '').trim();\r\n\r\n const bold = BOLD_LEAD.exec(paragraph);\r\n if (bold) return (bold[1] ?? '').trim();\r\n\r\n return null;\r\n}\r\n\r\nexport function chunkAssistantText(text: string, maxChars: number): AssistantChunk[] {\r\n const paragraphs = text\r\n // Normalize first: every rule below is written against `\\n`, and a CRLF\r\n // file defeats all of them at once. `\\r\\n\\r\\n` holds no two *consecutive*\r\n // `\\n`, so the paragraph split silently returns the whole document as one\r\n // paragraph, headings are never detected, and the file degrades into a few\r\n // coarse size-based chunks. Docs are read straight off a Windows working\r\n // tree where git checks files out as CRLF, so this is the normal case\r\n // there, not an edge one -- it cut this repo's README from 37 sections to 8.\r\n .replace(/\\r\\n?/g, '\\n')\r\n .split(/\\n{2,}/)\r\n .map((p) => p.trim())\r\n .filter(Boolean);\r\n\r\n if (paragraphs.length === 0) return [];\r\n\r\n const chunks: AssistantChunk[] = [];\r\n let buffer: string[] = [];\r\n let bufferHeading: string | null = null;\r\n\r\n const bufferChars = () => buffer.reduce((n, p) => n + p.length, 0) + Math.max(0, buffer.length - 1) * 2;\r\n\r\n const flush = () => {\r\n if (buffer.length === 0) return;\r\n chunks.push({ heading: bufferHeading, text: truncate(buffer.join('\\n\\n'), maxChars) });\r\n buffer = [];\r\n bufferHeading = null;\r\n };\r\n\r\n for (const paragraph of paragraphs) {\r\n const heading = sectionStart(paragraph);\r\n const startsNewSection = heading !== null && buffer.length > 0;\r\n const wouldOverflow = buffer.length > 0 && bufferChars() + 2 + paragraph.length > maxChars;\r\n\r\n if (startsNewSection || wouldOverflow) flush();\r\n if (heading !== null) bufferHeading = heading;\r\n\r\n buffer.push(paragraph);\r\n }\r\n flush();\r\n\r\n return chunks;\r\n}\r\n","/**\r\n * Best-effort secret redaction for collected text before it is ever written\r\n * to the FTS index.\r\n *\r\n * This is a safety net, not a guarantee -- pattern-based redaction cannot\r\n * catch every shape a secret can take. It exists because conversation text\r\n * is the collector most likely to contain something sensitive (a pasted\r\n * credential, a key a user asked for help debugging), but a committed `.env`\r\n * or a hard-coded key makes the code-diff collector a real second candidate,\r\n * which is what the `high-confidence` profile below is for.\r\n */\r\n\r\ninterface Rule {\r\n name: string;\r\n pattern: RegExp;\r\n /**\r\n * The match is a secret by its own shape, with no reliance on the\r\n * surrounding text. Only these rules are safe to run over source code:\r\n * the shape rules match strings nothing else produces, while the\r\n * key/value rule matches ordinary code such as\r\n * `const apiKey = process.env.API_KEY` and would corrupt the very lines\r\n * a diff is indexed for.\r\n */\r\n highConfidence: boolean;\r\n}\r\n\r\nconst RULES: Rule[] = [\r\n {\r\n name: 'private-key-block',\r\n pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,\r\n highConfidence: true,\r\n },\r\n { name: 'aws-access-key', pattern: /\\bAKIA[0-9A-Z]{16}\\b/g, highConfidence: true },\r\n { name: 'github-token', pattern: /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/g, highConfidence: true },\r\n { name: 'slack-token', pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/g, highConfidence: true },\r\n {\r\n name: 'jwt',\r\n pattern: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\b/g,\r\n highConfidence: true,\r\n },\r\n // key/token/secret/password = \"value\" or : value, in code, JSON, env-file or prose form.\r\n {\r\n name: 'key-value-secret',\r\n pattern: /\\b((?:api[_-]?key|secret|password|passwd|token|access[_-]?key)s?)\\s*[:=]\\s*['\"]?[A-Za-z0-9_\\-./+=]{8,}['\"]?/gi,\r\n highConfidence: false,\r\n },\r\n];\r\n\r\nexport interface RedactResult {\r\n text: string;\r\n redactedCount: number;\r\n}\r\n\r\n/**\r\n * `all` runs every rule -- the right trade for prose, where a false positive\r\n * costs a mangled sentence. `high-confidence` runs only the shape rules, for\r\n * text that *is* code and must survive redaction intact.\r\n */\r\nexport type RedactProfile = 'all' | 'high-confidence';\r\n\r\nexport function redact(text: string, profile: RedactProfile = 'all'): RedactResult {\r\n let redactedCount = 0;\r\n let out = text;\r\n\r\n for (const rule of RULES) {\r\n if (profile === 'high-confidence' && !rule.highConfidence) continue;\r\n out = out.replace(rule.pattern, (_match: string, ...rest: unknown[]) => {\r\n redactedCount += 1;\r\n // `String.replace` passes (match, ...groups, offset, wholeString), so for\r\n // a rule with no capture group `rest[0]` is the *offset* -- a number, and\r\n // truthy at any position but the very first. Testing the type rather than\r\n // truthiness is what keeps a match position out of the indexed corpus.\r\n const key = typeof rest[0] === 'string' ? rest[0] : null;\r\n // Keep the key name for key/value matches so the redaction is legible\r\n // (\"apiKey: [redacted]\" reads better than a bare \"[redacted]\").\r\n return key ? `${key}: [redacted]` : '[redacted]';\r\n });\r\n }\r\n\r\n return { text: out, redactedCount };\r\n}\r\n","import { makeNodeId } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport type { FileTouch, MemoryNode } from '../core/types.js';\r\nimport { chunkAssistantText } from '../conversation/chunk.js';\r\nimport { redact } from '../conversation/redact.js';\r\nimport type { RawConversationTurn } from '../conversation/types.js';\r\n\r\nexport interface ConversationCollectorOptions {\r\n /** Default 2500 -- final safety cap on one chunk's assembled Q+A body. */\r\n maxBodyChars?: number;\r\n /**\r\n * Default 900 -- target size for one assistant-reply chunk before it's\r\n * closed out and a new node started. Small enough that a single\r\n * explanatory point stays retrievable on its own, not buried inside\r\n * several unrelated ones in the same long reply.\r\n */\r\n maxChunkChars?: number;\r\n}\r\n\r\nconst DEFAULT_MAX_BODY_CHARS = 2500;\r\nconst DEFAULT_MAX_CHUNK_CHARS = 900;\r\nconst MAX_TITLE_CHARS = 200;\r\nconst MAX_FILES_PER_NODE = 20;\r\n\r\nconst EXPLANATION_MARKERS =\r\n /\\b(because|the reason|design decision|trade-?off|instead of|rationale|so that)\\b/i;\r\nconst TRIVIAL_ACK = /^(ok|okay|thanks?|got it|sounds good|👍|done)\\.?!?$/i;\r\n\r\n/**\r\n * Prior importance of one chunk of a conversation exchange.\r\n *\r\n * Scored per chunk, not per whole exchange -- a genuinely explanatory point\r\n * should outrank a routine one even when they came from the same reply.\r\n * A much noisier signal than a commit type, so scores stay closer to the\r\n * middle. The intuition mirrors `scoreCommit`: text that explains a *why*\r\n * is worth far more to a future query than text that just confirms\r\n * something happened.\r\n */\r\nexport function scoreConversationTurn(userText: string, replyText: string): number {\r\n const text = `${userText}\\n${replyText}`;\r\n let score = 0.3;\r\n\r\n if (EXPLANATION_MARKERS.test(text)) score += 0.25;\r\n if (replyText.length > 400) score += 0.15;\r\n else if (replyText.length < 80) score -= 0.1;\r\n\r\n if (TRIVIAL_ACK.test(userText.trim())) score -= 0.2;\r\n if (userText.trim().endsWith('?')) score += 0.05;\r\n\r\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\r\n}\r\n\r\nconst FILE_PATH = /\\b(?:[\\w.-]+\\/)*[\\w-]+\\.(?:ts|tsx|js|jsx|json|md|py|rs|go|java|c|cpp|h|hpp|css|html|ya?ml|toml|sql|sh|ps1)\\b/g;\r\n\r\n/**\r\n * Heuristic file-mention extraction, not a parsed diff -- lets a\r\n * conversation node join the `node_files` path index like git/shell nodes\r\n * do, so \"why is this file the way it is\" can be answered from either\r\n * direction. False positives (a path-shaped word in prose) are possible and\r\n * acceptable for a best-effort cross-reference.\r\n */\r\nexport function extractMentionedFiles(text: string): FileTouch[] {\r\n const seen = new Set<string>();\r\n const files: FileTouch[] = [];\r\n\r\n for (const match of text.matchAll(FILE_PATH)) {\r\n const path = match[0];\r\n if (seen.has(path)) continue;\r\n seen.add(path);\r\n files.push({ path, insertions: null, deletions: null, binary: false });\r\n if (files.length >= MAX_FILES_PER_NODE) break;\r\n }\r\n\r\n return files;\r\n}\r\n\r\n/**\r\n * Truncating `${userFirstLine}${suffix}` as one string would silently drop\r\n * the suffix whenever the question alone already reached MAX_TITLE_CHARS --\r\n * exactly the case for a long original question, which is also exactly the\r\n * case where a reply is most likely to have been split into many chunks\r\n * that then all render with one indistinguishable title. Truncate the\r\n * question first, leaving guaranteed room for whatever comes after it.\r\n */\r\nfunction withSuffix(base: string, suffix: string): string {\r\n const budget = Math.max(20, MAX_TITLE_CHARS - suffix.length);\r\n return `${truncate(base, budget)}${suffix}`;\r\n}\r\n\r\nfunction chunkTitle(userFirstLine: string, heading: string | null, index: number, count: number): string {\r\n if (heading) return withSuffix(userFirstLine, ` — ${heading}`);\r\n if (count > 1) return withSuffix(userFirstLine, ` (part ${index + 1}/${count})`);\r\n return truncate(userFirstLine, MAX_TITLE_CHARS);\r\n}\r\n\r\n/**\r\n * One exchange becomes one node per chunk of its assistant reply (see\r\n * `chunkAssistantText`), each pairing the *original* question with just\r\n * that chunk -- so every node is independently self-contained and\r\n * searchable, not dependent on a sibling node for context. Short exchanges\r\n * (the common case) produce exactly one chunk, unchanged from before.\r\n */\r\nexport function toMemoryNodes(\r\n turn: RawConversationTurn,\r\n projectId: string,\r\n opts: ConversationCollectorOptions = {},\r\n): MemoryNode[] {\r\n const maxBody = opts.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;\r\n const maxChunk = opts.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;\r\n\r\n const userRedacted = redact(turn.userText);\r\n const userFirstLine = userRedacted.text.split(/\\r?\\n/)[0] ?? userRedacted.text;\r\n\r\n const assistantRedacted = redact(turn.assistantText);\r\n const chunks = chunkAssistantText(assistantRedacted.text, maxChunk);\r\n // Redaction found something but chunking produced nothing to attach it to\r\n // (a reply that was pure whitespace after redaction) -- shouldn't happen\r\n // since callers filter out empty assistantText first, but stay defensive.\r\n if (chunks.length === 0) return [];\r\n\r\n return chunks.map((chunk, index) => {\r\n const body = [`Q: ${userRedacted.text}`, '', `A: ${chunk.text}`].join('\\n');\r\n\r\n return {\r\n id: makeNodeId(projectId, 'conversation_turn', `${turn.naturalKey}:${index}`),\r\n kind: 'conversation_turn',\r\n projectId,\r\n ts: turn.ts,\r\n source: `conversation:${turn.source}`,\r\n title: chunkTitle(userFirstLine, chunk.heading, index, chunks.length),\r\n body: truncate(body, maxBody),\r\n files: extractMentionedFiles(`${userRedacted.text}\\n${chunk.text}`),\r\n signal: scoreConversationTurn(userRedacted.text, chunk.text),\r\n meta: {\r\n cwd: turn.cwd,\r\n source: turn.source,\r\n chunkIndex: index,\r\n chunkCount: chunks.length,\r\n heading: chunk.heading,\r\n // Redaction runs once over the whole reply before chunking (so a\r\n // secret can never straddle a chunk boundary and slip through) --\r\n // this is the turn's total, repeated on every chunk it produced,\r\n // not a per-chunk count.\r\n redactedCount: userRedacted.redactedCount + assistantRedacted.redactedCount,\r\n },\r\n };\r\n });\r\n}\r\n\r\nexport function collectConversationTurns(\r\n turns: readonly RawConversationTurn[],\r\n projectId: string,\r\n opts: ConversationCollectorOptions = {},\r\n): MemoryNode[] {\r\n return turns.filter((t) => t.assistantText.length > 0).flatMap((turn) => toMemoryNodes(turn, projectId, opts));\r\n}\r\n","import type { FileTouch } from '../core/types.js';\n\n/**\n * Pure parsing layer for `git log` output. No I/O here, so it is cheap to\n * unit-test against the ugly real-world cases (renames, binaries, messages\n * containing newlines / tabs / arrows).\n */\n\nexport const RECORD_SEP = '\\x1E';\nexport const UNIT_SEP = '\\x1F';\n\n/**\n * Field order must stay in sync with `parseCommitRecord`.\n *\n * ASCII RS/US are used as delimiters rather than a made-up token: they are\n * control characters that git never emits itself and that essentially never\n * occur in commit messages.\n */\nexport const GIT_LOG_FORMAT =\n '%x1e%H%x1f%h%x1f%P%x1f%an%x1f%ae%x1f%aI%x1f%cI%x1f%s%x1f%B%x1f';\n\nconst FIELD_COUNT = 10; // 9 format fields + the trailing --numstat block\n\nexport interface RawCommit {\n sha: string;\n shortSha: string;\n parents: string[];\n authorName: string;\n authorEmail: string;\n /** ISO-8601 with offset (`%aI`). */\n authoredAt: string;\n committedAt: string;\n subject: string;\n /** Full message including the subject line (`%B`), trimmed. */\n message: string;\n /** Message with the subject line stripped; empty for one-line commits. */\n messageBody: string;\n files: FileTouch[];\n isMerge: boolean;\n}\n\n/**\n * Split a stdout buffer into complete records.\n *\n * Returns the trailing partial record so the caller can carry it into the next\n * chunk. `flush` treats whatever remains as complete.\n */\nexport function splitRecords(buffer: string, flush = false): { records: string[]; rest: string } {\n const parts = buffer.split(RECORD_SEP);\n // parts[0] is whatever preceded the first RS -- empty on a fresh stream.\n const rest = flush ? '' : (parts.pop() ?? '');\n const records = parts.filter((p) => p.trim().length > 0);\n if (flush && rest.trim().length > 0) records.push(rest);\n return { records, rest };\n}\n\nexport function parseCommitRecord(record: string): RawCommit | null {\n // Tolerate a leading separator so callers can pass raw `git log` slices\n // without going through `splitRecords` first.\n let payload = record;\n while (payload.startsWith(RECORD_SEP)) payload = payload.slice(RECORD_SEP.length);\n\n const parts = payload.split(UNIT_SEP);\n if (parts.length < FIELD_COUNT) return null;\n\n const sha = parts[0] ?? '';\n if (!/^[0-9a-f]{7,64}$/i.test(sha)) return null;\n\n // A commit message containing a literal US would add fields; everything\n // between the subject and the final block still belongs to the body.\n const numstatBlock = parts[parts.length - 1] ?? '';\n const message = parts.slice(8, parts.length - 1).join(UNIT_SEP).trim();\n const subject = (parts[7] ?? '').trim();\n const parents = (parts[2] ?? '').trim().split(/\\s+/).filter(Boolean);\n\n return {\n sha,\n shortSha: parts[1] ?? '',\n parents,\n authorName: parts[3] ?? '',\n authorEmail: parts[4] ?? '',\n authoredAt: parts[5] ?? '',\n committedAt: parts[6] ?? '',\n subject,\n message,\n messageBody: stripSubject(message, subject),\n files: parseNumstat(numstatBlock),\n isMerge: parents.length > 1,\n };\n}\n\nfunction stripSubject(message: string, subject: string): string {\n if (!subject || !message.startsWith(subject)) return message;\n return message.slice(subject.length).trim();\n}\n\nconst NUMSTAT_LINE = /^(\\d+|-)\\t(\\d+|-)\\t(.*)$/;\n\nexport function parseNumstat(block: string): FileTouch[] {\n const files: FileTouch[] = [];\n\n for (const line of block.split('\\n')) {\n const m = NUMSTAT_LINE.exec(line.trimEnd());\n if (!m) continue;\n\n const [, addRaw = '', delRaw = '', pathRaw = ''] = m;\n const binary = addRaw === '-' || delRaw === '-';\n const { path, previousPath } = resolveRenamePath(unquoteGitPath(pathRaw));\n\n const touch: FileTouch = {\n path,\n insertions: binary ? null : Number(addRaw),\n deletions: binary ? null : Number(delRaw),\n binary,\n };\n if (previousPath) touch.previousPath = previousPath;\n files.push(touch);\n }\n\n return files;\n}\n\n/**\n * `--numstat` reports renames in two shapes:\n * src/{old => new}/file.ts (shared prefix/suffix factored out)\n * old/file.ts => new/file.ts (no shared parts)\n */\nexport function resolveRenamePath(raw: string): { path: string; previousPath?: string } {\n const braced = /^(.*)\\{(.*?) => (.*?)\\}(.*)$/.exec(raw);\n if (braced) {\n const [, prefix = '', from = '', to = '', suffix = ''] = braced;\n return {\n path: collapseSlashes(prefix + to + suffix),\n previousPath: collapseSlashes(prefix + from + suffix),\n };\n }\n\n const plain = raw.split(' => ');\n if (plain.length === 2) {\n return { path: (plain[1] ?? '').trim(), previousPath: (plain[0] ?? '').trim() };\n }\n\n return { path: raw };\n}\n\nfunction collapseSlashes(p: string): string {\n return p.replace(/\\/{2,}/g, '/').replace(/^\\//, '');\n}\n\n/**\n * git still quotes paths containing `\"`, backslashes or control characters\n * even with `core.quotePath=false`.\n */\nexport function unquoteGitPath(p: string): string {\n if (p.length < 2 || !p.startsWith('\"') || !p.endsWith('\"')) return p;\n const inner = p.slice(1, -1);\n\n // Octal escapes are raw *bytes*, so decode into a byte buffer and let UTF-8\n // decoding reassemble multi-byte characters.\n const bytes: number[] = [];\n\n for (let i = 0; i < inner.length; i += 1) {\n const ch = inner[i] ?? '';\n if (ch !== '\\\\') {\n for (const b of Buffer.from(ch, 'utf8')) bytes.push(b);\n continue;\n }\n\n const esc = inner[i + 1] ?? '';\n const simple: Record<string, number> = { n: 0x0a, t: 0x09, r: 0x0d, a: 0x07, b: 0x08, f: 0x0c, v: 0x0b };\n if (esc in simple) {\n bytes.push(simple[esc] as number);\n i += 1;\n } else if (esc === '\"' || esc === '\\\\') {\n bytes.push(esc.charCodeAt(0));\n i += 1;\n } else if (/[0-7]/.test(esc)) {\n const octal = inner.slice(i + 1, i + 4);\n bytes.push(parseInt(octal, 8) & 0xff);\n i += 3;\n } else {\n bytes.push(0x5c); // lone backslash\n }\n }\n\n return Buffer.from(bytes).toString('utf8');\n}\n","import { GitError, gitStream } from './exec.js';\r\nimport { RECORD_SEP, splitRecords, UNIT_SEP, unquoteGitPath } from './parse.js';\r\n\r\n/**\r\n * Reading and parsing of commit *patches*, as opposed to `log.ts`'s commit\r\n * metadata + `--numstat` summary.\r\n *\r\n * The two are deliberately separate walks rather than one `git log -p\r\n * --numstat` call. Combining them puts the numstat block and the patch in the\r\n * same trailing field, where a diff line such as `-1\\t2\\tfoo` is\r\n * indistinguishable from a real numstat row -- the commit collector would then\r\n * invent file entries out of a diff of any tab-separated file. The cost is one\r\n * extra `git log` process per sync; the benefit is that neither parser has to\r\n * guess which kind of line it is looking at.\r\n */\r\n\r\nexport const GIT_DIFF_LOG_FORMAT = '%x1e%H%x1f%h%x1f%aI%x1f%s%x1f';\r\n\r\n/** 4 format fields + the trailing patch block. */\r\nconst FIELD_COUNT = 5;\r\n\r\nexport type FileDiffStatus = 'added' | 'deleted' | 'renamed' | 'modified';\r\n\r\nexport interface RawFileDiff {\r\n /** Repo-relative path, post-rename. */\r\n path: string;\r\n /** Set only when this file was renamed or copied in this commit. */\r\n previousPath?: string;\r\n status: FileDiffStatus;\r\n /** git printed `Binary files ... differ` (or a binary patch) instead of hunks. */\r\n binary: boolean;\r\n insertions: number;\r\n deletions: number;\r\n hunkCount: number;\r\n /** The unified-diff hunks for this file, without the `diff --git`/`index` preamble. */\r\n patch: string;\r\n}\r\n\r\nexport interface RawCommitDiff {\r\n sha: string;\r\n shortSha: string;\r\n /** ISO-8601 with offset (`%aI`), matching the commit node's `ts`. */\r\n authoredAt: string;\r\n subject: string;\r\n files: RawFileDiff[];\r\n}\r\n\r\nexport interface ReadCommitDiffsOptions {\r\n /** Revision to walk back from. Default `HEAD`. */\r\n rev?: string;\r\n /** Exclusive lower bound -- walks `<afterCommit>..<rev>`. Used for incremental sync. */\r\n afterCommit?: string | null;\r\n /** Any git date expression, e.g. `90.days.ago` or `2025-01-01`. */\r\n since?: string | null;\r\n /** Hard cap on commits walked. Diffs are far bulkier than commit messages, so callers normally set one. */\r\n maxCount?: number | null;\r\n /** Context lines around each hunk. Default 3, git's own default. */\r\n contextLines?: number;\r\n /** Restrict to pathspecs. */\r\n paths?: string[];\r\n}\r\n\r\n/** Errors that just mean \"there is nothing to read\", not \"something broke\". */\r\nconst EMPTY_HISTORY = /does not have any commits yet|unknown revision|bad revision|ambiguous argument/i;\r\n\r\nexport function buildDiffLogArgs(opts: ReadCommitDiffsOptions = {}): string[] {\r\n const { rev = 'HEAD', afterCommit, since, maxCount, contextLines = 3, paths } = opts;\r\n\r\n const args = [\r\n 'log',\r\n `--format=${GIT_DIFF_LOG_FORMAT}`,\r\n '--patch',\r\n '--no-color',\r\n // A merge produces no patch at all unless `-m`/`--cc` is passed, and the\r\n // combined diff those print is a different format from the one parsed\r\n // here. Excluding merges up front keeps the parser honest about what it\r\n // supports; the merge itself is still remembered as a `git_commit` node.\r\n '--no-merges',\r\n '--find-renames',\r\n // Never run a user-configured textconv filter: it would execute an\r\n // arbitrary program from repo config during a sync, and its output is not\r\n // the diff we claim to be indexing.\r\n '--no-textconv',\r\n `--unified=${Math.max(0, contextLines)}`,\r\n ];\r\n\r\n if (maxCount && maxCount > 0) args.push(`--max-count=${maxCount}`);\r\n if (since) args.push(`--since=${since}`);\r\n\r\n args.push(afterCommit ? `${afterCommit}..${rev}` : rev);\r\n\r\n if (paths?.length) args.push('--', ...paths);\r\n\r\n return args;\r\n}\r\n\r\n/** Walk commit patches, yielding one commit's file diffs at a time. */\r\nexport async function* readCommitDiffs(\r\n cwd: string,\r\n opts: ReadCommitDiffsOptions = {},\r\n): AsyncGenerator<RawCommitDiff> {\r\n const args = buildDiffLogArgs(opts);\r\n let buffer = '';\r\n\r\n try {\r\n for await (const chunk of gitStream(cwd, args)) {\r\n buffer += chunk;\r\n const { records, rest } = splitRecords(buffer);\r\n buffer = rest;\r\n for (const record of records) {\r\n const commit = parseCommitDiffRecord(record);\r\n if (commit) yield commit;\r\n }\r\n }\r\n } catch (err) {\r\n if (err instanceof GitError && EMPTY_HISTORY.test(err.stderr)) return;\r\n throw err;\r\n }\r\n\r\n for (const record of splitRecords(buffer, true).records) {\r\n const commit = parseCommitDiffRecord(record);\r\n if (commit) yield commit;\r\n }\r\n}\r\n\r\nexport function parseCommitDiffRecord(record: string): RawCommitDiff | null {\r\n let payload = record;\r\n while (payload.startsWith(RECORD_SEP)) payload = payload.slice(RECORD_SEP.length);\r\n\r\n const parts = payload.split(UNIT_SEP);\r\n if (parts.length < FIELD_COUNT) return null;\r\n\r\n const sha = parts[0] ?? '';\r\n if (!/^[0-9a-f]{7,64}$/i.test(sha)) return null;\r\n\r\n // Same defence as `parseCommitRecord`: a subject containing a literal US\r\n // would add fields, and everything between it and the final block still\r\n // belongs to the subject rather than to the patch.\r\n const patchBlock = parts[parts.length - 1] ?? '';\r\n const subject = parts.slice(3, parts.length - 1).join(UNIT_SEP).trim();\r\n\r\n return {\r\n sha,\r\n shortSha: parts[1] ?? '',\r\n authoredAt: parts[2] ?? '',\r\n subject,\r\n files: parseFileDiffs(patchBlock),\r\n };\r\n}\r\n\r\nconst FILE_HEADER = 'diff --git ';\r\nconst HUNK_HEADER = /^@@ -\\d+(?:,\\d+)? \\+\\d+(?:,\\d+)? @@/;\r\n\r\n/**\r\n * Split one commit's patch into per-file diffs.\r\n *\r\n * Section boundaries are `diff --git` lines at column 0. A hunk body can never\r\n * produce one: every line inside a hunk carries a ` `, `+`, `-` or `\\` prefix,\r\n * so an added line reading `diff --git ...` arrives as `+diff --git ...`.\r\n */\r\nexport function parseFileDiffs(block: string): RawFileDiff[] {\r\n const files: RawFileDiff[] = [];\r\n let section: string[] | null = null;\r\n\r\n const flush = () => {\r\n if (!section) return;\r\n const parsed = parseFileSection(section);\r\n if (parsed) files.push(parsed);\r\n section = null;\r\n };\r\n\r\n for (const raw of block.split('\\n')) {\r\n const line = raw.endsWith('\\r') ? raw.slice(0, -1) : raw;\r\n if (line.startsWith(FILE_HEADER)) {\r\n flush();\r\n section = [line];\r\n } else if (section) {\r\n section.push(line);\r\n }\r\n }\r\n flush();\r\n\r\n return files;\r\n}\r\n\r\nfunction parseFileSection(lines: string[]): RawFileDiff | null {\r\n let status: FileDiffStatus = 'modified';\r\n let binary = false;\r\n let fromPath: string | null = null;\r\n let toPath: string | null = null;\r\n let renamedFrom: string | null = null;\r\n let hunkStart = -1;\r\n\r\n for (let i = 0; i < lines.length; i += 1) {\r\n const line = lines[i] ?? '';\r\n\r\n if (HUNK_HEADER.test(line)) {\r\n hunkStart = i;\r\n break;\r\n }\r\n\r\n if (line.startsWith('new file mode')) status = 'added';\r\n else if (line.startsWith('deleted file mode')) status = 'deleted';\r\n else if (line.startsWith('rename from ')) renamedFrom = unquoteGitPath(line.slice('rename from '.length));\r\n else if (line.startsWith('rename to ')) status = 'renamed';\r\n else if (line.startsWith('copy from ')) renamedFrom = unquoteGitPath(line.slice('copy from '.length));\r\n else if (line.startsWith('Binary files ') || line.startsWith('GIT binary patch')) binary = true;\r\n else if (line.startsWith('--- ')) fromPath = stripDiffPathPrefix(line.slice(4));\r\n else if (line.startsWith('+++ ')) toPath = stripDiffPathPrefix(line.slice(4));\r\n }\r\n\r\n const header = parseDiffGitPaths(lines[0] ?? '');\r\n const path = toPath ?? header.b ?? fromPath ?? header.a;\r\n if (!path) return null;\r\n\r\n const previousPath = renamedFrom ?? (status === 'renamed' ? (fromPath ?? header.a ?? undefined) : undefined);\r\n\r\n // Nothing textual to index: a binary blob, or a metadata-only change (mode\r\n // bits, a pure rename). The commit node already records that the file was\r\n // touched, so dropping these costs no information.\r\n if (binary || hunkStart === -1) {\r\n return {\r\n path,\r\n ...(previousPath && previousPath !== path ? { previousPath } : {}),\r\n status,\r\n binary,\r\n insertions: 0,\r\n deletions: 0,\r\n hunkCount: 0,\r\n patch: '',\r\n };\r\n }\r\n\r\n const hunkLines = lines.slice(hunkStart);\r\n let insertions = 0;\r\n let deletions = 0;\r\n let hunkCount = 0;\r\n\r\n for (const line of hunkLines) {\r\n if (HUNK_HEADER.test(line)) hunkCount += 1;\r\n else if (line.startsWith('+')) insertions += 1;\r\n else if (line.startsWith('-')) deletions += 1;\r\n }\r\n\r\n return {\r\n path,\r\n ...(previousPath && previousPath !== path ? { previousPath } : {}),\r\n status,\r\n binary: false,\r\n insertions,\r\n deletions,\r\n hunkCount,\r\n patch: hunkLines.join('\\n').trimEnd(),\r\n };\r\n}\r\n\r\n/** `--- a/src/foo.ts` → `src/foo.ts`; `--- /dev/null` → `null`. */\r\nfunction stripDiffPathPrefix(raw: string): string | null {\r\n const cleaned = unquoteGitPath(raw.trim());\r\n if (cleaned === '/dev/null') return null;\r\n return cleaned.replace(/^[ab]\\//, '');\r\n}\r\n\r\n/**\r\n * Best-effort paths from a `diff --git a/x b/y` line.\r\n *\r\n * Only used when the `---`/`+++` lines are absent, which happens for binary\r\n * and metadata-only changes -- both of which are dropped anyway, so this is a\r\n * label for a skipped file rather than the identity of an indexed one. An\r\n * unquoted path containing ` b/` is genuinely ambiguous in this format; git\r\n * quotes the awkward cases, and the quoted form is parsed exactly.\r\n */\r\nexport function parseDiffGitPaths(headerLine: string): { a: string | null; b: string | null } {\r\n const rest = headerLine.slice(FILE_HEADER.length);\r\n\r\n if (rest.startsWith('\"')) {\r\n const match = /^(\"(?:[^\"\\\\]|\\\\.)*\")\\s+(\"(?:[^\"\\\\]|\\\\.)*\"|\\S+)$/.exec(rest);\r\n if (match) {\r\n return { a: stripDiffPathPrefix(match[1] ?? ''), b: stripDiffPathPrefix(match[2] ?? '') };\r\n }\r\n }\r\n\r\n const quotedSecond = /^(\\S+)\\s+(\"(?:[^\"\\\\]|\\\\.)*\")$/.exec(rest);\r\n if (quotedSecond) {\r\n return { a: stripDiffPathPrefix(quotedSecond[1] ?? ''), b: stripDiffPathPrefix(quotedSecond[2] ?? '') };\r\n }\r\n\r\n const split = / b\\//.exec(rest);\r\n if (!split || split.index <= 0) return { a: null, b: null };\r\n\r\n return {\r\n a: stripDiffPathPrefix(rest.slice(0, split.index)),\r\n b: stripDiffPathPrefix(rest.slice(split.index + 1)),\r\n };\r\n}\r\n","import { GitError, gitStream } from './exec.js';\nimport { GIT_LOG_FORMAT, parseCommitRecord, splitRecords, type RawCommit } from './parse.js';\n\nexport interface ReadCommitsOptions {\n /** Revision to walk back from. Default `HEAD`. */\n rev?: string;\n /** Exclusive lower bound -- walks `<afterCommit>..<rev>`. Used for incremental sync. */\n afterCommit?: string | null;\n /** Any git date expression, e.g. `90.days.ago` or `2025-01-01`. */\n since?: string | null;\n maxCount?: number | null;\n /** Merge commits carry PR titles, so they are kept by default. */\n includeMerges?: boolean;\n /** Restrict to pathspecs. */\n paths?: string[];\n}\n\n/** Errors that just mean \"there is nothing to read\", not \"something broke\". */\nconst EMPTY_HISTORY = /does not have any commits yet|unknown revision|bad revision|ambiguous argument/i;\n\nexport function buildLogArgs(opts: ReadCommitsOptions = {}): string[] {\n const { rev = 'HEAD', afterCommit, since, maxCount, includeMerges = true, paths } = opts;\n\n const args = ['log', `--format=${GIT_LOG_FORMAT}`, '--numstat', '--no-color'];\n\n if (!includeMerges) args.push('--no-merges');\n if (maxCount && maxCount > 0) args.push(`--max-count=${maxCount}`);\n if (since) args.push(`--since=${since}`);\n\n args.push(afterCommit ? `${afterCommit}..${rev}` : rev);\n\n if (paths?.length) args.push('--', ...paths);\n\n return args;\n}\n\n/**\n * Walk commit history, yielding one parsed commit at a time.\n *\n * The generator is lazy: breaking out of the loop kills the underlying `git`\n * process, so `--limit`-style callers never pay for the full history.\n */\nexport async function* readCommits(cwd: string, opts: ReadCommitsOptions = {}): AsyncGenerator<RawCommit> {\n const args = buildLogArgs(opts);\n let buffer = '';\n\n try {\n for await (const chunk of gitStream(cwd, args)) {\n buffer += chunk;\n const { records, rest } = splitRecords(buffer);\n buffer = rest;\n for (const record of records) {\n const commit = parseCommitRecord(record);\n if (commit) yield commit;\n }\n }\n } catch (err) {\n if (err instanceof GitError && EMPTY_HISTORY.test(err.stderr)) return;\n throw err;\n }\n\n for (const record of splitRecords(buffer, true).records) {\n const commit = parseCommitRecord(record);\n if (commit) yield commit;\n }\n}\n","import { makeNodeId } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport type { FileTouch, MemoryNode } from '../core/types.js';\r\nimport { readCommits, type ReadCommitsOptions } from '../git/log.js';\r\nimport type { RawCommit } from '../git/parse.js';\r\n\r\n/**\r\n * Maps raw git commits onto MemoryNodes.\r\n *\r\n * This is the only place that knows a commit is a commit -- everything\r\n * downstream (storage, search, context packing) sees generic nodes.\r\n */\r\n\r\nexport interface GitCommitCollectorOptions extends ReadCommitsOptions {\r\n /** Keep at most this many files per node, biggest churn first. Default 40. */\r\n maxFilesPerNode?: number;\r\n /** Hard cap on node body size, to bound index and embedding cost. Default 4000. */\r\n maxBodyChars?: number;\r\n}\r\n\r\nconst DEFAULTS = { maxFilesPerNode: 40, maxBodyChars: 4000 } as const;\r\nconst MAX_TITLE_CHARS = 200;\r\n\r\nexport interface ConventionalHeader {\r\n type: string | null;\r\n scope: string | null;\r\n breaking: boolean;\r\n description: string;\r\n}\r\n\r\nconst CONVENTIONAL = /^([a-z]+)(?:\\(([^)]*)\\))?(!)?:\\s*(.+)$/i;\r\n\r\nexport function parseConventionalHeader(subject: string): ConventionalHeader {\r\n const m = CONVENTIONAL.exec(subject.trim());\r\n if (!m) return { type: null, scope: null, breaking: false, description: subject.trim() };\r\n return {\r\n type: (m[1] ?? '').toLowerCase(),\r\n scope: m[2] ?? null,\r\n breaking: Boolean(m[3]),\r\n description: (m[4] ?? '').trim(),\r\n };\r\n}\r\n\r\n/**\r\n * Prior importance by commit type.\r\n *\r\n * The intuition: when an agent asks \"why is this code like this?\", a `fix` or\r\n * `refactor` explains far more than a `chore` or `style`.\r\n *\r\n * Exported because the diff collector scores a file's patch from the same\r\n * commit type. A second copy there would be free to drift, which is exactly\r\n * how the four `signalColor` copies ended up disagreeing.\r\n */\r\nexport const TYPE_WEIGHTS: Record<string, number> = {\r\n fix: 0.8,\r\n feat: 0.8,\r\n revert: 0.78,\r\n perf: 0.7,\r\n refactor: 0.68,\r\n security: 0.85,\r\n test: 0.45,\r\n docs: 0.35,\r\n build: 0.32,\r\n ci: 0.28,\r\n chore: 0.25,\r\n style: 0.2,\r\n};\r\n\r\nconst AUTOMATED = /^(merge (branch|pull request|remote)|bump |update dependenc|\\[bot\\]|revert \"merge)/i;\r\n\r\nexport function scoreCommit(commit: RawCommit): number {\r\n const header = parseConventionalHeader(commit.subject);\r\n\r\n let score = header.type ? (TYPE_WEIGHTS[header.type] ?? 0.5) : 0.5;\r\n\r\n if (header.breaking) score += 0.12;\r\n // A commit that bothered to explain *why* is worth more than a bare subject.\r\n if (commit.messageBody.length > 120) score += 0.1;\r\n if (commit.isMerge) score = Math.min(score, 0.3);\r\n if (AUTOMATED.test(commit.subject)) score -= 0.15;\r\n\r\n const churn = commit.files.reduce((n, f) => n + (f.insertions ?? 0) + (f.deletions ?? 0), 0);\r\n // Very large commits are usually vendoring / generated code, not intent.\r\n if (commit.files.length > 100 || churn > 5000) score *= 0.75;\r\n if (commit.files.length <= 1 && churn <= 3) score -= 0.05;\r\n\r\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\r\n}\r\n\r\nfunction byChurnDesc(a: FileTouch, b: FileTouch): number {\r\n const ca = (a.insertions ?? 0) + (a.deletions ?? 0);\r\n const cb = (b.insertions ?? 0) + (b.deletions ?? 0);\r\n return cb - ca;\r\n}\r\n\r\nfunction renderFileLine(f: FileTouch): string {\r\n const churn = f.binary ? 'binary' : `+${f.insertions ?? 0}/-${f.deletions ?? 0}`;\r\n return f.previousPath ? ` ${f.path} (${churn}, renamed from ${f.previousPath})` : ` ${f.path} (${churn})`;\r\n}\r\n\r\nexport function toMemoryNode(\r\n commit: RawCommit,\r\n projectId: string,\r\n opts: GitCommitCollectorOptions = {},\r\n): MemoryNode {\r\n const maxFiles = opts.maxFilesPerNode ?? DEFAULTS.maxFilesPerNode;\r\n const maxBody = opts.maxBodyChars ?? DEFAULTS.maxBodyChars;\r\n\r\n const header = parseConventionalHeader(commit.subject);\r\n const keptFiles = [...commit.files].sort(byChurnDesc).slice(0, maxFiles);\r\n\r\n const insertions = commit.files.reduce((n, f) => n + (f.insertions ?? 0), 0);\r\n const deletions = commit.files.reduce((n, f) => n + (f.deletions ?? 0), 0);\r\n\r\n const bodyParts = [commit.subject];\r\n if (commit.messageBody) bodyParts.push('', commit.messageBody);\r\n if (keptFiles.length) {\r\n bodyParts.push('', 'Files changed:', ...keptFiles.map(renderFileLine));\r\n if (commit.files.length > keptFiles.length) {\r\n bodyParts.push(` ...and ${commit.files.length - keptFiles.length} more file(s)`);\r\n }\r\n }\r\n\r\n return {\r\n id: makeNodeId(projectId, 'git_commit', commit.sha),\r\n kind: 'git_commit',\r\n projectId,\r\n ts: commit.authoredAt,\r\n source: 'git',\r\n title: truncate(commit.subject || `(no subject) ${commit.shortSha}`, MAX_TITLE_CHARS),\r\n body: truncate(bodyParts.join('\\n'), maxBody),\r\n files: keptFiles,\r\n signal: scoreCommit(commit),\r\n meta: {\r\n sha: commit.sha,\r\n shortSha: commit.shortSha,\r\n parents: commit.parents,\r\n authorName: commit.authorName,\r\n authorEmail: commit.authorEmail,\r\n committedAt: commit.committedAt,\r\n isMerge: commit.isMerge,\r\n filesChanged: commit.files.length,\r\n insertions,\r\n deletions,\r\n conventionalType: header.type,\r\n conventionalScope: header.scope,\r\n breaking: header.breaking,\r\n },\r\n };\r\n}\r\n\r\n/** Stream commits from `cwd`'s repository as MemoryNodes. */\r\nexport async function* collectGitCommits(\r\n cwd: string,\r\n projectId: string,\r\n opts: GitCommitCollectorOptions = {},\r\n): AsyncGenerator<MemoryNode> {\r\n for await (const commit of readCommits(cwd, opts)) {\r\n yield toMemoryNode(commit, projectId, opts);\r\n }\r\n}\r\n","import { redact } from '../conversation/redact.js';\r\nimport { makeNodeId } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport type { MemoryNode } from '../core/types.js';\r\nimport { readCommitDiffs, type RawCommitDiff, type RawFileDiff, type ReadCommitDiffsOptions } from '../git/diff.js';\r\nimport { parseConventionalHeader, TYPE_WEIGHTS } from './git-commits.js';\r\n\r\n/**\r\n * Maps commit patches onto MemoryNodes, one per changed file.\r\n *\r\n * The `git_commit` node answers \"what changed and why did the author say they\r\n * changed it\"; this one answers \"what did the change actually look like\".\r\n * Those are different questions, and the second is the one an agent asks when\r\n * it is about to edit the same lines -- the commit node's `Files changed:`\r\n * list can say `src/git/exec.ts (+41/-6)` without containing a single line of\r\n * the code that makes the answer.\r\n *\r\n * One node per file rather than per commit: a commit that touches six files\r\n * would otherwise pack six unrelated patches into one budgeted summary, and\r\n * retrieval could only ever return all of them or none.\r\n */\r\n\r\nexport const DIFF_SOURCE = 'diff';\r\n\r\nexport interface DiffCollectorOptions extends ReadCommitDiffsOptions {\r\n /** Keep at most this many files per commit, biggest churn first. Default 20. */\r\n maxFilesPerCommit?: number;\r\n /** Hard cap on one node's body, to bound index and embedding cost. Default 2000. */\r\n maxBodyChars?: number;\r\n}\r\n\r\nconst DEFAULTS = { maxFilesPerCommit: 20, maxBodyChars: 2000 } as const;\r\nconst MAX_TITLE_CHARS = 200;\r\n\r\n/**\r\n * Paths whose diff is machine-written.\r\n *\r\n * A lockfile churns on every dependency bump and would otherwise dominate the\r\n * corpus with thousands of lines nobody ever wrote or will ever ask about.\r\n * The commit node still records that the file changed.\r\n */\r\nconst GENERATED_PATHS: RegExp[] = [\r\n /(^|\\/)(node_modules|dist|build|out|coverage|vendor|third_party)\\//,\r\n /(^|\\/)(package-lock\\.json|npm-shrinkwrap\\.json|yarn\\.lock|pnpm-lock\\.yaml|composer\\.lock|Cargo\\.lock|poetry\\.lock|Gemfile\\.lock|go\\.sum)$/,\r\n /\\.(min\\.js|min\\.css|map|snap)$/,\r\n];\r\n\r\nexport function isGeneratedPath(path: string): boolean {\r\n return GENERATED_PATHS.some((re) => re.test(path));\r\n}\r\n\r\nconst TEST_PATHS = /(^|\\/)(tests?|__tests__|spec|e2e)\\/|\\.(test|spec)\\.[cm]?[jt]sx?$/;\r\n\r\n/**\r\n * Prior importance of one file's patch.\r\n *\r\n * Anchored on the commit's own type -- a patch inside a `fix:` is worth more\r\n * than the same patch inside a `chore:` -- then adjusted for what the patch\r\n * itself looks like. The size adjustments both point the same way: a surgical\r\n * change is usually the interesting one, and a thousand-line rewrite is\r\n * usually a move, a reformat or generated output that slipped past the path\r\n * filter.\r\n */\r\nexport function scoreFileDiff(subject: string, file: RawFileDiff): number {\r\n const header = parseConventionalHeader(subject);\r\n let score = header.type ? (TYPE_WEIGHTS[header.type] ?? 0.5) : 0.5;\r\n\r\n if (header.breaking) score += 0.1;\r\n if (TEST_PATHS.test(file.path)) score -= 0.1;\r\n if (file.status === 'added') score += 0.05;\r\n if (file.status === 'deleted') score -= 0.1;\r\n\r\n const churn = file.insertions + file.deletions;\r\n if (churn <= 2) score -= 0.05;\r\n if (churn > 400) score *= 0.75;\r\n\r\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\r\n}\r\n\r\nfunction byChurnDesc(a: RawFileDiff, b: RawFileDiff): number {\r\n return b.insertions + b.deletions - (a.insertions + a.deletions);\r\n}\r\n\r\n/** Files worth indexing: real text hunks, hand-written path. */\r\nexport function indexableFiles(files: readonly RawFileDiff[]): RawFileDiff[] {\r\n return files.filter((f) => !f.binary && f.hunkCount > 0 && !isGeneratedPath(f.path));\r\n}\r\n\r\nconst STATUS_LABEL: Record<RawFileDiff['status'], string> = {\r\n added: 'added',\r\n deleted: 'deleted',\r\n renamed: 'renamed',\r\n modified: 'modified',\r\n};\r\n\r\nfunction fileTitle(shortSha: string, subject: string, file: RawFileDiff): string {\r\n return truncate(`${file.path} @ ${shortSha} — ${subject}`, MAX_TITLE_CHARS);\r\n}\r\n\r\nexport function toMemoryNodes(\r\n commit: RawCommitDiff,\r\n projectId: string,\r\n opts: DiffCollectorOptions = {},\r\n): MemoryNode[] {\r\n const maxFiles = opts.maxFilesPerCommit ?? DEFAULTS.maxFilesPerCommit;\r\n const maxBody = opts.maxBodyChars ?? DEFAULTS.maxBodyChars;\r\n\r\n const kept = indexableFiles(commit.files).sort(byChurnDesc).slice(0, maxFiles);\r\n\r\n return kept.map((file) => {\r\n const churn = `+${file.insertions}/-${file.deletions}`;\r\n const renamePart = file.previousPath ? `, renamed from ${file.previousPath}` : '';\r\n const head = [\r\n `${commit.subject} (${commit.shortSha})`,\r\n `${STATUS_LABEL[file.status]} ${file.path} (${churn}, ${file.hunkCount} hunk${file.hunkCount === 1 ? '' : 's'}${renamePart})`,\r\n '',\r\n ].join('\\n');\r\n\r\n // High-confidence rules only: this body is source code, and the key/value\r\n // rule would rewrite ordinary lines like `apiKey = config.apiKey` into a\r\n // redaction marker. See redact.ts.\r\n const { text: patch } = redact(file.patch, 'high-confidence');\r\n\r\n return {\r\n id: makeNodeId(projectId, 'code_diff', `${commit.sha}:${file.path}`),\r\n kind: 'code_diff',\r\n projectId,\r\n ts: commit.authoredAt,\r\n source: DIFF_SOURCE,\r\n title: fileTitle(commit.shortSha, commit.subject, file),\r\n body: truncate(head + patch, maxBody),\r\n files: [\r\n {\r\n path: file.path,\r\n ...(file.previousPath ? { previousPath: file.previousPath } : {}),\r\n insertions: file.insertions,\r\n deletions: file.deletions,\r\n binary: false,\r\n },\r\n ],\r\n signal: scoreFileDiff(commit.subject, file),\r\n meta: {\r\n sha: commit.sha,\r\n shortSha: commit.shortSha,\r\n path: file.path,\r\n status: file.status,\r\n hunkCount: file.hunkCount,\r\n insertions: file.insertions,\r\n deletions: file.deletions,\r\n subject: commit.subject,\r\n },\r\n };\r\n });\r\n}\r\n\r\n/** Stream file-level diff nodes from `cwd`'s repository. */\r\nexport async function* collectCommitDiffs(\r\n cwd: string,\r\n projectId: string,\r\n opts: DiffCollectorOptions = {},\r\n): AsyncGenerator<MemoryNode> {\r\n for await (const commit of readCommitDiffs(cwd, opts)) {\r\n for (const node of toMemoryNodes(commit, projectId, opts)) yield node;\r\n }\r\n}\r\n","import { chunkAssistantText } from '../conversation/chunk.js';\nimport { makeNodeId } from '../core/ids.js';\nimport { truncate } from '../core/text.js';\nimport type { MemoryNode } from '../core/types.js';\nimport type { RawDocFile } from '../docs/types.js';\n\n/**\n * Maps a repo's tracked `.md` files onto MemoryNodes, one per section.\n *\n * The gap this closes was found by dogfooding the live MCP server (see\n * README.md's Phase 3 row): a design-rationale question answered from\n * README.md prose came back empty, because git/shell/conversation are the\n * only sources that were ever read. Chunking reuses `chunkAssistantText`\n * from the conversation collector rather than inventing a second heading\n * splitter -- it already treats literal `#`..`######` lines as section\n * boundaries, which is exactly what a real markdown file is made of (the\n * bold-lead-paragraph case it also handles just never triggers here).\n */\n\nexport interface DocsCollectorOptions {\n /** Default 2000 -- final safety cap on one section's body. */\n maxBodyChars?: number;\n /** Default 1200 -- target size for one section chunk before it's split further. */\n maxChunkChars?: number;\n}\n\nconst DEFAULT_MAX_BODY_CHARS = 2000;\nconst DEFAULT_MAX_CHUNK_CHARS = 1200;\nconst MAX_TITLE_CHARS = 200;\n\nconst EXPLANATION_MARKERS = /\\b(because|the reason|design decision|trade-?off|instead of|rationale|why)\\b/i;\n\n/**\n * Prior importance of one doc section.\n *\n * Deliberately close to the middle, like a conversation chunk's score: a\n * doc file mixes genuine design rationale with routine scaffolding (a table\n * of contents entry, a install-step list), and only the source text itself\n * tells them apart.\n */\nexport function scoreDocSection(path: string, heading: string | null, text: string): number {\n let score = 0.45;\n\n if (EXPLANATION_MARKERS.test(text)) score += 0.25;\n if (/(^|\\/)readme\\.md$/i.test(path)) score += 0.1;\n if (heading === null) score -= 0.1; // preamble text with no section of its own\n if (text.length < 80) score -= 0.15;\n\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\n}\n\nfunction slugify(heading: string | null, index: number): string {\n if (heading === null) return `_preamble-${index}`;\n const slug = heading\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return slug || `_section-${index}`;\n}\n\nfunction sectionTitle(path: string, heading: string | null, index: number, count: number): string {\n if (heading) return truncate(`${path} — ${heading}`, MAX_TITLE_CHARS);\n if (count > 1) return truncate(`${path} (part ${index + 1}/${count})`, MAX_TITLE_CHARS);\n return truncate(path, MAX_TITLE_CHARS);\n}\n\nexport function toMemoryNodes(file: RawDocFile, projectId: string, opts: DocsCollectorOptions = {}): MemoryNode[] {\n const maxBody = opts.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;\n const maxChunk = opts.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;\n\n const chunks = chunkAssistantText(file.content, maxChunk);\n if (chunks.length === 0) return [];\n\n // Stable ids keyed by heading text, not raw index -- a section added\n // earlier in the file must not silently reshuffle every node after it.\n // Duplicate headings (e.g. two \"Why\" sections) fall back to an occurrence\n // count so they still get distinct, deterministic keys.\n const seenSlugs = new Map<string, number>();\n\n return chunks.map((chunk, index) => {\n const baseSlug = slugify(chunk.heading, index);\n const occurrence = seenSlugs.get(baseSlug) ?? 0;\n seenSlugs.set(baseSlug, occurrence + 1);\n const naturalKey = occurrence === 0 ? `${file.path}#${baseSlug}` : `${file.path}#${baseSlug}:${occurrence}`;\n\n return {\n id: makeNodeId(projectId, 'doc_section', naturalKey),\n kind: 'doc_section',\n projectId,\n ts: file.ts,\n source: 'docs',\n title: sectionTitle(file.path, chunk.heading, index, chunks.length),\n body: truncate(chunk.text, maxBody),\n files: [{ path: file.path, insertions: null, deletions: null, binary: false }],\n signal: scoreDocSection(file.path, chunk.heading, chunk.text),\n meta: {\n path: file.path,\n heading: chunk.heading,\n chunkIndex: index,\n chunkCount: chunks.length,\n },\n };\n });\n}\n\nexport function collectDocFiles(files: readonly RawDocFile[], projectId: string, opts: DocsCollectorOptions = {}): MemoryNode[] {\n return files.flatMap((file) => toMemoryNodes(file, projectId, opts));\n}\n","import { sha256Hex } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport { scoreConversationTurn } from '../collectors/conversation.js';\r\nimport { redact } from '../conversation/redact.js';\r\nimport type { RawConversationTurn } from '../conversation/types.js';\r\n\r\n/** One working session's worth of exchanges, in the order they happened. */\r\nexport interface SessionGroup {\r\n sessionKey: string;\r\n source: string;\r\n cwd: string | null;\r\n turns: RawConversationTurn[];\r\n startedAt: string;\r\n endedAt: string;\r\n}\r\n\r\nexport interface SessionPrompt {\r\n prompt: string;\r\n /**\r\n * Identity of exactly what was sent to the model.\r\n *\r\n * Hashing the finished prompt rather than the raw session means a change\r\n * to the template, the truncation limits, or the turn-selection rule all\r\n * invalidate the cached summary, and nothing else does. With a\r\n * temperature-0 model, an unchanged hash provably implies an unchanged\r\n * summary, which is what makes skipping the work safe rather than merely\r\n * cheap.\r\n */\r\n hash: string;\r\n /** Turns that actually made it into the prompt, after any budget trimming. */\r\n includedTurns: number;\r\n}\r\n\r\nexport const MAX_USER_CHARS = 400;\r\nexport const MAX_REPLY_CHARS = 700;\r\nexport const DEFAULT_MAX_PROMPT_CHARS = 12_000;\r\n\r\n/**\r\n * Split a flat list of exchanges into sessions, ordered oldest first.\r\n *\r\n * Grouping is by the session id the reader recorded, never by time gap: two\r\n * sessions on the same repository can overlap in wall-clock time, and a long\r\n * pause inside one session is just lunch, not a boundary.\r\n */\r\nexport function groupTurnsIntoSessions(turns: readonly RawConversationTurn[]): SessionGroup[] {\r\n const groups = new Map<string, SessionGroup>();\r\n\r\n for (const turn of turns) {\r\n const existing = groups.get(turn.sessionKey);\r\n if (existing) {\r\n existing.turns.push(turn);\r\n if (turn.ts < existing.startedAt) existing.startedAt = turn.ts;\r\n if (turn.ts > existing.endedAt) existing.endedAt = turn.ts;\r\n existing.cwd ??= turn.cwd;\r\n continue;\r\n }\r\n\r\n groups.set(turn.sessionKey, {\r\n sessionKey: turn.sessionKey,\r\n source: turn.source,\r\n cwd: turn.cwd,\r\n turns: [turn],\r\n startedAt: turn.ts,\r\n endedAt: turn.ts,\r\n });\r\n }\r\n\r\n for (const group of groups.values()) {\r\n group.turns.sort((a, b) => a.ts.localeCompare(b.ts));\r\n }\r\n\r\n return [...groups.values()].sort((a, b) => a.startedAt.localeCompare(b.startedAt));\r\n}\r\n\r\n/**\r\n * Sessions quiet for long enough to be worth summarizing.\r\n *\r\n * A session still being typed into would be summarized on one sync and\r\n * re-summarized on the next, burning a model call each time to produce a\r\n * summary that was already out of date when it was written. Waiting for the\r\n * session to settle costs nothing -- the exchanges are already indexed\r\n * individually by the conversation collector.\r\n */\r\nexport function selectSettledSessions(\r\n sessions: readonly SessionGroup[],\r\n settleMinutes: number,\r\n now: Date = new Date(),\r\n): SessionGroup[] {\r\n const cutoff = now.getTime() - settleMinutes * 60_000;\r\n return sessions.filter((s) => {\r\n const ended = Date.parse(s.endedAt);\r\n // An unparseable timestamp is treated as settled rather than skipped\r\n // forever: the alternative silently drops a whole session from memory.\r\n return Number.isNaN(ended) || ended <= cutoff;\r\n });\r\n}\r\n\r\n/** Exported so a test can size a prompt budget against it without hard-coding a length. */\r\nexport const SESSION_INSTRUCTIONS = `You are summarizing one working session between a developer and an AI coding assistant, so that a future assistant can recall what happened without re-reading the transcript.\r\n\r\nYour first line MUST begin with \"TITLE: \" and nothing else. Start your reply with those six characters.\r\n\r\nWrite your answer in exactly this shape:\r\nTITLE: <under 15 words, naming the specific work, e.g. \"Raised the Node floor to 22 after a CI failure\">\r\n- <a decision that was made, and why>\r\n- <a problem that was diagnosed, and its cause>\r\n- <what was left unfinished or explicitly deferred>\r\n\r\nRules:\r\n- Answer in English, whatever language the transcript is in. This summary is stored in a keyword index that cannot segment languages without spaces between words.\r\n- The title must name this session specifically. \"Summary of the session\" or \"Project update\" are wrong.\r\n- Prefer reasons over narration. \"Chose X over Y because Z\" is worth more than \"worked on X\".\r\n- Only state what the transcript supports. Do not guess or invent.\r\n- Between 3 and 6 bullets. No preamble, no closing remarks, no markdown bold.`;\r\n\r\n/**\r\n * Build the prompt for one session.\r\n *\r\n * Every exchange is redacted before it reaches the model. The model is local,\r\n * so this is not about exfiltration -- it is that the model's output is\r\n * stored and searchable, and a secret quoted into a summary would be indexed\r\n * in plain text just like any other node body.\r\n */\r\nexport function buildSessionPrompt(session: SessionGroup, maxPromptChars = DEFAULT_MAX_PROMPT_CHARS): SessionPrompt {\r\n const rendered = session.turns.map((turn) => {\r\n const user = redact(turn.userText).text;\r\n const reply = redact(turn.assistantText).text;\r\n return {\r\n ts: turn.ts,\r\n text: [`[${turn.ts}] developer: ${truncate(user, MAX_USER_CHARS)}`, `assistant: ${truncate(reply, MAX_REPLY_CHARS)}`].join(\r\n '\\n',\r\n ),\r\n signal: scoreConversationTurn(user, reply),\r\n };\r\n });\r\n\r\n // Over budget, keep the highest-signal exchanges and put them back in\r\n // order. Dropping the tail instead would lose the end of the session,\r\n // which is where conclusions live; dropping the head would lose the task.\r\n // Selecting by the same score the conversation collector already uses\r\n // keeps \"what counts as important\" defined in exactly one place.\r\n const budget = maxPromptChars - SESSION_INSTRUCTIONS.length;\r\n const kept: typeof rendered = [];\r\n let used = 0;\r\n\r\n for (const turn of [...rendered].sort((a, b) => b.signal - a.signal)) {\r\n if (used + turn.text.length > budget) continue;\r\n kept.push(turn);\r\n used += turn.text.length;\r\n }\r\n kept.sort((a, b) => a.ts.localeCompare(b.ts));\r\n\r\n const body = kept.map((t) => t.text).join('\\n\\n');\r\n const prompt = `${SESSION_INSTRUCTIONS}\\n\\n---\\n\\n${body}\\n\\n---\\n\\nSummary:`;\r\n\r\n return { prompt, hash: sha256Hex(prompt), includedTurns: kept.length };\r\n}\r\n\r\nexport interface ParsedSummary {\r\n title: string;\r\n body: string;\r\n}\r\n\r\n/**\r\n * Title to use when the model's own is unusable: the first line of the\r\n * question that opened the session.\r\n *\r\n * The same convention `conversation.ts` uses for a turn's title, and for the\r\n * same reason -- it is the human's own words, so it is always specific to\r\n * this session even when it is not elegant.\r\n */\r\nexport function sessionFallbackTitle(session: SessionGroup): string {\r\n const opening = session.turns[0];\r\n if (!opening) return 'Working session';\r\n const line = redact(opening.userText).text.split(/\\r?\\n/)[0]?.trim();\r\n return line && line.length > 0 ? line : 'Working session';\r\n}\r\n\r\nconst MAX_TITLE_CHARS = 200;\r\n\r\n/** Titles that name no particular session, and so tell a future reader nothing. */\r\nconst GENERIC_TITLE = /^(a |the )?(session |conversation |project |work )?(summary|update|overview|recap|status)\\b/i;\r\n\r\n/**\r\n * Titles where the model describes the role it was asked to play instead of\r\n * naming what happened -- \"Role: Lead Systems Engineer for NexusMem\" tells a\r\n * future reader nothing a `GENERIC_TITLE` check would catch, because it names\r\n * no summary-ish word at all.\r\n *\r\n * Bilingual on purpose, not Thai-only: `SESSION_INSTRUCTIONS` asks for\r\n * English, but the 3B model does not reliably comply (that is also why Thai\r\n * titles exist in the index at all), so an English-only guard would still\r\n * miss the same shape in English. Anchored at the start and matched against\r\n * multi-character Thai words rather than single letters, so a compound word\r\n * that happens to start with the same syllables -- \"คุณภาพ\" (quality) begins\r\n * with \"คุณ\" (you) -- is not caught; see the test suite's anti-false-positive\r\n * case using a real title from this project's own history.\r\n */\r\nconst ROLE_PREAMBLE_TITLE =\r\n /^(role\\s*[::]|you\\s*(?:'re|are)\\s+(acting as|serving as|playing the role of|a\\b)|i\\s*(?:'m|am)\\s+(acting as|a\\b)|acting as\\b|บทบาท\\s*[::]|ในฐานะ|คุณ(กำลัง)?(ทำหน้าที่เป็น|เป็น|รับบทบาทเป็น)|(ผม|ฉัน)(กำลัง)?(ทำหน้าที่เป็น|เป็น))/i;\r\n\r\n/**\r\n * Strip the decoration a small model adds even when told not to.\r\n * `**Chose X:**` and `- Chose X` both need to become `Chose X`.\r\n */\r\nfunction cleanTitle(line: string): string {\r\n return line\r\n .replace(/^[-*#>\\s]+/, '')\r\n .replace(/\\*+/g, '')\r\n .replace(/\\s*:\\s*$/, '')\r\n .trim();\r\n}\r\n\r\n/**\r\n * Pull a title and body out of whatever the model returned.\r\n *\r\n * The `fallbackTitle` is not a formality -- it is what the title becomes\r\n * whenever the model's own first line cannot be trusted. Dogfooding a 3B\r\n * model over 14 real sessions produced nine unusable titles: conversational\r\n * preambles in the transcript's language, single bullets carried over with\r\n * their markdown, and a bare \"Summary of the Session\". An earlier version\r\n * accepted any first line as a title, which is how all nine reached the\r\n * index. A summary is still never rejected for bad formatting -- only its\r\n * title is replaced, and the model's full text is kept as the body.\r\n */\r\nexport function parseSummary(raw: string, fallbackTitle: string): ParsedSummary | null {\r\n const text = redact(raw).text.trim();\r\n if (text.length === 0) return null;\r\n\r\n const lines = text.split(/\\r?\\n/);\r\n const firstIndex = lines.findIndex((line) => line.trim().length > 0);\r\n if (firstIndex === -1) return null;\r\n\r\n const first = lines[firstIndex]!.trim();\r\n const labelled = /^TITLE:\\s*(.+)$/i.exec(first);\r\n const fallback = truncate(cleanTitle(fallbackTitle) || 'Working session', MAX_TITLE_CHARS);\r\n\r\n // No TITLE line at all means the model wrote prose from the first\r\n // character. Its opening line is then an introduction, not a name for the\r\n // session, so it belongs in the body and nowhere else.\r\n if (!labelled) return { title: fallback, body: text };\r\n\r\n const candidate = cleanTitle(labelled[1]!);\r\n const rest = lines\r\n .slice(firstIndex + 1)\r\n .join('\\n')\r\n .trim();\r\n\r\n return {\r\n title:\r\n candidate.length > 0 && !GENERIC_TITLE.test(candidate) && !ROLE_PREAMBLE_TITLE.test(candidate)\r\n ? truncate(candidate, MAX_TITLE_CHARS)\r\n : fallback,\r\n // A model that emitted only a title still gets a usable node: the title\r\n // doubles as the body rather than storing an empty one.\r\n body: rest.length > 0 ? rest : candidate,\r\n };\r\n}\r\n","import { makeNodeId } from '../core/ids.js';\r\nimport { truncate } from '../core/text.js';\r\nimport type { MemoryNode } from '../core/types.js';\r\nimport type { RawConversationTurn } from '../conversation/types.js';\r\nimport type { SummarizationProvider } from '../slm/provider.js';\r\nimport {\r\n buildSessionPrompt,\r\n groupTurnsIntoSessions,\r\n parseSummary,\r\n selectSettledSessions,\r\n sessionFallbackTitle,\r\n type SessionGroup,\r\n} from '../slm/summarize.js';\r\nimport { extractMentionedFiles } from './conversation.js';\r\n\r\nexport const SESSION_SOURCE_PREFIX = 'session';\r\n\r\nexport interface SessionCollectorOptions {\r\n /** Minutes of quiet before a session is considered finished. Default 30. */\r\n settleMinutes?: number;\r\n /** Character budget for one prompt. Default 12000. */\r\n maxPromptChars?: number;\r\n /** Final cap on a stored summary body. Default 2500. */\r\n maxBodyChars?: number;\r\n /** Sessions summarized in one sync. Default 10 -- each is a model call measured in seconds. */\r\n maxSessions?: number;\r\n /** Injected for testing. */\r\n now?: Date;\r\n /**\r\n * Returns the content hash already stored for a session, or null.\r\n *\r\n * The collector asks rather than reads so it stays free of the store: the\r\n * sync layer owns database access, and this stays a pure\r\n * transcripts-plus-model function that a test can drive with a Map.\r\n */\r\n knownHash?: (sessionKey: string) => string | null;\r\n onProgress?: (done: number, total: number) => void;\r\n}\r\n\r\nconst DEFAULT_SETTLE_MINUTES = 30;\r\nconst DEFAULT_MAX_BODY_CHARS = 2500;\r\nconst DEFAULT_MAX_SESSIONS = 10;\r\n\r\nexport interface SessionCollectorResult {\r\n nodes: MemoryNode[];\r\n /** Sessions already summarized at the same content hash, so not re-sent to the model. */\r\n cached: number;\r\n /** Sessions still being worked in, so not eligible yet. */\r\n unsettled: number;\r\n /**\r\n * Eligible sessions left for a later sync because `maxSessions` was\r\n * reached. Distinct from `unsettled`: these are ready and merely queued,\r\n * and without the distinction a first sync of a long-lived repository\r\n * reports a number well below the session count with nothing to explain\r\n * the gap.\r\n */\r\n deferred: number;\r\n /** Sessions the model failed to summarize. */\r\n failed: number;\r\n /** True if the model produced nothing at all -- almost always \"no chat model pulled\". */\r\n providerUnavailable: boolean;\r\n}\r\n\r\n/**\r\n * Signal for a session summary.\r\n *\r\n * Above a typical conversation turn (0.3-0.7) because a summary is the\r\n * distilled form of many turns, and a query that matches it is better served\r\n * by the overview than by one exchange out of forty. Held below the top of\r\n * the commit range so a summary can never outrank the commit that a question\r\n * is literally about. Longer sessions score higher: more exchanges distilled\r\n * into the same budget is a denser node, not a longer one.\r\n */\r\nexport function scoreSession(turnCount: number): number {\r\n const score = 0.6 + Math.min(0.25, turnCount / 100);\r\n return Number(score.toFixed(3));\r\n}\r\n\r\nfunction toNode(\r\n session: SessionGroup,\r\n projectId: string,\r\n summary: { title: string; body: string },\r\n meta: { hash: string; model: string; includedTurns: number },\r\n maxBodyChars: number,\r\n): MemoryNode {\r\n const header = `Session of ${session.startedAt.slice(0, 10)} — ${session.turns.length} exchange(s)`;\r\n const body = `${header}\\n\\n${summary.body}`;\r\n\r\n return {\r\n id: makeNodeId(projectId, 'session_summary', session.sessionKey),\r\n kind: 'session_summary',\r\n projectId,\r\n // The session's end, not its start: a summary describes a finished piece\r\n // of work, and recency ranking should treat it as being as fresh as the\r\n // last thing that happened in it.\r\n ts: session.endedAt,\r\n source: `${SESSION_SOURCE_PREFIX}:${session.source}`,\r\n title: truncate(summary.title, 200),\r\n body: truncate(body, maxBodyChars),\r\n // Drawn from the whole session rather than only the summary: the model\r\n // mentions few paths, but `node_files` is what lets \"why is this file\r\n // like this\" reach the session that explains it.\r\n files: extractMentionedFiles(session.turns.map((t) => `${t.userText}\\n${t.assistantText}`).join('\\n')),\r\n signal: scoreSession(session.turns.length),\r\n meta: {\r\n sessionKey: session.sessionKey,\r\n source: session.source,\r\n turnCount: session.turns.length,\r\n summarizedTurns: meta.includedTurns,\r\n startedAt: session.startedAt,\r\n endedAt: session.endedAt,\r\n cwd: session.cwd,\r\n model: meta.model,\r\n contentHash: meta.hash,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Summarize each finished session into a single node.\r\n *\r\n * This is the one collector that costs real compute, so three things bound\r\n * it: only settled sessions are eligible, a session whose prompt hashes to\r\n * what is already stored is skipped entirely, and no more than\r\n * `maxSessions` reach the model in one sync. The rest wait for the next run.\r\n */\r\nexport async function collectSessionSummaries(\r\n turns: readonly RawConversationTurn[],\r\n projectId: string,\r\n provider: SummarizationProvider,\r\n opts: SessionCollectorOptions = {},\r\n): Promise<SessionCollectorResult> {\r\n const maxBodyChars = opts.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;\r\n const maxSessions = opts.maxSessions ?? DEFAULT_MAX_SESSIONS;\r\n\r\n const all = groupTurnsIntoSessions(turns);\r\n const settled = selectSettledSessions(all, opts.settleMinutes ?? DEFAULT_SETTLE_MINUTES, opts.now);\r\n\r\n const nodes: MemoryNode[] = [];\r\n let cached = 0;\r\n let failed = 0;\r\n let attempted = 0;\r\n\r\n // Newest first: if the cap bites, the sessions a developer is most likely\r\n // to ask about are the ones that got summarized.\r\n const candidates = [...settled].sort((a, b) => b.endedAt.localeCompare(a.endedAt));\r\n const pending: Array<{ session: SessionGroup; prompt: ReturnType<typeof buildSessionPrompt> }> = [];\r\n\r\n for (const session of candidates) {\r\n const prompt = buildSessionPrompt(session, opts.maxPromptChars);\r\n if (opts.knownHash?.(session.sessionKey) === prompt.hash) {\r\n cached += 1;\r\n continue;\r\n }\r\n pending.push({ session, prompt });\r\n }\r\n\r\n for (const { session, prompt } of pending.slice(0, maxSessions)) {\r\n const raw = await provider.complete(prompt.prompt);\r\n attempted += 1;\r\n\r\n const summary = raw === null ? null : parseSummary(raw, sessionFallbackTitle(session));\r\n if (!summary) {\r\n failed += 1;\r\n // Nothing was stored, so the hash was never recorded and the next sync\r\n // retries this session -- a model that was merely busy gets another go.\r\n continue;\r\n }\r\n\r\n nodes.push(\r\n toNode(\r\n session,\r\n projectId,\r\n summary,\r\n { hash: prompt.hash, model: provider.identity, includedTurns: prompt.includedTurns },\r\n maxBodyChars,\r\n ),\r\n );\r\n opts.onProgress?.(nodes.length, Math.min(pending.length, maxSessions));\r\n }\r\n\r\n return {\r\n nodes,\r\n cached,\r\n unsettled: all.length - settled.length,\r\n deferred: Math.max(0, pending.length - maxSessions),\r\n failed,\r\n providerUnavailable: attempted > 0 && nodes.length === 0,\r\n };\r\n}\r\n","import { makeNodeId } from '../core/ids.js';\nimport { truncate } from '../core/text.js';\nimport type { MemoryNode } from '../core/types.js';\nimport type { RawShellEntry } from '../shell/types.js';\n\nexport interface ShellCollectorOptions {\n /** Default 1000 -- commands are short; no need for the 4000-char body cap commits use. */\n maxBodyChars?: number;\n}\n\nconst DEFAULT_MAX_BODY_CHARS = 1000;\nconst MAX_TITLE_CHARS = 200;\n\nconst NOISE = /^(cd|ls|dir|pwd|clear|cls|exit|history|whoami|date|type|cat|more|less|ll|la)\\b/i;\nconst BUILD_TEST =\n /^(npm|pnpm|yarn)\\s+(run\\s+)?(test|build|lint|typecheck|tsc)\\b|^(pytest|go\\s+test|cargo\\s+(test|build)|mvn\\s+test|gradle\\s+test|dotnet\\s+(test|build))\\b/i;\nconst INSTALL = /^(npm|pnpm|yarn)\\s+(install|add|remove|uninstall|ci)\\b|^pip\\s+install\\b|^(cargo\\s+add|go\\s+get|composer\\s+require)\\b/i;\nconst GIT_CMD = /^git\\s+/i;\nconst GIT_DESTRUCTIVE = /^git\\s+(push\\s+.*--force|reset\\s+--hard|clean\\s+-[a-z]*f|branch\\s+-d)\\b/i;\nconst RISKY = /(rm\\s+-rf|remove-item\\s+.*-recurse|del\\s+\\/s|drop\\s+(table|database)|--force\\b|truncate\\s+table)/i;\n\n/**\n * Prior importance by command shape.\n *\n * Kept deliberately coarse: this is a much weaker signal than a commit's\n * conventional-commit type, so scores cluster closer to the middle. `git`\n * commands score low by default because the git collector already captures\n * that history with far richer detail (diff, files, message) -- the shell\n * trace of `git commit -m \"...\"` would just be redundant noise next to it.\n */\nexport function scoreShellCommand(entry: RawShellEntry): number {\n const cmd = entry.command.trim();\n\n let score: number;\n if (NOISE.test(cmd)) score = 0.1;\n else if (RISKY.test(cmd) || GIT_DESTRUCTIVE.test(cmd)) score = 0.75;\n else if (INSTALL.test(cmd)) score = 0.6;\n else if (BUILD_TEST.test(cmd)) score = 0.55;\n else if (GIT_CMD.test(cmd)) score = 0.2;\n else score = 0.35;\n\n // Exit code is only known from the hook; an unknown code leaves score untouched.\n if (entry.exitCode !== null) {\n score += entry.exitCode !== 0 ? 0.25 : 0.05;\n }\n\n if (cmd.length > 60) score += 0.05;\n else if (cmd.length <= 3) score -= 0.05;\n\n return Number(Math.min(1, Math.max(0.05, score)).toFixed(3));\n}\n\nfunction renderBody(entry: RawShellEntry, maxChars: number): string {\n const parts = [`$ ${entry.command}`];\n const metaLine: string[] = [];\n if (entry.cwd) metaLine.push(`cwd: ${entry.cwd}`);\n if (entry.exitCode !== null) metaLine.push(`exit: ${entry.exitCode}`);\n if (entry.durationMs !== null) metaLine.push(`duration: ${entry.durationMs}ms`);\n if (metaLine.length) parts.push('', metaLine.join(' '));\n return truncate(parts.join('\\n'), maxChars);\n}\n\nexport function toMemoryNode(entry: RawShellEntry, projectId: string, opts: ShellCollectorOptions = {}): MemoryNode {\n const maxBody = opts.maxBodyChars ?? DEFAULT_MAX_BODY_CHARS;\n const titleLine = entry.command.split(/\\r?\\n/)[0] ?? entry.command;\n\n return {\n id: makeNodeId(projectId, 'shell_command', entry.naturalKey),\n kind: 'shell_command',\n projectId,\n ts: entry.ts,\n source: `shell:${entry.shell}`,\n title: truncate(titleLine, MAX_TITLE_CHARS),\n body: renderBody(entry, maxBody),\n files: [],\n signal: scoreShellCommand(entry),\n meta: {\n command: entry.command,\n cwd: entry.cwd,\n exitCode: entry.exitCode,\n durationMs: entry.durationMs,\n tsApprox: entry.tsApprox,\n shell: entry.shell,\n },\n };\n}\n\nexport function collectShellHistory(\n entries: readonly RawShellEntry[],\n projectId: string,\n opts: ShellCollectorOptions = {},\n): MemoryNode[] {\n return entries.map((entry) => toMemoryNode(entry, projectId, opts));\n}\n","import { readFile } from 'node:fs/promises';\r\nimport { basename } from 'node:path';\r\nimport { listTranscriptFiles } from './paths.js';\r\nimport type { RawConversationTurn } from './types.js';\r\n\r\n/**\r\n * Parses Claude Code's local session transcript format.\r\n *\r\n * This format is internal and undocumented -- it is not a published API,\r\n * just what was observed on disk at `~/.claude/projects/<slug>/*.jsonl`\r\n * (one JSON object per line; `type: 'user' | 'assistant' | ...` records\r\n * threaded by `parentUuid`/`uuid`, message content shaped like the\r\n * Anthropic Messages API). Treat every assumption here as liable to break\r\n * on a Claude Code update: parse defensively, skip what doesn't match\r\n * rather than throw, and never let a shape change break `sync` for\r\n * git/shell.\r\n */\r\n\r\ninterface ContentBlock {\r\n type?: string;\r\n text?: string;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface TranscriptLine {\r\n type?: string;\r\n uuid?: string;\r\n parentUuid?: string | null;\r\n isSidechain?: boolean;\r\n timestamp?: string;\r\n cwd?: string;\r\n message?: {\r\n role?: string;\r\n content?: string | ContentBlock[];\r\n };\r\n}\r\n\r\nfunction parseLine(raw: string): TranscriptLine | null {\r\n const trimmed = raw.trim();\r\n if (!trimmed) return null;\r\n try {\r\n return JSON.parse(trimmed) as TranscriptLine;\r\n } catch {\r\n return null; // a torn write or a record shape we don't recognise -- skip, don't fail sync\r\n }\r\n}\r\n\r\n/** A real human message, not a tool-result being fed back into the model. */\r\nfunction extractUserText(line: TranscriptLine): string | null {\r\n const content = line.message?.content;\r\n if (typeof content === 'string') return content.trim() || null;\r\n\r\n if (Array.isArray(content)) {\r\n if (content.some((b) => b.type === 'tool_result')) return null;\r\n const text = content\r\n .filter((b) => b.type === 'text' && typeof b.text === 'string')\r\n .map((b) => b.text)\r\n .join('\\n')\r\n .trim();\r\n return text || null;\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/** The assistant's prose reply. Tool calls and internal `thinking` are deliberately excluded. */\r\nfunction extractAssistantText(line: TranscriptLine): string {\r\n const content = line.message?.content;\r\n if (!Array.isArray(content)) return '';\r\n return content\r\n .filter((b) => b.type === 'text' && typeof b.text === 'string')\r\n .map((b) => b.text)\r\n .join('\\n')\r\n .trim();\r\n}\r\n\r\nexport interface ParseTranscriptOptions {\r\n source?: string;\r\n /**\r\n * Identifies the session these lines came from. Claude Code names each\r\n * transcript file after its session id, so the caller passes the file's\r\n * basename; nothing inside the records is relied on for this.\r\n */\r\n sessionId?: string;\r\n}\r\n\r\n/**\r\n * Group a transcript into exchanges: one real human message plus every bit\r\n * of assistant prose that follows it, up to the next human message.\r\n *\r\n * Tool calls in between are not part of the exchange's *text* -- they are\r\n * execution detail, already captured with far more structure by the git and\r\n * shell collectors when they matter. Sidechain records (sub-agent work\r\n * spawned via a Task-like tool) are excluded; they are not the primary\r\n * human/assistant dialogue this collector is after.\r\n */\r\nexport function parseClaudeCodeTranscript(raw: string, opts: ParseTranscriptOptions = {}): RawConversationTurn[] {\r\n const source = opts.source ?? 'claude-code';\r\n const sessionKey = `${source}:${opts.sessionId ?? 'unknown'}`;\r\n const turns: RawConversationTurn[] = [];\r\n\r\n let current: { uuid: string; userText: string; ts: string; cwd: string | null; assistantParts: string[] } | null = null;\r\n\r\n const flush = () => {\r\n if (!current) return;\r\n const assistantText = current.assistantParts.join('\\n\\n').trim();\r\n turns.push({\r\n naturalKey: `claude-code:${current.uuid}`,\r\n userText: current.userText,\r\n assistantText,\r\n ts: current.ts,\r\n cwd: current.cwd,\r\n source,\r\n sessionKey,\r\n });\r\n current = null;\r\n };\r\n\r\n for (const rawLine of raw.split(/\\r?\\n/)) {\r\n const line = parseLine(rawLine);\r\n if (!line || line.isSidechain) continue;\r\n\r\n if (line.type === 'user') {\r\n const userText = extractUserText(line);\r\n if (userText === null) continue; // a tool-result record, not a human message\r\n\r\n flush();\r\n current = {\r\n uuid: line.uuid ?? `noid-${turns.length}`,\r\n userText,\r\n ts: line.timestamp ?? new Date(0).toISOString(),\r\n cwd: line.cwd ?? null,\r\n assistantParts: [],\r\n };\r\n continue;\r\n }\r\n\r\n if (line.type === 'assistant' && current) {\r\n const text = extractAssistantText(line);\r\n if (text) current.assistantParts.push(text);\r\n }\r\n }\r\n\r\n flush();\r\n return turns;\r\n}\r\n\r\n/**\r\n * Read every transcript recorded for this repo and parse them all.\r\n *\r\n * Re-reads full files rather than tracking a per-file cursor: exchange\r\n * grouping needs to see a user turn's eventual assistant reply to close it\r\n * out, and a naive line-offset cursor can't guarantee it lands between two\r\n * exchanges rather than inside one. Content-addressed ids (the source's own\r\n * `uuid`) make re-processing a no-op cost at the store layer, same trade\r\n * the shell scrape fallback makes -- simpler and always correct beats\r\n * incremental and occasionally wrong.\r\n */\r\nexport async function collectClaudeCodeTranscripts(repoRoot: string): Promise<RawConversationTurn[]> {\r\n const files = await listTranscriptFiles(repoRoot);\r\n const turns: RawConversationTurn[] = [];\r\n\r\n for (const file of files) {\r\n const raw = await readFile(file, 'utf8');\r\n turns.push(...parseClaudeCodeTranscript(raw, { sessionId: basename(file, '.jsonl') }));\r\n }\r\n\r\n return turns;\r\n}\r\n","import { existsSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Claude Code's own slugging rule for a project path, reverse-engineered\n * from what is actually on disk (undocumented -- see claude-code-reader.ts).\n * `D:\\ai-projects\\NexusMem` -> `D--ai-projects-NexusMem`: every path\n * separator and drive-letter colon becomes `-`; everything else, including\n * hyphens already in folder names, passes through untouched.\n */\nexport function claudeProjectSlug(repoRoot: string): string {\n return repoRoot.replace(/[\\\\/:]/g, '-');\n}\n\nexport function claudeProjectTranscriptDir(repoRoot: string): string {\n return join(homedir(), '.claude', 'projects', claudeProjectSlug(repoRoot));\n}\n\n/** Every session transcript recorded for this repo, oldest-looking name first is not guaranteed -- callers should sort if order matters. */\nexport async function listTranscriptFiles(repoRoot: string): Promise<string[]> {\n const dir = claudeProjectTranscriptDir(repoRoot);\n if (!existsSync(dir)) return [];\n\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile() && e.name.endsWith('.jsonl')).map((e) => join(dir, e.name));\n}\n","import { readFile, stat } from 'node:fs/promises';\r\nimport { join } from 'node:path';\r\nimport { git } from '../git/exec.js';\r\nimport type { RawDocFile } from './types.js';\r\n\r\n/**\r\n * `.md` pathspecs to index by default.\r\n *\r\n * A bare `*.md` is not anchored to the repo root -- git's default (non-glob)\r\n * pathspec matching runs per path component, so it already reaches\r\n * `docs/phase-2-spec.md` as well as the top-level `README.md` (confirmed\r\n * against this repo). One pattern covers both.\r\n */\r\nconst DEFAULT_PATHSPECS = ['*.md'];\r\n\r\nexport interface ListDocFilesOptions {\r\n include?: string[];\r\n}\r\n\r\n/**\r\n * Tracked-only by design: `git ls-files` already excludes `node_modules`,\r\n * `.git` and the self-ignoring `.nexusmem/` workspace (see\r\n * config/workspace.ts) for free, the same way the git collector gets commit\r\n * history without hand-rolled ignore logic.\r\n */\r\nexport async function listDocFiles(repoRoot: string, opts: ListDocFilesOptions = {}): Promise<string[]> {\r\n const pathspecs = opts.include ?? DEFAULT_PATHSPECS;\r\n const out = await git(repoRoot, ['ls-files', '--', ...pathspecs]);\r\n return out\r\n .split('\\n')\r\n .map((line) => line.trim())\r\n .filter(Boolean);\r\n}\r\n\r\nexport interface DocScan {\r\n files: RawDocFile[];\r\n /**\r\n * Tracked paths that could not be read this run.\r\n *\r\n * Reported rather than silently dropped because \"produced no sections\" and\r\n * \"was never looked at\" mean opposite things to a caller that prunes: nodes\r\n * from a file in here must be kept, or one unreadable file would wipe every\r\n * section it had ever contributed.\r\n */\r\n unreadable: string[];\r\n}\r\n\r\nexport async function readDocFiles(repoRoot: string, opts: ListDocFilesOptions = {}): Promise<DocScan> {\r\n const paths = await listDocFiles(repoRoot, opts);\r\n const files: RawDocFile[] = [];\r\n const unreadable: string[] = [];\r\n\r\n for (const relPath of paths) {\r\n const path = relPath.replace(/\\\\/g, '/');\r\n const absPath = join(repoRoot, relPath);\r\n let content: string;\r\n let mtime: Date;\r\n try {\r\n [content, { mtime }] = await Promise.all([readFile(absPath, 'utf8'), stat(absPath)]);\r\n } catch {\r\n // Tracked in git but missing on disk (deleted-but-not-staged, a\r\n // worktree quirk) -- skip rather than fail the whole sync over it.\r\n unreadable.push(path);\r\n continue;\r\n }\r\n\r\n files.push({ path, content, ts: mtime.toISOString() });\r\n }\r\n\r\n return { files, unreadable };\r\n}\r\n","import { existsSync } from 'node:fs';\nimport { readFile, stat } from 'node:fs/promises';\nimport { sha256Hex } from '../core/ids.js';\nimport { readHookLog, type HookLogEntry } from './hook-log.js';\nimport { parseBashHistory } from './parse-bash.js';\nimport { parsePsReadLineHistory } from './parse-psreadline.js';\nimport { parseZshHistory } from './parse-zsh.js';\nimport { bashHistoryPath, hookLogPath, psReadLineHistoryPath, zshHistoryPath } from './paths.js';\nimport type { RawShellEntry } from './types.js';\n\nexport interface ShellSourceResult {\n /** The `shell:<name>` suffix used for both MemoryNode.source and the sync_state key. */\n name: string;\n entries: RawShellEntry[];\n /** Hook source only: how far into the log this read reached, to persist as the next cursor. */\n cursorAfter?: string;\n}\n\nexport interface CollectShellHistoryOptions {\n /** How many lines to keep from scrape-based (non-hook) sources. Default 300. */\n tailLines?: number;\n /** Repo root, for scoping hook-log entries to this project by cwd. */\n repoRoot?: string;\n /** Previous cursor for the hook log (a line count), or null to read from the start. */\n hookCursor?: string | null;\n /**\n * Once the hook is installed, its PowerShell coverage is authoritative --\n * the raw PSReadLine file duplicates the same commands with worse data\n * (no cwd, no exit code) and is skipped. Bash/zsh scraping is unaffected;\n * the hook only covers PowerShell. Default true.\n */\n preferHook?: boolean;\n}\n\nfunction isUnderRoot(cwd: string, root: string): boolean {\n const norm = (p: string) => p.replace(/\\\\/g, '/').replace(/\\/+$/, '').toLowerCase();\n const c = norm(cwd);\n const r = norm(root);\n return c === r || c.startsWith(`${r}/`);\n}\n\nfunction hookEntryToRaw(e: HookLogEntry): RawShellEntry {\n return {\n naturalKey: `pwsh-hook:${e.ts}:${sha256Hex(e.command).slice(0, 12)}`,\n command: e.command,\n ts: e.ts,\n tsApprox: false,\n exitCode: e.exitCode,\n cwd: e.cwd,\n durationMs: e.durationMs,\n shell: 'pwsh-hook',\n };\n}\n\nasync function tryReadScrapeSource(\n path: string,\n parse: (raw: string, mtimeMs: number, opts: { tailLines?: number }) => RawShellEntry[],\n tailLines: number,\n): Promise<RawShellEntry[] | null> {\n if (!existsSync(path)) return null;\n const [raw, stats] = await Promise.all([readFile(path, 'utf8'), stat(path)]);\n return parse(raw, stats.mtimeMs, { tailLines });\n}\n\n/** Enumerate and read every shell-history source available on this machine. */\nexport async function collectAvailableShellHistory(opts: CollectShellHistoryOptions = {}): Promise<ShellSourceResult[]> {\n const results: ShellSourceResult[] = [];\n const tailLines = opts.tailLines ?? 300;\n const preferHook = opts.preferHook ?? true;\n\n const hookPath = hookLogPath();\n const hookExists = existsSync(hookPath);\n\n if (hookExists) {\n const fromLine = Number(opts.hookCursor ?? '0') || 0;\n const { entries, totalLines } = await readHookLog(hookPath, fromLine);\n const scoped = opts.repoRoot ? entries.filter((e) => isUnderRoot(e.cwd, opts.repoRoot!)) : entries;\n results.push({ name: 'pwsh-hook', entries: scoped.map(hookEntryToRaw), cursorAfter: String(totalLines) });\n }\n\n const skipPwshScrape = preferHook && hookExists;\n if (!skipPwshScrape && process.platform === 'win32') {\n const entries = await tryReadScrapeSource(psReadLineHistoryPath(), parsePsReadLineHistory, tailLines);\n if (entries) results.push({ name: 'pwsh', entries });\n }\n\n const bashEntries = await tryReadScrapeSource(bashHistoryPath(), parseBashHistory, tailLines);\n if (bashEntries) results.push({ name: 'bash', entries: bashEntries });\n\n const zshEntries = await tryReadScrapeSource(zshHistoryPath(), parseZshHistory, tailLines);\n if (zshEntries) results.push({ name: 'zsh', entries: zshEntries });\n\n return results;\n}\n","import { appendFile, mkdir, readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\n\n/**\n * One line of the opt-in hook log: a JSONL file the installed shell hook\n * appends to on every command. This is the high-quality tier -- exact\n * timestamp, cwd and exit code, none of which the scrape-based fallbacks can\n * offer.\n */\nexport interface HookLogEntry {\n ts: string;\n cwd: string;\n exitCode: number | null;\n durationMs: number | null;\n command: string;\n}\n\n/** A malformed line (typically a torn write from a crash mid-append) is skipped, not fatal. */\nexport function parseHookLogLine(line: string): HookLogEntry | null {\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n let obj: unknown;\n try {\n obj = JSON.parse(trimmed);\n } catch {\n return null;\n }\n if (typeof obj !== 'object' || obj === null) return null;\n\n const o = obj as Record<string, unknown>;\n if (typeof o.ts !== 'string' || typeof o.cwd !== 'string' || typeof o.command !== 'string') return null;\n\n return {\n ts: o.ts,\n cwd: o.cwd,\n exitCode: typeof o.exitCode === 'number' ? o.exitCode : null,\n durationMs: typeof o.durationMs === 'number' ? o.durationMs : null,\n command: o.command,\n };\n}\n\nexport interface ReadHookLogResult {\n entries: HookLogEntry[];\n /** Total lines currently in the file -- the caller's next cursor. */\n totalLines: number;\n}\n\n/**\n * Read lines appended since `fromLine`.\n *\n * `fromLine` beyond the file's current length means the file was rotated or\n * cleared out from under a stale cursor -- treated the same way a stale git\n * cursor is: fall back to reading everything, rather than silently skipping\n * history that is actually new.\n */\nexport async function readHookLog(path: string, fromLine: number): Promise<ReadHookLogResult> {\n let raw: string;\n try {\n raw = await readFile(path, 'utf8');\n } catch {\n return { entries: [], totalLines: fromLine };\n }\n\n const lines = raw.split(/\\r?\\n/).filter((l) => l.length > 0);\n const slice = fromLine > 0 && fromLine <= lines.length ? lines.slice(fromLine) : lines;\n const entries = slice.map(parseHookLogLine).filter((e): e is HookLogEntry => e !== null);\n\n return { entries, totalLines: lines.length };\n}\n\n/** Append one entry. Exposed for tests; the real writer is the installed PowerShell hook. */\nexport async function appendHookLogEntry(path: string, entry: HookLogEntry): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await appendFile(path, `${JSON.stringify(entry)}\\n`, 'utf8');\n}\n","import { sha256Hex } from '../core/ids.js';\nimport type { RawShellEntry } from './types.js';\nimport type { ScrapeOptions } from './parse-psreadline.js';\n\n/** A `#<epoch>` comment line precedes the command when `HISTTIMEFORMAT` is set. */\nconst EPOCH_COMMENT = /^#(\\d{9,10})$/;\n\n/**\n * Parse `.bash_history`.\n *\n * Real timestamps are used when present (`HISTTIMEFORMAT` is set); otherwise\n * falls back to the same backward-from-mtime approximation as PSReadLine.\n */\nexport function parseBashHistory(raw: string, mtimeMs: number, opts: ScrapeOptions = {}): RawShellEntry[] {\n const lines = raw.split(/\\r?\\n/);\n\n const prelim: Array<{ command: string; ts: string | null }> = [];\n let pendingEpoch: number | null = null;\n\n for (const line of lines) {\n if (line.trim().length === 0) continue;\n\n const m = EPOCH_COMMENT.exec(line.trim());\n if (m) {\n pendingEpoch = Number(m[1]);\n continue;\n }\n\n prelim.push({ command: line, ts: pendingEpoch !== null ? new Date(pendingEpoch * 1000).toISOString() : null });\n pendingEpoch = null;\n }\n\n const tail = opts.tailLines ? prelim.slice(-opts.tailLines) : prelim;\n const startIndex = prelim.length - tail.length;\n\n return tail.map((p, i) => {\n const fromEnd = tail.length - 1 - i;\n const approx = p.ts === null;\n return {\n naturalKey: `bash:${startIndex + i}:${sha256Hex(p.command).slice(0, 12)}`,\n command: p.command,\n ts: p.ts ?? new Date(mtimeMs - fromEnd * 1000).toISOString(),\n tsApprox: approx,\n exitCode: null,\n cwd: null,\n durationMs: null,\n shell: 'bash',\n };\n });\n}\n","import { sha256Hex } from '../core/ids.js';\nimport type { RawShellEntry } from './types.js';\n\nexport interface ScrapeOptions {\n /** Keep only the last N lines. Unbounded by default. */\n tailLines?: number;\n}\n\n/**\n * Parse PowerShell's `ConsoleHost_history.txt`.\n *\n * One command per line, no metadata at all -- not a timestamp, not an exit\n * code, not a cwd. Multi-line entries (a function typed at the prompt) are\n * not reconstructed; each physical line is treated as its own command. Good\n * enough for the common one-liner case, which is nearly all shell history.\n */\nexport function parsePsReadLineHistory(raw: string, mtimeMs: number, opts: ScrapeOptions = {}): RawShellEntry[] {\n const allLines = raw.split(/\\r?\\n/).filter((l) => l.trim().length > 0);\n const tail = opts.tailLines ? allLines.slice(-opts.tailLines) : allLines;\n const startIndex = allLines.length - tail.length;\n\n return tail.map((command, i) => {\n // No per-line timestamp exists, so space entries backward from the\n // file's mtime, one synthetic second apart -- preserves order and keeps\n // recency ranking roughly sane without ever being presented as exact.\n const fromEnd = tail.length - 1 - i;\n return {\n naturalKey: `pwsh:${startIndex + i}:${sha256Hex(command).slice(0, 12)}`,\n command,\n ts: new Date(mtimeMs - fromEnd * 1000).toISOString(),\n tsApprox: true,\n exitCode: null,\n cwd: null,\n durationMs: null,\n shell: 'pwsh',\n };\n });\n}\n","import { sha256Hex } from '../core/ids.js';\nimport type { RawShellEntry } from './types.js';\nimport type { ScrapeOptions } from './parse-psreadline.js';\n\n/** `EXTENDED_HISTORY` format: `: <epoch>:<duration>;<command>`. */\nconst EXTENDED_PREFIX = /^: (\\d+):(\\d+);(.*)$/;\n\n/**\n * Parse `.zsh_history`.\n *\n * Handles the (very common, oh-my-zsh-default) extended-history format with\n * real epoch timestamps, and its backslash-continuation convention for\n * commands typed across multiple physical lines. Falls back to the same\n * backward-from-mtime approximation as PSReadLine when extended history is\n * off.\n */\nexport function parseZshHistory(raw: string, mtimeMs: number, opts: ScrapeOptions = {}): RawShellEntry[] {\n const rawLines = raw.split(/\\r?\\n/);\n const prelim: Array<{ command: string; ts: string | null; durationMs: number | null }> = [];\n\n let i = 0;\n while (i < rawLines.length) {\n const line = rawLines[i] ?? '';\n if (line.trim().length === 0) {\n i += 1;\n continue;\n }\n\n const m = EXTENDED_PREFIX.exec(line);\n let epoch: number | null = null;\n let duration: number | null = null;\n let cmd: string;\n\n if (m) {\n epoch = Number(m[1]);\n duration = Number(m[2]);\n cmd = m[3] ?? '';\n } else {\n cmd = line;\n }\n\n // A trailing backslash means the command continues on the next physical line.\n while (cmd.endsWith('\\\\') && i + 1 < rawLines.length) {\n i += 1;\n cmd = `${cmd.slice(0, -1)}\\n${rawLines[i]}`;\n }\n\n prelim.push({ command: cmd, ts: epoch !== null ? new Date(epoch * 1000).toISOString() : null, durationMs: duration });\n i += 1;\n }\n\n const tail = opts.tailLines ? prelim.slice(-opts.tailLines) : prelim;\n const startIndex = prelim.length - tail.length;\n\n return tail.map((p, idx) => {\n const fromEnd = tail.length - 1 - idx;\n const approx = p.ts === null;\n return {\n naturalKey: `zsh:${startIndex + idx}:${sha256Hex(p.command).slice(0, 12)}`,\n command: p.command,\n ts: p.ts ?? new Date(mtimeMs - fromEnd * 1000).toISOString(),\n tsApprox: approx,\n exitCode: null,\n cwd: null,\n durationMs: p.durationMs,\n shell: 'zsh',\n };\n });\n}\n","import type { Database as DB } from 'better-sqlite3';\nimport { makeNodeId, sha256Hex } from '../core/ids.js';\nimport type { NodeKind } from '../core/types.js';\n\ninterface StoredNodeRow {\n id: string;\n kind: string;\n project_id: string;\n ts: string;\n ts_epoch: number;\n source: string;\n title: string;\n body: string;\n signal: number;\n meta: string;\n created_at: number;\n}\n\ninterface StoredFileRow {\n path: string;\n previous_path: string | null;\n insertions: number | null;\n deletions: number | null;\n is_binary: number;\n}\n\nexport interface ProjectIdReconcileResult {\n oldProjectId: string;\n /** Rows re-inserted under a freshly recomputed id -- genuinely new content. */\n migrated: number;\n /** Rows reassigned to the new project id in place, keeping their existing id. */\n reassigned: number;\n /** Rows dropped because an equivalent row already exists under the new id. */\n deduped: number;\n /** Rows left untouched under the old id -- their natural key can't be reconstructed. */\n skipped: number;\n}\n\nfunction recomputeByNaturalKey(\n db: DB,\n oldProjectId: string,\n newProjectId: string,\n kind: NodeKind,\n source: string | null,\n computeNaturalKey: (row: StoredNodeRow, meta: Record<string, unknown>) => string | null,\n): { migrated: number; deduped: number; skipped: number } {\n const rows = (\n source\n ? db.prepare('SELECT * FROM nodes WHERE project_id = ? AND kind = ? AND source = ?').all(oldProjectId, kind, source)\n : db.prepare('SELECT * FROM nodes WHERE project_id = ? AND kind = ?').all(oldProjectId, kind)\n ) as StoredNodeRow[];\n\n const nodeExists = db.prepare('SELECT 1 FROM nodes WHERE id = ?');\n const insertNode = db.prepare(\n `INSERT INTO nodes (id, kind, project_id, ts, ts_epoch, source, title, body, signal, meta, created_at)\n VALUES (@id, @kind, @projectId, @ts, @tsEpoch, @source, @title, @body, @signal, @meta, @createdAt)`,\n );\n const readFiles = db.prepare('SELECT path, previous_path, insertions, deletions, is_binary FROM node_files WHERE node_id = ?');\n const insertFile = db.prepare(\n `INSERT INTO node_files (node_id, path, previous_path, insertions, deletions, is_binary)\n VALUES (@nodeId, @path, @previousPath, @insertions, @deletions, @isBinary)`,\n );\n const dropEmbedding = db.prepare('DELETE FROM nodes_vec WHERE rowid = (SELECT rowid FROM nodes WHERE id = ?)');\n const deleteNode = db.prepare('DELETE FROM nodes WHERE id = ?');\n\n let migrated = 0;\n let deduped = 0;\n let skipped = 0;\n\n for (const row of rows) {\n let meta: Record<string, unknown>;\n try {\n meta = JSON.parse(row.meta) as Record<string, unknown>;\n } catch {\n skipped += 1;\n continue;\n }\n\n const naturalKey = computeNaturalKey(row, meta);\n if (naturalKey === null) {\n skipped += 1;\n continue;\n }\n\n const newId = makeNodeId(newProjectId, kind, naturalKey);\n\n if (nodeExists.get(newId)) {\n deduped += 1;\n } else {\n insertNode.run({\n id: newId,\n kind: row.kind,\n projectId: newProjectId,\n ts: row.ts,\n tsEpoch: row.ts_epoch,\n source: row.source,\n title: row.title,\n body: row.body,\n signal: row.signal,\n meta: row.meta,\n createdAt: row.created_at,\n });\n for (const file of readFiles.all(row.id) as StoredFileRow[]) {\n insertFile.run({\n nodeId: newId,\n path: file.path,\n previousPath: file.previous_path,\n insertions: file.insertions,\n deletions: file.deletions,\n isBinary: file.is_binary,\n });\n }\n migrated += 1;\n }\n\n dropEmbedding.run(row.id);\n deleteNode.run(row.id); // cascades node_files; nodes_fts cleans itself via its own AFTER DELETE trigger\n }\n\n return { migrated, deduped, skipped };\n}\n\n/**\n * Bring nodes stranded under a previous project id forward to the current one.\n *\n * `makeProjectId` (core/project.ts) is derived from the repo's git remote URL\n * on purpose, so the same repo re-cloned to a new path or machine keeps\n * sharing memory. The case that design doesn't cover is the mirror one: the\n * path stays put but the remote URL changes (a GitHub rename, an org\n * transfer) -- which silently mints a *different* id and strands every node\n * synced under the old one, invisible to every future `status`/`query`/MCP\n * call even though it is still sitting in the same `memory.db` file. Found\n * live 2026-08-15 after this repo's own GitHub account was renamed: 903\n * nodes went dark this way.\n *\n * Only kinds whose original natural key survives in what's already stored\n * are recomputed and re-inserted (`session_summary` via `meta.sessionKey`;\n * hook-sourced `shell_command` via `ts` + `meta.command`, matching\n * `shell/detect.ts`'s `pwsh-hook:${ts}:${sha256(command)}` scheme).\n * `conversation_turn`'s natural key embeds a transcript UUID that is never\n * persisted on the node, so it can't be recomputed -- those rows are instead\n * reassigned to the new project id in place, keeping their existing id.\n *\n * Deliberately NOT migrated:\n * - `git_commit` / `code_diff`: git history is immutable, so a normal `sync`\n * already re-derives every commit and diff under the new id. A stale copy\n * under the old id carries no information a fresh sync doesn't already\n * have, so replaying the id scheme (just the commit sha) buys nothing.\n * - `doc_section`: current file content is always fully rescanned, so a\n * fresh sync already reproduces every section still present in the repo.\n * Migrating would mean replaying `docs.ts`'s slug+occurrence scheme for\n * sections that (empirically, checked live) always turned out to already\n * be present under the new id anyway.\n * - Pre-hook shell scrape sources (`shell:pwsh`, `shell:bash`, `shell:zsh`):\n * their natural key includes a scrape-time list position that was never\n * stored, so it cannot be reconstructed. This is also already-known dead\n * noise the project intends to prune separately -- see nexusmem-constraints\n * in the maintainer's notes -- so leaving it under the now-inert old id is\n * no different in effect from pruning it.\n */\nexport function reconcileProjectId(db: DB, oldProjectId: string, newProjectId: string): ProjectIdReconcileResult {\n return db.transaction((): ProjectIdReconcileResult => {\n const sessions = recomputeByNaturalKey(db, oldProjectId, newProjectId, 'session_summary', null, (_row, meta) =>\n typeof meta.sessionKey === 'string' ? meta.sessionKey : null,\n );\n\n const hookShell = recomputeByNaturalKey(\n db,\n oldProjectId,\n newProjectId,\n 'shell_command',\n 'shell:pwsh-hook',\n (row, meta) =>\n typeof meta.command === 'string' ? `pwsh-hook:${row.ts}:${sha256Hex(meta.command).slice(0, 12)}` : null,\n );\n\n const reassigned = db\n .prepare(`UPDATE nodes SET project_id = ? WHERE project_id = ? AND kind = 'conversation_turn'`)\n .run(newProjectId, oldProjectId).changes;\n\n return {\n oldProjectId,\n migrated: sessions.migrated + hookShell.migrated,\n reassigned,\n deduped: sessions.deduped + hookShell.deduped,\n skipped: sessions.skipped + hookShell.skipped,\n };\n })();\n}\n","import type { MemoryStore } from '../store/store.js';\r\nimport type { EmbeddingProvider } from './embed.js';\r\n\r\n/** `meta` key holding the identity of whatever produced the vectors currently in `nodes_vec`. */\r\nexport const EMBEDDING_IDENTITY_KEY = 'embedding.identity';\r\n\r\nexport interface EmbedPendingResult {\r\n embedded: number;\r\n skipped: number;\r\n /** True if the provider never produced a single vector -- likely means Ollama isn't reachable at all. */\r\n providerUnavailable: boolean;\r\n /** Vectors discarded because the provider that made them is no longer the one in use. */\r\n invalidated: number;\r\n /**\r\n * Nodes still without a vector when the pass ended -- whether because\r\n * `maxNodes` capped it, the provider gave up, or individual texts failed.\r\n * Measured, not inferred, so the CLI never has to guess whether the\r\n * backlog was actually cleared.\r\n */\r\n remaining: number;\r\n}\r\n\r\nexport interface EmbedPendingOptions {\r\n /** Texts per provider request. Default 32. */\r\n batchSize?: number;\r\n /** Rows read from SQLite per page. Default 500. */\r\n pageSize?: number;\r\n /**\r\n * Hard cap on nodes attempted in one pass. Default: none -- a single sync\r\n * drains the whole backlog, which is the point of batching.\r\n */\r\n maxNodes?: number;\r\n /** Consecutive all-failed requests tolerated before giving up. Default 3. */\r\n failureTolerance?: number;\r\n /** Called after each request with cumulative attempts and the backlog size measured at the start. */\r\n onProgress?: (attempted: number, total: number) => void;\r\n /**\r\n * Called before any embedding when a provider change forced the existing\r\n * vectors to be dropped.\r\n *\r\n * A callback rather than just the returned count because the re-embed that\r\n * follows is the longest part of a sync: reporting it afterwards means the\r\n * user watches an unexplained progress bar and only learns the reason once\r\n * it has finished. Observed exactly that way while dogfooding this change.\r\n */\r\n onInvalidated?: (count: number) => void;\r\n}\r\n\r\nconst DEFAULT_BATCH_SIZE = 32;\r\nconst DEFAULT_PAGE_SIZE = 500;\r\nconst DEFAULT_FAILURE_TOLERANCE = 3;\r\n\r\n/**\r\n * Bring the stored vectors and the current provider back into agreement.\r\n *\r\n * Any mismatch invalidates the whole corpus rather than part of it: vectors\r\n * from two providers occupy different spaces, `nodes_vec` records no\r\n * per-row provenance, and a KNN over the mixture returns confident\r\n * nonsense. A corpus with no recorded identity counts as a mismatch -- it\r\n * predates this key, so it was built by the unnormalised `/api/embeddings`\r\n * endpoint (see embed.ts) and is not comparable with anything produced now.\r\n *\r\n * The cost is honest and bounded: nodes are untouched, so a re-embed\r\n * rebuilds what was dropped. It is reported, never silent.\r\n */\r\nfunction reconcileProviderIdentity(store: MemoryStore, provider: EmbeddingProvider): number {\r\n if (store.getMeta(EMBEDDING_IDENTITY_KEY) === provider.identity) return 0;\r\n\r\n const invalidated = store.dropAllEmbeddings();\r\n // Written after the drop, so a crash in between merely repeats a no-op\r\n // drop on the next run rather than leaving stale vectors under a new name.\r\n store.setMeta(EMBEDDING_IDENTITY_KEY, provider.identity);\r\n return invalidated;\r\n}\r\n\r\n/** One request's worth of embeddings, positionally aligned, whether or not the provider can batch. */\r\nasync function embedTexts(provider: EmbeddingProvider, texts: readonly string[]): Promise<(Float32Array | null)[]> {\r\n if (provider.embedBatch) return provider.embedBatch(texts);\r\n\r\n const out: (Float32Array | null)[] = [];\r\n for (const text of texts) out.push(await provider.embed(text));\r\n return out;\r\n}\r\n\r\nfunction chunk<T>(items: readonly T[], size: number): T[][] {\r\n const out: T[][] = [];\r\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\r\n return out;\r\n}\r\n\r\n/**\r\n * Embed every node for a project that doesn't have a vector yet.\r\n *\r\n * A node with no embedding just doesn't participate in vector search --\r\n * BM25 still finds it. This is additive, not a gate: sync always succeeds\r\n * whether or not an embedding provider is available.\r\n *\r\n * Drains the entire backlog by default. It used to stop after 200 nodes,\r\n * which meant a large repository needed several `sync` runs before vector\r\n * search covered it, with nothing in the output saying so. Two things make\r\n * one pass safe to leave uncapped:\r\n *\r\n * - **Paging is monotonic in rowid**, so a node the provider failed on is\r\n * passed over rather than retried forever (see `findNodesNeedingEmbedding`).\r\n * - **A dead provider is detected in seconds, not in timeouts × corpus.**\r\n * `failureTolerance` consecutive all-failed requests end the pass, so an\r\n * Ollama that isn't running costs three requests, not ten thousand.\r\n */\r\nexport async function embedPendingNodes(\r\n store: MemoryStore,\r\n provider: EmbeddingProvider,\r\n projectId: string,\r\n opts: EmbedPendingOptions = {},\r\n): Promise<EmbedPendingResult> {\r\n const batchSize = opts.batchSize ?? DEFAULT_BATCH_SIZE;\r\n const pageSize = opts.pageSize ?? DEFAULT_PAGE_SIZE;\r\n const failureTolerance = opts.failureTolerance ?? DEFAULT_FAILURE_TOLERANCE;\r\n const maxNodes = opts.maxNodes ?? Number.POSITIVE_INFINITY;\r\n\r\n const invalidated = reconcileProviderIdentity(store, provider);\r\n if (invalidated > 0) opts.onInvalidated?.(invalidated);\r\n\r\n const total = Math.min(store.countNodesNeedingEmbedding(projectId), maxNodes);\r\n\r\n let embedded = 0;\r\n let skipped = 0;\r\n let attempted = 0;\r\n let consecutiveFailedRequests = 0;\r\n let cursor = 0;\r\n\r\n outer: while (attempted < maxNodes) {\r\n const page = store.findNodesNeedingEmbedding(projectId, Math.min(pageSize, maxNodes - attempted), cursor);\r\n if (page.length === 0) break;\r\n // Advance before writing anything: the cursor is a position in the walk,\r\n // not a record of success, and must move past failures too.\r\n cursor = page[page.length - 1]!.rowid;\r\n\r\n for (const group of chunk(page, batchSize)) {\r\n const vectors = await embedTexts(\r\n provider,\r\n group.map((node) => `${node.title}\\n${node.body}`),\r\n );\r\n\r\n let embeddedHere = 0;\r\n for (const [index, node] of group.entries()) {\r\n const vector = vectors[index];\r\n if (vector && vector.length === provider.dimension) {\r\n store.upsertEmbedding(node.rowid, vector);\r\n embedded += 1;\r\n embeddedHere += 1;\r\n } else {\r\n skipped += 1;\r\n }\r\n }\r\n\r\n attempted += group.length;\r\n consecutiveFailedRequests = embeddedHere === 0 ? consecutiveFailedRequests + 1 : 0;\r\n opts.onProgress?.(attempted, total);\r\n\r\n if (consecutiveFailedRequests >= failureTolerance) break outer;\r\n }\r\n }\r\n\r\n return {\r\n embedded,\r\n skipped,\r\n // Unchanged meaning: nothing came back at all. Reached far sooner now --\r\n // `failureTolerance` requests instead of the whole first page.\r\n providerUnavailable: attempted > 0 && embedded === 0,\r\n invalidated,\r\n remaining: store.countNodesNeedingEmbedding(projectId),\r\n };\r\n}\r\n","import { makeProjectId } from '../core/project.js';\nimport { readRepoInfo, type RepoInfo } from '../git/repo.js';\nimport { readConfig, resolveWorkspace, type NexusConfig, type Workspace } from '../config/workspace.js';\n\nexport interface CliContext {\n repo: RepoInfo;\n ws: Workspace;\n projectId: string;\n config: NexusConfig;\n}\n\n/**\n * Resolve the repo, its workspace and its config.\n *\n * The project id is always recomputed from the repo rather than trusted from\n * config, so that moving or re-cloning a repo cannot silently split its memory\n * across two namespaces.\n */\nexport async function loadContext(cwd: string): Promise<CliContext> {\n const repo = await readRepoInfo(cwd);\n const ws = resolveWorkspace(repo.root);\n const config = await readConfig(ws);\n return { repo, ws, projectId: makeProjectId({ root: repo.root, originUrl: repo.originUrl }), config };\n}\n","import pc from 'picocolors';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport { renderContextBlock } from '../../retrieval/pack.js';\r\nimport { runCrossProjectQuery, runHybridQuery } from '../../retrieval/query-pipeline.js';\r\nimport { openAllProjectSources } from '../../retrieval/sources.js';\r\nimport { MemoryStore } from '../../store/store.js';\r\nimport { OllamaEmbeddingProvider } from '../../vector/embed.js';\r\nimport { loadContext } from '../context.js';\r\n\r\nexport interface QueryOptions {\r\n cwd: string;\r\n query: string;\r\n /** Token budget for the packed context that gets printed to stdout. */\r\n budget: number;\r\n /** How many FTS/vector candidates to rank/pack from, before the budget is applied. */\r\n candidates: number;\r\n halfLifeDays?: number;\r\n /** Skip embedding the query and vector search entirely -- BM25-only, same as before hybrid retrieval existed. */\r\n noVector?: boolean;\r\n /** Search every registered repository, not just this one. */\r\n allProjects?: boolean;\r\n json: boolean;\r\n}\r\n\r\nexport async function runQuery(opts: QueryOptions): Promise<number> {\r\n const { repo, ws, projectId } = await loadContext(opts.cwd);\r\n\r\n // Exactly one of these owns the database handles: cross-project mode opens\r\n // this repo's database as one source among several, so opening it twice\r\n // would leave a second connection for the same file with nothing to do.\r\n const opened = opts.allProjects\r\n ? await openAllProjectSources({ projectId, root: repo.root, dbPath: ws.dbPath })\r\n : null;\r\n let store: MemoryStore | null = null;\r\n\r\n try {\r\n const queryOpts = {\r\n budget: opts.budget,\r\n candidates: opts.candidates,\r\n halfLifeDays: opts.halfLifeDays,\r\n embeddingProvider: opts.noVector ? null : new OllamaEmbeddingProvider(),\r\n };\r\n\r\n let result;\r\n if (opened) {\r\n result = await runCrossProjectQuery(opened.sources, opts.query, queryOpts);\r\n } else {\r\n store = MemoryStore.open(ws.dbPath);\r\n result = await runHybridQuery(store, projectId, opts.query, queryOpts);\r\n }\r\n const { bm25Count, vectorCount, hits, packed } = result;\r\n\r\n if (opened && !opts.json) {\r\n const searched = opened.sources.map((s) => s.label).join(', ');\r\n process.stderr.write(`${pc.dim('scope ')} ${opened.sources.length} project(s): ${searched}\\n`);\r\n for (const { entry } of opened.unreadable) {\r\n process.stderr.write(`${pc.yellow('unreadable')} ${entry.root} -- skipped\\n`);\r\n }\r\n if (opened.missing.length > 0) {\r\n process.stderr.write(\r\n `${pc.dim('skipped')} ${opened.missing.length} registered project(s) whose database is not on disk` +\r\n ` ${pc.dim('(nexusmem projects --prune to forget them)')}\\n`,\r\n );\r\n }\r\n }\r\n\r\n const matched = hits.length;\r\n\r\n // Packer efficiency: the packed context against the summed raw bodies of\r\n // *the same candidate set*. It measures ranking + budgeted packing\r\n // against its own input, which is what makes it useful for tuning.\r\n //\r\n // Deliberately NOT called \"token saved\": the baseline is hypothetical --\r\n // without NexusMem you'd never have sent these candidate bodies at all,\r\n // so this says nothing about a session's actual token bill. End-to-end\r\n // saving is measured against what the agent would otherwise have read,\r\n // and is a separate number entirely (README § Benchmarks).\r\n //\r\n // Can go negative: for a handful of small matches, fixed per-node\r\n // formatting overhead can outweigh what little there was to trim. The\r\n // efficiency comes from dropping low-score matches entirely once\r\n // candidates exceed the budget, and from truncating large bodies --\r\n // neither has much to work with on a tiny, already-terse result set.\r\n const rawTokens = hits.reduce((n, h) => n + approxTokens(h.body), 0);\r\n const packerEfficiency = rawTokens > 0 ? 1 - packed.tokensUsed / rawTokens : 0;\r\n\r\n if (opts.json) {\r\n process.stdout.write(\r\n `${JSON.stringify(\r\n {\r\n query: opts.query,\r\n matched,\r\n bm25Matched: bm25Count,\r\n vectorMatched: vectorCount,\r\n packed: packed.nodes,\r\n tokensUsed: packed.tokensUsed,\r\n tokensBudget: packed.tokensBudget,\r\n droppedForBudget: packed.droppedForBudget,\r\n droppedForDiversity: packed.droppedForDiversity,\r\n },\r\n null,\r\n 2,\r\n )}\\n`,\r\n );\r\n return 0;\r\n }\r\n\r\n if (matched === 0) {\r\n process.stderr.write(`${pc.yellow('no matches')} for \"${opts.query}\"\\n`);\r\n return 0;\r\n }\r\n\r\n process.stderr.write(\r\n [\r\n `${pc.dim('matched')} ${bm25Count} bm25${vectorCount > 0 ? ` + ${vectorCount} vector` : ''}, packed ${pc.bold(String(packed.nodes.length))} into budget`,\r\n `${pc.dim('tokens ')} ${packed.tokensUsed}/${packed.tokensBudget}` +\r\n (packed.droppedForBudget ? pc.dim(` (${packed.droppedForBudget} dropped for budget)`) : '') +\r\n (packed.droppedForDiversity ? pc.dim(` (${packed.droppedForDiversity} dropped for diversity)`) : ''),\r\n rawTokens > 0\r\n ? `${pc.dim('vs raw ')} ${rawTokens} tokens if these same matches were sent unpacked ${packerEfficiency >= 0 ? pc.green(`(${(packerEfficiency * 100).toFixed(0)}% packer efficiency)`) : pc.yellow(`(${(-packerEfficiency * 100).toFixed(0)}% larger -- overhead dominates on small result sets)`)}`\r\n : '',\r\n '',\r\n ]\r\n .filter(Boolean)\r\n .join('\\n'),\r\n );\r\n\r\n process.stdout.write(`${renderContextBlock(opts.query, packed)}\\n`);\r\n return 0;\r\n } finally {\r\n opened?.close();\r\n store?.close();\r\n }\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectConversationTurns } from '../../collectors/conversation.js';\r\nimport { collectClaudeCodeTranscripts } from '../../conversation/claude-code-reader.js';\r\nimport { claudeProjectTranscriptDir, listTranscriptFiles } from '../../conversation/paths.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { CONVERSATION_SIGNAL_BANDS, formatSignal } from '../format.js';\r\n\r\nexport interface ScanConversationOptions {\r\n cwd: string;\r\n minSignal: number;\r\n json: boolean;\r\n}\r\n\r\nexport async function runScanConversation(opts: ScanConversationOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const files = await listTranscriptFiles(repo.root);\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n files.length\r\n ? `${pc.dim('transcripts')} ${files.length} file(s) in ${claudeProjectTranscriptDir(repo.root)}\\n\\n`\r\n : `${pc.yellow('no transcripts found')} at ${claudeProjectTranscriptDir(repo.root)}\\n`,\r\n );\r\n }\r\n\r\n const turns = await collectClaudeCodeTranscripts(repo.root);\r\n const nodes = collectConversationTurns(turns, projectId).filter((n) => n.signal >= opts.minSignal);\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n for (const node of nodes) process.stdout.write(`${formatNode(node)}\\n`);\r\n\r\n const redactedTotal = nodes.reduce((n, x) => n + (Number(x.meta.redactedCount) || 0), 0);\r\n const approxTotal = nodes.reduce((n, x) => n + approxTokens(x.body), 0);\r\n process.stderr.write(\r\n `\\n${pc.bold(String(nodes.length))} of ${turns.length} exchange(s) above threshold ${pc.dim(`~${approxTotal.toLocaleString()} tokens if sent raw`)}` +\r\n (redactedTotal > 0 ? ` ${pc.yellow(`${redactedTotal} secret-like value(s) redacted`)}` : '') +\r\n '\\n',\r\n );\r\n\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n return [formatSignal(node.signal, CONVERSATION_SIGNAL_BANDS), node.ts.slice(0, 16).replace('T', ' '), node.title].join(' ');\r\n}\r\n","import pc from 'picocolors';\r\n\r\n/**\r\n * Shared rendering for the `scan-*` commands.\r\n *\r\n * These commands all print one line per candidate node, led by its signal, so\r\n * a user can eyeball what a collector would ingest before committing to a\r\n * sync. The signal column previously existed as four near-identical private\r\n * copies, which had already drifted: three graded high signal green while\r\n * `scan-shell` graded it red, so the same column meant opposite things\r\n * depending on which command produced it.\r\n */\r\n\r\n/**\r\n * Cutoffs for the three signal bands, per source.\r\n *\r\n * These stay per-source on purpose. Collectors do not score on a shared\r\n * scale -- a commit's conventional-commit type is much stronger evidence than\r\n * a command's shape, so `scoreShellCommand` clusters near the middle while\r\n * git commits use the full range. One global cutoff would paint every shell\r\n * entry the same color and say nothing.\r\n */\r\nexport interface SignalBands {\r\n /** At or above this, the signal is high for this source. */\r\n high: number;\r\n /** At or above this (but below `high`), middling. */\r\n medium: number;\r\n}\r\n\r\nexport const GIT_SIGNAL_BANDS: SignalBands = { high: 0.7, medium: 0.45 };\r\nexport const SHELL_SIGNAL_BANDS: SignalBands = { high: 0.6, medium: 0.4 };\r\nexport const CONVERSATION_SIGNAL_BANDS: SignalBands = { high: 0.55, medium: 0.35 };\r\nexport const DOCS_SIGNAL_BANDS: SignalBands = { high: 0.55, medium: 0.35 };\r\n/** Same cutoffs as git: a diff's score is anchored on its commit's type, so it lives on the same scale. */\r\nexport const DIFF_SIGNAL_BANDS: SignalBands = { high: 0.7, medium: 0.45 };\r\n\r\nexport type SignalBand = 'high' | 'medium' | 'low';\r\n\r\n/**\r\n * Which band a signal falls in, given its source's cutoffs.\r\n *\r\n * Split out from the coloring so the threshold logic is assertable: under a\r\n * non-TTY test runner picocolors emits no escape codes, which would make a\r\n * test of the rendered string blind to exactly the kind of divergence this\r\n * module exists to prevent.\r\n */\r\nexport function signalBand(signal: number, bands: SignalBands): SignalBand {\r\n if (signal >= bands.high) return 'high';\r\n if (signal >= bands.medium) return 'medium';\r\n return 'low';\r\n}\r\n\r\n/**\r\n * The one place a band becomes a color.\r\n *\r\n * Being a single map is the actual fix for the drift: a per-source palette is\r\n * now unrepresentable rather than merely discouraged, so \"high is green\" holds\r\n * for every command by construction. Thresholds vary by source; the color\r\n * language does not.\r\n */\r\nconst BAND_COLOR: Record<SignalBand, (s: string) => string> = {\r\n high: pc.green,\r\n medium: pc.yellow,\r\n low: pc.dim,\r\n};\r\n\r\n/** A node's signal as a fixed-width, color-graded figure. */\r\nexport function formatSignal(signal: number, bands: SignalBands): string {\r\n return BAND_COLOR[signalBand(signal, bands)](signal.toFixed(2));\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectCommitDiffs } from '../../collectors/diffs.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { DIFF_SIGNAL_BANDS, formatSignal } from '../format.js';\r\nimport { summarize } from './scan-git.js';\r\n\r\nexport interface ScanDiffOptions {\r\n cwd: string;\r\n since?: string;\r\n /** Commits to walk, not nodes to emit -- one commit yields one node per changed file. */\r\n limit?: number;\r\n json: boolean;\r\n minSignal: number;\r\n}\r\n\r\nconst DEFAULT_SCAN_COMMITS = 50;\r\n\r\nexport async function runScanDiff(opts: ScanDiffOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n [\r\n `${pc.dim('repo ')} ${repo.root}`,\r\n `${pc.dim('branch ')} ${repo.branch ?? pc.yellow('(detached)')}`,\r\n `${pc.dim('project')} ${pc.cyan(projectId)}`,\r\n '',\r\n ].join('\\n'),\r\n );\r\n }\r\n\r\n const nodes: MemoryNode[] = [];\r\n\r\n for await (const node of collectCommitDiffs(repo.root, projectId, {\r\n since: opts.since ?? null,\r\n maxCount: opts.limit ?? DEFAULT_SCAN_COMMITS,\r\n })) {\r\n if (node.signal < opts.minSignal) continue;\r\n nodes.push(node);\r\n if (!opts.json) process.stdout.write(`${formatNode(node)}\\n`);\r\n }\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n // The same summary the other scan commands print, from the same helper --\r\n // \"~N tokens if sent raw\" has to mean one thing across all of them.\r\n process.stderr.write(`\\n${summarize(nodes)}\\n`);\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n const sha = String(node.meta.shortSha ?? '').padEnd(9);\r\n const churn = `+${node.meta.insertions ?? 0}/-${node.meta.deletions ?? 0}`;\r\n return [\r\n formatSignal(node.signal, DIFF_SIGNAL_BANDS),\r\n pc.dim(node.ts.slice(0, 10)),\r\n pc.magenta(sha),\r\n String(node.meta.path ?? ''),\r\n pc.dim(`(${churn}, ${node.meta.hunkCount ?? 0} hunk(s))`),\r\n ].join(' ');\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectGitCommits } from '../../collectors/git-commits.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { formatSignal, GIT_SIGNAL_BANDS } from '../format.js';\r\n\r\nexport interface ScanGitOptions {\r\n cwd: string;\r\n since?: string;\r\n limit?: number;\r\n merges: boolean;\r\n json: boolean;\r\n minSignal: number;\r\n}\r\n\r\nexport async function runScanGit(opts: ScanGitOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n [\r\n `${pc.dim('repo ')} ${repo.root}`,\r\n `${pc.dim('branch ')} ${repo.branch ?? pc.yellow('(detached)')}`,\r\n `${pc.dim('origin ')} ${repo.originUrl ?? pc.dim('(none)')}`,\r\n `${pc.dim('project')} ${pc.cyan(projectId)}`,\r\n '',\r\n ].join('\\n'),\r\n );\r\n }\r\n\r\n const nodes: MemoryNode[] = [];\r\n const collectOpts = {\r\n since: opts.since ?? null,\r\n maxCount: opts.limit ?? null,\r\n includeMerges: opts.merges,\r\n };\r\n\r\n for await (const node of collectGitCommits(repo.root, projectId, collectOpts)) {\r\n if (node.signal < opts.minSignal) continue;\r\n nodes.push(node);\r\n if (!opts.json) process.stdout.write(`${formatNode(node)}\\n`);\r\n }\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n process.stderr.write(`\\n${summarize(nodes)}\\n`);\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n const sha = String(node.meta.shortSha ?? '').padEnd(9);\r\n const date = node.ts.slice(0, 10);\r\n const files = Number(node.meta.filesChanged ?? 0);\r\n const churn = `+${node.meta.insertions ?? 0}/-${node.meta.deletions ?? 0}`;\r\n return [\r\n formatSignal(node.signal, GIT_SIGNAL_BANDS),\r\n pc.dim(date),\r\n pc.magenta(sha),\r\n node.title,\r\n pc.dim(`(${files} file${files === 1 ? '' : 's'}, ${churn})`),\r\n ].join(' ');\r\n}\r\n\r\n/** Exported for tests: the token total it reports must match every other scan command's. */\r\nexport function summarize(nodes: MemoryNode[]): string {\r\n if (nodes.length === 0) return pc.yellow('no commits matched');\r\n\r\n const timestamps = nodes.map((n) => n.ts).sort();\r\n const avgSignal = nodes.reduce((n, x) => n + x.signal, 0) / nodes.length;\r\n // The shared helper, not a local re-derivation: every scan command prints\r\n // this same \"tokens if sent raw\" figure, so they must all count it alike.\r\n const totalTokens = nodes.reduce((n, x) => n + approxTokens(x.body), 0);\r\n\r\n const fileHits = new Map<string, number>();\r\n for (const node of nodes) {\r\n for (const f of node.files) fileHits.set(f.path, (fileHits.get(f.path) ?? 0) + 1);\r\n }\r\n const hottest = [...fileHits.entries()]\r\n .sort((a, b) => b[1] - a[1])\r\n .slice(0, 5)\r\n .map(([path, count]) => ` ${String(count).padStart(3)}x ${path}`);\r\n\r\n return [\r\n `${pc.bold(String(nodes.length))} nodes ${pc.dim(`${timestamps[0]?.slice(0, 10)} .. ${timestamps.at(-1)?.slice(0, 10)}`)}`,\r\n ` avg signal ${avgSignal.toFixed(3)} ~${totalTokens.toLocaleString()} tokens if sent raw`,\r\n hottest.length ? ` hottest files:\\n${hottest.join('\\n')}` : '',\r\n ]\r\n .filter(Boolean)\r\n .join('\\n');\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectDocFiles } from '../../collectors/docs.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readDocFiles } from '../../docs/read.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { DOCS_SIGNAL_BANDS, formatSignal } from '../format.js';\r\n\r\nexport interface ScanDocsOptions {\r\n cwd: string;\r\n minSignal: number;\r\n json: boolean;\r\n}\r\n\r\nexport async function runScanDocs(opts: ScanDocsOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const { files, unreadable } = await readDocFiles(repo.root);\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n files.length\r\n ? `${pc.dim('tracked .md files')} ${files.map((f) => f.path).join(', ')}\\n\\n`\r\n : `${pc.yellow('no tracked .md files found')}\\n`,\r\n );\r\n if (unreadable.length > 0) {\r\n process.stderr.write(`${pc.yellow('unreadable')} ${unreadable.join(', ')}\\n\\n`);\r\n }\r\n }\r\n\r\n const nodes = collectDocFiles(files, projectId).filter((n) => n.signal >= opts.minSignal);\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n for (const node of nodes) process.stdout.write(`${formatNode(node)}\\n`);\r\n\r\n const approxTotal = nodes.reduce((n, x) => n + approxTokens(x.body), 0);\r\n process.stderr.write(\r\n `\\n${pc.bold(String(nodes.length))} section(s) from ${files.length} file(s) above threshold ${pc.dim(`~${approxTotal.toLocaleString()} tokens if sent raw`)}\\n`,\r\n );\r\n\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n return [formatSignal(node.signal, DOCS_SIGNAL_BANDS), node.title].join(' ');\r\n}\r\n","import pc from 'picocolors';\r\nimport { collectSessionSummaries } from '../../collectors/sessions.js';\r\nimport { collectClaudeCodeTranscripts } from '../../conversation/claude-code-reader.js';\r\nimport { claudeProjectTranscriptDir } from '../../conversation/paths.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { DEFAULT_SLM_MODEL, OllamaChatProvider } from '../../slm/provider.js';\r\nimport { buildSessionPrompt, groupTurnsIntoSessions, selectSettledSessions } from '../../slm/summarize.js';\r\n\r\nexport interface ScanSessionOptions {\r\n cwd: string;\r\n model: string;\r\n settleMinutes: number;\r\n maxSessions: number;\r\n /** Show what would be sent to the model, and send nothing. */\r\n dryRun: boolean;\r\n json: boolean;\r\n}\r\n\r\n/**\r\n * Preview session summarization without writing anything to the database.\r\n *\r\n * `--dry-run` is the interesting mode: it prints the prompt each session\r\n * would produce, which is the only way to see what the model is actually\r\n * being shown after redaction and budget trimming.\r\n */\r\nexport async function runScanSession(opts: ScanSessionOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const turns = await collectClaudeCodeTranscripts(repo.root);\r\n if (turns.length === 0) {\r\n process.stderr.write(`${pc.yellow('no transcripts found')} at ${claudeProjectTranscriptDir(repo.root)}\\n`);\r\n return 0;\r\n }\r\n\r\n const sessions = groupTurnsIntoSessions(turns);\r\n const settled = selectSettledSessions(sessions, opts.settleMinutes);\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n `${pc.dim('sessions')} ${sessions.length} found, ${settled.length} settled (quiet ${opts.settleMinutes}m+)\\n\\n`,\r\n );\r\n }\r\n\r\n if (opts.dryRun) {\r\n const previews = settled.slice(0, opts.maxSessions).map((session) => {\r\n const { prompt, hash, includedTurns } = buildSessionPrompt(session);\r\n return {\r\n sessionKey: session.sessionKey,\r\n startedAt: session.startedAt,\r\n endedAt: session.endedAt,\r\n turns: session.turns.length,\r\n includedTurns,\r\n hash,\r\n promptChars: prompt.length,\r\n prompt,\r\n };\r\n });\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(previews, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n for (const preview of previews) {\r\n process.stdout.write(\r\n `${pc.bold(preview.sessionKey)} ${preview.startedAt.slice(0, 16).replace('T', ' ')} ` +\r\n `${preview.includedTurns}/${preview.turns} turn(s), ${preview.promptChars} chars\\n${preview.prompt}\\n\\n`,\r\n );\r\n }\r\n return 0;\r\n }\r\n\r\n const result = await collectSessionSummaries(turns, projectId, new OllamaChatProvider({ model: opts.model }), {\r\n settleMinutes: opts.settleMinutes,\r\n maxSessions: opts.maxSessions,\r\n onProgress: (done, total) => {\r\n if (!opts.json) process.stderr.write(` ${pc.dim(`summarizing ${done}/${total}`)}\\n`);\r\n },\r\n });\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(result.nodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n for (const node of result.nodes) {\r\n process.stdout.write(`${pc.bold(node.title)}\\n${pc.dim(node.ts.slice(0, 16).replace('T', ' '))}\\n${node.body}\\n\\n`);\r\n }\r\n\r\n if (result.providerUnavailable) {\r\n process.stderr.write(\r\n `${pc.yellow('model unavailable')} -- is Ollama running with \\`${opts.model}\\` pulled? (\\`ollama pull ${opts.model}\\`)\\n`,\r\n );\r\n return 0;\r\n }\r\n\r\n process.stderr.write(\r\n `${pc.bold(String(result.nodes.length))} summarized` +\r\n (result.failed > 0 ? `, ${pc.yellow(`${result.failed} failed`)}` : '') +\r\n ` ${pc.dim(`(model ${opts.model})`)}\\n`,\r\n );\r\n return 0;\r\n}\r\n\r\nexport const SCAN_SESSION_DEFAULT_MODEL = DEFAULT_SLM_MODEL;\r\n","import pc from 'picocolors';\r\nimport { collectShellHistory } from '../../collectors/shell-history.js';\r\nimport { makeProjectId } from '../../core/project.js';\r\nimport { approxTokens } from '../../core/text.js';\r\nimport type { MemoryNode } from '../../core/types.js';\r\nimport { readRepoInfo } from '../../git/repo.js';\r\nimport { collectAvailableShellHistory } from '../../shell/detect.js';\r\nimport { formatSignal, SHELL_SIGNAL_BANDS } from '../format.js';\r\n\r\nexport interface ScanShellOptions {\r\n cwd: string;\r\n tailLines: number;\r\n minSignal: number;\r\n json: boolean;\r\n}\r\n\r\nexport async function runScanShell(opts: ScanShellOptions): Promise<number> {\r\n const repo = await readRepoInfo(opts.cwd);\r\n const projectId = makeProjectId({ root: repo.root, originUrl: repo.originUrl });\r\n\r\n const results = await collectAvailableShellHistory({ tailLines: opts.tailLines, repoRoot: repo.root });\r\n\r\n if (!opts.json) {\r\n process.stderr.write(\r\n results.length\r\n ? `${pc.dim('sources found')} ${results.map((r) => r.name).join(', ')}\\n\\n`\r\n : `${pc.yellow('no shell history source found on this machine')}\\n`,\r\n );\r\n }\r\n\r\n const allNodes: MemoryNode[] = [];\r\n for (const result of results) {\r\n const nodes = collectShellHistory(result.entries, projectId).filter((n) => n.signal >= opts.minSignal);\r\n allNodes.push(...nodes);\r\n\r\n if (!opts.json) {\r\n process.stdout.write(`${pc.bold(`shell:${result.name}`)} ${pc.dim(`(${nodes.length} of ${result.entries.length} above threshold)`)}\\n`);\r\n for (const node of nodes) process.stdout.write(`${formatNode(node)}\\n`);\r\n process.stdout.write('\\n');\r\n }\r\n }\r\n\r\n if (opts.json) {\r\n process.stdout.write(`${JSON.stringify(allNodes, null, 2)}\\n`);\r\n return 0;\r\n }\r\n\r\n const approxTotal = allNodes.reduce((n, x) => n + approxTokens(x.body), 0);\r\n process.stderr.write(`${pc.bold(String(allNodes.length))} node(s) total ${pc.dim(`~${approxTotal.toLocaleString()} tokens if sent raw`)}\\n`);\r\n\r\n return 0;\r\n}\r\n\r\nfunction formatNode(node: MemoryNode): string {\r\n const approx = node.meta.tsApprox ? pc.dim('~') : ' ';\r\n const exit = node.meta.exitCode;\r\n // Red is reserved for the failure itself. The signal column grades\r\n // importance, not danger, and reads green-for-high like every other\r\n // `scan-*` command -- see cli/format.ts.\r\n const exitLabel = typeof exit === 'number' && exit !== 0 ? pc.red(`exit ${exit}`) : '';\r\n return [formatSignal(node.signal, SHELL_SIGNAL_BANDS), approx + node.ts.slice(0, 16).replace('T', ' '), node.title, exitLabel]\r\n .filter(Boolean)\r\n .join(' ');\r\n}\r\n","import { statSync } from 'node:fs';\nimport pc from 'picocolors';\nimport { getChainStats } from '../../correlate/failure-fix.js';\nimport { MemoryStore } from '../../store/store.js';\nimport { currentSchemaVersion, LATEST_SCHEMA_VERSION } from '../../store/schema.js';\nimport { loadContext } from '../context.js';\n\nexport interface StatusOptions {\n cwd: string;\n}\n\nfunction humanBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nfunction fileSize(path: string): number {\n try {\n return statSync(path).size;\n } catch {\n return 0;\n }\n}\n\nexport async function runStatus(opts: StatusOptions): Promise<number> {\n const { repo, ws, projectId } = await loadContext(opts.cwd);\n const store = MemoryStore.open(ws.dbPath);\n\n try {\n const stats = store.stats(projectId);\n const sources = store.listSyncState(projectId);\n const gitCursor = sources.find((s) => s.source === 'git')?.cursor ?? null;\n const schema = currentSchemaVersion(store.raw);\n const chains = getChainStats(store, projectId);\n\n // WAL content counts towards what is actually on disk.\n const dbBytes = fileSize(ws.dbPath) + fileSize(`${ws.dbPath}-wal`);\n\n const kinds = Object.entries(stats.byKind)\n .sort((a, b) => b[1] - a[1])\n .map(([kind, n]) => ` ${String(n).padStart(6)} ${kind}`);\n\n process.stdout.write(\n [\n `${pc.dim('repo ')} ${repo.root}`,\n `${pc.dim('branch ')} ${repo.branch ?? pc.yellow('(detached)')}`,\n `${pc.dim('project ')} ${pc.cyan(projectId)}`,\n `${pc.dim('schema ')} v${schema}${schema === LATEST_SCHEMA_VERSION ? '' : pc.yellow(` (latest is v${LATEST_SCHEMA_VERSION})`)}`,\n `${pc.dim('database')} ${ws.dbPath} ${pc.dim(`(${humanBytes(dbBytes)})`)}`,\n '',\n `${pc.bold(String(stats.total))} node(s)${stats.total ? ` ${pc.dim(`${stats.oldest?.slice(0, 10)} .. ${stats.newest?.slice(0, 10)}`)}` : ''}`,\n ...kinds,\n stats.total ? ` ${pc.dim(`${stats.distinctFiles} distinct file path(s)`)}` : '',\n '',\n sources.length ? pc.dim('sources') : pc.yellow('no sources synced yet'),\n ...sources.map((s) => {\n const when = s.lastRunAt ? new Date(s.lastRunAt).toISOString().slice(0, 16).replace('T', ' ') : 'never';\n const cursorLabel = s.source === 'git' ? (s.cursor?.slice(0, 7) ?? '-') : (s.cursor ?? '-');\n return ` ${s.source.padEnd(14)} ${pc.dim(`last run ${when}`)} ${pc.dim(`cursor ${cursorLabel}`)}`;\n }),\n gitCursor && gitCursor !== repo.head ? `${pc.yellow('git behind HEAD')} — run ${pc.bold('nexusmem sync')}` : '',\n '',\n chains.failuresTotal\n ? `${pc.dim('chains ')} ${pc.bold(String(chains.resolvedTotal))}/${chains.failuresTotal} failure(s) resolved ${pc.dim(`(${chains.resolvedByRetry} retry, ${chains.resolvedByDiscussion} discussion)`)}${\n chains.resolvedTotal < chains.failuresTotal ? ` — run ${pc.bold('nexusmem sync --link-failures')} to link more` : ''\n }`\n : '',\n ]\n .filter((line) => line !== '')\n .join('\\n')\n .concat('\\n'),\n );\n\n return 0;\n } finally {\n store.close();\n }\n}\n"],"mappings":"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","names":["pc","resolve","chunk","code","signal","mkdir","readFile","writeFile","homedir","join","join","join","homedir","readFile","mkdir","writeFile","pc","existsSync","mkdir","readFile","writeFile","join","z","z","join","readFile","existsSync","mkdir","writeFile","dirname","dirname","chunk","pc","pc","pc","z","basename","DEFAULT_BASE_URL","DEFAULT_TIMEOUT_MS","pc","chunk","FIELD_COUNT","chunk","EMPTY_HISTORY","chunk","MAX_TITLE_CHARS","DEFAULTS","MAX_TITLE_CHARS","byChurnDesc","toMemoryNodes","DEFAULT_MAX_BODY_CHARS","DEFAULT_MAX_CHUNK_CHARS","MAX_TITLE_CHARS","EXPLANATION_MARKERS","toMemoryNodes","chunk","MAX_TITLE_CHARS","DEFAULT_MAX_BODY_CHARS","DEFAULT_MAX_BODY_CHARS","MAX_TITLE_CHARS","toMemoryNode","readFile","basename","existsSync","homedir","join","readFile","basename","readFile","join","join","readFile","existsSync","readFile","stat","mkdir","readFile","dirname","existsSync","readFile","stat","pc","chunk","git","basename","chunk","z","pc","pc","pc","pc","pc","pc","pc","pc","formatNode","summarize","pc","formatNode","summarize","pc","pc","formatNode","pc","pc","pc","pc","formatNode","pc","pc","pc"]}
|