@orquentra/codebuddy 2.8.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CLAUDE_CODEX.md +33 -0
  2. package/README.md +27 -3
  3. package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
  4. package/dist/{chunk-GKNYMNFM.js → chunk-4DKYKDHI.js} +6 -1
  5. package/dist/chunk-4DKYKDHI.js.map +1 -0
  6. package/dist/{chunk-6KGYAFG7.js → chunk-7QMHIJBA.js} +30 -3
  7. package/dist/chunk-7QMHIJBA.js.map +1 -0
  8. package/dist/{chunk-6NZEZJYD.js → chunk-ESC4Y3L7.js} +3 -5
  9. package/dist/{chunk-6NZEZJYD.js.map → chunk-ESC4Y3L7.js.map} +1 -1
  10. package/dist/chunk-GQKWUHMA.js +20 -0
  11. package/dist/chunk-GQKWUHMA.js.map +1 -0
  12. package/dist/{chunk-J4T6HHI4.js → chunk-L6BRYZ6G.js} +537 -316
  13. package/dist/chunk-L6BRYZ6G.js.map +1 -0
  14. package/dist/{chunk-UK6XTS2Y.js → chunk-LRQNPYCK.js} +13 -19
  15. package/dist/chunk-LRQNPYCK.js.map +1 -0
  16. package/dist/{chunk-GXOML4UZ.js → chunk-LW75SKZR.js} +2 -2
  17. package/dist/{chunk-EJQVGFH2.js → chunk-UOTKHSQ5.js} +26 -9
  18. package/dist/chunk-UOTKHSQ5.js.map +1 -0
  19. package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
  20. package/dist/chunk-V7PT6ER5.js.map +1 -0
  21. package/dist/{chunk-ZNLSCV3J.js → chunk-XSNW6Y3X.js} +1253 -315
  22. package/dist/chunk-XSNW6Y3X.js.map +1 -0
  23. package/dist/{chunk-6DONJW3W.js → chunk-YJPCS2GP.js} +274 -69
  24. package/dist/chunk-YJPCS2GP.js.map +1 -0
  25. package/dist/{chunk-XL2HLACE.js → chunk-YN7TEIUH.js} +1 -1
  26. package/dist/chunk-YN7TEIUH.js.map +1 -0
  27. package/dist/{chunk-TLWGZVPC.js → chunk-YNSTIMCF.js} +746 -66
  28. package/dist/chunk-YNSTIMCF.js.map +1 -0
  29. package/dist/{chunk-NY2YXP3R.js → chunk-YWDH6DQ6.js} +70 -4
  30. package/dist/chunk-YWDH6DQ6.js.map +1 -0
  31. package/dist/cli/index.js +23 -9
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  34. package/dist/index.d.ts +6 -4
  35. package/dist/index.js +9 -9
  36. package/dist/install-EU35ERK5.js +30 -0
  37. package/dist/langgraph/index.d.ts +1 -1
  38. package/dist/lean-43ADYTBW.js +28 -0
  39. package/dist/{main-QUASSFP7.js → main-SFC5LV4E.js} +1333 -437
  40. package/dist/main-SFC5LV4E.js.map +1 -0
  41. package/dist/{operations-WJR7AZZF.js → operations-6Z6ZDVBP.js} +3 -3
  42. package/dist/{pretool-command-JTKX6G3M.js → pretool-command-B5GY5T6E.js} +3 -3
  43. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  44. package/dist/{runtime-OQQ22VJF.js → runtime-W5AW32SD.js} +15 -7
  45. package/dist/{summaries-ML53R7EL.js → summaries-32PHOB6L.js} +3 -4
  46. package/dist/{zvec-XDZNZ2OL.js → zvec-24HLAFGN.js} +3 -4
  47. package/dist/zvec-24HLAFGN.js.map +1 -0
  48. package/package.json +11 -2
  49. package/dist/chunk-6DONJW3W.js.map +0 -1
  50. package/dist/chunk-6KGYAFG7.js.map +0 -1
  51. package/dist/chunk-EJQVGFH2.js.map +0 -1
  52. package/dist/chunk-GKNYMNFM.js.map +0 -1
  53. package/dist/chunk-ITQO632W.js +0 -673
  54. package/dist/chunk-ITQO632W.js.map +0 -1
  55. package/dist/chunk-J4T6HHI4.js.map +0 -1
  56. package/dist/chunk-NY2YXP3R.js.map +0 -1
  57. package/dist/chunk-TLWGZVPC.js.map +0 -1
  58. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  59. package/dist/chunk-W7GAXL6C.js.map +0 -1
  60. package/dist/chunk-XL2HLACE.js.map +0 -1
  61. package/dist/chunk-ZNLSCV3J.js.map +0 -1
  62. package/dist/install-BXM56QCD.js +0 -19
  63. package/dist/lean-command-7DFW32MA.js +0 -19
  64. package/dist/main-QUASSFP7.js.map +0 -1
  65. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  66. /package/dist/{chunk-GXOML4UZ.js.map → chunk-LW75SKZR.js.map} +0 -0
  67. /package/dist/{install-BXM56QCD.js.map → install-EU35ERK5.js.map} +0 -0
  68. /package/dist/{lean-command-7DFW32MA.js.map → lean-43ADYTBW.js.map} +0 -0
  69. /package/dist/{operations-WJR7AZZF.js.map → operations-6Z6ZDVBP.js.map} +0 -0
  70. /package/dist/{pretool-command-JTKX6G3M.js.map → pretool-command-B5GY5T6E.js.map} +0 -0
  71. /package/dist/{runtime-OQQ22VJF.js.map → prompt-gate-TDTFFQW2.js.map} +0 -0
  72. /package/dist/{summaries-ML53R7EL.js.map → runtime-W5AW32SD.js.map} +0 -0
  73. /package/dist/{zvec-XDZNZ2OL.js.map → summaries-32PHOB6L.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../package.json","../src/version.ts","../src/core/memory-file-store.ts","../src/core/memory-lifecycle.ts","../src/db/schema.ts","../src/core/memory-search.ts"],"sourcesContent":["{\n \"name\": \"@orquentra/codebuddy\",\n \"version\": \"2.8.0\",\n \"description\": \"Local-first project context and memory for AI coding agents.\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"author\": \"Orquentra Inc\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Orquentra-Inc/CodeBuddy\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"bin\": {\n \"codebuddy\": \"./dist/cli/index.js\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./langgraph\": {\n \"types\": \"./dist/langgraph/index.d.ts\",\n \"import\": \"./dist/langgraph/index.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"src/db/migrations\",\n \"README.md\",\n \"CLAUDE_CODEX.md\",\n \"ARCHITECTURE.md\",\n \"WORKFLOW.md\",\n \"LICENSE\",\n \"docker-compose.yml\",\n \"examples\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx src/cli/index.ts\",\n \"db:generate\": \"drizzle-kit generate\",\n \"db:migrate\": \"drizzle-kit migrate\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run --passWithNoTests\",\n \"test:pg\": \"bash scripts/test-pg-recovery.sh\",\n \"eval:agent-ab\": \"tsx scripts/eval-agent-ab.mts\",\n \"eval:memory-ab\": \"tsx scripts/eval-memory-ab.mts\",\n \"eval:retrieval\": \"tsx scripts/eval-retrieval.mts\",\n \"eval:savings\": \"tsx scripts/eval-task-savings.mts\",\n \"branches:prune\": \"bash scripts/prune-merged-branches.sh\",\n \"lint\": \"biome check .\",\n \"format\": \"biome format --write .\",\n \"prepublishOnly\": \"node scripts/check-release.mjs && npm run typecheck && npm test && npm run build\",\n \"eval:semantic\": \"tsx scripts/eval-semantic.mts\"\n },\n \"keywords\": [\n \"mcp\",\n \"memory\",\n \"agent\",\n \"multi-agent\",\n \"huggingface\",\n \"pgvector\",\n \"postgres\",\n \"langgraph\",\n \"claude\"\n ],\n \"homepage\": \"https://github.com/Orquentra-Inc/CodeBuddy\",\n \"bugs\": {\n \"url\": \"https://github.com/Orquentra-Inc/CodeBuddy/issues\"\n },\n \"dependencies\": {\n \"@clack/prompts\": \"^1.8.1\",\n \"@huggingface/inference\": \"^4.13.19\",\n \"@modelcontextprotocol/sdk\": \"^1.17.5\",\n \"commander\": \"^14.0.0\",\n \"drizzle-orm\": \"^0.45.2\",\n \"js-tiktoken\": \"^1.0.21\",\n \"p-queue\": \"^10.0.0\",\n \"p-retry\": \"^8.0.1\",\n \"p-timeout\": \"^8.1.0\",\n \"picocolors\": \"^1.1.1\",\n \"pino\": \"^10.4.0\",\n \"postgres\": \"^3.4.7\",\n \"yaml\": \"^2.9.0\",\n \"zod\": \"^4.6.7\"\n },\n \"peerDependencies\": {\n \"@langchain/langgraph\": \"^0.2.0 || ^0.3.0\"\n },\n \"peerDependenciesMeta\": {\n \"@langchain/langgraph\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.5.0\",\n \"@types/node\": \"^24.3.0\",\n \"drizzle-kit\": \"^0.31.10\",\n \"tsup\": \"^8.5.0\",\n \"tsx\": \"^4.20.5\",\n \"typescript\": \"^5.9.2\",\n \"vitest\": \"^4.1.12\"\n },\n \"optionalDependencies\": {\n \"@huggingface/transformers\": \"^4.3.2\"\n }\n}\n","import pkg from \"../package.json\" with { type: \"json\" };\n\n/**\n * The single source of truth for the product version. `package.json` is the\n * canonical value (npm reads it for the published package); the CLI `--version`\n * flag and the MCP server handshake both import it from here so a version bump\n * only ever touches `package.json`. The bundler inlines this at build time, so\n * there is no runtime file read.\n */\nexport const VERSION: string = pkg.version;\n","import { mkdir, readdir, readFile, rm, stat } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { parse as parseYaml, stringify as stringifyYaml } from \"yaml\";\nimport { z } from \"zod\";\nimport { redactPersisted } from \"../core/sensitive.js\";\nimport { VERSION } from \"../version.js\";\nimport {\n assertNoSymlinkBetween,\n composeMarkdown,\n parseFrontMatter,\n writeAtomic,\n} from \"./markdown-store-fs.js\";\nimport { isEligibleMemory } from \"./memory-lifecycle.js\";\n\nexport const FACT_CATEGORIES = [\"general\", \"incident\"] as const;\nexport type FactCategory = (typeof FACT_CATEGORIES)[number];\nexport const INCIDENT_SEVERITIES = [\"low\", \"medium\", \"high\", \"critical\"] as const;\nexport type IncidentSeverity = (typeof INCIDENT_SEVERITIES)[number];\n\nconst factFrontMatterSchema = z.object({\n schemaVersion: z.literal(1),\n id: z.string().regex(/^fact_[a-z0-9]+$/),\n namespace: z.string().min(1),\n type: z.literal(\"fact\"),\n subject: z.string(),\n predicate: z.string(),\n object: z.string(),\n /** Explicit single-valued claim identity; facts sharing it are rival assertions. */\n propositionKey: z.string().min(1).nullable().default(null),\n /** The caller's `remember` idempotency key, so a retry after reindex dedupes. */\n idempotencyKey: z.string().min(1).max(200).nullable().default(null),\n confidence: z.number().min(0).max(1),\n createdAt: z.preprocess(\n (value) => (value instanceof Date ? value.toISOString() : value),\n z.string().datetime(),\n ),\n createdByAgent: z.string().nullable(),\n sourceInteractionId: z.string().nullable(),\n sourceDeleted: z.boolean(),\n category: z.enum(FACT_CATEGORIES).default(\"general\"),\n paths: z.array(z.string().min(1)).default([]),\n severity: z.enum(INCIDENT_SEVERITIES).nullable().default(null),\n introducedBy: z.string().nullable().default(null),\n resolvedBy: z.string().nullable().default(null),\n fingerprint: z\n .string()\n .regex(/^[a-f0-9]{64}$/)\n .nullable()\n .default(null),\n sourcePlanId: z.string().nullable().default(null),\n sourceSessionId: z.string().nullable().default(null),\n verification: z.array(z.string()).default([]),\n supersededBy: z.string().nullable().default(null),\n /** Namespaces granted a live reference to this fact (durable form of reference shares). */\n sharedWith: z.array(z.string().min(1)).default([]),\n});\n\nconst summaryFrontMatterSchema = z.object({\n schemaVersion: z.literal(1),\n id: z.string().regex(/^sum_[a-z0-9]+$/),\n namespace: z.string().min(1),\n type: z.literal(\"summary\"),\n sessionId: z.string().min(1),\n version: z.number().int().positive(),\n tokenCount: z.number().int().nonnegative(),\n createdAt: z.preprocess(\n (value) => (value instanceof Date ? value.toISOString() : value),\n z.string().datetime(),\n ),\n createdByAgent: z.string().nullable(),\n});\n\nexport type FactFile = z.infer<typeof factFrontMatterSchema> & {\n content: string;\n path: string;\n};\n\nexport type FactFileWrite = Omit<\n FactFile,\n | \"path\"\n | \"schemaVersion\"\n | \"type\"\n | \"category\"\n | \"paths\"\n | \"severity\"\n | \"introducedBy\"\n | \"resolvedBy\"\n | \"fingerprint\"\n | \"sourcePlanId\"\n | \"sourceSessionId\"\n | \"verification\"\n | \"supersededBy\"\n | \"sharedWith\"\n | \"propositionKey\"\n | \"idempotencyKey\"\n> &\n Partial<\n Pick<\n FactFile,\n | \"category\"\n | \"paths\"\n | \"severity\"\n | \"introducedBy\"\n | \"resolvedBy\"\n | \"fingerprint\"\n | \"sourcePlanId\"\n | \"sourceSessionId\"\n | \"verification\"\n | \"supersededBy\"\n | \"sharedWith\"\n | \"propositionKey\"\n | \"idempotencyKey\"\n >\n >;\nexport type IncidentFactFile = FactFile & {\n category: \"incident\";\n severity: IncidentSeverity;\n};\nexport type SummaryFile = z.infer<typeof summaryFrontMatterSchema> & {\n content: string;\n path: string;\n};\nexport type SummaryFileWrite = Omit<SummaryFile, \"path\" | \"schemaVersion\" | \"type\">;\n/** A managed memory file that exists on disk but could not be parsed. */\nexport type InvalidMemoryFile = { id: string; path: string; error: string };\n\n/** Files read concurrently while scanning. */\nconst SCAN_BATCH = 64;\n\n/** Parsed memory files by absolute path, valid while mtime and size match. */\nconst parsedFiles = new Map<string, { mtimeMs: number; size: number; item: { id: string } }>();\nconst loadedCaches = new Set<string>();\n\n/**\n * The parse cache also lives on disk (`.codebuddy/cache/parsed-<dir>.json`,\n * gitignored and rebuildable like the index manifest), so each short-lived\n * hook process re-parses only files that changed since any earlier process.\n * Entries are still checked against each file's mtime and size, the cache is\n * discarded when the CodeBuddy version changes (the parsed shape may differ),\n * and a missing or corrupt cache only costs a full parse.\n */\nfunction parseCachePath(root: string, directory: string): string {\n return join(\n root,\n \".codebuddy\",\n \"cache\",\n `parsed-${directory.split(sep).at(-1) ?? \"memory\"}.json`,\n );\n}\n\nasync function loadParseCache(root: string, directory: string): Promise<void> {\n if (loadedCaches.has(directory)) return;\n loadedCaches.add(directory);\n try {\n const raw = JSON.parse(await readFile(parseCachePath(root, directory), \"utf8\")) as {\n version: string;\n files: Record<string, { mtimeMs: number; size: number; item: { id: string } }>;\n };\n if (raw.version !== VERSION) return;\n for (const [name, entry] of Object.entries(raw.files)) {\n const path = join(directory, name);\n if (!parsedFiles.has(path)) parsedFiles.set(path, entry);\n }\n } catch {\n // No cache yet, or unreadable: parse from the files.\n }\n}\n\nasync function saveParseCache(root: string, directory: string): Promise<void> {\n const files: Record<string, unknown> = {};\n for (const [path, entry] of parsedFiles) {\n if (dirname(path) === directory) files[relative(directory, path)] = entry;\n }\n try {\n const path = parseCachePath(root, directory);\n await mkdir(dirname(path), { recursive: true });\n await writeAtomic(path, JSON.stringify({ version: VERSION, files }));\n } catch {\n // A cache that cannot be written only costs a re-parse next time.\n }\n}\n\nexport class MemoryFileStore {\n readonly repositoryRoot: string;\n readonly factsDirectory: string;\n readonly summariesDirectory: string;\n\n constructor(repositoryRoot = process.cwd()) {\n this.repositoryRoot = resolve(repositoryRoot);\n this.factsDirectory = join(this.repositoryRoot, \".codebuddy\", \"memory\", \"facts\");\n this.summariesDirectory = join(this.repositoryRoot, \".codebuddy\", \"memory\", \"summaries\");\n }\n\n async writeFact(raw: FactFileWrite): Promise<string> {\n // Persistence boundary: every fact writer (capture, remember, outcomes,\n // migration, resolve/supersede rewrites) is redacted here.\n const input = redactPersisted(raw);\n const path = this.factPath(input.id);\n await this.prepareDirectory(this.factsDirectory);\n await assertNoSymlinkBetween(this.repositoryRoot, dirname(path));\n\n const metadata = {\n schemaVersion: 1,\n id: input.id,\n namespace: input.namespace,\n type: \"fact\",\n subject: input.subject,\n predicate: input.predicate,\n object: input.object,\n propositionKey: input.propositionKey,\n idempotencyKey: input.idempotencyKey,\n confidence: input.confidence,\n createdAt: input.createdAt,\n createdByAgent: input.createdByAgent,\n sourceInteractionId: input.sourceInteractionId,\n sourceDeleted: input.sourceDeleted,\n category: input.category,\n paths: input.paths,\n severity: input.severity,\n introducedBy: input.introducedBy,\n resolvedBy: input.resolvedBy,\n fingerprint: input.fingerprint,\n sourcePlanId: input.sourcePlanId,\n sourceSessionId: input.sourceSessionId,\n verification: input.verification,\n supersededBy: input.supersededBy,\n sharedWith: input.sharedWith,\n };\n const frontMatter = stringifyYaml(stripUndefinedAndDefaultIncidentFields(metadata));\n\n await writeAtomic(path, composeMarkdown(frontMatter, input.content));\n return path;\n }\n\n async writeSummary(input: SummaryFileWrite): Promise<string> {\n const path = this.summaryPath(input.id);\n await this.prepareDirectory(this.summariesDirectory);\n await assertNoSymlinkBetween(this.repositoryRoot, dirname(path));\n const metadata = stringifyYaml({\n schemaVersion: 1,\n id: input.id,\n namespace: input.namespace,\n type: \"summary\",\n sessionId: input.sessionId,\n version: input.version,\n tokenCount: input.tokenCount,\n createdAt: input.createdAt,\n createdByAgent: input.createdByAgent,\n });\n await writeAtomic(path, composeMarkdown(metadata, input.content));\n return path;\n }\n\n async readFact(pathOrId: string): Promise<FactFile> {\n return this.readFactFile(pathOrId, false);\n }\n\n /** `trusted`: the caller (scan) already proved the path is a regular file in a checked directory. */\n private async readFactFile(pathOrId: string, trusted: boolean): Promise<FactFile> {\n const path = pathOrId.endsWith(\".md\") ? this.safePath(pathOrId) : this.factPath(pathOrId);\n if (!trusted) await assertNoSymlinkBetween(this.repositoryRoot, path);\n const parsed = parseFrontMatter(await readFile(path, \"utf8\"));\n const metadata = factFrontMatterSchema.parse(parseYaml(parsed.frontMatter));\n return {\n ...metadata,\n content: parsed.content.trim(),\n path,\n };\n }\n\n async listFacts(): Promise<FactFile[]> {\n return (await this.scanFacts()).facts;\n }\n\n /**\n * Parse every managed fact file. Hand-written markdown is ignored, and a\n * schema-invalid/corrupt fact is reported in `invalid` (not thrown) so one\n * bad file can't break recall, capture or reindexing for the whole project.\n * Invalid files stay on disk for manual repair.\n */\n async scanFacts(): Promise<{ facts: FactFile[]; invalid: InvalidMemoryFile[] }> {\n const scan = await this.scan(this.factsDirectory, /^fact_[a-z0-9]+\\.md$/, (path, trusted) =>\n this.readFactFile(path, trusted),\n );\n return { facts: scan.items, invalid: scan.invalid };\n }\n\n /**\n * List incident facts for the namespace, ranked by severity then recency.\n * Unlike {@link findIncidentFactsForPaths} this does not filter by path, so\n * callers (e.g. the context engine's project bootstrap) can surface the\n * repository's incident hotspots before any specific edit target is known.\n */\n async listIncidentFacts(\n input: { namespace?: string; includeResolved?: boolean } = {},\n ): Promise<IncidentFactFile[]> {\n const incidents = (await this.listFacts()).filter(\n (fact): fact is IncidentFactFile =>\n fact.category === \"incident\" && Boolean(fact.severity) && isEligibleMemory(fact, input),\n );\n\n return incidents.sort((left, right) => {\n const severity = severityRank(right.severity) - severityRank(left.severity);\n if (severity !== 0) return severity;\n return right.createdAt.localeCompare(left.createdAt);\n });\n }\n\n async findIncidentFactsForPaths(input: {\n namespace?: string;\n paths: string[];\n includeResolved?: boolean;\n }): Promise<IncidentFactFile[]> {\n const wanted = new Set(input.paths.map(normalizeMemoryPath));\n if (wanted.size === 0) return [];\n\n return (\n await this.listIncidentFacts({\n ...(input.namespace !== undefined ? { namespace: input.namespace } : {}),\n ...(input.includeResolved !== undefined ? { includeResolved: input.includeResolved } : {}),\n })\n ).filter((fact) => fact.paths.map(normalizeMemoryPath).some((path) => wanted.has(path)));\n }\n\n async readSummary(pathOrId: string): Promise<SummaryFile> {\n const path = pathOrId.endsWith(\".md\") ? this.safePath(pathOrId) : this.summaryPath(pathOrId);\n await assertNoSymlinkBetween(this.repositoryRoot, path);\n const parsed = parseFrontMatter(await readFile(path, \"utf8\"));\n return {\n ...summaryFrontMatterSchema.parse(parseYaml(parsed.frontMatter)),\n content: parsed.content.trim(),\n path,\n };\n }\n\n async listSummaries(): Promise<SummaryFile[]> {\n return (await this.scanSummaries()).summaries;\n }\n\n async scanSummaries(): Promise<{ summaries: SummaryFile[]; invalid: InvalidMemoryFile[] }> {\n const scan = await this.scan(this.summariesDirectory, /^sum_[a-z0-9]+\\.md$/, (path) =>\n this.readSummary(path),\n );\n return { summaries: scan.items, invalid: scan.invalid };\n }\n\n /**\n * Parse every managed file in `directory`. Hot path: the prompt hook and\n * capture scan facts several times per process, and the MCP server for its\n * lifetime, so each parsed file is cached by path, mtime and size (a changed\n * file is re-read, from any process) and files are read in parallel batches.\n * The directory chain is checked for symlinks once; a `withFileTypes` entry\n * that is a symlink is never `isFile()`, so no file escapes through one.\n */\n private async scan<T extends { id: string }>(\n directory: string,\n managedName: RegExp,\n read: (path: string, trusted: boolean) => Promise<T>,\n ): Promise<{ items: T[]; invalid: InvalidMemoryFile[] }> {\n try {\n await this.prepareDirectory(directory);\n await assertNoSymlinkBetween(this.repositoryRoot, directory);\n await loadParseCache(this.repositoryRoot, directory);\n let reparsed = false;\n const entries = (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isFile() && managedName.test(entry.name))\n .sort((left, right) => left.name.localeCompare(right.name));\n // Forget files that were deleted, so the cache never outgrows the directory.\n const live = new Set(entries.map((entry) => join(directory, entry.name)));\n for (const path of [...parsedFiles.keys()]) {\n if (dirname(path) === directory && !live.has(path)) {\n parsedFiles.delete(path);\n reparsed = true;\n }\n }\n const outcomes: Array<{ item: T } | { invalid: InvalidMemoryFile }> = [];\n for (let start = 0; start < entries.length; start += SCAN_BATCH) {\n const batch = entries.slice(start, start + SCAN_BATCH);\n outcomes.push(\n ...(await Promise.all(\n batch.map(async (entry) => {\n const path = join(directory, entry.name);\n const id = entry.name.slice(0, -\".md\".length);\n try {\n const info = await stat(path);\n const cached = parsedFiles.get(path);\n const fresh =\n cached !== undefined &&\n cached.mtimeMs === info.mtimeMs &&\n cached.size === info.size;\n const item = fresh ? (cached.item as T) : await read(path, true);\n if (!fresh) reparsed = true;\n if (item.id !== id) {\n throw new Error(`file ID ${id} differs from front matter ID ${item.id}`);\n }\n parsedFiles.set(path, { mtimeMs: info.mtimeMs, size: info.size, item });\n return { item: { ...item } };\n } catch (error) {\n parsedFiles.delete(path);\n return {\n invalid: {\n id,\n path,\n error: (error as Error).message.split(\"\\n\")[0]?.slice(0, 300) ?? \"unreadable\",\n },\n };\n }\n }),\n )),\n );\n }\n if (reparsed) await saveParseCache(this.repositoryRoot, directory);\n const result: { items: T[]; invalid: InvalidMemoryFile[] } = { items: [], invalid: [] };\n for (const outcome of outcomes) {\n if (\"item\" in outcome) result.items.push(outcome.item);\n else result.invalid.push(outcome.invalid);\n }\n return result;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { items: [], invalid: [] };\n throw error;\n }\n }\n\n async deleteFact(id: string): Promise<void> {\n await rm(this.factPath(id), { force: true });\n }\n\n async markIncidentResolved(id: string, resolvedBy: string): Promise<void> {\n const fact = await this.readFact(id);\n await this.writeFact(copyFact(fact, { resolvedBy }));\n }\n\n async markFactSuperseded(id: string, supersededBy: string): Promise<void> {\n const fact = await this.readFact(id);\n await this.writeFact(copyFact(fact, { supersededBy }));\n }\n\n async setFactSharedWith(id: string, sharedWith: string[]): Promise<void> {\n const fact = await this.readFact(id);\n await this.writeFact(copyFact(fact, { sharedWith: [...new Set(sharedWith)].sort() }));\n }\n\n /** Persist interaction deletion in Markdown before the derived DB is updated. */\n async markSourceInteractionDeleted(\n namespace: string,\n sourceInteractionId: string,\n ): Promise<void> {\n const { facts } = await this.scanFacts();\n for (const fact of facts) {\n if (fact.namespace !== namespace || fact.sourceInteractionId !== sourceInteractionId)\n continue;\n if (fact.sourceDeleted) continue;\n await this.writeFact(copyFact(fact, { sourceDeleted: true }));\n }\n }\n\n /** Persist that a fact's source interaction was forgotten, keeping its provenance id. */\n async markFactSourceDeleted(id: string): Promise<void> {\n const fact = await this.readFact(id);\n await this.writeFact(copyFact(fact, { sourceDeleted: true }));\n }\n\n private factPath(id: string): string {\n if (!/^fact_[a-z0-9]+$/.test(id)) {\n throw new Error(`Invalid fact id: ${id}`);\n }\n return this.safePath(join(this.factsDirectory, `${id}.md`));\n }\n\n private summaryPath(id: string): string {\n if (!/^sum_[a-z0-9]+$/.test(id)) throw new Error(`Invalid summary id: ${id}`);\n return this.safePath(join(this.summariesDirectory, `${id}.md`));\n }\n\n private safePath(path: string): string {\n const absolute = resolve(path);\n const relativePath = relative(this.repositoryRoot, absolute);\n if (relativePath === \"..\" || relativePath.startsWith(`..${sep}`) || relativePath === \"\") {\n throw new Error(`Memory path escapes repository root: ${path}`);\n }\n return absolute;\n }\n\n private async prepareDirectory(directory: string): Promise<void> {\n await assertNoSymlinkBetween(this.repositoryRoot, dirname(directory));\n await mkdir(directory, { recursive: true, mode: 0o700 });\n }\n}\n\nfunction copyFact(\n fact: FactFile,\n patch: Partial<Pick<FactFile, \"resolvedBy\" | \"supersededBy\" | \"sourceDeleted\" | \"sharedWith\">>,\n): FactFileWrite {\n const { path: _path, schemaVersion: _schemaVersion, type: _type, ...write } = fact;\n return { ...write, ...patch };\n}\n\nfunction stripUndefinedAndDefaultIncidentFields(\n metadata: Record<string, unknown>,\n): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(metadata).filter(([key, value]) => {\n if (value === undefined) return false;\n if (key === \"category\" && value === \"general\") return false;\n if ((key === \"paths\" || key === \"sharedWith\") && Array.isArray(value) && value.length === 0)\n return false;\n if (\n [\"severity\", \"introducedBy\", \"resolvedBy\", \"propositionKey\", \"idempotencyKey\"].includes(\n key,\n ) &&\n (value === null || value === undefined)\n ) {\n return false;\n }\n return true;\n }),\n );\n}\n\nfunction normalizeMemoryPath(path: string): string {\n return path.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n}\n\nfunction severityRank(severity: IncidentSeverity): number {\n switch (severity) {\n case \"critical\":\n return 4;\n case \"high\":\n return 3;\n case \"medium\":\n return 2;\n case \"low\":\n return 1;\n }\n}\n","/** Lifecycle fields shared by Markdown facts and indexed (database) fact rows. */\nexport type MemoryLifecycle = {\n namespace?: string;\n sourceDeleted: boolean;\n supersededBy?: string | null;\n category?: string;\n resolvedBy?: string | null;\n};\n\n/**\n * The single eligibility rule for durable memory evidence. A fact is eligible\n * when it belongs to the requested namespace, its source was not deleted, and\n * it was not superseded. A resolved incident is eligible only for historical\n * inspection (`includeResolved`). File context, risk signals, and database\n * recall apply this before ranking or deduplication, so ineligible evidence\n * can never displace or suppress eligible evidence.\n */\nexport function isEligibleMemory(\n fact: MemoryLifecycle,\n options: { namespace?: string; includeResolved?: boolean } = {},\n): boolean {\n if (options.namespace && fact.namespace !== options.namespace) return false;\n if (fact.sourceDeleted || fact.supersededBy) return false;\n if (fact.category === \"incident\" && fact.resolvedBy && !options.includeResolved) return false;\n return true;\n}\n","import { sql } from \"drizzle-orm\";\nimport {\n boolean,\n customType,\n index,\n integer,\n jsonb,\n numeric,\n pgEnum,\n pgTable,\n text,\n timestamp,\n uniqueIndex,\n} from \"drizzle-orm/pg-core\";\n\nexport const VECTOR_DIMENSIONS = 384;\nexport const HNSW_M = 16;\nexport const HNSW_EF_CONSTRUCTION = 64;\n\nconst vector = customType<{ data: string; driverData: string }>({\n dataType() {\n return `vector(${VECTOR_DIMENSIONS})`;\n },\n});\n\nexport const memoryTypeEnum = pgEnum(\"memory_type\", [\"interaction\", \"fact\", \"summary\"]);\nexport const interactionRoleEnum = pgEnum(\"interaction_role\", [\"user\", \"assistant\", \"system\"]);\nexport const embeddingOwnerTypeEnum = pgEnum(\"embedding_owner_type\", [\n \"interaction\",\n \"fact\",\n \"summary\",\n]);\nexport const embeddingStatusEnum = pgEnum(\"embedding_status\", [\n \"pending\",\n \"processing\",\n \"ready\",\n \"failed\",\n]);\nexport const shareModeEnum = pgEnum(\"share_mode\", [\"reference\", \"snapshot\"]);\nexport const modelCallTypeEnum = pgEnum(\"model_call_type\", [\"embedding\", \"llm\"]);\nexport const modelCallStatusEnum = pgEnum(\"model_call_status\", [\n \"success\",\n \"failed\",\n \"timeout\",\n \"rate_limited\",\n \"gated\",\n \"fallback\",\n]);\nexport const auditActionEnum = pgEnum(\"audit_action\", [\n \"remember\",\n \"remember_batch\",\n \"share\",\n \"forget\",\n \"config\",\n]);\n\nconst createdAt = timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow();\nconst updatedAt = timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow();\n\nexport const namespaces = pgTable(\n \"namespaces\",\n {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n createdAt,\n updatedAt,\n },\n (table) => ({\n nameUnique: uniqueIndex(\"namespaces_name_unique\").on(table.name),\n }),\n);\n\nexport const interactions = pgTable(\n \"interactions\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n sessionId: text(\"session_id\").notNull(),\n role: interactionRoleEnum(\"role\").notNull().default(\"user\"),\n content: text(\"content\").notNull(),\n contentHash: text(\"content_hash\").notNull(),\n tokenCount: integer(\"token_count\").notNull().default(0),\n createdByAgent: text(\"created_by_agent\"),\n createdAt,\n },\n (table) => ({\n namespaceContentHashUnique: uniqueIndex(\"interactions_namespace_content_hash_unique\").on(\n table.namespaceId,\n table.contentHash,\n ),\n namespaceSessionCreatedIdx: index(\"interactions_namespace_session_created_idx\").on(\n table.namespaceId,\n table.sessionId,\n table.createdAt,\n ),\n }),\n);\n\nexport const sessionSummaries = pgTable(\n \"session_summaries\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n sessionId: text(\"session_id\").notNull(),\n content: text(\"content\").notNull(),\n version: integer(\"version\").notNull().default(1),\n tokenCount: integer(\"token_count\").notNull().default(0),\n createdByAgent: text(\"created_by_agent\"),\n /** See facts.fileBacked. */\n fileBacked: boolean(\"file_backed\").notNull().default(false),\n createdAt,\n },\n (table) => ({\n namespaceSessionVersionIdx: index(\"session_summaries_namespace_session_version_idx\").on(\n table.namespaceId,\n table.sessionId,\n table.version,\n ),\n }),\n);\n\nexport const facts = pgTable(\n \"facts\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n subject: text(\"subject\").notNull(),\n predicate: text(\"predicate\").notNull(),\n object: text(\"object\").notNull(),\n propositionKey: text(\"proposition_key\"),\n /** Kept apart from content_hash so a reindex (which recomputes the hash) keeps it. */\n idempotencyKey: text(\"idempotency_key\"),\n content: text(\"content\").notNull(),\n contentHash: text(\"content_hash\").notNull(),\n confidence: numeric(\"confidence\", { precision: 5, scale: 4 }).notNull().default(\"1\"),\n /**\n * Historical provenance only — deliberately not a foreign key. Interactions\n * are ephemeral DB rows; Markdown facts must re-import after a rebuild that\n * lost them, and the ID must survive so `sourceDeleted` can be tracked.\n */\n sourceInteractionId: text(\"source_interaction_id\"),\n sourceDeleted: boolean(\"source_deleted\").notNull().default(false),\n supersededBy: text(\"superseded_by\"),\n /**\n * True once a reindex imported this row from its Markdown file. Only such\n * rows are deleted when the file disappears; DB-only legacy rows are kept.\n */\n fileBacked: boolean(\"file_backed\").notNull().default(false),\n embeddingModel: text(\"embedding_model\"),\n createdByAgent: text(\"created_by_agent\"),\n createdAt,\n updatedAt,\n },\n (table) => ({\n namespaceContentHashUnique: uniqueIndex(\"facts_namespace_content_hash_unique\").on(\n table.namespaceId,\n table.contentHash,\n ),\n namespaceSubjectIdx: index(\"facts_namespace_subject_idx\").on(table.namespaceId, table.subject),\n namespaceIdempotencyIdx: index(\"facts_namespace_idempotency_idx\").on(\n table.namespaceId,\n table.idempotencyKey,\n ),\n sourceInteractionIdx: index(\"facts_source_interaction_idx\").on(table.sourceInteractionId),\n }),\n);\n\nexport const embeddings = pgTable(\n \"embeddings\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n ownerType: embeddingOwnerTypeEnum(\"owner_type\").notNull(),\n ownerId: text(\"owner_id\").notNull(),\n vector: vector(\"vector\").notNull(),\n embeddingModel: text(\"embedding_model\").notNull(),\n embeddingSpace: text(\"embedding_space\"),\n status: embeddingStatusEnum(\"status\").notNull().default(\"pending\"),\n attempts: integer(\"attempts\").notNull().default(0),\n lastError: text(\"last_error\"),\n /** Lease deadline while status='processing'; null otherwise. */\n leaseUntil: timestamp(\"lease_until\", { withTimezone: true }),\n leaseToken: text(\"lease_token\"),\n createdAt,\n updatedAt,\n },\n (table) => ({\n ownerUnique: uniqueIndex(\"embeddings_owner_unique\").on(table.ownerType, table.ownerId),\n namespaceModelStatusIdx: index(\"embeddings_namespace_model_status_idx\").on(\n table.namespaceId,\n table.embeddingModel,\n table.status,\n ),\n namespaceSpaceStatusIdx: index(\"embeddings_namespace_space_status_idx\").on(\n table.namespaceId,\n table.embeddingSpace,\n table.status,\n ),\n vectorHnswIdx: index(\"embeddings_vector_hnsw_idx\").using(\n \"hnsw\",\n sql`${table.vector} vector_cosine_ops`,\n ),\n }),\n);\n\nexport const shares = pgTable(\n \"shares\",\n {\n id: text(\"id\").primaryKey(),\n fromNamespaceId: text(\"from_namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n toNamespaceId: text(\"to_namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n sourceFactId: text(\"source_fact_id\")\n .notNull()\n .references(() => facts.id, { onDelete: \"cascade\" }),\n snapshotFactId: text(\"snapshot_fact_id\").references(() => facts.id, { onDelete: \"set null\" }),\n mode: shareModeEnum(\"mode\").notNull().default(\"reference\"),\n tombstoned: boolean(\"tombstoned\").notNull().default(false),\n createdByAgent: text(\"created_by_agent\"),\n createdAt,\n },\n (table) => ({\n shareUnique: uniqueIndex(\"shares_target_source_unique\").on(\n table.toNamespaceId,\n table.sourceFactId,\n table.mode,\n ),\n fromNamespaceIdx: index(\"shares_from_namespace_idx\").on(table.fromNamespaceId),\n }),\n);\n\nexport const checkpoints = pgTable(\n \"checkpoints\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n threadId: text(\"thread_id\").notNull(),\n checkpointId: text(\"checkpoint_id\").notNull(),\n parentId: text(\"parent_id\"),\n channelValues: jsonb(\"channel_values\").notNull().default(sql`'{}'::jsonb`),\n metadata: jsonb(\"metadata\").notNull().default(sql`'{}'::jsonb`),\n createdAt,\n },\n (table) => ({\n checkpointUnique: uniqueIndex(\"checkpoints_thread_checkpoint_unique\").on(\n table.namespaceId,\n table.threadId,\n table.checkpointId,\n ),\n }),\n);\n\nexport const modelCalls = pgTable(\n \"model_calls\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\").references(() => namespaces.id, { onDelete: \"set null\" }),\n model: text(\"model\").notNull(),\n type: modelCallTypeEnum(\"type\").notNull(),\n latencyMs: integer(\"latency_ms\"),\n tokens: integer(\"tokens\"),\n status: modelCallStatusEnum(\"status\").notNull(),\n error: text(\"error\"),\n metadata: jsonb(\"metadata\").notNull().default(sql`'{}'::jsonb`),\n createdAt,\n },\n (table) => ({\n modelCreatedIdx: index(\"model_calls_model_created_idx\").on(table.model, table.createdAt),\n namespaceCreatedIdx: index(\"model_calls_namespace_created_idx\").on(\n table.namespaceId,\n table.createdAt,\n ),\n }),\n);\n\nexport const auditLog = pgTable(\n \"audit_log\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\").references(() => namespaces.id, { onDelete: \"set null\" }),\n action: auditActionEnum(\"action\").notNull(),\n entityType: text(\"entity_type\").notNull(),\n entityId: text(\"entity_id\"),\n createdByAgent: text(\"created_by_agent\"),\n metadata: jsonb(\"metadata\").notNull().default(sql`'{}'::jsonb`),\n createdAt,\n },\n (table) => ({\n namespaceActionCreatedIdx: index(\"audit_log_namespace_action_created_idx\").on(\n table.namespaceId,\n table.action,\n table.createdAt,\n ),\n }),\n);\n","import { VECTOR_DIMENSIONS } from \"../db/schema.js\";\n\n/** Bound both DB hydration and the lexical query work independently of caller input. */\nexport function candidateLimit(limit: number): number {\n if (!Number.isFinite(limit)) return 256;\n return Math.max(1, Math.min(1024, Math.trunc(limit)));\n}\n\n/** pgvector stores float4, so values must stay finite after float4 rounding. */\nexport function validSearchVector(vector: number[]): boolean {\n return (\n vector.length === VECTOR_DIMENSIONS &&\n vector.every((value) => Number.isFinite(Math.fround(value))) &&\n vector.some((value) => value !== 0)\n );\n}\n\n/** Common prompt scaffolding should not make unrelated facts look relevant. */\nconst QUERY_STOPWORDS = new Set([\n \"a\",\n \"about\",\n \"after\",\n \"again\",\n \"all\",\n \"also\",\n \"always\",\n \"an\",\n \"and\",\n \"any\",\n \"are\",\n \"as\",\n \"at\",\n \"back\",\n \"be\",\n \"been\",\n \"before\",\n \"being\",\n \"but\",\n \"by\",\n \"can\",\n \"could\",\n \"did\",\n \"do\",\n \"does\",\n \"done\",\n \"each\",\n \"even\",\n \"ever\",\n \"for\",\n \"from\",\n \"get\",\n \"gets\",\n \"got\",\n \"had\",\n \"has\",\n \"have\",\n \"here\",\n \"how\",\n \"i\",\n \"if\",\n \"in\",\n \"into\",\n \"is\",\n \"it\",\n \"its\",\n \"just\",\n \"like\",\n \"make\",\n \"me\",\n \"more\",\n \"most\",\n \"my\",\n \"never\",\n \"no\",\n \"not\",\n \"now\",\n \"of\",\n \"on\",\n \"only\",\n \"or\",\n \"other\",\n \"our\",\n \"out\",\n \"over\",\n \"please\",\n \"really\",\n \"same\",\n \"should\",\n \"so\",\n \"some\",\n \"sometimes\",\n \"still\",\n \"such\",\n \"than\",\n \"that\",\n \"the\",\n \"their\",\n \"them\",\n \"then\",\n \"there\",\n \"these\",\n \"they\",\n \"this\",\n \"those\",\n \"through\",\n \"to\",\n \"too\",\n \"under\",\n \"up\",\n \"us\",\n \"very\",\n \"was\",\n \"we\",\n \"were\",\n \"what\",\n \"when\",\n \"where\",\n \"which\",\n \"while\",\n \"who\",\n \"why\",\n \"will\",\n \"with\",\n \"would\",\n \"yet\",\n \"you\",\n \"your\",\n]);\n\n/** Literal, case-insensitive terms; no SQL wildcard or query-language syntax. */\nexport function lexicalTerms(query: string): string[] {\n return [\n ...new Set(\n query\n .slice(0, 4096)\n .toLowerCase()\n .match(/[\\p{L}\\p{N}_]+/gu) ?? [],\n ),\n ]\n .filter((term) => !QUERY_STOPWORDS.has(term))\n .slice(0, 32)\n .map((term) => term.slice(0, 64));\n}\n\n/**\n * Everyday coding words. Shared alone they do not say which fact a prompt is\n * about; in a small fact corpus they would otherwise look rare.\n */\nconst GENERIC_TERMS = new Set(\n (\n \"add added adding bug build call change changed changes check code config configure configuration \" +\n \"create data default delete error errors feature file files fix fixed handle issue line load logic \" +\n \"new option options problem project read remove result return run running save set setting settings \" +\n \"show step support test tests thing time update updated use used value values version want way work \" +\n \"works write\"\n ).split(\" \"),\n);\n/** A lone shared term admits a fact only if at most this share of the facts hold it… */\nconst RARE_TERM_SHARE = 0.005;\n/** …or at most this many (so small corpora are not held to a fraction of one fact). */\nconst RARE_TERM_MIN_DF = 2;\n\n/**\n * Lexical admission for per-prompt fact recall (#9): at least two distinct\n * non-generic shared terms, or one that is rare across the fact corpus\n * (document frequency ≤ max(2, 0.5% of facts)) and not generic. One common\n * word in common (\"session\" in an unrelated deploy prompt) is a coincidence.\n */\nexport function admitsLexically(\n shared: string[],\n documentFrequency: Map<string, number>,\n corpusSize: number,\n): boolean {\n const specific = shared.filter((term) => !GENERIC_TERMS.has(term));\n if (specific.length >= 2) return true;\n const rare = Math.max(RARE_TERM_MIN_DF, Math.floor(RARE_TERM_SHARE * corpusSize));\n return specific.some((term) => (documentFrequency.get(term) ?? 0) <= rare);\n}\n\n/** Words every captured decision shares (\"we decided to use …\"); they carry no claim. */\nconst CLAIM_BOILERPLATE = new Set([\n \"decided\",\n \"decide\",\n \"decision\",\n \"made\",\n \"use\",\n \"uses\",\n \"used\",\n \"using\",\n \"now\",\n \"going\",\n \"will\",\n]);\n\n/**\n * Jaccard overlap of two texts' content words (stopwords dropped), 0..1.\n * Used to spot near-duplicate facts: at capture (so a reworded decision is not\n * saved twice) and at recall (so two copies never take two of the few slots).\n */\nexport function termOverlap(a: string, b: string): number {\n const terms = (text: string) =>\n new Set(\n (text.toLowerCase().match(/[\\p{L}\\p{N}_]+/gu) ?? []).filter(\n (term) => !QUERY_STOPWORDS.has(term) && !CLAIM_BOILERPLATE.has(term),\n ),\n );\n const left = terms(a);\n const right = terms(b);\n if (left.size === 0 || right.size === 0) return 0;\n let shared = 0;\n for (const term of left) if (right.has(term)) shared += 1;\n return shared / (left.size + right.size - shared);\n}\n\n/** Score complete-word evidence, favoring explicit fact fields over free text. */\nexport function lexicalFactScore(\n fact: { subject: string; object: string; content: string },\n terms: string[],\n): number {\n if (terms.length === 0) return 0;\n const structured = new Set(\n `${fact.subject} ${fact.object}`.toLowerCase().match(/[\\p{L}\\p{N}_]+/gu) ?? [],\n );\n const prose = new Set(fact.content.toLowerCase().match(/[\\p{L}\\p{N}_]+/gu) ?? []);\n const points = terms.reduce(\n (total, term) => total + (structured.has(term) ? 2 : 0) + (prose.has(term) ? 1 : 0),\n 0,\n );\n return points / (3 * terms.length);\n}\n\nexport function cosine(a: number[], b: number[]): number {\n if (a.length !== b.length) return 0;\n let dot = 0;\n let na = 0;\n let nb = 0;\n for (let i = 0; i < a.length; i++) {\n const av = a[i] ?? 0;\n const bv = b[i] ?? 0;\n dot += av * bv;\n na += av * av;\n nb += bv * bv;\n }\n const denom = Math.sqrt(na) * Math.sqrt(nb);\n return denom === 0 ? 0 : dot / denom;\n}\n"],"mappings":"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","names":["facts","vector","text"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/pretool.ts","../src/hooks/pretool-command.ts"],"sourcesContent":["import { open, stat } from \"node:fs/promises\";\nimport { extname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { extractSymbolTable, supportsSymbols } from \"../indexer/symbols.js\";\nimport type { HookInput } from \"./runtime.js\";\n\n/**\n * PreToolUse tool guard (Claude Code): cut tool output that would otherwise\n * stay in context for the rest of the session, without touching permissions.\n *\n * - Read of a large file with no range → denied with an outline (symbols or\n * headings with line ranges) and how to read a range or the whole file.\n * The model sees the deny reason; any Read with offset/limit passes.\n * - Grep in content mode with no head_limit → the same input plus a limit.\n * - Bash that only prints a large file whole (`cat`, `less`, `head -n 5000`) →\n * denied the same way; any other command (pipes, builds, tests) passes.\n *\n * Never emits `permissionDecision: \"allow\"`: Claude Code checks permissions on\n * the (possibly rewritten) input as usual. Verified on Claude Code 2.1.287.\n */\n/**\n * Only files whose full read is clearly expensive: below this, an agent that\n * re-reads the whole file after the outline (observed live) costs a turn for\n * nothing.\n */\nexport const TOOL_GUARD = {\n readLines: 1_000,\n readBytes: 80_000,\n /** Symbols shorter than this are noise in an outline (one-line constants). */\n outlineMinSymbolLines: 3,\n /** Fewer top-level symbols than this: list class methods too. */\n minTopLevelEntries: 10,\n outlineEntries: 40,\n blockLines: 100,\n grepHeadLimit: 100,\n} as const;\n\n/** Read renders these itself (images, PDFs, notebooks); never guard them. */\nconst SPECIAL_READ = new Set([\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".gif\",\n \".webp\",\n \".bmp\",\n \".svg\",\n \".pdf\",\n \".ipynb\",\n]);\nconst MAX_GUARD_BYTES = 2_000_000;\n\nexport type PreToolDecision = {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\";\n permissionDecision?: \"deny\";\n permissionDecisionReason?: string;\n updatedInput?: Record<string, unknown>;\n };\n} | null;\n\nexport async function decidePreTool(\n input: HookInput,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<PreToolDecision> {\n if (env.CODEBUDDY_TOOL_GUARD === \"0\") return null;\n const toolInput = input.tool_input ?? {};\n if (input.tool_name === \"Grep\") return capGrep(toolInput);\n if (input.tool_name === \"Read\") return guardRead(resolve(input.cwd ?? process.cwd()), toolInput);\n if (input.tool_name === \"Bash\") return guardBash(resolve(input.cwd ?? process.cwd()), toolInput);\n return null;\n}\n\nfunction capGrep(toolInput: Record<string, unknown>): PreToolDecision {\n if (toolInput.output_mode !== \"content\" || toolInput.head_limit !== undefined) return null;\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n updatedInput: { ...toolInput, head_limit: TOOL_GUARD.grepHeadLimit },\n },\n };\n}\n\n/** A file inside the repository too large to read whole, with its outline; else null. */\nexport async function largeFile(\n root: string,\n filePath: string,\n): Promise<{ path: string; lines: number; size: number; outline: string[] } | null> {\n const absolute = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);\n const rel = relative(root, absolute);\n if (rel === \"\" || rel === \"..\" || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n if (SPECIAL_READ.has(extname(absolute).toLowerCase())) return null;\n let size: number;\n try {\n const info = await stat(absolute);\n if (!info.isFile()) return null;\n size = info.size;\n } catch {\n return null;\n }\n if (size > MAX_GUARD_BYTES) return null; // Read refuses huge files itself.\n const text = await readText(absolute);\n if (text === null) return null;\n const lines = text.split(/\\r?\\n/);\n if (lines.at(-1) === \"\") lines.pop();\n if (lines.length <= TOOL_GUARD.readLines && size <= TOOL_GUARD.readBytes) return null;\n const path = rel.replace(/\\\\/g, \"/\");\n return { path, lines: lines.length, size, outline: outlineOf(path, text, lines) };\n}\n\nasync function guardRead(\n root: string,\n toolInput: Record<string, unknown>,\n): Promise<PreToolDecision> {\n if (toolInput.offset !== undefined || toolInput.limit !== undefined) return null;\n const filePath = toolInput.file_path;\n if (typeof filePath !== \"string\" || !filePath) return null;\n const file = await largeFile(root, filePath);\n if (!file) return null;\n return deny(file, [\n \"Read only the part you need with offset and limit (offset = first line, limit = line count).\",\n \"To find specific text, use Grep with output_mode content and -n instead of reading the file.\",\n `To read the whole file anyway, call Read again with limit: ${file.lines}.`,\n ]);\n}\n\nfunction deny(\n file: { path: string; lines: number; size: number; outline: string[] },\n advice: string[],\n): PreToolDecision {\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: [\n `CodeBuddy read guard: ${file.path} is ${file.lines} lines (~${Math.ceil(file.size / 4)} tokens), too large to read whole.`,\n `Outline: ${file.outline.join(\"; \")}`,\n ...advice,\n ].join(\"\\n\"),\n },\n };\n}\n\n/** Programs that print whole files; `head`/`tail` only when asked for more lines than the guard. */\nconst WHOLE_FILE_PRINTERS = new Set([\"cat\", \"less\", \"more\", \"nl\", \"bat\", \"batcat\"]);\n/**\n * Anything that makes a command more than one plain read: pipes, lists,\n * redirection, substitution, globbing over many files. Such commands are never\n * touched, so builds, tests, pipelines and scripts behave exactly as before.\n */\nconst SHELL_SYNTAX = /[|;&<>`$(){}*?[\\]\\n\\\\]/;\n\n/**\n * Bash that would print a large file whole (`cat src/big.ts`, `head -n 5000 x`).\n * Claude reads files through Bash to get around the Read guard; the output then\n * stays in context for the rest of the session. Only a single plain read\n * command of a file over the Read guard's size is denied, with the same outline\n * and how to read a range; every other command passes untouched.\n */\nasync function guardBash(\n root: string,\n toolInput: Record<string, unknown>,\n): Promise<PreToolDecision> {\n const command = typeof toolInput.command === \"string\" ? toolInput.command.trim() : \"\";\n if (!command || SHELL_SYNTAX.test(command)) return null;\n const tokens = (command.match(/\"[^\"]*\"|'[^']*'|\\S+/g) ?? []).map((token) =>\n token.replace(/^([\"'])(.*)\\1$/, \"$2\"),\n );\n // Leading `VAR=value` assignments do not change what runs.\n while (tokens[0] && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0])) tokens.shift();\n const program = (tokens.shift() ?? \"\").split(\"/\").at(-1) ?? \"\";\n const files: string[] = [];\n if (WHOLE_FILE_PRINTERS.has(program)) {\n for (const token of tokens) if (!token.startsWith(\"-\")) files.push(token);\n } else if (program === \"head\" || program === \"tail\") {\n let lines = 10;\n for (let index = 0; index < tokens.length; index += 1) {\n const token = tokens[index] ?? \"\";\n const count = /^-n$|^--lines$/.test(token)\n ? tokens[++index]\n : (token.match(/^(?:-n|--lines=)\\+?(\\d+)$/)?.[1] ?? token.match(/^-(\\d+)$/)?.[1]);\n if (count !== undefined && /^\\+?\\d+$/.test(count)) lines = Number(count.replace(\"+\", \"\"));\n else if (token === \"-f\" || token === \"-F\" || token === \"--follow\") return null;\n else if (!token.startsWith(\"-\")) files.push(token);\n }\n if (lines <= TOOL_GUARD.readLines) return null;\n } else {\n return null;\n }\n for (const filePath of files) {\n const file = await largeFile(root, filePath);\n if (file) {\n return deny(file, [\n \"Read only the part you need with the Read tool (offset = first line, limit = line count) or `sed -n 'START,ENDp' FILE`.\",\n \"To find specific text, use Grep with output_mode content and -n instead of printing the file.\",\n `To read the whole file anyway, use Read with limit: ${file.lines}.`,\n ]);\n }\n }\n return null;\n}\n\n/** Whole file as UTF-8 text, or null when it looks binary. */\nasync function readText(path: string): Promise<string | null> {\n const handle = await open(path, \"r\");\n try {\n const content = await handle.readFile();\n if (content.includes(0)) return null;\n return content.toString(\"utf8\");\n } finally {\n await handle.close();\n }\n}\n\nfunction outlineOf(path: string, text: string, lines: string[]): string[] {\n const cap = (entries: string[]) =>\n entries.length > TOOL_GUARD.outlineEntries\n ? [\n ...entries.slice(0, TOOL_GUARD.outlineEntries),\n `… ${entries.length - TOOL_GUARD.outlineEntries} more`,\n ]\n : entries;\n const all = supportsSymbols(path) ? extractSymbolTable(text, path) : [];\n if (all[0]?.kind === \"heading\") {\n return cap(all.map((heading) => `${heading.signature.slice(0, 80)} L${heading.startLine}`));\n }\n if (all.length > 0) {\n const table = all.filter(\n (symbol) => symbol.endLine - symbol.startLine + 1 >= TOOL_GUARD.outlineMinSymbolLines,\n );\n const topLevel = table.filter((symbol) => symbol.kind !== \"method\");\n // One big class says little on its own; then list its methods (`Class.method`).\n const symbols = topLevel.length >= TOOL_GUARD.minTopLevelEntries ? topLevel : table;\n if (symbols.length > 0) {\n // The symbol table is capped, so say where the outline stops covering the file.\n const covered = Math.max(...symbols.map((symbol) => symbol.endLine));\n return [\n ...cap(symbols.map((s) => `${s.name} L${s.startLine}-${s.endLine}`)),\n ...(covered < lines.length ? [`not outlined L${covered + 1}-${lines.length}`] : []),\n ];\n }\n }\n const blocks: string[] = [];\n for (let start = 1; start <= lines.length; start += TOOL_GUARD.blockLines) {\n blocks.push(`L${start}-${Math.min(lines.length, start + TOOL_GUARD.blockLines - 1)}`);\n }\n return cap(blocks);\n}\n","import { recordTrace } from \"../tracing/log.js\";\nimport { decidePreTool } from \"./pretool.js\";\nimport { hookRoot } from \"./root.js\";\nimport { failSoft, readHookInput, writeStdout } from \"./stdio.js\";\n\n/** `codebuddy hooks pretool`: runs on every Read/Grep, so it loads only its own module. */\nexport async function runPreToolHookCommand(): Promise<void> {\n await failSoft(async () => {\n const input = await readHookInput();\n const decision = await decidePreTool(input);\n if (!decision) return;\n await writeStdout(JSON.stringify(decision));\n const reason = decision.hookSpecificOutput.permissionDecisionReason;\n if (decision.hookSpecificOutput.permissionDecision === \"deny\" && reason) {\n // Measured, not modelled: the file the agent did not load whole, minus the\n // outline it got instead. Shown by `codebuddy dashboard`.\n const full = Number(reason.match(/\\(~(\\d+) tokens\\)/)?.[1] ?? 0);\n const outline = Math.ceil(reason.length / 4);\n await recordTrace(hookRoot(input.cwd), {\n kind: \"guard\",\n name: input.tool_name ?? \"tool\",\n ms: 0,\n ok: true,\n tokensOut: outline,\n ...(input.session_id ? { session: input.session_id } : {}),\n detail: {\n file: reason.match(/read guard: (\\S+) is/)?.[1] ?? \"\",\n avoided: Math.max(0, full - outline),\n },\n });\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/savings/tokens.ts","../src/context/session-ledger.ts","../src/context/tests.ts"],"sourcesContent":["import { countTokens } from \"../planner/budget.js\";\n\n/**\n * The one place token counts are computed. Strings use the same deterministic\n * cl100k tokenizer as planner budgets; objects are measured by their JSON\n * serialization, which matches how they travel over MCP.\n */\n\nexport function estimateTokens(value: unknown): number {\n if (value === undefined || value === null) return 0;\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\n if (!text) return 0;\n return countTokens(text);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readdir, readFile, rm, stat } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { generateEntityId } from \"../core/ids.js\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { estimateTokens } from \"../savings/tokens.js\";\n\nconst VERSION = 2;\nconst MAX_CAPSULES = 128;\nconst MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\nconst MAX_PENDING = 8;\nconst PENDING_MAX_AGE_MS = 24 * 60 * 60 * 1000;\nconst STALE_LOCK_MS = 60_000;\n/** Acknowledged versions kept per section, so switching back to a task reuses them. */\nconst MAX_ACKNOWLEDGED_PER_KEY = 8;\n\ntype LedgerRecord = { hash: string; lastSentAt: string };\n/** A delivery sent to a client that has not yet confirmed receipt. */\ntype PendingDelivery = { createdAt: string; records: Record<string, string> };\n/** One acknowledged version of an `exchange` section: content hash and when it was sent. */\ntype AcknowledgedVersion = { hash: string; sentAt: string };\ntype LedgerFile = {\n version: 2;\n updatedAt: string;\n /** `deliver`/`markSent` capsules (hooks): one current hash per key. */\n records: Record<string, LedgerRecord>;\n pending?: Record<string, PendingDelivery>;\n /** `exchange` sections (MCP): every recently acknowledged version, newest first. */\n acknowledged?: Record<string, AcknowledgedVersion[]>;\n};\n\nexport type ContextCapsule = { key: string; text: string };\nexport type CapsuleDelivery = {\n /** Full text of new or changed capsules, in input order. */\n lines: string[];\n /** Keys of capsules already sent this session and not resent. */\n unchanged: string[];\n full: number;\n referenced: number;\n sentTokens: number;\n avoidedTokens: number;\n};\n\nexport type SectionHash = { key: string; hash: string };\nexport type AcknowledgedExchange = {\n deliveryId: string;\n /** Keys whose content the client confirmed receiving and that have not changed. */\n unchanged: string[];\n ack: \"none\" | \"accepted\" | \"unknown\";\n};\n\nexport class SessionCapsuleLedger {\n private readonly directory: string;\n\n constructor(repositoryRoot = process.cwd()) {\n this.directory = join(resolve(repositoryRoot), \".codebuddy\", \"cache\", \"session\");\n }\n\n async deliver(\n sessionId: string,\n capsules: ContextCapsule[],\n now = new Date(),\n ): Promise<CapsuleDelivery> {\n await mkdir(this.directory, { recursive: true, mode: 0o700 });\n const release = await this.acquire(sessionId);\n try {\n const path = this.path(sessionId);\n const ledger = await this.read(path, now);\n const lines: string[] = [];\n const unchanged: string[] = [];\n let full = 0;\n let referenced = 0;\n let sentTokens = 0;\n let avoidedTokens = 0;\n\n for (const capsule of capsules) {\n const hash = digest(capsule.text);\n const existing = ledger.records[capsule.key];\n if (existing?.hash === hash) {\n unchanged.push(capsule.key);\n referenced++;\n avoidedTokens += estimateTokens(capsule.text);\n } else {\n lines.push(capsule.text);\n full++;\n sentTokens += estimateTokens(capsule.text);\n }\n ledger.records[capsule.key] = { hash, lastSentAt: now.toISOString() };\n }\n\n await this.write(path, ledger, now);\n return { lines, unchanged, full, referenced, sentTokens, avoidedTokens };\n } finally {\n await release();\n }\n }\n\n /** Keys of capsules whose current text was already recorded as sent this session. */\n async seen(\n sessionId: string,\n capsules: ContextCapsule[],\n now = new Date(),\n ): Promise<Set<string>> {\n const ledger = await this.read(this.path(sessionId), now);\n return new Set(\n capsules\n .filter((capsule) => ledger.records[capsule.key]?.hash === digest(capsule.text))\n .map((capsule) => capsule.key),\n );\n }\n\n /**\n * Record capsules as delivered. Call only after their text actually reached\n * the client, so capsules that were clipped or failed to write stay unseen\n * and are delivered by a later prompt.\n */\n async markSent(sessionId: string, capsules: ContextCapsule[], now = new Date()): Promise<void> {\n if (capsules.length === 0) return;\n await mkdir(this.directory, { recursive: true, mode: 0o700 });\n const release = await this.acquire(sessionId);\n try {\n const path = this.path(sessionId);\n const ledger = await this.read(path, now);\n for (const capsule of capsules) {\n ledger.records[capsule.key] = { hash: digest(capsule.text), lastSentAt: now.toISOString() };\n }\n await this.write(path, ledger, now);\n } finally {\n await release();\n }\n }\n\n /**\n * Acknowledged delivery for clients that can lose context (MCP). Unlike\n * `deliver`, sending marks nothing as seen: the sent hashes are held as a\n * pending delivery until the client echoes its `deliveryId` back as `ack` on\n * a later call. Only acknowledged, unchanged sections sent within `ttlMs` are\n * reported unchanged, so a dropped response or a compacted client context\n * fails safe to a full resend.\n *\n * A section keeps its last few acknowledged versions (by content hash), not\n * just the latest, so returning to an earlier task in the same session does\n * not resend what the client already holds. `full` means the client lost its\n * context: every acknowledged version and unconfirmed delivery is forgotten.\n */\n async exchange(\n sessionId: string,\n sections: SectionHash[],\n options: {\n ack?: string;\n ttlMs: number;\n /** The client lost its context: nothing acknowledged before this call counts. */\n full?: boolean;\n now?: Date;\n /** Finalize the response before recording hashes of the actual sent sections. */\n prepare?: (exchange: AcknowledgedExchange) => SectionHash[];\n },\n ): Promise<AcknowledgedExchange> {\n const now = options.now ?? new Date();\n await mkdir(this.directory, { recursive: true, mode: 0o700 });\n const release = await this.acquire(sessionId);\n try {\n const path = this.path(sessionId);\n const ledger = await this.read(path, now);\n let pending = ledger.pending ?? {};\n let acknowledged = ledger.acknowledged ?? {};\n\n let ack: AcknowledgedExchange[\"ack\"] = \"none\";\n if (options.ack !== undefined) {\n const confirmed = pending[options.ack];\n if (confirmed) {\n for (const [key, hash] of Object.entries(confirmed.records)) {\n acknowledged[key] = [\n { hash, sentAt: confirmed.createdAt },\n ...(acknowledged[key] ?? []).filter((version) => version.hash !== hash),\n ]\n .sort((left, right) => right.sentAt.localeCompare(left.sentAt))\n .slice(0, MAX_ACKNOWLEDGED_PER_KEY);\n }\n delete pending[options.ack];\n ack = \"accepted\";\n } else {\n ack = \"unknown\";\n }\n }\n if (options.full) {\n acknowledged = {};\n pending = {};\n }\n\n const unchanged: string[] = [];\n const sent: Record<string, string> = {};\n for (const section of sections) {\n const held = acknowledged[section.key]?.some((version) => {\n const age = now.getTime() - Date.parse(version.sentAt);\n return version.hash === section.hash && age >= 0 && age <= options.ttlMs;\n });\n if (held) {\n unchanged.push(section.key);\n } else {\n sent[section.key] = section.hash;\n }\n }\n\n const deliveryId = generateEntityId(\"dlv\", now.getTime());\n const exchange = { deliveryId, unchanged, ack };\n const delivered = options.prepare?.(exchange);\n pending[deliveryId] = {\n createdAt: now.toISOString(),\n records: delivered\n ? Object.fromEntries(delivered.map(({ key, hash }) => [key, hash]))\n : sent,\n };\n ledger.pending = Object.fromEntries(\n Object.entries(pending)\n .filter(([, item]) => now.getTime() - Date.parse(item.createdAt) <= PENDING_MAX_AGE_MS)\n .sort((left, right) => right[1].createdAt.localeCompare(left[1].createdAt))\n .slice(0, MAX_PENDING),\n );\n ledger.acknowledged = Object.fromEntries(\n Object.entries(acknowledged)\n .map(([key, versions]): [string, AcknowledgedVersion[]] => [\n key,\n versions.filter((version) => now.getTime() - Date.parse(version.sentAt) <= MAX_AGE_MS),\n ])\n .filter(([, versions]) => versions.length > 0)\n .slice(0, MAX_CAPSULES),\n );\n await this.write(path, ledger, now);\n return { deliveryId, unchanged, ack };\n } finally {\n await release();\n }\n }\n\n /** Forget everything sent in a session, e.g. after the client compacted its context. */\n async reset(sessionId: string): Promise<void> {\n await mkdir(this.directory, { recursive: true, mode: 0o700 });\n const release = await this.acquire(sessionId);\n try {\n await rm(this.path(sessionId), { force: true });\n } finally {\n await release();\n }\n }\n\n /** Remove expired disposable state; locked sessions are left for the next pass. */\n async pruneExpired(now = new Date()): Promise<number> {\n let removed = 0;\n for (const entry of await readdir(this.directory).catch(() => [])) {\n if (!entry.endsWith(\".json\")) continue;\n const path = join(this.directory, entry);\n try {\n await stat(`${path}.lock`);\n continue;\n } catch {\n // No active lock.\n }\n try {\n const metadata = await stat(path);\n if (now.getTime() - metadata.mtimeMs > MAX_AGE_MS) {\n await rm(path, { force: true });\n removed++;\n }\n } catch {\n // Another cleanup may have removed it already.\n }\n }\n return removed;\n }\n\n private async write(path: string, ledger: LedgerFile, now: Date): Promise<void> {\n ledger.updatedAt = now.toISOString();\n ledger.records = Object.fromEntries(\n Object.entries(ledger.records)\n .sort((left, right) => right[1].lastSentAt.localeCompare(left[1].lastSentAt))\n .slice(0, MAX_CAPSULES),\n );\n await writeAtomic(path, `${JSON.stringify(ledger, null, 2)}\\n`);\n }\n\n private async read(path: string, now: Date): Promise<LedgerFile> {\n try {\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as LedgerFile;\n if (parsed.version !== VERSION || typeof parsed.records !== \"object\") return empty(now);\n const records = Object.fromEntries(\n Object.entries(parsed.records).filter(([, record]) => {\n const age = now.getTime() - Date.parse(record.lastSentAt);\n return Number.isFinite(age) && age <= MAX_AGE_MS;\n }),\n );\n const pending =\n parsed.pending && typeof parsed.pending === \"object\" ? parsed.pending : undefined;\n const acknowledged =\n parsed.acknowledged && typeof parsed.acknowledged === \"object\"\n ? Object.fromEntries(\n Object.entries(parsed.acknowledged).filter(([, versions]) => Array.isArray(versions)),\n )\n : undefined;\n return {\n version: VERSION,\n updatedAt: parsed.updatedAt,\n records,\n ...(pending ? { pending } : {}),\n ...(acknowledged ? { acknowledged } : {}),\n };\n } catch {\n return empty(now);\n }\n }\n\n private path(sessionId: string): string {\n // The full identity participates in the key. Legacy sanitized filenames\n // are intentionally ignored, so a fresh session cannot inherit collisions.\n return join(this.directory, `${digest(sessionId)}.json`);\n }\n\n private async acquire(sessionId: string): Promise<() => Promise<void>> {\n const lock = `${this.path(sessionId)}.lock`;\n for (let attempt = 0; ; attempt++) {\n try {\n await mkdir(lock);\n return async () => rm(lock, { recursive: true, force: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\" || attempt >= 200) throw error;\n try {\n const age = Date.now() - (await stat(lock)).mtimeMs;\n if (age > STALE_LOCK_MS) {\n await rm(lock, { recursive: true, force: true });\n continue;\n }\n } catch {\n // The owner may have released the lock between checks.\n }\n await new Promise((resolveWait) => setTimeout(resolveWait, 10));\n }\n }\n }\n}\n\nfunction empty(now: Date): LedgerFile {\n return { version: VERSION, updatedAt: now.toISOString(), records: {} };\n}\n\nfunction digest(text: string): string {\n return createHash(\"sha256\").update(text).digest(\"hex\");\n}\n","import { access, readFile } from \"node:fs/promises\";\nimport { basename, dirname, extname, join } from \"node:path\";\n\n/**\n * Which test covers a file and how this repo runs its tests, from naming\n * conventions and project markers only. Agents otherwise spend turns finding\n * both; a wrong guess costs one failed command, so first match wins.\n */\nexport function findCoveringTest(path: string, paths: ReadonlySet<string>): string | null {\n return coveringTestCandidates(path).find((candidate) => paths.has(candidate)) ?? null;\n}\n\n/** Conventional covering-test paths for `path`, most specific first. */\nexport function coveringTestCandidates(path: string): string[] {\n const ext = extname(path);\n const stem = basename(path, ext);\n const dir = dirname(path);\n const join2 = (...parts: string[]) => parts.filter((part) => part && part !== \".\").join(\"/\");\n // `src/a/x.ts` mirrors to `test/a/x.test.ts`: drop a leading source root.\n const mirrored = dir.replace(/^(src|lib|app)(\\/|$)/, \"\");\n return ext === \".py\"\n ? [\n join2(dir, `test_${stem}.py`),\n join2(dir, `${stem}_test.py`),\n join2(dir, \"tests\", `test_${stem}.py`),\n // `src/pkg/sub/x.py` → `tests/pkg/sub/test_x.py`, `tests/sub/test_x.py`, `tests/test_x.py`.\n join2(\"tests\", mirrored, `test_${stem}.py`),\n join2(\"tests\", mirrored.replace(/^[^/]+\\/?/, \"\"), `test_${stem}.py`),\n join2(\"tests\", `test_${stem}.py`),\n ]\n : ext === \".go\"\n ? [join2(dir, `${stem}_test.go`)]\n : [\n join2(dir, `${stem}.test${ext}`),\n join2(dir, `${stem}.spec${ext}`),\n join2(dir, \"__tests__\", `${stem}.test${ext}`),\n join2(dir, \"__tests__\", `${stem}${ext}`),\n join2(\"test\", mirrored, `${stem}.test${ext}`),\n join2(\"tests\", mirrored, `${stem}.test${ext}`),\n ];\n}\n\nconst NPM_PLACEHOLDER = /no test specified/;\n\nexport async function detectTestCommand(repositoryRoot: string): Promise<string | null> {\n const exists = (file: string) =>\n access(join(repositoryRoot, file)).then(\n () => true,\n () => false,\n );\n try {\n const pkg = JSON.parse(await readFile(join(repositoryRoot, \"package.json\"), \"utf8\")) as {\n scripts?: Record<string, unknown>;\n packageManager?: unknown;\n };\n const script = pkg.scripts?.test;\n if (typeof script === \"string\" && script.trim() && !NPM_PLACEHOLDER.test(script)) {\n const declared =\n typeof pkg.packageManager === \"string\" ? pkg.packageManager.split(/[\\s@]/, 1)[0] : \"\";\n // `bun test` is Bun's own runner, not the package's test script.\n if (declared === \"bun\") return \"bun run test\";\n if (declared && [\"npm\", \"pnpm\", \"yarn\"].includes(declared)) return `${declared} test`;\n if (await exists(\"pnpm-lock.yaml\")) return \"pnpm test\";\n if (await exists(\"yarn.lock\")) return \"yarn test\";\n if ((await exists(\"bun.lockb\")) || (await exists(\"bun.lock\"))) return \"bun run test\";\n return \"npm test\";\n }\n } catch {\n // No or unreadable package.json: try other ecosystems.\n }\n for (const marker of [\"pyproject.toml\", \"pytest.ini\", \"setup.cfg\"]) {\n if (await exists(marker)) return \"pytest\";\n }\n if (await exists(\"go.mod\")) return \"go test ./...\";\n if (await exists(\"Cargo.toml\")) return \"cargo test\";\n return null;\n}\n"],"mappings":"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","names":["readFile","join","join2"]}