@jefuriiij/synthra 0.1.16 → 0.1.17
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 +18 -0
- package/dist/cli/index.js +53 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/index.js +1 -1
- package/dist/dashboard/index.js.map +1 -1
- package/dist/server/index.js +52 -13
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../package.json","../../src/cli/index.ts","../../src/dashboard/server.ts","../../src/shared/logger.ts","../../src/server/port.ts","../../src/dashboard/delta.ts","../../src/shared/paths.ts","../../src/shared/pricing.ts","../../src/shared/project-registry.ts","../../src/dashboard/public/index.html","../../src/dashboard/public/style.css","../../src/hooks/installer.ts","../../src/hooks/scripts/pre-compact.ps1","../../src/hooks/scripts/pre-compact.sh","../../src/hooks/scripts/pre-tool-use.ps1","../../src/hooks/scripts/pre-tool-use.sh","../../src/hooks/scripts/prime.ps1","../../src/hooks/scripts/prime.sh","../../src/hooks/scripts/stop.ps1","../../src/hooks/scripts/stop.sh","../../src/server/http.ts","../../src/activity/activity-log.ts","../../src/activity/file-watcher.ts","../../src/activity/git-watcher.ts","../../src/cli/scan-command.ts","../../src/scanner/extract.ts","../../src/scanner/hash.ts","../../src/scanner/keywords.ts","../../src/scanner/parser.ts","../../src/scanner/parsers/_generic.ts","../../src/scanner/parsers/c.ts","../../src/scanner/parsers/cpp.ts","../../src/scanner/parsers/csharp.ts","../../src/scanner/parsers/dart.ts","../../src/scanner/parsers/go.ts","../../src/scanner/parsers/java.ts","../../src/scanner/parsers/kotlin.ts","../../src/scanner/parsers/php.ts","../../src/scanner/parsers/python.ts","../../src/scanner/parsers/ruby.ts","../../src/scanner/parsers/rust.ts","../../src/scanner/parsers/typescript.ts","../../src/scanner/parsers/svelte.ts","../../src/scanner/parsers/vue.ts","../../src/scanner/walker.ts","../../src/graph/store.ts","../../src/cli/bootstrap.ts","../../src/hooks/claude-md.ts","../../src/graph/rank.ts","../../src/graph/retrieve.ts","../../src/memory/branches.ts","../../src/memory/context-md.ts","../../src/memory/context-store.ts","../../src/memory/index.ts","../../src/packer/format.ts","../../src/packer/inline.ts","../../src/packer/signatures.ts","../../src/packer/tests.ts","../../src/packer/index.ts","../../src/server/mcp.ts","../../src/server/routes/activity.ts","../../src/server/routes/context-update.ts","../../src/server/routes/gate.ts","../../src/server/routes/log.ts","../../src/server/routes/pack.ts","../../src/server/routes/prime.ts","../../src/shared/config.ts","../../src/cli/session-discovery.ts","../../src/cli/cleanup.ts","../../src/cli/dashboard-command.ts","../../src/cli/self-update.ts","../../src/cli/serve-command.ts","../../src/cli/start-claude.ts"],"sourcesContent":["{\r\n \"name\": \"@jefuriiij/synthra\",\r\n \"version\": \"0.1.16\",\r\n \"publishConfig\": {\r\n \"access\": \"public\"\r\n },\r\n \"description\": \"Local context engine for AI coding assistants — graph-based context, branch-aware memory, real-time human-activity awareness, deterministic Grep/Glob gating, and a live token dashboard.\",\r\n \"type\": \"module\",\r\n \"bin\": {\r\n \"syn\": \"./bin/syn\",\r\n \"synthra\": \"./bin/syn\"\r\n },\r\n \"scripts\": {\r\n \"build\": \"tsup\",\r\n \"dev\": \"tsup --watch\",\r\n \"test\": \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n \"typecheck\": \"tsc --noEmit\"\r\n },\r\n \"files\": [\r\n \"dist\",\r\n \"bin\",\r\n \"README.md\",\r\n \"CHANGELOG.md\",\r\n \"LICENSE\",\r\n \"ROADMAP.md\"\r\n ],\r\n \"keywords\": [\r\n \"claude-code\",\r\n \"mcp\",\r\n \"context-engine\",\r\n \"code-graph\",\r\n \"ai-coding\",\r\n \"token-savings\"\r\n ],\r\n \"author\": \"Jeff (@jefuriiij)\",\r\n \"license\": \"MIT\",\r\n \"homepage\": \"https://github.com/jefuriiij/synthra#readme\",\r\n \"repository\": {\r\n \"type\": \"git\",\r\n \"url\": \"git+https://github.com/jefuriiij/synthra.git\"\r\n },\r\n \"bugs\": {\r\n \"url\": \"https://github.com/jefuriiij/synthra/issues\"\r\n },\r\n \"engines\": {\r\n \"node\": \">=18\"\r\n },\r\n \"dependencies\": {\r\n \"@hono/node-server\": \"^1.18.0\",\r\n \"chokidar\": \"^5.0.0\",\r\n \"cross-spawn\": \"^7.0.6\",\r\n \"hono\": \"^4.12.23\",\r\n \"ignore\": \"^7.0.0\",\r\n \"sade\": \"^1.8.1\",\r\n \"tree-sitter-wasms\": \"^0.1.12\",\r\n \"web-tree-sitter\": \"^0.25.10\"\r\n },\r\n \"devDependencies\": {\r\n \"@types/cross-spawn\": \"^6.0.6\",\r\n \"@types/node\": \"^25.9.1\",\r\n \"tsup\": \"^8.5.1\",\r\n \"typescript\": \"^6.0.3\",\r\n \"vitest\": \"^4.1.7\"\r\n }\r\n}\r\n","// `syn` entry point. Parses args and dispatches to commands.\r\n//\r\n// Commands:\r\n// syn [path] → default: scan + start MCP + dashboard +\r\n// register MCP for IDE extension; block on\r\n// Ctrl+C. NO claude CLI spawn — use the IDE.\r\n// syn . [path] → alias for default\r\n// syn . --launch-cli → also spawn `claude` here (M3 behavior)\r\n// syn scan [path] → scan only\r\n// syn serve [path] → start MCP server only\r\n// syn dashboard [path] → run only the dashboard server\r\n// syn --resume <id> [path] → resume an existing Claude session\r\n// (only meaningful with --launch-cli)\r\n\r\nimport sade from \"sade\";\r\nimport { resolve } from \"node:path\";\r\n\r\n// Inlined at build time by tsup's JSON loader so `syn --version` always\r\n// matches the published package.\r\nimport pkgJson from \"../../package.json\" with { type: \"json\" };\r\n\r\nimport { startDashboard, type DashboardServerHandle } from \"../dashboard/server.js\";\r\nimport { installHooks } from \"../hooks/installer.js\";\r\nimport { startServer, type ServerHandle } from \"../server/http.js\";\r\nimport { loadConfig } from \"../shared/config.js\";\r\nimport { log } from \"../shared/logger.js\";\r\nimport { resolvePaths } from \"../shared/paths.js\";\r\nimport { recordProject } from \"../shared/project-registry.js\";\r\nimport { cleanup } from \"./cleanup.js\";\r\nimport { dashboardCommand } from \"./dashboard-command.js\";\r\nimport { scanCommand, type ScanResult } from \"./scan-command.js\";\r\nimport { promptForUpdateOrLog, runStartupChangelogCheck } from \"./self-update.js\";\r\nimport { serveCommand } from \"./serve-command.js\";\r\nimport { registerMcp, spawnClaude, unregisterMcp } from \"./start-claude.js\";\r\n\r\nconst VERSION = (pkgJson as { version: string }).version;\r\n\r\ninterface DefaultOpts {\r\n resume?: string;\r\n \"launch-cli\"?: boolean;\r\n}\r\n\r\ninterface BannerInfo {\r\n projectRoot: string;\r\n scan: ScanResult;\r\n mcpUrl: string;\r\n dashboardUrl: string | null;\r\n mcpRegistered: boolean;\r\n}\r\n\r\nfunction printReadyBanner(info: BannerInfo): void {\r\n log.info(\"\");\r\n log.info(` ✅ scanned ${info.scan.parsed} files · ${info.scan.symbolCount} symbols · ${info.scan.edgeCount} edges`);\r\n if (info.mcpRegistered) {\r\n log.info(` 🧠 MCP ${info.mcpUrl} → registered as 'synthra'`);\r\n } else {\r\n log.info(` 🧠 MCP ${info.mcpUrl} ⚠ registration with claude failed`);\r\n }\r\n if (info.dashboardUrl) {\r\n log.info(` 📊 Dashboard ${info.dashboardUrl}`);\r\n } else {\r\n log.info(` 📊 Dashboard (failed to start; data is still logged to .synthra-graph/)`);\r\n }\r\n log.info(` 🪝 Hooks installed in .claude/settings.local.json`);\r\n log.info(\"\");\r\n log.info(` 🤖 Ready — open the Claude Code IDE extension (or run \\`claude\\` in another terminal).`);\r\n log.info(` Synthra's tools and gate will be active for that session.`);\r\n log.info(\"\");\r\n log.info(` Press Ctrl+C here when you're done.`);\r\n log.info(\"\");\r\n}\r\n\r\nfunction waitForSignal(): Promise<NodeJS.Signals> {\r\n return new Promise((resolve) => {\r\n const handler = (sig: NodeJS.Signals) => {\r\n process.off(\"SIGINT\", handler);\r\n process.off(\"SIGTERM\", handler);\r\n resolve(sig);\r\n };\r\n process.on(\"SIGINT\", handler);\r\n process.on(\"SIGTERM\", handler);\r\n });\r\n}\r\n\r\nasync function defaultFlow(rawPath: string, opts: DefaultOpts): Promise<void> {\r\n const launchCli = opts[\"launch-cli\"] === true;\r\n const projectRoot = resolve(rawPath);\r\n const paths = resolvePaths(projectRoot);\r\n const cfg = loadConfig();\r\n\r\n // If the running binary is newer than last-seen (e.g. user upgraded via\r\n // `npm install -g …@latest` directly), print the changelog for what they\r\n // just got. Silent if already on latest-seen or on fresh install.\r\n await runStartupChangelogCheck();\r\n\r\n // Always-fresh registry check (no cache). If a newer version is on npm AND\r\n // we're on a TTY, prompts [y/N]. On 'y', runs npm install, prints the new\r\n // version's changelog, and exits with re-run instructions. On 'n' / no\r\n // update / non-TTY, continues silently. SYN_NO_UPDATE_CHECK=1 opts out.\r\n await promptForUpdateOrLog();\r\n\r\n // 1. bootstrap + scan + record in the global registry so the dashboard\r\n // can list this project alongside any others.\r\n await recordProject(projectRoot);\r\n const scan = await scanCommand(rawPath);\r\n\r\n // 2. MCP server (background within this process)\r\n const mcpHandle: ServerHandle = await startServer(paths);\r\n\r\n // 3. Dashboard (optional — non-fatal if it fails)\r\n let dashboardHandle: DashboardServerHandle | null = null;\r\n try {\r\n dashboardHandle = await startDashboard(paths, cfg.dashboardPort);\r\n } catch (err) {\r\n log.warn(`dashboard failed to start on port ${cfg.dashboardPort}: ${(err as Error).message}`);\r\n }\r\n\r\n // 4. Install hooks + register MCP so the IDE / external claude can see it\r\n await installHooks(paths);\r\n const mcpRegistered = await registerMcp(cfg.claudeBin, mcpHandle.port, projectRoot);\r\n\r\n let claudeExitCode = 0;\r\n try {\r\n if (launchCli) {\r\n claudeExitCode = await spawnClaude(cfg.claudeBin, {\r\n cwd: projectRoot,\r\n resumeSessionId: opts.resume,\r\n });\r\n log.info(`claude exited with code ${claudeExitCode}`);\r\n } else {\r\n printReadyBanner({\r\n projectRoot,\r\n scan,\r\n mcpUrl: mcpHandle.url,\r\n dashboardUrl: dashboardHandle?.url ?? null,\r\n mcpRegistered,\r\n });\r\n const sig = await waitForSignal();\r\n log.info(`received ${sig} — shutting down…`);\r\n }\r\n } finally {\r\n await unregisterMcp(cfg.claudeBin, projectRoot).catch(() => undefined);\r\n if (dashboardHandle) {\r\n await dashboardHandle.stop().catch((err) =>\r\n log.warn(`dashboard stop error: ${(err as Error).message}`),\r\n );\r\n }\r\n await mcpHandle.stop().catch((err) =>\r\n log.warn(`MCP server stop error: ${(err as Error).message}`),\r\n );\r\n await cleanup(paths).catch((err) =>\r\n log.warn(`cleanup error: ${(err as Error).message}`),\r\n );\r\n }\r\n}\r\n\r\nexport function buildProgram() {\r\n const prog = sade(\"syn\");\r\n prog\r\n .version(VERSION)\r\n .describe(\"Local context engine for AI coding assistants.\");\r\n\r\n prog\r\n .command(\". [path]\", \"Scan + MCP + dashboard + hooks. Default flow — use with the Claude Code IDE extension.\", {\r\n default: true,\r\n })\r\n .option(\"--resume <id>\", \"Resume an existing Claude session (only with --launch-cli)\")\r\n .option(\"--launch-cli\", \"Also spawn `claude` CLI in this terminal (legacy M3 behavior)\", false)\r\n .action(async (path: string | undefined, opts: DefaultOpts) => {\r\n await defaultFlow(path ?? \".\", opts);\r\n });\r\n\r\n prog\r\n .command(\"scan [path]\", \"Scan only — walk + parse + write graph.\")\r\n .action(async (path: string | undefined) => {\r\n await scanCommand(path ?? \".\");\r\n });\r\n\r\n prog\r\n .command(\"serve [path]\", \"Start the HTTP MCP server against a scanned project.\")\r\n .action(async (path: string | undefined) => {\r\n await serveCommand(path ?? \".\");\r\n });\r\n\r\n prog\r\n .command(\"dashboard [path]\", \"Run the token dashboard server (localhost:8901).\")\r\n .action(async (path: string | undefined) => {\r\n await dashboardCommand(path ?? \".\");\r\n });\r\n\r\n return prog;\r\n}\r\n\r\nexport async function main(argv: string[]): Promise<void> {\r\n const prog = buildProgram();\r\n prog.parse(argv);\r\n}\r\n","// Standalone dashboard server. Default port 8901 (override via\r\n// SYN_DASHBOARD_PORT); falls back through a small range 8901–8910 if the\r\n// preferred port is busy (so we can coexist with other co-installed\r\n// AI-context tools that also expose a dashboard).\r\n// Reads .synthra-graph/token_log.jsonl + .synthra-graph/gate_log.jsonl for the\r\n// given project and renders a live SPA backed by GET /data polled every 2s.\r\n\r\nimport { serve } from \"@hono/node-server\";\r\nimport { Hono } from \"hono\";\r\n\r\n// Tsup inlines this import at build time so `c.html` can echo whatever\r\n// version is running. Replaces the v__SYN_VERSION__ placeholder in the\r\n// dashboard footer on every GET /.\r\nimport pkgJson from \"../../package.json\" with { type: \"json\" };\r\n\r\nimport { log } from \"../shared/logger.js\";\r\nimport type { SynthraPaths } from \"../shared/paths.js\";\r\nimport { findFreePort } from \"../server/port.js\";\r\nimport { computeDashboardData } from \"./delta.js\";\r\n\r\nimport indexHtml from \"./public/index.html\";\r\nimport styleCss from \"./public/style.css\";\r\n\r\nconst FALLBACK_RANGE = 9; // try preferredPort + [0..9]\r\nconst VERSION = (pkgJson as { version: string }).version;\r\n// How many recent turns/gates the /data payload carries. The dashboard\r\n// paginates turns client-side (25/page); the donut uses the uncapped\r\n// per-project model aggregate, so it isn't bounded by this.\r\nconst RECENT_N = Number(process.env.SYN_DASHBOARD_RECENT_N) || 500;\r\n\r\nexport interface DashboardServerHandle {\r\n port: number;\r\n url: string;\r\n stop(): Promise<void>;\r\n}\r\n\r\nexport async function startDashboard(\r\n paths: SynthraPaths,\r\n preferredPort = 8901,\r\n): Promise<DashboardServerHandle> {\r\n const port = await findFreePort(preferredPort, preferredPort + FALLBACK_RANGE);\r\n if (port !== preferredPort) {\r\n log.info(\r\n `dashboard port ${preferredPort} was busy — bound to ${port} instead (likely another dashboard from a coexisting tool).`,\r\n );\r\n }\r\n const app = new Hono();\r\n\r\n app.get(\"/\", (c) => c.html(indexHtml.replaceAll(\"__SYN_VERSION__\", VERSION)));\r\n\r\n app.get(\"/style.css\", (c) => {\r\n c.header(\"Content-Type\", \"text/css; charset=utf-8\");\r\n c.header(\"Cache-Control\", \"no-cache\");\r\n return c.body(styleCss);\r\n });\r\n\r\n app.get(\"/health\", (c) => c.json({ ok: true }));\r\n\r\n app.get(\"/data\", async (c) => {\r\n const data = await computeDashboardData(paths, RECENT_N);\r\n return c.json(data);\r\n });\r\n\r\n const nodeServer = serve({ fetch: app.fetch, port, hostname: \"127.0.0.1\" });\r\n\r\n return {\r\n port,\r\n url: `http://127.0.0.1:${port}`,\r\n async stop() {\r\n await new Promise<void>((resolve, reject) => {\r\n nodeServer.close((err) => (err ? reject(err) : resolve()));\r\n });\r\n },\r\n };\r\n}\r\n","// Minimal logger. Prefixes Synthra output with [syn].\n\ntype Level = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LEVEL_PRIORITY: Record<Level, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nlet activeLevel: Level = (process.env.SYN_LOG_LEVEL as Level) ?? \"info\";\n\nexport function setLevel(level: Level): void {\n activeLevel = level;\n}\n\nfunction shouldLog(level: Level): boolean {\n return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[activeLevel];\n}\n\nfunction emit(level: Level, msg: string, ...args: unknown[]): void {\n if (!shouldLog(level)) return;\n const stream = level === \"error\" || level === \"warn\" ? process.stderr : process.stdout;\n stream.write(`[syn] ${msg}${args.length ? \" \" + args.map(String).join(\" \") : \"\"}\\n`);\n}\n\nexport const log = {\n debug: (m: string, ...a: unknown[]) => emit(\"debug\", m, ...a),\n info: (m: string, ...a: unknown[]) => emit(\"info\", m, ...a),\n warn: (m: string, ...a: unknown[]) => emit(\"warn\", m, ...a),\n error: (m: string, ...a: unknown[]) => emit(\"error\", m, ...a),\n};\n","// Finds a free port in the 8080–8099 range. Writes the chosen port to\n// .synthra-graph/mcp_port so PowerShell/Bash hook scripts can read it.\n// TODO: M2\n\nimport { createServer } from \"node:net\";\n\nexport const PORT_RANGE_START = 8080;\nexport const PORT_RANGE_END = 8099;\n\nexport async function findFreePort(\n start = PORT_RANGE_START,\n end = PORT_RANGE_END,\n): Promise<number> {\n for (let port = start; port <= end; port++) {\n if (await isFree(port)) return port;\n }\n throw new Error(`Synthra: no free port in ${start}-${end}`);\n}\n\nfunction isFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const s = createServer();\n s.once(\"error\", () => resolve(false));\n s.once(\"listening\", () => s.close(() => resolve(true)));\n s.listen(port, \"127.0.0.1\");\n });\n}\n","// Reads token_log.jsonl + gate_log.jsonl for the active project AND every\r\n// project registered in ~/.synthra/projects.json, then computes the\r\n// dashboard's rendered shape: per-project + global aggregate + recent calls\r\n// across all projects.\r\n\r\nimport { readFile } from \"node:fs/promises\";\r\n\r\nimport { resolvePaths, type SynthraPaths } from \"../shared/paths.js\";\r\nimport { estimateCostUsd } from \"../shared/pricing.js\";\r\nimport { listProjects } from \"../shared/project-registry.js\";\r\n\r\nconst AVG_TOKENS_PER_BLOCKED_GREP = 500;\r\n\r\nexport interface TokenLogEntry {\r\n /** Stop-hook-supplied timestamp (preferred). */\r\n ts?: string;\r\n /** Server-side fallback added by handleLog when ts isn't provided. */\r\n written_at?: string;\r\n input_tokens: number;\r\n output_tokens: number;\r\n cache_creation_input_tokens?: number;\r\n cache_read_input_tokens?: number;\r\n model: string;\r\n description?: string;\r\n project: string;\r\n}\r\n\r\nexport interface GateLogEntry {\r\n ts: string;\r\n tool: string;\r\n decision: \"allow\" | \"block\";\r\n query: string | null;\r\n reason?: string;\r\n}\r\n\r\nexport interface ProjectStats {\r\n path: string;\r\n name: string;\r\n last_seen: string | null;\r\n total_turns: number;\r\n total_input_tokens: number;\r\n total_output_tokens: number;\r\n total_cache_read: number;\r\n total_cache_create: number;\r\n total_gate_calls: number;\r\n blocked_count: number;\r\n estimated_tokens_saved: number;\r\n estimated_cost_usd: number;\r\n models: Record<string, number>;\r\n}\r\n\r\nexport interface RecentTurn {\r\n ts: string;\r\n project_name: string;\r\n project_path: string;\r\n input: number;\r\n output: number;\r\n cache_read: number;\r\n cache_create: number;\r\n model: string;\r\n cost_usd: number;\r\n}\r\n\r\nexport interface RecentGate {\r\n ts: string;\r\n project_name: string;\r\n project_path: string;\r\n tool: string;\r\n decision: \"allow\" | \"block\";\r\n query: string | null;\r\n}\r\n\r\nexport interface DashboardData {\r\n active: {\r\n project_root: string;\r\n project_name: string;\r\n stats: ProjectStats;\r\n };\r\n global: {\r\n project_count: number;\r\n total_turns: number;\r\n total_input_tokens: number;\r\n total_output_tokens: number;\r\n total_cache_read: number;\r\n total_cache_create: number;\r\n total_gate_calls: number;\r\n blocked_count: number;\r\n estimated_tokens_saved: number;\r\n saved_percent: number;\r\n estimated_cost_usd: number;\r\n };\r\n projects: ProjectStats[];\r\n recent_turns: RecentTurn[];\r\n recent_gates: RecentGate[];\r\n}\r\n\r\nasync function readJsonl<T>(path: string): Promise<T[]> {\r\n try {\r\n const text = await readFile(path, \"utf8\");\r\n return text\r\n .split(/\\r?\\n/)\r\n .filter((l) => l.length > 0)\r\n .map((l) => {\r\n try {\r\n return JSON.parse(l) as T;\r\n } catch {\r\n return null;\r\n }\r\n })\r\n .filter((v): v is T => v !== null);\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\nfunction basename(p: string): string {\r\n const parts = p.split(/[\\\\/]/);\r\n return parts[parts.length - 1] || p;\r\n}\r\n\r\ninterface ProjectFiles {\r\n path: string;\r\n name: string;\r\n last_seen: string | null;\r\n tokens: TokenLogEntry[];\r\n gates: GateLogEntry[];\r\n}\r\n\r\nfunction summarize(p: ProjectFiles): ProjectStats {\r\n let totalIn = 0;\r\n let totalOut = 0;\r\n let totalCacheRead = 0;\r\n let totalCacheCreate = 0;\r\n let costUsd = 0;\r\n const models: Record<string, number> = {};\r\n\r\n for (const t of p.tokens) {\r\n totalIn += t.input_tokens ?? 0;\r\n totalOut += t.output_tokens ?? 0;\r\n totalCacheRead += t.cache_read_input_tokens ?? 0;\r\n totalCacheCreate += t.cache_creation_input_tokens ?? 0;\r\n costUsd += estimateCostUsd(t);\r\n if (t.model) models[t.model] = (models[t.model] ?? 0) + 1;\r\n }\r\n\r\n const blocked = p.gates.filter((g) => g.decision === \"block\").length;\r\n const saved = blocked * AVG_TOKENS_PER_BLOCKED_GREP;\r\n\r\n return {\r\n path: p.path,\r\n name: p.name,\r\n last_seen: p.last_seen,\r\n total_turns: p.tokens.length,\r\n total_input_tokens: totalIn,\r\n total_output_tokens: totalOut,\r\n total_cache_read: totalCacheRead,\r\n total_cache_create: totalCacheCreate,\r\n total_gate_calls: p.gates.length,\r\n blocked_count: blocked,\r\n estimated_tokens_saved: saved,\r\n estimated_cost_usd: Math.round(costUsd * 100) / 100,\r\n models,\r\n };\r\n}\r\n\r\nasync function loadProjectFiles(\r\n path: string,\r\n name: string,\r\n lastSeen: string | null,\r\n): Promise<ProjectFiles> {\r\n const paths = resolvePaths(path);\r\n const [rawTokens, gates] = await Promise.all([\r\n readJsonl<TokenLogEntry>(paths.tokenLog),\r\n readJsonl<GateLogEntry>(paths.gateLog),\r\n ]);\r\n return { path, name, last_seen: lastSeen, tokens: dedupeTokens(rawTokens), gates };\r\n}\r\n\r\n/**\r\n * Collapse duplicate token-log entries from co-installed AI tools.\r\n *\r\n * Synthra is friendly with other tools that share the .synthra-graph/\r\n * token_log.jsonl shape — if a second tool's Stop hook also writes to\r\n * it, both fire on the same turn and emit nearly-identical entries\r\n * within ~10ms, double-counting every metric in the dashboard.\r\n *\r\n * Strategy: group by (project, usage counts, second-rounded timestamp);\r\n * inside a group, keep the entry with the most credible model field —\r\n * a real Claude model > \"<synthetic>\" > empty.\r\n */\r\nfunction dedupeTokens(entries: TokenLogEntry[]): TokenLogEntry[] {\r\n const score = (model: string | undefined): number => {\r\n if (!model) return 0;\r\n if (model === \"<synthetic>\") return 1;\r\n return 2; // real model name\r\n };\r\n\r\n const groups = new Map<string, TokenLogEntry[]>();\r\n for (const e of entries) {\r\n const ts = e.ts ?? e.written_at ?? \"\";\r\n const second = ts.slice(0, 19); // YYYY-MM-DDTHH:mm:ss\r\n const key = [\r\n e.project ?? \"\",\r\n e.input_tokens ?? 0,\r\n e.output_tokens ?? 0,\r\n e.cache_creation_input_tokens ?? 0,\r\n e.cache_read_input_tokens ?? 0,\r\n second,\r\n ].join(\"|\");\r\n const arr = groups.get(key) ?? [];\r\n arr.push(e);\r\n groups.set(key, arr);\r\n }\r\n\r\n const out: TokenLogEntry[] = [];\r\n for (const arr of groups.values()) {\r\n if (arr.length === 1) {\r\n out.push(arr[0]!);\r\n continue;\r\n }\r\n arr.sort((a, b) => score(b.model) - score(a.model));\r\n out.push(arr[0]!);\r\n }\r\n\r\n // Preserve chronological order in the per-project list.\r\n out.sort((a, b) => {\r\n const at = a.ts ?? a.written_at ?? \"\";\r\n const bt = b.ts ?? b.written_at ?? \"\";\r\n return at.localeCompare(bt);\r\n });\r\n return out;\r\n}\r\n\r\nexport async function computeDashboardData(\r\n activePaths: SynthraPaths,\r\n recentN = 500,\r\n): Promise<DashboardData> {\r\n const registered = await listProjects();\r\n\r\n // Always include the active project, even if not yet in the registry.\r\n const activePath = activePaths.projectRoot;\r\n const activeName = basename(activePath);\r\n const knownPaths = new Set(registered.map((p) => p.path));\r\n const allEntries: Array<{ path: string; name: string; last_seen: string | null }> = [\r\n ...registered.map((p) => ({ path: p.path, name: p.name, last_seen: p.last_seen })),\r\n ];\r\n if (!knownPaths.has(activePath)) {\r\n allEntries.unshift({ path: activePath, name: activeName, last_seen: null });\r\n }\r\n\r\n const loaded = await Promise.all(\r\n allEntries.map((e) => loadProjectFiles(e.path, e.name, e.last_seen)),\r\n );\r\n\r\n const projects = loaded\r\n .map(summarize)\r\n .sort((a, b) => b.total_input_tokens + b.total_output_tokens - (a.total_input_tokens + a.total_output_tokens));\r\n\r\n const activeFiles =\r\n loaded.find((p) => p.path === activePath) ?? {\r\n path: activePath,\r\n name: activeName,\r\n last_seen: null,\r\n tokens: [],\r\n gates: [],\r\n };\r\n const activeStats = summarize(activeFiles);\r\n\r\n // Global aggregates\r\n let g_in = 0,\r\n g_out = 0,\r\n g_cr = 0,\r\n g_cc = 0,\r\n g_gate = 0,\r\n g_block = 0,\r\n g_cost = 0,\r\n g_turns = 0;\r\n for (const s of projects) {\r\n g_turns += s.total_turns;\r\n g_in += s.total_input_tokens;\r\n g_out += s.total_output_tokens;\r\n g_cr += s.total_cache_read;\r\n g_cc += s.total_cache_create;\r\n g_gate += s.total_gate_calls;\r\n g_block += s.blocked_count;\r\n g_cost += s.estimated_cost_usd;\r\n }\r\n const g_saved = g_block * AVG_TOKENS_PER_BLOCKED_GREP;\r\n const g_used = g_in + g_out + g_cc;\r\n const g_saved_pct = g_used + g_saved > 0 ? (g_saved / (g_used + g_saved)) * 100 : 0;\r\n\r\n // Recent turns + gates across all projects, sorted by ts descending\r\n const allTurns: RecentTurn[] = [];\r\n const allGates: RecentGate[] = [];\r\n for (const p of loaded) {\r\n for (const t of p.tokens) {\r\n allTurns.push({\r\n // Fall back to written_at — the Stop hook today posts entries without\r\n // a `ts` field, and the server tags them with written_at on receive.\r\n ts: t.ts ?? t.written_at ?? \"\",\r\n project_name: p.name,\r\n project_path: p.path,\r\n input: t.input_tokens ?? 0,\r\n output: t.output_tokens ?? 0,\r\n cache_read: t.cache_read_input_tokens ?? 0,\r\n cache_create: t.cache_creation_input_tokens ?? 0,\r\n model: t.model ?? \"\",\r\n cost_usd: Math.round(estimateCostUsd(t) * 1000) / 1000,\r\n });\r\n }\r\n for (const gate of p.gates) {\r\n allGates.push({\r\n ts: gate.ts,\r\n project_name: p.name,\r\n project_path: p.path,\r\n tool: gate.tool,\r\n decision: gate.decision,\r\n query: gate.query,\r\n });\r\n }\r\n }\r\n allTurns.sort((a, b) => (a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0));\r\n allGates.sort((a, b) => (a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0));\r\n\r\n return {\r\n active: {\r\n project_root: activePath,\r\n project_name: activeName,\r\n stats: activeStats,\r\n },\r\n global: {\r\n project_count: projects.length,\r\n total_turns: g_turns,\r\n total_input_tokens: g_in,\r\n total_output_tokens: g_out,\r\n total_cache_read: g_cr,\r\n total_cache_create: g_cc,\r\n total_gate_calls: g_gate,\r\n blocked_count: g_block,\r\n estimated_tokens_saved: g_saved,\r\n saved_percent: Math.round(g_saved_pct * 10) / 10,\r\n estimated_cost_usd: Math.round(g_cost * 100) / 100,\r\n },\r\n projects,\r\n recent_turns: allTurns.slice(0, recentN),\r\n recent_gates: allGates.slice(0, recentN),\r\n };\r\n}\r\n\r\n// Legacy shapes from the M2 stub — kept for compat.\r\nexport interface TurnBreakdown {\r\n systemPromptTokens: number;\r\n conversationHistoryTokens: number;\r\n synthraPackTokens: number;\r\n userMessageTokens: number;\r\n responseTokens: number;\r\n totalTokens: number;\r\n costUsd: number;\r\n}\r\n\r\nexport interface SavingsDelta {\r\n withSynthra: TurnBreakdown;\r\n estimatedWithoutSynthra: TurnBreakdown;\r\n savedUsd: number;\r\n savedPercent: number;\r\n}\r\n\r\nexport function computeDelta(breakdown: TurnBreakdown, blockedGreps: number): SavingsDelta {\r\n const savedTokens = blockedGreps * AVG_TOKENS_PER_BLOCKED_GREP;\r\n const without: TurnBreakdown = {\r\n ...breakdown,\r\n conversationHistoryTokens: breakdown.conversationHistoryTokens + savedTokens,\r\n totalTokens: breakdown.totalTokens + savedTokens,\r\n costUsd: breakdown.costUsd + (savedTokens / 1_000_000) * 3,\r\n };\r\n const savedUsd = without.costUsd - breakdown.costUsd;\r\n const savedPercent = without.totalTokens > 0 ? (savedTokens / without.totalTokens) * 100 : 0;\r\n return {\r\n withSynthra: breakdown,\r\n estimatedWithoutSynthra: without,\r\n savedUsd,\r\n savedPercent: Math.round(savedPercent * 10) / 10,\r\n };\r\n}\r\n","// Resolves Synthra's storage locations inside a project root.\n\nimport { join } from \"node:path\";\n\nexport interface SynthraPaths {\n projectRoot: string;\n graphDir: string;\n contextDir: string;\n infoGraph: string;\n symbolIndex: string;\n sessionState: string;\n activityLog: string;\n tokenLog: string;\n gateLog: string;\n mcpPort: string;\n mcpServerLog: string;\n mcpServerErrLog: string;\n contextStore: string;\n contextMd: string;\n branchesDir: string;\n claudeDir: string;\n claudeSettings: string;\n claudeHooksDir: string;\n claudeMd: string;\n gitignore: string;\n}\n\nexport function resolvePaths(projectRoot: string): SynthraPaths {\n const graphDir = join(projectRoot, \".synthra-graph\");\n const contextDir = join(projectRoot, \".synthra\");\n const claudeDir = join(projectRoot, \".claude\");\n\n return {\n projectRoot,\n graphDir,\n contextDir,\n infoGraph: join(graphDir, \"info_graph.json\"),\n symbolIndex: join(graphDir, \"symbol_index.json\"),\n sessionState: join(graphDir, \"session.json\"),\n activityLog: join(graphDir, \"activity.jsonl\"),\n tokenLog: join(graphDir, \"token_log.jsonl\"),\n gateLog: join(graphDir, \"gate_log.jsonl\"),\n mcpPort: join(graphDir, \"mcp_port\"),\n mcpServerLog: join(graphDir, \"mcp_server.log\"),\n mcpServerErrLog: join(graphDir, \"mcp_server.err.log\"),\n contextStore: join(contextDir, \"context-store.json\"),\n contextMd: join(contextDir, \"CONTEXT.md\"),\n branchesDir: join(contextDir, \"branches\"),\n claudeDir,\n claudeSettings: join(claudeDir, \"settings.local.json\"),\n claudeHooksDir: join(claudeDir, \"hooks\"),\n claudeMd: join(projectRoot, \"CLAUDE.md\"),\n gitignore: join(projectRoot, \".gitignore\"),\n };\n}\n","// Approximate per-million-token pricing for Claude models, in USD.\n// Sourced from Anthropic's published rates. Tilde everywhere — these can shift.\n//\n// Used only for the dashboard's \"~$X\" estimate; not for billing.\n\nexport interface ModelPricing {\n /** Cost per 1M raw-input tokens. */\n input: number;\n /** Cost per 1M output tokens. */\n output: number;\n /** Cost per 1M cache-read tokens (typically ~10% of input). */\n cacheRead: number;\n /** Cost per 1M cache-creation tokens (typically input × 1.25). */\n cacheCreate: number;\n}\n\nconst PRICING: Record<string, ModelPricing> = {\n // Opus-class models — premium tier\n \"claude-opus-4-7\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n \"claude-opus-4-6\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n \"claude-opus-4-5\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n // Sonnet-class — workhorse\n \"claude-sonnet-4-6\": { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 },\n \"claude-sonnet-4-5\": { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 },\n // Haiku-class — fast and cheap\n \"claude-haiku-4-5\": { input: 1, output: 5, cacheRead: 0.1, cacheCreate: 1.25 },\n};\n\nconst FALLBACK: ModelPricing = { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 };\n\nexport function pricingFor(model: string | undefined | null): ModelPricing {\n if (!model) return FALLBACK;\n const direct = PRICING[model];\n if (direct) return direct;\n // Loose prefix match: \"claude-opus-…\" / \"claude-sonnet-…\" / \"claude-haiku-…\"\n if (model.includes(\"opus\")) return PRICING[\"claude-opus-4-7\"] ?? FALLBACK;\n if (model.includes(\"sonnet\")) return PRICING[\"claude-sonnet-4-6\"] ?? FALLBACK;\n if (model.includes(\"haiku\")) return PRICING[\"claude-haiku-4-5\"] ?? FALLBACK;\n return FALLBACK;\n}\n\nexport interface UsageRecord {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n model?: string;\n}\n\n/** Approximate USD cost of a single usage record. */\nexport function estimateCostUsd(usage: UsageRecord): number {\n const p = pricingFor(usage.model);\n return (\n (usage.input_tokens / 1_000_000) * p.input +\n (usage.output_tokens / 1_000_000) * p.output +\n ((usage.cache_read_input_tokens ?? 0) / 1_000_000) * p.cacheRead +\n ((usage.cache_creation_input_tokens ?? 0) / 1_000_000) * p.cacheCreate\n );\n}\n","// Global registry of projects that have run `syn .` on this machine.\n// Stored at ~/.synthra/projects.json so the dashboard can enumerate them\n// without walking the filesystem.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\n\nconst REGISTRY_DIR = join(homedir(), \".synthra\");\nconst REGISTRY_PATH = join(REGISTRY_DIR, \"projects.json\");\nconst SCHEMA_VERSION = 1;\n\nexport interface ProjectRegistryEntry {\n path: string; // absolute project root\n name: string; // basename for display\n first_seen: string; // ISO timestamp\n last_seen: string; // ISO timestamp\n}\n\ninterface Registry {\n schema_version: number;\n projects: ProjectRegistryEntry[];\n}\n\nasync function readRegistry(): Promise<Registry> {\n try {\n const raw = await readFile(REGISTRY_PATH, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<Registry>;\n if (!Array.isArray(parsed.projects)) return { schema_version: SCHEMA_VERSION, projects: [] };\n return { schema_version: parsed.schema_version ?? SCHEMA_VERSION, projects: parsed.projects };\n } catch {\n return { schema_version: SCHEMA_VERSION, projects: [] };\n }\n}\n\nasync function writeRegistry(registry: Registry): Promise<void> {\n await mkdir(dirname(REGISTRY_PATH), { recursive: true });\n await writeFile(REGISTRY_PATH, JSON.stringify(registry, null, 2) + \"\\n\", \"utf8\");\n}\n\n/** Upsert this project's entry. Updates `last_seen`; preserves `first_seen`. */\nexport async function recordProject(projectRoot: string): Promise<void> {\n const now = new Date().toISOString();\n const registry = await readRegistry();\n const existing = registry.projects.find((p) => p.path === projectRoot);\n if (existing) {\n existing.last_seen = now;\n existing.name = basename(projectRoot);\n } else {\n registry.projects.push({\n path: projectRoot,\n name: basename(projectRoot),\n first_seen: now,\n last_seen: now,\n });\n }\n try {\n await writeRegistry(registry);\n } catch {\n // Registry is best-effort — a write failure shouldn't block the session.\n }\n}\n\nexport async function listProjects(): Promise<ProjectRegistryEntry[]> {\n const registry = await readRegistry();\n // Sort by last_seen descending so the most active project surfaces first.\n return registry.projects\n .slice()\n .sort((a, b) => (a.last_seen > b.last_seen ? -1 : a.last_seen < b.last_seen ? 1 : 0));\n}\n\nexport { REGISTRY_PATH, REGISTRY_DIR };\n","<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Synthra · Dashboard</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n <link rel=\"stylesheet\" href=\"./style.css\" />\n</head>\n<body>\n\n <!-- ============ Top nav ============ -->\n <header class=\"topnav\">\n <div class=\"brand\">\n <div class=\"brand-mark\"></div>\n <div class=\"brand-name\">Synth<em>ra</em></div>\n <div class=\"brand-eyebrow\">Dashboard</div>\n <div class=\"nav-date\">\n <span class=\"nd-weekday\" id=\"hero-weekday\">—</span>\n <span class=\"nd-day\" id=\"hero-day\">—</span>\n <span class=\"nd-month\" id=\"hero-month\">—</span>\n </div>\n </div>\n <div class=\"top-right\">\n <span class=\"status-pill\">\n <span class=\"dot\" id=\"dot\"></span>\n <span id=\"status\">connecting…</span>\n </span>\n </div>\n <div class=\"topnav-right\">\n <div class=\"nav-active has-tooltip\" data-tooltip=\"The project directory Synthra is currently watching — the most recent `syn .` session on this machine.\">\n <span class=\"na-label\">Active</span>\n <span class=\"na-value\" id=\"active-project\" title=\"—\">—</span>\n </div>\n <span class=\"port-badge\">port <span class=\"mono\" id=\"port-num\">8901</span></span>\n <button class=\"faq-btn has-tooltip\" id=\"faq-btn\" data-tooltip=\"Open the FAQ — explains where every number on this dashboard comes from, how cost is calculated, and what the savings floor actually measures.\" aria-label=\"Open FAQ\">?</button>\n </div>\n </header>\n\n <!-- ============ Main 3-column grid ============ -->\n <main class=\"grid-main\">\n\n <!-- ===== Left ===== -->\n <aside class=\"col-left\">\n <div class=\"card donut-card has-tooltip\" data-tooltip=\"Which Claude models you've been calling, weighted by turn count. Opus = slow and expensive; Sonnet = workhorse; Haiku = cheap and fast. Helps you see where your budget is actually going.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Model usage</div>\n <div class=\"card-meta\">by turns</div>\n </div>\n <div class=\"donut-wrap\">\n <svg viewBox=\"0 0 140 140\" class=\"donut\" id=\"donut-svg\" aria-hidden=\"true\">\n <circle cx=\"70\" cy=\"70\" r=\"52\" class=\"donut-track\"/>\n </svg>\n <div class=\"donut-center\">\n <div class=\"donut-total\" id=\"donut-total\">0</div>\n <div class=\"donut-total-k\">turns</div>\n </div>\n </div>\n <div class=\"donut-legend\" id=\"donut-legend\"></div>\n </div>\n\n <!-- Projects — colored bar chart by turns -->\n <div class=\"card projects-card has-tooltip\" data-tooltip=\"Every project Synthra has tracked on this machine, ranked by how many turns ran there. Each project carries its own color. Click any row to open its full cost & token breakdown.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Projects</div>\n <div class=\"card-meta\">by turns</div>\n </div>\n <div class=\"proj-chart\" id=\"proj-chart\"></div>\n </div>\n </aside>\n\n <!-- ===== Center ===== -->\n <div class=\"col-center\">\n\n <!-- Metric strip — divider-separated, no individual card chrome -->\n <div class=\"metric-strip\">\n <div class=\"metric-item has-tooltip\" data-tooltip=\"Total back-and-forth exchanges with Claude across all projects. One turn = you send a message, Claude responds. Counted from the Stop hook against transcript JSONL files.\">\n <div class=\"m-label\">Turns</div>\n <div class=\"m-value\" id=\"m-turns\">0</div>\n </div>\n <div class=\"metric-item has-tooltip\" data-tooltip=\"↓ Input — new, uncached tokens you sent to Claude. Usually small (a few hundred per turn) because most of the conversation history comes from prompt cache.\">\n <div class=\"m-label\">↓ Input</div>\n <div class=\"m-value\" id=\"m-input\">0</div>\n </div>\n <div class=\"metric-item has-tooltip\" data-tooltip=\"↑ Output — tokens Claude generated in its responses. Most expensive line item per turn (~5× input rate on Opus). High output usually means long code edits.\">\n <div class=\"m-label\">↑ Output</div>\n <div class=\"m-value\" id=\"m-output\">0</div>\n </div>\n <div class=\"metric-item has-tooltip\" data-tooltip=\"⟲ Cache read — tokens reused from the prompt cache (system prompt, conversation history, Synthra's pre-packed context). Cheap, around 10% of the input rate. The bulk of every long session.\">\n <div class=\"m-label\">⟲ Cache R</div>\n <div class=\"m-value\" id=\"m-cache-r\">0</div>\n </div>\n <div class=\"metric-item has-tooltip\" data-tooltip=\"+ Cache write — tokens newly added to the prompt cache so future turns can read them cheaply. Premium-priced (~125% of input rate) but pays back across the session.\">\n <div class=\"m-label\">+ Cache W</div>\n <div class=\"m-value\" id=\"m-cache-w\">0</div>\n </div>\n </div>\n\n <!-- Savings hero -->\n <div class=\"card savings has-tooltip\" data-tooltip=\"What Synthra has saved you, as a deliberately conservative floor estimate. Each time the gate blocks an exploratory Grep/Glob, we credit 500 tokens × $3 per million-token input rate. Real savings are usually higher because the formula ignores cache thrash and follow-up Reads that the block also prevents. The audit line below shows the exact math live.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Synthra savings <span class=\"src-badge estimated\">floor</span></div>\n <div class=\"card-meta\" id=\"savings-pct\">— off</div>\n </div>\n <div class=\"savings-body\">\n <div class=\"savings-figure\">\n <div class=\"savings-money\" id=\"savings-money\">$0.00</div>\n <div class=\"savings-tokens\"><span id=\"savings-tokens\">0</span> tokens avoided</div>\n </div>\n <div class=\"savings-bar\">\n <div class=\"savings-actual\" id=\"savings-actual-bar\" style=\"width:100%\"></div>\n <div class=\"savings-saved\" id=\"savings-saved-bar\" style=\"width:0%\"></div>\n </div>\n <div class=\"savings-legend\">\n <div class=\"sl-row\"><span class=\"sl-dot actual\"></span>You paid <b id=\"savings-actual-amt\">$0.00</b></div>\n <div class=\"sl-row\"><span class=\"sl-dot saved\"></span>Baseline <b id=\"savings-baseline-amt\">$0.00</b></div>\n </div>\n </div>\n <div class=\"savings-audit\">\n <span class=\"audit-formula\">\n <b id=\"audit-blocks\">0</b> blocks × <b>500</b> tokens × <b>$3</b> / M input rate = <b id=\"audit-result\" class=\"audit-result\">$0.00</b>\n </span>\n </div>\n </div>\n\n <!-- Recent turns -->\n <div class=\"card turns-card has-tooltip\" data-tooltip=\"Every conversational turn Synthra has observed across all your projects, newest first. Each row shows when, which project, which model, and how the cost broke down between fresh input, generated output, and cache.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Recent turns</div>\n <div class=\"card-meta\" id=\"turns-count\">— shown</div>\n </div>\n <div class=\"turns-scroll\">\n <table class=\"turns-table\">\n <thead>\n <tr>\n <th class=\"has-tooltip\" data-tooltip=\"When this turn happened, in your local time. Turns from today show as a time; older turns show the date.\">Time</th>\n <th class=\"has-tooltip\" data-tooltip=\"Which project directory the turn ran in. Color-matched to the Projects chart on the left.\">Project</th>\n <th class=\"has-tooltip\" data-tooltip=\"The Claude model used for this turn — Opus, Sonnet, or Haiku — shown in the color-coded pill.\">Model</th>\n <th class=\"num has-tooltip\" data-tooltip=\"↓ Input — raw, uncached tokens sent to Claude this turn. Usually tiny (a few hundred) because conversation history is served from the prompt cache, not re-sent fresh.\">In</th>\n <th class=\"num has-tooltip\" data-tooltip=\"↑ Output — tokens Claude generated in its response. The most expensive line item per turn (~5× the input rate on Opus). Big numbers usually mean long code edits.\">Out</th>\n <th class=\"num has-tooltip\" data-tooltip=\"Cache Read / Cache Write. R = tokens reused from earlier turns (cheap, ~10% of the input rate). W = tokens newly written to the cache so future turns can read them (premium, ~125% of the input rate).\">Cache R/W</th>\n <th class=\"num has-tooltip\" data-tooltip=\"Estimated USD for this turn: input + output + cache read + cache write, each multiplied by the published Anthropic per-model rate.\">Cost</th>\n </tr>\n </thead>\n <tbody id=\"turns-body\"></tbody>\n </table>\n <p class=\"empty hidden\" id=\"turns-empty\">No turns logged yet. Run <code>syn .</code> in any project and chat with Claude.</p>\n </div>\n <div class=\"turns-pager hidden\" id=\"turns-pager\">\n <button type=\"button\" id=\"turns-prev\" aria-label=\"Previous page\">‹ Prev</button>\n <span class=\"mono\" id=\"turns-page-label\">page 1 of 1</span>\n <button type=\"button\" id=\"turns-next\" aria-label=\"Next page\">Next ›</button>\n </div>\n </div>\n\n </div>\n\n <!-- ===== Right ===== -->\n <aside class=\"col-right\">\n\n <!-- Cost hero -->\n <div class=\"card cost-hero has-tooltip\" data-tooltip=\"Your all-time Claude spend across every project Synthra has tracked on this machine. Token counts come from Claude's transcript JSONL files; dollar amounts are computed by multiplying those counts by Anthropic's published per-model rates. See the FAQ for full rate tables.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Total spend · <em>all time</em></div>\n </div>\n <div class=\"big-money\" id=\"big-cost\">$0.<em>00</em></div>\n <div class=\"cost-sub\">\n <div class=\"cs-row\">\n <span class=\"cs-k\">Tokens (in+out)</span>\n <span class=\"cs-v\" id=\"cs-tokens\">0</span>\n </div>\n <div class=\"cs-row\">\n <span class=\"cs-k\">Avg / turn</span>\n <span class=\"cs-v\" id=\"cs-avg\">$0.00</span>\n </div>\n </div>\n </div>\n\n <!-- The Moat -->\n <div class=\"card moat has-tooltip\" data-tooltip=\"Synthra's PreToolUse hook intercepts. Each block = Synthra recognized the graph already had high-confidence context for the query, so it stopped Claude from running an exploratory Grep or Glob. The list below shows the latest decisions across all projects.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">The <em>Moat</em></div>\n <div class=\"card-meta\">PreToolUse</div>\n </div>\n <div class=\"moat-value\"><span id=\"blocks\">0</span> <em>blocks</em></div>\n <div class=\"gate-mini\" id=\"gate-mini\"></div>\n </div>\n\n </aside>\n </main>\n\n <!-- ============ Project dialog ============ -->\n <div class=\"dialog-backdrop hidden\" id=\"dialog-backdrop\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"dialog\">\n <button class=\"dialog-close\" id=\"dialog-close\" aria-label=\"Close\">×</button>\n <div class=\"dialog-eyebrow\">Project · <em>details</em></div>\n <div class=\"dialog-name\" id=\"d-name\">—</div>\n <div class=\"dialog-path\" id=\"d-path\">—</div>\n <div class=\"dialog-grid\">\n <div class=\"dg-cell\">\n <div class=\"dg-k\">Total cost</div>\n <div class=\"dg-v money\" id=\"d-cost\">$0.00</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">Turns</div>\n <div class=\"dg-v\" id=\"d-turns\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">↓ Raw input</div>\n <div class=\"dg-v\" id=\"d-input\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">↑ Output</div>\n <div class=\"dg-v\" id=\"d-output\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">⟲ Cache read</div>\n <div class=\"dg-v\" id=\"d-cache-r\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">+ Cache write</div>\n <div class=\"dg-v\" id=\"d-cache-w\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">Moat blocks</div>\n <div class=\"dg-v\" id=\"d-blocks\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">Last active</div>\n <div class=\"dg-v dg-v-sm\" id=\"d-last\">—</div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ============ FAQ dialog ============ -->\n <div class=\"dialog-backdrop hidden\" id=\"faq-backdrop\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Dashboard FAQ\">\n <div class=\"dialog dialog-faq\">\n <button class=\"dialog-close\" id=\"faq-close\" aria-label=\"Close\">×</button>\n <div class=\"dialog-eyebrow\">FAQ · <em>where every number comes from</em></div>\n <div class=\"dialog-name\">Understanding your dashboard</div>\n <div class=\"dialog-path\">Synthra reports what Claude actually used — read from transcripts, not estimated.</div>\n\n <div class=\"faq-content\">\n\n <details open>\n <summary>Where does Synthra get these numbers from?</summary>\n <div class=\"faq-body\">\n <p>Synthra <strong>does not estimate</strong> token counts — it reads them directly from Claude's own log files. Every number on this dashboard is traceable:</p>\n <table>\n <tr><td>Turns, ↓ In, ↑ Out, Cache R/W</td><td>Parsed from Claude's transcript JSONLs at <code>~/.claude/projects/<encoded-cwd>/*.jsonl</code></td></tr>\n <tr><td>Model used per turn</td><td>From each turn's <code>model</code> field in the same transcripts</td></tr>\n <tr><td>Moat blocks + gate decisions</td><td>From <code>.synthra-graph/gate_log.jsonl</code> inside each project</td></tr>\n <tr><td>Active project, project list</td><td>From <code>~/.synthra/projects.json</code> (built up as you run <code>syn .</code>)</td></tr>\n <tr><td>Total spend (USD)</td><td>Above token counts × Anthropic's published per-model rates — see \"How is cost calculated?\" below</td></tr>\n <tr><td>Synthra savings (USD)</td><td><strong>Estimated</strong> — see \"About the Savings (floor) card\" below</td></tr>\n </table>\n <p>The cost-calculation logic lives in <code>src/shared/pricing.ts</code>; the aggregation logic in <code>src/dashboard/delta.ts</code>. Both are linked at the bottom.</p>\n </div>\n </details>\n\n <details>\n <summary>What do the columns in Recent Turns mean?</summary>\n <div class=\"faq-body\">\n <table>\n <tr><td><code>Time</code></td><td>When this turn happened (local time)</td></tr>\n <tr><td><code>Project</code></td><td>Which directory the turn ran in</td></tr>\n <tr><td><code>Model</code></td><td>Claude model used — Opus, Sonnet, or Haiku (color-coded in the model pill)</td></tr>\n <tr><td><code>In</code></td><td>Raw input tokens — brand-new content sent to Claude this turn, not cached</td></tr>\n <tr><td><code>Out</code></td><td>Tokens Claude generated in its response</td></tr>\n <tr><td><code>Cache R/W</code></td><td>Cache <strong>R</strong>ead (reused from prior turns) / Cache <strong>W</strong>rite (newly cached for future turns)</td></tr>\n <tr><td><code>Cost</code></td><td>Per-turn USD estimate using Anthropic's published rates</td></tr>\n </table>\n <p><strong>Why is Raw Input often tiny (e.g. 6 tokens)?</strong> Because Claude Code aggressively caches the system prompt, CLAUDE.md, tool definitions, and conversation history. On each turn, only your brand-new message is \"raw input\" — everything else is a cheap cache read. This is normal and saves significant money.</p>\n </div>\n </details>\n\n <details>\n <summary>How is cost calculated?</summary>\n <div class=\"faq-body\">\n <p>Each token type has a different per-million-token rate. Synthra uses these rates (defined in <code>src/shared/pricing.ts</code>):</p>\n <table>\n <thead><tr><td><strong>Token type</strong></td><td><strong>Haiku 4.5</strong></td><td><strong>Sonnet 4.x</strong></td><td><strong>Opus 4.x</strong></td></tr></thead>\n <tr><td>Raw Input</td><td>$1.00/M</td><td>$3.00/M</td><td>$15.00/M</td></tr>\n <tr><td>Cache Write</td><td>$1.25/M</td><td>$3.75/M</td><td>$18.75/M</td></tr>\n <tr><td>Cache Read</td><td>$0.10/M</td><td>$0.30/M</td><td>$1.50/M</td></tr>\n <tr><td>Output</td><td>$5.00/M</td><td>$15.00/M</td><td>$75.00/M</td></tr>\n </table>\n <p><strong>Cost</strong> = (Input × input rate) + (Output × output rate) + (Cache Read × read rate) + (Cache Write × write rate)</p>\n <p>Cache reads are <strong>10× cheaper</strong> than raw input. Cache writes are <strong>25% more expensive</strong> than raw input. So Claude Code's caching strategy pays for itself quickly across a session.</p>\n <div class=\"warning\"><span class=\"icon\">⚠</span>These are <strong>Anthropic API rates</strong>, not your plan billing. If you're on Claude Pro, Team, Max, or Enterprise, your actual billing is different — the costs shown are estimates of <em>API-equivalent</em> usage, useful for comparing sessions against each other. See <a href=\"https://www.anthropic.com/pricing\" target=\"_blank\" rel=\"noopener noreferrer\">anthropic.com/pricing</a> for the source of these rates.</div>\n </div>\n </details>\n\n <details>\n <summary>What is the total context size per turn?</summary>\n <div class=\"faq-body\">\n <p><code>Total context = Raw Input + Cache Read + Cache Write</code></p>\n <p>Example: if Raw Input is 6, Cache Read is 60K, and Cache Write is 13K, your turn used ~73K tokens of context — but 99.99% was efficiently cached, so you only paid the cache-read rate on most of it. The Recent Turns table lets you scan this row by row.</p>\n <p>Anthropic's prompt-caching mechanics are documented at <a href=\"https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching\" target=\"_blank\" rel=\"noopener noreferrer\">docs.anthropic.com/.../prompt-caching</a>.</p>\n </div>\n </details>\n\n <details>\n <summary>About the \"Savings (floor)\" card</summary>\n <div class=\"faq-body\">\n <p>This is the only number on the dashboard that's <strong>estimated</strong> rather than measured. Here's the math:</p>\n <p class=\"formula-box\">savings = blocks × 500 tokens × $3 per million input rate</p>\n <p>Each time Synthra's PreToolUse hook blocks a <code>Grep</code> or <code>Glob</code> call (because the graph already has high-confidence context), we credit a deliberately conservative 500 tokens at the Sonnet input rate.</p>\n <p>It under-counts on purpose because the formula ignores:</p>\n <ul>\n <li><strong>Cache thrash</strong> — the blocked tool result would have been written to the cache at ~125% of the input rate, which we don't count</li>\n <li><strong>Cascading reads</strong> — Claude usually follows a Grep with several <code>Read</code> calls, which the block also prevents but we don't credit</li>\n <li><strong>Bigger codebases</strong> — actual Grep results often exceed 500 tokens by 3–6× in a real repo</li>\n </ul>\n <p>Real savings are typically <strong>2–5× the floor</strong>. The audit row on the Savings card shows the formula live so you can verify the math.</p>\n </div>\n </details>\n\n <details>\n <summary>What is \"The Moat\"?</summary>\n <div class=\"faq-body\">\n <p>\"The Moat\" is Synthra's <strong>PreToolUse hook</strong>. Every time Claude Code is about to run a <code>Grep</code> or <code>Glob</code>, the hook calls Synthra's local server first and asks: \"do you already have high-confidence context for this query?\"</p>\n <p>If yes, Synthra returns <code>{\"decision\":\"block\"}</code> — Claude can't run the tool. Claude then has to use Synthra's graph tools (<code>graph_continue</code>, <code>graph_read</code>) instead, which return the answer without burning tokens on exploration. This is deterministic enforcement — not prose policy. Claude literally can't disobey.</p>\n <p>The Moat card shows total blocks; the inline list below shows the most recent gate decisions (block vs allow) with the originating query.</p>\n <p>Every decision is logged to <code>.synthra-graph/gate_log.jsonl</code> for the project.</p>\n </div>\n </details>\n\n <details>\n <summary>How does Synthra build the codebase graph?</summary>\n <div class=\"faq-body\">\n <p>When you run <code>syn .</code> in a project, Synthra walks the file tree (respecting <code>.gitignore</code> + <code>.synthraignore</code>) and parses each file with <strong>tree-sitter</strong> WebAssembly grammars. Currently 14 languages are supported:</p>\n <p>TypeScript, JavaScript, JSX/TSX, Python, Svelte, Vue, Go, Rust, Java, Kotlin, PHP, Ruby, C/C++, C#, and Dart.</p>\n <p>For each file we extract: function and class definitions, exports, imports, and test-to-source links. The output is a structured graph stored at <code>.synthra-graph/info_graph.json</code> with a symbol index at <code>.synthra-graph/symbol_index.json</code>.</p>\n <p>The graph is what makes pre-injection and The Moat work — both query it before Claude ever has to Grep.</p>\n </div>\n </details>\n\n <details>\n <summary>Where does Synthra store data on disk?</summary>\n <div class=\"faq-body\">\n <p>Synthra uses two folders per project, intentionally separated:</p>\n <table>\n <tr><td><code>.synthra-graph/</code></td><td><strong>Gitignored</strong>. Heavy generated state — the graph, symbol index, token + gate logs, session info. Rebuilt by <code>syn scan</code>.</td></tr>\n <tr><td><code>.synthra/</code></td><td><strong>Git-tracked.</strong> Decisions, context notes, branch-scoped memory — the part teammates inherit when they clone the repo.</td></tr>\n </table>\n <p>Plus one global file at <code>~/.synthra/projects.json</code> that tracks every project on this machine.</p>\n </div>\n </details>\n\n <details>\n <summary>How does branch-aware memory work?</summary>\n <div class=\"faq-body\">\n <p>Inside <code>.synthra/</code>, context is partitioned by git branch:</p>\n <ul>\n <li><code>.synthra/context-store.json</code> — decisions on the default branch</li>\n <li><code>.synthra/CONTEXT.md</code> — narrative notes on the default branch</li>\n <li><code>.synthra/branches/<sanitized-name>/</code> — overrides on feature branches</li>\n </ul>\n <p>When you switch branches, Synthra's git-watcher (using <code>fs.watch</code> on <code>.git/HEAD</code>) detects the change and reloads the right context. Decisions scoped to a feature branch don't leak back to <code>main</code> until merge.</p>\n </div>\n </details>\n\n <details>\n <summary>How does Synthra actually reduce my Claude bill?</summary>\n <div class=\"faq-body\">\n <p>Three mechanisms, in order of impact:</p>\n <ul>\n <li><strong>Pre-injection</strong> — at session start, Synthra packs ~4K tokens of graph context (function signatures, top inline bodies, file relationships) into Claude's prompt. Claude doesn't have to Grep / Read to discover what's in the codebase — it already knows.</li>\n <li><strong>The Moat</strong> — the PreToolUse hook deterministically blocks exploratory Grep/Glob when the graph already has high-confidence context. Counts on the Moat card.</li>\n <li><strong>Branch-aware memory</strong> — decisions and CONTEXT notes persist in <code>.synthra/</code>, so Claude doesn't have to be re-told what was decided last session.</li>\n </ul>\n <p>The dashboard shows real token counts from Claude's own logs so you can see the effect over time, not just take Synthra's word for it.</p>\n </div>\n </details>\n\n <details>\n <summary>Why do long conversations get expensive?</summary>\n <div class=\"faq-body\">\n <p>Each turn re-sends the entire conversation history as input. Even cached, the cumulative input grows roughly <strong>quadratically</strong>:</p>\n <table>\n <thead><tr><td><strong>Turns</strong></td><td><strong>Per-turn input</strong></td><td><strong>Cumulative input</strong></td></tr></thead>\n <tr><td>10</td><td>~2K (mostly cached)</td><td>~110K</td></tr>\n <tr><td>30</td><td>~2K</td><td>~930K</td></tr>\n <tr><td>50</td><td>~2K</td><td>~2.55M</td></tr>\n </table>\n <p>Prompt caching helps a lot (cache reads are 10× cheaper than fresh input), but context still grows. This is why Synthra's pre-injection matters: starting with the answer already in context means you reach a useful state in fewer turns.</p>\n <p><strong>Tip:</strong> Use <code>/compact</code> in Claude Code or start fresh sessions when a thread feels stale.</p>\n </div>\n </details>\n\n <details>\n <summary>Sources & references</summary>\n <div class=\"faq-body\">\n <p>Synthra is open source. Every number on this dashboard can be cross-checked:</p>\n <ul class=\"link-list\">\n <li><a href=\"https://github.com/jefuriiij/synthra\" target=\"_blank\" rel=\"noopener noreferrer\">github.com/jefuriiij/synthra</a> — source code, issues, roadmap</li>\n <li><a href=\"https://www.npmjs.com/package/@jefuriiij/synthra\" target=\"_blank\" rel=\"noopener noreferrer\">npm: @jefuriiij/synthra</a> — release history, install instructions</li>\n <li><a href=\"https://www.anthropic.com/pricing\" target=\"_blank\" rel=\"noopener noreferrer\">anthropic.com/pricing</a> — official rate table Synthra uses</li>\n <li><a href=\"https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching\" target=\"_blank\" rel=\"noopener noreferrer\">Anthropic docs · prompt caching</a> — explains cache read/write behavior</li>\n <li><code>src/shared/pricing.ts</code> — the file in this repo holding the rate table</li>\n <li><code>src/dashboard/delta.ts</code> — where dashboard aggregates are computed</li>\n <li><code>src/server/routes/gate.ts</code> — the Moat implementation</li>\n </ul>\n </div>\n </details>\n\n </div>\n </div>\n </div>\n\n <!-- ============ Footer ============ -->\n <footer class=\"foot\">\n <div>Synth<em>ra</em> · v__SYN_VERSION__</div>\n <div>Cost figures approximate · @jefuriiij</div>\n </footer>\n\n <script>\n const $ = (sel) => document.querySelector(sel);\n const SAVED_RATE_PER_M = 3.00; // USD per million tokens — conservative input rate\n\n // ----- model classification -----\n function modelFamily(model) {\n if (!model) return 'unknown';\n const m = model.toLowerCase();\n if (m === '<synthetic>') return 'unknown';\n if (m.includes('opus')) return 'opus';\n if (m.includes('sonnet')) return 'sonnet';\n if (m.includes('haiku')) return 'haiku';\n return 'unknown';\n }\n function modelLabel(model) {\n if (!model || model === '<synthetic>') return 'synthetic';\n return model.replace(/^claude-/, '');\n }\n\n // ----- formatting -----\n function fmt(n) {\n if (typeof n !== 'number' || !Number.isFinite(n)) return '0';\n if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + '<em>M</em>';\n if (n >= 1_000) return (n / 1_000).toFixed(1) + '<em>k</em>';\n return n.toLocaleString();\n }\n function fmtPlain(n) {\n if (typeof n !== 'number' || !Number.isFinite(n)) return '0';\n if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';\n if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';\n return n.toLocaleString();\n }\n function fmtCostBig(usd) {\n if (typeof usd !== 'number' || !Number.isFinite(usd)) usd = 0;\n let s;\n if (usd >= 1) s = usd.toFixed(2);\n else if (usd >= 0.01) s = usd.toFixed(3);\n else s = usd.toFixed(4);\n const dot = s.indexOf('.');\n if (dot === -1) return '$' + s;\n return '$' + s.slice(0, dot) + '.<em>' + s.slice(dot + 1) + '</em>';\n }\n function fmtCostFlat(usd) {\n if (typeof usd !== 'number' || !Number.isFinite(usd)) usd = 0;\n if (usd >= 1) return '$' + usd.toFixed(2);\n if (usd >= 0.01) return '$' + usd.toFixed(3);\n return '$' + usd.toFixed(4);\n }\n function fmtTs(iso) {\n try {\n const d = new Date(iso);\n const today = new Date();\n const isToday = d.toDateString() === today.toDateString();\n if (isToday) return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n return d.toLocaleDateString([], { month: 'short', day: 'numeric' });\n } catch { return iso; }\n }\n\n let lastData = null;\n let turnsPage = 1;\n const TURNS_PER_PAGE = 25;\n\n // ----- date in hero -----\n function setHeroDate() {\n const now = new Date();\n $('#hero-day').textContent = now.getDate();\n $('#hero-weekday').textContent = now.toLocaleDateString([], { weekday: 'short' });\n $('#hero-month').textContent = now.toLocaleDateString([], { month: 'long' });\n }\n\n // ----- renderers -----\n function renderSession(turns) {\n const agg = turns.reduce((a, t) => {\n a.turns += 1;\n a.input += t.input || 0;\n a.output += t.output || 0;\n a.cacheR += t.cache_read || 0;\n a.cacheW += t.cache_create || 0;\n return a;\n }, { turns: 0, input: 0, output: 0, cacheR: 0, cacheW: 0 });\n\n $('#m-turns').innerHTML = fmt(agg.turns);\n $('#m-input').innerHTML = fmt(agg.input);\n $('#m-output').innerHTML = fmt(agg.output);\n $('#m-cache-r').innerHTML = fmt(agg.cacheR);\n $('#m-cache-w').innerHTML = fmt(agg.cacheW);\n }\n\n function renderSavings(g) {\n const tokensSaved = g.estimated_tokens_saved || 0;\n const blocks = g.blocked_count || 0;\n const savedUsd = tokensSaved * SAVED_RATE_PER_M / 1_000_000;\n const actualUsd = g.estimated_cost_usd || 0;\n const baselineUsd = actualUsd + savedUsd;\n const savedPct = baselineUsd > 0 ? (savedUsd / baselineUsd) * 100 : 0;\n const actualPct = baselineUsd > 0 ? (actualUsd / baselineUsd) * 100 : 100;\n\n $('#savings-money').textContent = fmtCostFlat(savedUsd);\n $('#savings-pct').textContent = savedPct.toFixed(1) + '% off';\n $('#savings-tokens').textContent = fmtPlain(tokensSaved);\n $('#savings-actual-bar').style.width = actualPct.toFixed(2) + '%';\n $('#savings-saved-bar').style.width = savedPct.toFixed(2) + '%';\n $('#savings-actual-amt').textContent = fmtCostFlat(actualUsd);\n $('#savings-baseline-amt').textContent = fmtCostFlat(baselineUsd);\n\n // Audit row — live formula\n $('#audit-blocks').textContent = blocks.toLocaleString();\n $('#audit-result').textContent = fmtCostFlat(savedUsd);\n }\n\n function renderCostHero(g) {\n $('#big-cost').innerHTML = fmtCostBig(g.estimated_cost_usd);\n const totalTokens = (g.total_input_tokens || 0) + (g.total_output_tokens || 0);\n $('#cs-tokens').textContent = fmtPlain(totalTokens);\n const avg = g.total_turns > 0 ? g.estimated_cost_usd / g.total_turns : 0;\n $('#cs-avg').textContent = fmtCostFlat(avg);\n }\n\n function renderMoat(g) {\n $('#blocks').textContent = fmtPlain(g.blocked_count);\n }\n\n function renderTurns(turns) {\n const tbody = $('#turns-body');\n const empty = $('#turns-empty');\n const pager = $('#turns-pager');\n tbody.innerHTML = '';\n if (!turns.length) {\n empty.classList.remove('hidden');\n pager.classList.add('hidden');\n $('#turns-count').textContent = '0 shown';\n return;\n }\n empty.classList.add('hidden');\n\n const totalPages = Math.max(1, Math.ceil(turns.length / TURNS_PER_PAGE));\n // Clamp in case the list shrank (dedup / data churn) since last render.\n if (turnsPage > totalPages) turnsPage = totalPages;\n if (turnsPage < 1) turnsPage = 1;\n const start = (turnsPage - 1) * TURNS_PER_PAGE;\n const pageItems = turns.slice(start, start + TURNS_PER_PAGE);\n\n $('#turns-count').textContent =\n 'showing ' + (start + 1) + '–' + (start + pageItems.length) + ' of ' + turns.length;\n\n const frag = document.createDocumentFragment();\n for (const t of pageItems) {\n const family = modelFamily(t.model);\n const tr = document.createElement('tr');\n tr.innerHTML =\n '<td class=\"ts\">' + fmtTs(t.ts || t.written_at) + '</td>' +\n '<td class=\"proj\">' + (t.project_name || '—') + '</td>' +\n '<td><span class=\"model-pill ' + family + '\"><span class=\"sq\"></span>' + modelLabel(t.model) + '</span></td>' +\n '<td class=\"num\">' + fmtPlain(t.input || 0) + '</td>' +\n '<td class=\"num\">' + fmtPlain(t.output || 0) + '</td>' +\n '<td class=\"num\">' + fmtPlain(t.cache_read || 0) + ' / ' + fmtPlain(t.cache_create || 0) + '</td>' +\n '<td class=\"num cost\">' + fmtCostFlat(t.cost_usd || 0) + '</td>';\n frag.appendChild(tr);\n }\n tbody.appendChild(frag);\n\n if (totalPages <= 1) {\n pager.classList.add('hidden');\n } else {\n pager.classList.remove('hidden');\n $('#turns-page-label').textContent = 'page ' + turnsPage + ' of ' + totalPages;\n $('#turns-prev').disabled = turnsPage <= 1;\n $('#turns-next').disabled = turnsPage >= totalPages;\n }\n }\n\n function gotoTurnsPage(delta) {\n turnsPage += delta;\n renderTurns((lastData && lastData.recent_turns) || []);\n }\n\n function renderGateMini(gates) {\n const el = $('#gate-mini');\n el.innerHTML = '';\n if (!gates.length) {\n el.innerHTML = '<div class=\"empty\">No gate decisions yet.</div>';\n return;\n }\n const frag = document.createDocumentFragment();\n for (const g of gates.slice(0, 50)) {\n const row = document.createElement('div');\n row.className = 'gate-row';\n const cls = g.decision === 'block' ? 'block' : 'allow';\n row.innerHTML =\n '<span class=\"g-ts\">' + fmtTs(g.ts) + '</span>' +\n '<span class=\"g-decision ' + cls + '\">' + (g.decision || '—').toUpperCase() + '</span>' +\n '<span class=\"g-q\">' + (g.query || g.tool || '—') + '</span>';\n frag.appendChild(row);\n }\n el.appendChild(frag);\n }\n\n // ----- Project colors (stable per name) -----\n const PROJECT_COLORS = [\n 'oklch(78% 0.14 220)', // cyan\n 'oklch(75% 0.14 155)', // green\n 'oklch(78% 0.13 75)', // amber\n 'oklch(72% 0.14 285)', // violet\n 'oklch(72% 0.14 20)', // rose\n 'oklch(74% 0.13 195)', // teal\n 'oklch(80% 0.12 250)', // periwinkle\n 'oklch(76% 0.13 330)', // magenta\n ];\n function projColor(name) {\n let h = 0;\n for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;\n return PROJECT_COLORS[h % PROJECT_COLORS.length];\n }\n\n function renderProjects(projects) {\n const el = $('#proj-chart');\n el.innerHTML = '';\n if (!projects.length) {\n el.innerHTML = '<div class=\"empty\">No projects yet.</div>';\n return;\n }\n const ranked = [...projects].sort((a, b) => (b.total_turns || 0) - (a.total_turns || 0));\n const max = Math.max(1, ...ranked.map((p) => p.total_turns || 0));\n const frag = document.createDocumentFragment();\n for (const p of ranked) {\n const turns = p.total_turns || 0;\n const pct = Math.max(4, Math.round((turns / max) * 100));\n const row = document.createElement('button');\n row.className = 'proj-row';\n row.type = 'button';\n row.style.setProperty('--pc', projColor(p.name));\n row.innerHTML =\n '<span class=\"pr-top\">' +\n '<span class=\"pr-dot\"></span>' +\n '<span class=\"pr-name\" title=\"' + (p.path || p.name) + '\">' + p.name + '</span>' +\n '<span class=\"pr-turns\">' + fmtPlain(turns) + '</span>' +\n '<span class=\"pr-arrow\">›</span>' +\n '</span>' +\n '<span class=\"pr-bar\"><span class=\"pr-fill\" style=\"width:' + pct + '%\"></span></span>';\n row.addEventListener('click', () => openProjectDialog(p.name));\n frag.appendChild(row);\n }\n el.appendChild(frag);\n }\n\n // ----- Project dialog -----\n function lastActiveFor(name) {\n const turns = lastData?.recent_turns || [];\n for (const t of turns) {\n if (t.project_name === name) {\n const ts = t.ts || t.written_at;\n if (!ts) return '—';\n try {\n return new Date(ts).toLocaleString([], {\n year: 'numeric', month: 'short', day: 'numeric',\n hour: '2-digit', minute: '2-digit',\n });\n } catch { return ts; }\n }\n }\n return '—';\n }\n\n function openProjectDialog(name) {\n const p = (lastData?.projects || []).find((x) => x.name === name);\n if (!p) return;\n $('#d-name').textContent = p.name;\n $('#d-name').style.setProperty('--pc', projColor(p.name));\n $('#d-name').classList.add('has-accent');\n $('#d-path').textContent = p.path || '';\n $('#d-cost').textContent = fmtCostFlat(p.estimated_cost_usd);\n $('#d-turns').textContent = fmtPlain(p.total_turns);\n $('#d-input').textContent = fmtPlain(p.total_input_tokens);\n $('#d-output').textContent = fmtPlain(p.total_output_tokens);\n $('#d-cache-r').textContent = fmtPlain(p.total_cache_read);\n $('#d-cache-w').textContent = fmtPlain(p.total_cache_create);\n $('#d-blocks').textContent = fmtPlain(p.blocked_count);\n $('#d-last').textContent = lastActiveFor(p.name);\n $('#dialog-backdrop').classList.remove('hidden');\n }\n\n function closeProjectDialog() {\n $('#dialog-backdrop').classList.add('hidden');\n }\n\n $('#dialog-close').addEventListener('click', closeProjectDialog);\n $('#dialog-backdrop').addEventListener('click', (e) => {\n if (e.target.id === 'dialog-backdrop') closeProjectDialog();\n });\n\n // ----- FAQ dialog -----\n const faqBackdrop = $('#faq-backdrop');\n function openFaq() { faqBackdrop.classList.remove('hidden'); }\n function closeFaq() { faqBackdrop.classList.add('hidden'); }\n $('#faq-btn').addEventListener('click', openFaq);\n $('#faq-close').addEventListener('click', closeFaq);\n faqBackdrop.addEventListener('click', (e) => {\n if (e.target.id === 'faq-backdrop') closeFaq();\n });\n\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') { closeProjectDialog(); closeFaq(); }\n });\n\n $('#turns-prev').addEventListener('click', () => gotoTurnsPage(-1));\n $('#turns-next').addEventListener('click', () => gotoTurnsPage(1));\n\n // Reflect the actual port the dashboard is served on\n const portEl = $('#port-num');\n if (portEl && window.location.port) portEl.textContent = window.location.port;\n\n // ----- Donut chart (all-time model usage by turn count) -----\n // Takes a { model-string -> count } map summed across ALL projects, so it\n // reflects true all-time usage — independent of the recent-turns cap.\n function renderDonut(modelCounts) {\n const counts = { opus: 0, sonnet: 0, haiku: 0, unknown: 0 };\n for (const [model, n] of Object.entries(modelCounts || {})) counts[modelFamily(model)] += n;\n const total = counts.opus + counts.sonnet + counts.haiku + counts.unknown;\n\n const segs = [\n { fam: 'opus', label: 'Opus', n: counts.opus, color: 'var(--c-opus)' },\n { fam: 'sonnet', label: 'Sonnet', n: counts.sonnet, color: 'var(--c-sonnet)' },\n { fam: 'haiku', label: 'Haiku', n: counts.haiku, color: 'var(--c-haiku)' },\n { fam: 'unknown', label: 'Other', n: counts.unknown, color: 'var(--c-unknown)' },\n ].filter((s) => s.n > 0);\n\n const svg = $('#donut-svg');\n svg.querySelectorAll('.donut-seg').forEach((el) => el.remove());\n\n const C = 2 * Math.PI * 52; // ≈ 326.7\n let offset = 0;\n const ns = 'http://www.w3.org/2000/svg';\n if (total === 0) {\n // pleasant empty state — leave just the track\n } else {\n for (const s of segs) {\n const arc = (s.n / total) * C;\n const c = document.createElementNS(ns, 'circle');\n c.setAttribute('cx', '70');\n c.setAttribute('cy', '70');\n c.setAttribute('r', '52');\n c.setAttribute('fill', 'none');\n c.setAttribute('stroke', s.color);\n c.setAttribute('stroke-width', '14');\n c.setAttribute('stroke-dasharray', arc + ' ' + C);\n c.setAttribute('stroke-dashoffset', String(-offset));\n c.setAttribute('transform', 'rotate(-90 70 70)');\n c.setAttribute('stroke-linecap', segs.length === 1 ? 'round' : 'butt');\n c.classList.add('donut-seg');\n svg.appendChild(c);\n offset += arc;\n }\n }\n\n $('#donut-total').textContent = total;\n\n const legend = $('#donut-legend');\n legend.innerHTML = '';\n const lf = document.createDocumentFragment();\n const display = segs.length ? segs : [\n { fam: 'opus', label: 'Opus', n: 0, color: 'var(--c-opus)' },\n { fam: 'sonnet', label: 'Sonnet', n: 0, color: 'var(--c-sonnet)' },\n { fam: 'haiku', label: 'Haiku', n: 0, color: 'var(--c-haiku)' },\n ];\n for (const s of display) {\n const pct = total > 0 ? Math.round((s.n / total) * 100) : 0;\n const row = document.createElement('div');\n row.className = 'dl-row';\n row.innerHTML =\n '<span class=\"dl-dot\" style=\"background:' + s.color + '\"></span>' +\n '<span class=\"dl-name\">' + s.label + '</span>' +\n '<span class=\"dl-count\">' + s.n + '</span>' +\n '<span class=\"dl-pct\">' + pct + '%</span>';\n lf.appendChild(row);\n }\n legend.appendChild(lf);\n }\n\n // ----- master render -----\n function applyData(data) {\n const turns = data.recent_turns || [];\n const gates = data.recent_gates || [];\n\n renderSession(turns);\n renderSavings(data.global);\n renderCostHero(data.global);\n renderMoat(data.global);\n renderTurns(turns);\n renderGateMini(gates);\n\n // Donut: sum per-project model counts (uncapped, full history) so it\n // shows true all-time usage rather than just the recent-turns window.\n const modelCounts = {};\n for (const p of (data.projects || []))\n for (const [m, n] of Object.entries(p.models || {}))\n modelCounts[m] = (modelCounts[m] || 0) + n;\n renderDonut(modelCounts);\n }\n\n // ----- polling -----\n async function tick() {\n try {\n const res = await fetch('/data');\n if (!res.ok) throw new Error('HTTP ' + res.status);\n const data = await res.json();\n lastData = data;\n { const ap = data.active?.project_root || '—'; const el = $('#active-project'); el.textContent = ap; el.title = ap; }\n renderProjects(data.projects || []);\n applyData(data);\n $('#status').textContent = 'live · ' + new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n $('#dot').classList.add('live'); $('#dot').classList.remove('dead');\n } catch (e) {\n $('#status').textContent = 'offline';\n $('#dot').classList.add('dead'); $('#dot').classList.remove('live');\n }\n }\n\n // ----- Viewport-clamped tooltip -----\n const tooltipEl = document.createElement('div');\n tooltipEl.className = 'global-tooltip';\n document.body.appendChild(tooltipEl);\n let activeTooltipTarget = null;\n\n function positionTooltip(target) {\n const rect = target.getBoundingClientRect();\n const ttRect = tooltipEl.getBoundingClientRect();\n const margin = 12;\n let top = rect.top - ttRect.height - 10;\n let left = rect.left + rect.width / 2 - ttRect.width / 2;\n if (left < margin) left = margin;\n if (left + ttRect.width > window.innerWidth - margin) {\n left = window.innerWidth - ttRect.width - margin;\n }\n if (top < margin) {\n top = rect.bottom + 10;\n }\n if (top + ttRect.height > window.innerHeight - margin) {\n top = window.innerHeight - ttRect.height - margin;\n }\n tooltipEl.style.top = top + 'px';\n tooltipEl.style.left = left + 'px';\n }\n\n function showTooltip(target) {\n const text = target.getAttribute('data-tooltip');\n if (!text) return;\n tooltipEl.textContent = text;\n tooltipEl.classList.add('on');\n positionTooltip(target);\n }\n\n function hideTooltip() {\n tooltipEl.classList.remove('on');\n }\n\n document.addEventListener('mouseover', (e) => {\n const t = (e.target instanceof Element) ? e.target.closest('.has-tooltip') : null;\n if (t !== activeTooltipTarget) {\n activeTooltipTarget = t;\n if (t) showTooltip(t);\n else hideTooltip();\n }\n });\n document.addEventListener('scroll', hideTooltip, true);\n document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideTooltip(); });\n\n setHeroDate();\n tick();\n setInterval(tick, 10000);\n </script>\n</body>\n</html>\n","/* Synthra dashboard · v0.2 · Cool Marine\n Darkened surfaces; brand blue reserved for hero elements only.\n Layout: top nav + hero strip + 3-column main, fits 1280×720. */\n\n:root {\n /* Core palette */\n --ink: #04081A;\n --navy: #0A1530;\n --navy-2: #122549;\n --deep-blue: #1B3A78;\n --blue: #2C5DB8;\n --blue-bright: #5C8FE6;\n --sky: #9BC2EF;\n --mist: #D7E6F7;\n --bone: #F4F7FC;\n\n /* Text */\n --text: #ECF2FB;\n --text-dim: #A9BBD6;\n --text-mute: #6D80A0;\n\n /* Rules / dividers */\n --rule: rgba(155, 194, 239, .14);\n --rule-2: rgba(155, 194, 239, .06);\n --rule-hover: rgba(155, 194, 239, .28);\n\n /* Surfaces (darker than v0.1.2) */\n --surface-1: rgba(18, 37, 73, .14);\n --surface-2: rgba(18, 37, 73, .22);\n --surface-3: rgba(4, 8, 26, .55);\n\n /* Signal accents (OKLCH shared chroma) */\n --signal-cyan: oklch(78% 0.14 220);\n --signal-amber: oklch(78% 0.14 75);\n --signal-rose: oklch(70% 0.14 20);\n --signal-green: oklch(75% 0.14 155);\n --signal-violet: oklch(72% 0.14 285);\n\n /* Model family colors */\n --c-opus: #FF6338;\n --c-sonnet: #FFB938;\n --c-haiku: #7438FF;\n --c-unknown: #12CBF5;\n\n /* Money */\n --money: var(--signal-green);\n\n /* Type */\n --font-sans: \"Geist\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n --font-serif: \"Instrument Serif\", \"Times New Roman\", serif;\n --font-mono: \"Geist Mono\", ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;\n}\n\n/* ============================================================\n Reset + base\n ============================================================ */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml,\nbody {\n margin: 0;\n padding: 0;\n}\n\nhtml,\nbody {\n height: 100vh;\n overflow: hidden;\n}\n\nbody {\n background: var(--ink);\n color: var(--text);\n font-family: var(--font-sans);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n display: grid;\n grid-template-rows: auto 1fr auto;\n position: relative;\n}\n\n/* Layered backdrop — quieter */\nbody::before,\nbody::after {\n content: \"\";\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n}\n\nbody::before {\n background-image: radial-gradient(circle, rgba(155, 194, 239, .06) 1px, transparent 1.2px);\n background-size: 22px 22px;\n}\n\nbody::after {\n background:\n radial-gradient(60% 40% at 50% 105%, rgba(44, 93, 184, .16) 0%, rgba(10, 21, 48, 0) 65%),\n radial-gradient(30% 25% at 50% 0%, rgba(92, 143, 230, .06) 0%, transparent 70%);\n}\n\nbody>* {\n position: relative;\n z-index: 1;\n}\n\nbutton {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: transparent;\n color: inherit;\n}\n\na {\n color: inherit;\n text-decoration: none;\n}\n\n/* ============================================================\n Top nav\n ============================================================ */\n.topnav {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n align-items: center;\n height: 52px;\n padding: 0 24px;\n border-bottom: 1px solid var(--rule);\n background: linear-gradient(180deg, rgba(4, 8, 26, .7), rgba(4, 8, 26, .4));\n backdrop-filter: blur(10px);\n}\n\n.brand {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.brand-mark {\n width: 22px;\n height: 22px;\n border-radius: 7px;\n background: radial-gradient(120% 120% at 30% 30%, #6FA6E8 0%, #2C5DB8 45%, #0A1530 100%);\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22), 0 4px 12px -6px #2C5DB8;\n}\n\n.brand-name {\n font-size: 15px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--mist);\n}\n\n.brand-name em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n color: var(--sky);\n letter-spacing: 0;\n}\n\n.brand-eyebrow {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-left: 6px;\n padding-left: 10px;\n border-left: 1px solid var(--rule);\n}\n\n.top-right {\n display: flex;\n align-items: center;\n gap: 12px;\n grid-column: 2;\n justify-self: center;\n}\n\n.topnav-right {\n grid-column: 3;\n justify-self: end;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.port-badge {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-mute);\n padding: 6px 10px;\n border: 1px solid var(--rule);\n border-radius: 999px;\n background: rgba(4, 8, 26, .55);\n}\n\n.port-badge .mono {\n color: var(--text-dim);\n letter-spacing: 0.04em;\n text-transform: none;\n}\n\n.faq-btn {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 1px solid var(--rule);\n background: rgba(4, 8, 26, .55);\n color: var(--text-dim);\n font-family: var(--font-mono);\n font-size: 13px;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: background 180ms, border-color 180ms, color 180ms, transform 180ms;\n}\n\n.faq-btn:hover {\n background: rgba(155, 194, 239, .10);\n border-color: var(--rule-hover);\n color: var(--mist);\n transform: translateY(-1px);\n}\n\n.status-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n border: 1px solid var(--rule);\n border-radius: 999px;\n background: rgba(4, 8, 26, .55);\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n transition: border-color 240ms ease;\n}\n\n.status-pill:has(.dot.live) {\n border-color: rgba(155, 194, 239, .45);\n color: var(--mist);\n animation: pill-glow 2.4s ease-in-out infinite;\n}\n\n.status-pill:has(.dot.dead) {\n border-color: rgba(220, 90, 90, .40);\n color: oklch(80% 0.10 20);\n}\n\n@keyframes pill-glow {\n\n 0%,\n 100% {\n box-shadow: 0 0 14px -4px rgba(155, 194, 239, .30), inset 0 0 12px -8px rgba(155, 194, 239, .30);\n }\n\n 50% {\n box-shadow: 0 0 26px -2px rgba(155, 194, 239, .55), inset 0 0 18px -6px rgba(155, 194, 239, .45);\n }\n}\n\n.dot {\n width: 7px;\n height: 7px;\n border-radius: 2px;\n background: var(--text-mute);\n transition: background 200ms;\n}\n\n.dot.live {\n background: var(--signal-cyan);\n animation: dot-pulse 1.8s ease-in-out infinite;\n}\n\n.dot.dead {\n background: var(--signal-rose);\n box-shadow: 0 0 0 3px rgba(220, 90, 90, .10);\n}\n\n@keyframes dot-pulse {\n\n 0%,\n 100% {\n box-shadow:\n 0 0 0 3px rgba(155, 194, 239, .10),\n 0 0 6px rgba(155, 194, 239, .50);\n }\n\n 50% {\n box-shadow:\n 0 0 0 6px rgba(155, 194, 239, .05),\n 0 0 14px rgba(155, 194, 239, .90);\n }\n}\n\n/* ============================================================\n Hero strip\n ============================================================ */\n.hero-strip {\n display: flex;\n align-items: center;\n gap: 24px;\n padding: 14px 24px;\n border-bottom: 1px solid var(--rule);\n background: linear-gradient(90deg, rgba(27, 58, 120, .10) 0%, rgba(4, 8, 26, 0) 100%);\n position: relative;\n overflow: hidden;\n}\n\n.hero-spacer {\n flex: 1;\n}\n\n.date-block {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.d-day {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 38px;\n line-height: 1;\n letter-spacing: -0.04em;\n color: var(--mist);\n}\n\n.d-rest {\n display: flex;\n flex-direction: column;\n gap: 2px;\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-dim);\n}\n\n.d-rest .d-mute {\n color: var(--text-mute);\n}\n\n.active-block {\n display: flex;\n flex-direction: column;\n gap: 2px;\n text-align: right;\n max-width: 360px;\n overflow: hidden;\n}\n\n.ab-label {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.ab-value {\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--mist);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 360px;\n}\n\n/* ============================================================\n Main grid\n ============================================================ */\n.grid-main {\n display: grid;\n grid-template-columns: 260px 1fr 340px;\n gap: 16px;\n padding: 16px 24px;\n min-height: 0;\n z-index: 10;\n}\n\n.col-left,\n.col-center,\n.col-right {\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-height: 0;\n}\n\n/* ============================================================\n Panels / cards — darker\n ============================================================ */\n.panel,\n.card {\n position: relative;\n border: 1px solid var(--rule);\n border-radius: 14px;\n background: var(--surface-1);\n padding: 14px 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-height: 0;\n transition: border-color 180ms ease, background 180ms ease;\n}\n\n.card.has-tooltip {\n cursor: help;\n}\n\n.card.has-tooltip:hover {\n border-color: var(--rule-hover);\n background: var(--surface-2);\n}\n\n.card-head {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 12px;\n}\n\n.card-eyebrow {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.card-eyebrow em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n font-size: 12px;\n color: var(--sky);\n letter-spacing: 0;\n text-transform: none;\n}\n\n.card-meta {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n/* Legend panel */\n.panel {\n padding: 14px 14px 16px;\n gap: 14px;\n flex-shrink: 0;\n}\n\n.p-head {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n padding-bottom: 10px;\n border-bottom: 1px solid var(--rule-2);\n}\n\n.p-section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.p-section+.p-section {\n padding-top: 12px;\n border-top: 1px solid var(--rule-2);\n}\n\n.ps-head {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-bottom: 4px;\n}\n\n.check {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 3px 6px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-dim);\n letter-spacing: 0.02em;\n}\n\nbutton.check {\n border: 0;\n background: transparent;\n width: 100%;\n text-align: left;\n}\n\n.check-clickable {\n cursor: pointer;\n border-radius: 6px;\n padding: 5px 6px;\n transition: background 140ms, color 140ms, transform 140ms;\n}\n\n.check-clickable .pf-arrow {\n margin-left: auto;\n color: var(--text-mute);\n font-family: var(--font-mono);\n font-size: 12px;\n transition: color 140ms, transform 140ms;\n}\n\n.check-clickable:hover {\n background: rgba(155, 194, 239, .07);\n color: var(--mist);\n}\n\n.check-clickable:hover .pf-arrow {\n color: var(--sky);\n transform: translateX(2px);\n}\n\n.dot-sq {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n background: var(--text-mute);\n flex-shrink: 0;\n}\n\n.dot-sq.opus {\n background: var(--c-opus);\n}\n\n.dot-sq.sonnet {\n background: var(--c-sonnet);\n}\n\n.dot-sq.haiku {\n background: var(--c-haiku);\n}\n\n.dot-sq.unknown {\n background: var(--c-unknown);\n}\n\n.proj-filter {\n display: flex;\n flex-direction: column;\n gap: 1px;\n max-height: 90px;\n overflow-y: auto;\n}\n\n.pf-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 140px;\n}\n\n/* ============================================================\n Donut card (model usage)\n ============================================================ */\n.donut-card {\n flex: 1;\n gap: 10px;\n}\n\n.donut-wrap {\n position: relative;\n width: 140px;\n height: 140px;\n margin: 4px auto 0;\n}\n\n.donut {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.donut-track {\n fill: none;\n stroke: rgba(155, 194, 239, .07);\n stroke-width: 14;\n}\n\n.donut-seg {\n transition: stroke-dashoffset 400ms ease, stroke-dasharray 400ms ease;\n}\n\n.donut-center {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n pointer-events: none;\n}\n\n.donut-total {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 26px;\n letter-spacing: -0.02em;\n color: var(--mist);\n line-height: 1;\n}\n\n.donut-total-k {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-top: 4px;\n}\n\n.donut-legend {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 6px;\n padding-top: 10px;\n border-top: 1px solid var(--rule-2);\n}\n\n.dl-row {\n display: grid;\n grid-template-columns: auto 1fr auto;\n align-items: center;\n gap: 8px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-dim);\n}\n\n.dl-dot {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n}\n\n.dl-name {\n color: var(--text-dim);\n}\n\n.dl-pct {\n color: var(--mist);\n font-weight: 500;\n}\n\n/* ============================================================\n Center column — Metric strip (no card chrome, divider-separated)\n ============================================================ */\n.metric-strip {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n border: 1px solid var(--rule);\n border-radius: 14px;\n background: var(--surface-1);\n overflow: hidden;\n flex-shrink: 0;\n}\n\n.metric-item {\n padding: 14px 18px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n cursor: help;\n border-right: 1px solid var(--rule-2);\n transition: background 200ms ease;\n min-width: 0;\n}\n.metric-item:last-child { border-right: 0; }\n.metric-item:hover { background: var(--surface-2); }\n\n.m-label {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.m-value {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 26px;\n letter-spacing: -0.025em;\n color: var(--mist);\n line-height: 1;\n}\n\n.m-value em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n color: var(--sky);\n letter-spacing: -0.005em;\n}\n\n/* ============================================================\n Savings card\n ============================================================ */\n.card.savings {\n flex-shrink: 0;\n gap: 12px;\n background:\n linear-gradient(180deg, rgba(123, 255, 199, .05) 0%, rgba(4, 8, 26, .20) 50%),\n var(--surface-1);\n border-color: rgba(123, 255, 199, .18);\n}\n\n.card.savings:hover {\n border-color: rgba(123, 255, 199, .32);\n}\n\n.savings-body {\n display: grid;\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 18px;\n}\n\n.savings-figure {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.savings-money {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 34px;\n letter-spacing: -0.035em;\n line-height: 1;\n color: var(--money);\n}\n\n.savings-tokens {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.10em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-top: 4px;\n}\n\n.savings-bar {\n position: relative;\n height: 8px;\n border-radius: 999px;\n overflow: hidden;\n background: var(--surface-3);\n display: flex;\n}\n\n.savings-actual {\n height: 100%;\n background: rgba(215, 230, 247, .55);\n transition: width 500ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.savings-saved {\n height: 100%;\n background: var(--signal-green);\n transition: width 500ms cubic-bezier(0.16, 1, 0.3, 1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);\n}\n\n.savings-legend {\n grid-column: 2;\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 24px;\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.08em;\n color: var(--text-mute);\n margin-top: 8px;\n}\n\n.sl-row {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.sl-row b {\n color: var(--mist);\n font-weight: 500;\n letter-spacing: 0.04em;\n}\n\n.sl-dot {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n}\n\n.sl-dot.actual {\n background: var(--mist);\n}\n\n.sl-dot.saved {\n background: var(--signal-green);\n}\n\n/* ============================================================\n Recent turns table\n ============================================================ */\n.turns-card {\n flex: 1;\n padding: 0;\n overflow: hidden;\n}\n\n.turns-card .card-head {\n padding: 14px 16px 10px;\n border-bottom: 1px solid var(--rule-2);\n}\n\n.turns-scroll {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n}\n\n.turns-table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.turns-table thead th {\n position: sticky;\n top: 0;\n background: var(--ink);\n padding: 9px 16px;\n text-align: left;\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n font-weight: 500;\n border-bottom: 1px solid var(--rule);\n z-index: 1;\n}\n\n.turns-table thead th.num {\n text-align: right;\n}\n\n.turns-table tbody td {\n padding: 8px 16px;\n border-bottom: 1px solid var(--rule-2);\n color: var(--text-dim);\n font-size: 12px;\n}\n\n.turns-table tbody td.num {\n text-align: right;\n font-family: var(--font-mono);\n}\n\n.turns-table tbody td.cost {\n color: var(--money);\n font-family: var(--font-mono);\n font-weight: 500;\n}\n\n.turns-table tbody td.ts {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-mute);\n}\n\n.turns-table tbody td.proj {\n color: var(--mist);\n}\n\n.turns-table tbody tr:hover {\n background: rgba(155, 194, 239, .03);\n}\n\n.turns-table tbody tr:last-child td {\n border-bottom: 0;\n}\n\n/* Model pills */\n.model-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 2px 8px;\n border-radius: 999px;\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.04em;\n border: 1px solid var(--rule);\n background: rgba(4, 8, 26, .5);\n color: var(--mist);\n}\n\n.model-pill .sq {\n width: 6px;\n height: 6px;\n border-radius: 2px;\n background: var(--text-mute);\n}\n\n.model-pill.opus {\n color: #FF8A66;\n border-color: rgba(255, 99, 56, .32);\n background: rgba(255, 99, 56, .07);\n}\n\n.model-pill.opus .sq {\n background: var(--c-opus);\n}\n\n.model-pill.sonnet {\n color: #FFC766;\n border-color: rgba(255, 185, 56, .32);\n background: rgba(255, 185, 56, .07);\n}\n\n.model-pill.sonnet .sq {\n background: var(--c-sonnet);\n}\n\n.model-pill.haiku {\n color: #A878FF;\n border-color: rgba(116, 56, 255, .42);\n background: rgba(116, 56, 255, .10);\n}\n\n.model-pill.haiku .sq {\n background: var(--c-haiku);\n}\n\n.model-pill.unknown {\n color: #5BDDF7;\n border-color: rgba(18, 203, 245, .32);\n background: rgba(18, 203, 245, .07);\n font-style: italic;\n}\n\n.model-pill.unknown .sq {\n background: var(--c-unknown);\n}\n\n/* ============================================================\n Right column — Cost hero\n ============================================================ */\n.cost-hero {\n position: relative;\n overflow: hidden;\n background:\n radial-gradient(120% 80% at 50% 110%, rgba(44, 93, 184, .18) 0%, rgba(4, 8, 26, .20) 60%),\n var(--surface-1);\n padding: 16px 18px 18px;\n gap: 10px;\n flex-shrink: 0;\n}\n\n.big-money {\n position: relative;\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 42px;\n letter-spacing: -0.035em;\n line-height: 1;\n color: var(--money);\n margin-top: 2px;\n}\n\n.big-money em {\n font-family: inherit;\n font-style: normal;\n font-weight: inherit;\n color: inherit;\n letter-spacing: inherit;\n opacity: 1;\n}\n\n.cost-sub {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 4px;\n padding-top: 10px;\n border-top: 1px solid var(--rule-2);\n}\n\n.cs-row {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n font-family: var(--font-mono);\n font-size: 11px;\n}\n\n.cs-k {\n font-size: 10px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.cs-v {\n color: var(--mist);\n}\n\n/* ============================================================\n Moat card\n ============================================================ */\n.moat {\n flex: 1;\n gap: 8px;\n}\n\n.moat-value {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 34px;\n letter-spacing: -0.03em;\n line-height: 1;\n color: var(--mist);\n margin-top: 2px;\n}\n\n.moat-value em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n font-size: 18px;\n color: var(--sky);\n letter-spacing: 0;\n margin-left: 6px;\n}\n\n.gate-mini {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 6px;\n padding-top: 10px;\n border-top: 1px solid var(--rule-2);\n overflow-y: auto;\n flex: 1;\n min-height: 0;\n}\n\n.gate-row {\n display: grid;\n grid-template-columns: auto auto 1fr;\n align-items: center;\n gap: 8px;\n font-family: var(--font-mono);\n font-size: 10px;\n color: var(--text-dim);\n padding: 3px 0;\n}\n\n.gate-row .g-ts {\n color: var(--text-mute);\n font-size: 9px;\n min-width: 38px;\n}\n\n.gate-row .g-decision {\n font-size: 9px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n padding: 2px 6px;\n border-radius: 999px;\n}\n\n.gate-row .g-decision.block {\n color: var(--signal-rose);\n background: rgba(220, 90, 90, .06);\n}\n\n.gate-row .g-decision.allow {\n color: var(--text-mute);\n background: rgba(155, 194, 239, .03);\n}\n\n.gate-row .g-q {\n color: var(--mist);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ============================================================\n Tooltips\n ============================================================ */\n.has-tooltip {\n position: relative;\n}\n\n/* Global JS-positioned tooltip — viewport-clamped */\n.global-tooltip {\n position: fixed;\n top: 0;\n left: 0;\n background: linear-gradient(180deg, rgba(18, 37, 73, .98), rgba(10, 21, 48, .98));\n color: var(--mist);\n border: 1px solid var(--rule-hover);\n border-radius: 12px;\n padding: 14px 16px;\n font-family: var(--font-sans);\n font-size: 15px;\n font-weight: 400;\n text-transform: none;\n letter-spacing: 0;\n white-space: normal;\n width: 320px;\n max-width: calc(100vw - 24px);\n text-align: left;\n line-height: 1.55;\n box-shadow: 0 16px 36px rgba(0, 0, 0, .7);\n backdrop-filter: blur(10px);\n z-index: 99999;\n opacity: 0;\n pointer-events: none;\n transform: translateY(6px);\n transition: opacity 180ms ease, transform 180ms ease;\n}\n\n.global-tooltip.on {\n opacity: 1;\n transform: translateY(0);\n}\n\n/* ============================================================\n Footer\n ============================================================ */\n.foot {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 24px;\n border-top: 1px solid var(--rule);\n background: linear-gradient(0deg, rgba(4, 8, 26, .7), rgba(4, 8, 26, .4));\n backdrop-filter: blur(10px);\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.foot em {\n font-family: var(--font-serif);\n font-style: italic;\n text-transform: none;\n letter-spacing: 0;\n color: var(--sky);\n font-size: 12px;\n}\n\n.foot .mono {\n color: var(--text-dim);\n text-transform: none;\n letter-spacing: 0.04em;\n}\n\n/* ============================================================\n Empty state\n ============================================================ */\n.empty {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.06em;\n color: var(--text-mute);\n text-align: center;\n padding: 16px 8px;\n font-style: italic;\n text-transform: none;\n}\n\n/* Scrollbar styling */\n.turns-scroll::-webkit-scrollbar,\n.proj-chart::-webkit-scrollbar,\n.gate-mini::-webkit-scrollbar {\n width: 6px;\n}\n\n.turns-scroll::-webkit-scrollbar-thumb,\n.proj-chart::-webkit-scrollbar-thumb,\n.gate-mini::-webkit-scrollbar-thumb {\n background: var(--rule);\n border-radius: 999px;\n}\n\n.turns-scroll::-webkit-scrollbar-track,\n.proj-chart::-webkit-scrollbar-track,\n.gate-mini::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.hidden {\n display: none !important;\n}\n\n/* ============================================================\n Staggered cascade on first paint (one-time, MOTION 6)\n ============================================================ */\n@keyframes cascade-in {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .col-left > *,\n .col-center > *,\n .col-right > * {\n opacity: 0;\n animation: cascade-in 520ms cubic-bezier(0.16, 1, 0.3, 1) forwards;\n will-change: transform, opacity;\n }\n .col-left > *:nth-child(1) { animation-delay: 0ms; }\n .col-left > *:nth-child(2) { animation-delay: 120ms; }\n .col-center > *:nth-child(1) { animation-delay: 40ms; }\n .col-center > *:nth-child(2) { animation-delay: 140ms; }\n .col-center > *:nth-child(3) { animation-delay: 240ms; }\n .col-right > *:nth-child(1) { animation-delay: 80ms; }\n .col-right > *:nth-child(2) { animation-delay: 200ms; }\n\n /* Clear will-change after animation completes */\n .col-left > *,\n .col-center > *,\n .col-right > * {\n animation-fill-mode: forwards;\n }\n}\n\n/* ============================================================\n Source / basis annotations\n ============================================================ */\n.card-source {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.08em;\n text-transform: lowercase;\n color: var(--text-mute);\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-top: auto;\n padding-top: 8px;\n border-top: 1px solid var(--rule-2);\n width: 100%;\n}\n\n.src-badge {\n font-family: var(--font-mono);\n font-size: 8px;\n font-weight: 500;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n padding: 2px 6px;\n border-radius: 4px;\n flex-shrink: 0;\n}\n\n.src-badge.verified {\n color: var(--signal-green);\n background: rgba(123, 255, 199, .08);\n border: 1px solid rgba(123, 255, 199, .25);\n}\n\n.src-badge.estimated,\n.src-badge.estimated.floor {\n color: var(--signal-amber);\n background: rgba(255, 185, 56, .10);\n border: 1px solid rgba(255, 185, 56, .30);\n}\n\n.src-badge.priced {\n color: var(--signal-cyan);\n background: rgba(155, 194, 239, .08);\n border: 1px solid rgba(155, 194, 239, .25);\n}\n\n/* Eyebrow that contains a badge */\n.card-eyebrow .src-badge {\n margin-left: 4px;\n}\n\n/* Savings audit row — live formula reveal */\n.savings-audit {\n margin-top: 10px;\n padding: 10px 12px;\n border: 1px dashed rgba(255, 185, 56, .25);\n border-radius: 8px;\n background: rgba(255, 185, 56, .04);\n font-family: var(--font-mono);\n font-size: 10.5px;\n letter-spacing: 0.04em;\n color: var(--text-mute);\n text-align: center;\n}\n\n.savings-audit b {\n color: var(--text-dim);\n font-weight: 500;\n}\n\n.savings-audit .audit-result {\n color: var(--money);\n}\n\n/* ============================================================\n FAQ dialog\n ============================================================ */\n.dialog.dialog-faq {\n max-width: min(80vw, 1100px);\n width: 100%;\n max-height: 86vh;\n display: flex;\n flex-direction: column;\n padding: 28px 32px 24px;\n gap: 6px;\n}\n\n.dialog.dialog-faq .dialog-path {\n margin-bottom: 4px;\n word-break: normal;\n overflow-wrap: anywhere;\n}\n\n.faq-content {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n margin-top: 18px;\n padding-right: 8px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.faq-content::-webkit-scrollbar {\n width: 6px;\n}\n\n.faq-content::-webkit-scrollbar-thumb {\n background: var(--rule);\n border-radius: 999px;\n}\n\n.faq-content::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.faq-content details {\n border: 1px solid var(--rule);\n border-radius: 12px;\n background: var(--surface-1);\n overflow: hidden;\n transition: background 180ms, border-color 180ms;\n flex-shrink: 0;\n}\n\n.faq-content details:hover {\n border-color: rgba(155, 194, 239, .22);\n}\n\n.faq-content details[open] {\n background: var(--surface-2);\n border-color: var(--rule-hover);\n}\n\n.faq-content summary {\n cursor: pointer;\n padding: 14px 20px;\n font-family: var(--font-sans);\n font-size: 14px;\n font-weight: 500;\n color: var(--mist);\n list-style: none;\n display: flex;\n align-items: center;\n gap: 12px;\n user-select: none;\n}\n\n.faq-content summary::-webkit-details-marker {\n display: none;\n}\n\n.faq-content summary::before {\n content: \"›\";\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n color: var(--text-mute);\n font-family: var(--font-mono);\n font-size: 14px;\n transition: transform 220ms ease, color 220ms ease;\n}\n\n.faq-content details[open] summary::before {\n transform: rotate(90deg);\n color: var(--sky);\n}\n\n.faq-content .faq-body {\n padding: 0 22px 20px 46px;\n color: var(--text-dim);\n font-size: 13.5px;\n line-height: 1.7;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.faq-content .faq-body p {\n margin: 0;\n}\n\n.faq-content .faq-body ul {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.faq-content .faq-body li {\n margin: 0;\n}\n\n.faq-content .faq-body b,\n.faq-content .faq-body strong {\n color: var(--mist);\n font-weight: 500;\n}\n\n.faq-content .faq-body code {\n font-family: var(--font-mono);\n font-size: 12px;\n background: rgba(155, 194, 239, .08);\n padding: 2px 6px;\n border-radius: 4px;\n color: var(--mist);\n border: 1px solid rgba(155, 194, 239, .12);\n word-break: break-word;\n}\n\n.faq-content .faq-body a {\n color: var(--blue-bright);\n text-decoration: underline;\n text-decoration-color: rgba(92, 143, 230, .40);\n text-underline-offset: 3px;\n transition: color 140ms, text-decoration-color 140ms;\n}\n\n.faq-content .faq-body a:hover {\n color: var(--mist);\n text-decoration-color: var(--sky);\n}\n\n.faq-content .faq-body table {\n width: 100%;\n border-collapse: collapse;\n margin: 4px 0;\n font-size: 13px;\n table-layout: fixed;\n}\n\n.faq-content .faq-body thead td {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.10em;\n text-transform: uppercase;\n color: var(--text-mute);\n padding-bottom: 6px;\n border-bottom: 1px solid var(--rule);\n font-weight: 500;\n}\n\n.faq-content .faq-body td {\n padding: 9px 10px;\n border-bottom: 1px solid var(--rule-2);\n vertical-align: top;\n word-break: break-word;\n}\n\n.faq-content .faq-body tr:last-child td {\n border-bottom: 0;\n}\n\n.faq-content .faq-body td:first-child {\n color: var(--text-dim);\n width: 38%;\n}\n\n.faq-content .faq-body td:first-child code {\n font-size: 11.5px;\n}\n\n.faq-content .faq-body .formula-box {\n font-family: var(--font-mono);\n font-size: 12.5px;\n background: rgba(255, 185, 56, .06);\n padding: 12px 14px;\n border-radius: 8px;\n border: 1px dashed rgba(255, 185, 56, .30);\n color: var(--mist);\n letter-spacing: 0.02em;\n}\n\n.faq-content .faq-body .link-list {\n list-style: none;\n padding-left: 0;\n}\n\n.faq-content .faq-body .link-list li {\n padding-left: 18px;\n position: relative;\n}\n\n.faq-content .faq-body .link-list li::before {\n content: \"›\";\n position: absolute;\n left: 0;\n color: var(--sky);\n font-family: var(--font-mono);\n}\n\n.faq-content .faq-body .warning {\n margin-top: 14px;\n padding: 12px 14px;\n background: rgba(255, 185, 56, .06);\n border: 1px solid rgba(255, 185, 56, .25);\n border-left: 3px solid var(--signal-amber);\n border-radius: 8px;\n font-size: 12.5px;\n color: var(--text-dim);\n}\n\n.faq-content .faq-body .warning .icon {\n color: var(--signal-amber);\n margin-right: 8px;\n font-weight: 500;\n}\n\n/* ============================================================\n Project dialog\n ============================================================ */\n.dialog-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(4, 8, 26, .78);\n backdrop-filter: blur(10px);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n animation: dlg-fade 180ms ease;\n}\n\n@keyframes dlg-fade {\n from {\n opacity: 0;\n }\n\n to {\n opacity: 1;\n }\n}\n\n.dialog {\n position: relative;\n width: 100%;\n max-width: 520px;\n background:\n radial-gradient(120% 80% at 50% 0%, rgba(44, 93, 184, .22) 0%, rgba(4, 8, 26, .20) 60%),\n linear-gradient(180deg, rgba(18, 37, 73, .88) 0%, rgba(10, 21, 48, .96) 100%);\n border: 1px solid var(--rule-hover);\n border-radius: 18px;\n padding: 28px 32px 32px;\n box-shadow:\n 0 30px 80px -20px rgba(0, 0, 0, .7),\n inset 0 1px 0 rgba(255, 255, 255, .04);\n animation: dlg-rise 220ms cubic-bezier(.2, .7, .2, 1);\n}\n\n@keyframes dlg-rise {\n from {\n opacity: 0;\n transform: translateY(8px) scale(.98);\n }\n\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.dialog-close {\n position: absolute;\n top: 14px;\n right: 14px;\n width: 30px;\n height: 30px;\n border-radius: 50%;\n color: var(--text-mute);\n font-size: 22px;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 180ms, color 180ms;\n}\n\n.dialog-close:hover {\n background: rgba(155, 194, 239, .10);\n color: var(--mist);\n}\n\n.dialog-eyebrow {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-bottom: 10px;\n}\n\n.dialog-eyebrow em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n font-size: 12px;\n color: var(--sky);\n letter-spacing: 0;\n text-transform: none;\n}\n\n.dialog-name {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 28px;\n letter-spacing: -0.025em;\n color: var(--mist);\n line-height: 1.1;\n}\n\n.dialog-path {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-mute);\n margin-top: 6px;\n word-break: break-all;\n}\n\n.dialog-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 18px 24px;\n margin-top: 22px;\n padding-top: 20px;\n border-top: 1px solid var(--rule-2);\n}\n\n.dg-cell {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.dg-k {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.dg-v {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 22px;\n letter-spacing: -0.02em;\n color: var(--mist);\n line-height: 1;\n}\n\n.dg-v.money {\n color: var(--money);\n}\n\n.dg-v-sm {\n font-size: 13px;\n font-family: var(--font-mono);\n font-weight: 400;\n color: var(--text-dim);\n letter-spacing: 0;\n}\n\n/* ============================================================\n v0.3 visual refresh\n - merged model-family legend into donut (count column)\n - Projects -> colored bar chart\n - elevated Savings card\n ============================================================ */\n\n/* Left column sizing: donut natural height, projects fills + scrolls */\n.donut-card { flex: 0 0 auto; }\n\n/* Donut legend now carries a count column */\n.dl-row {\n grid-template-columns: auto 1fr auto auto;\n gap: 8px;\n padding: 1px 0;\n}\n.dl-count {\n font-family: var(--font-mono);\n font-size: 10px;\n color: var(--text-mute);\n letter-spacing: 0.04em;\n font-variant-numeric: tabular-nums;\n}\n.dl-pct {\n min-width: 30px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n}\n\n/* ---- Projects bar chart ---- */\n.projects-card {\n flex: 1 1 auto;\n min-height: 0;\n gap: 10px;\n}\n.proj-chart {\n display: flex;\n flex-direction: column;\n gap: 9px;\n overflow-y: auto;\n min-height: 0;\n flex: 1;\n padding-right: 2px;\n}\n.proj-row {\n display: flex;\n flex-direction: column;\n gap: 7px;\n width: 100%;\n text-align: left;\n padding: 8px;\n border-radius: 9px;\n background: transparent;\n border: 0;\n cursor: pointer;\n transition: background 150ms ease;\n}\n.proj-row:hover { background: rgba(155, 194, 239, .055); }\n.pr-top {\n display: grid;\n grid-template-columns: auto 1fr auto auto;\n align-items: center;\n gap: 9px;\n}\n.pr-dot {\n width: 9px;\n height: 9px;\n border-radius: 3px;\n background: var(--pc, var(--sky));\n box-shadow: 0 0 9px -1px var(--pc, var(--sky));\n flex-shrink: 0;\n}\n.pr-name {\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--text-dim);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n letter-spacing: 0.01em;\n transition: color 150ms ease;\n}\n.proj-row:hover .pr-name { color: var(--mist); }\n.pr-turns {\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--mist);\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.02em;\n}\n.pr-arrow {\n font-family: var(--font-mono);\n font-size: 13px;\n color: var(--text-mute);\n transition: color 150ms ease, transform 150ms ease;\n}\n.proj-row:hover .pr-arrow {\n color: var(--pc, var(--sky));\n transform: translateX(2px);\n}\n.pr-bar {\n position: relative;\n height: 5px;\n border-radius: 999px;\n background: var(--surface-3);\n overflow: hidden;\n}\n.pr-fill {\n display: block;\n height: 100%;\n border-radius: 999px;\n background: linear-gradient(90deg,\n color-mix(in oklch, var(--pc, var(--sky)) 45%, transparent) 0%,\n var(--pc, var(--sky)) 100%);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18);\n transition: width 640ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n/* ---- Elevated Savings card (priority focus) ---- */\n.card.savings {\n background:\n radial-gradient(120% 140% at 10% -10%, rgba(123, 255, 199, .14) 0%, rgba(4, 8, 26, .08) 44%),\n linear-gradient(180deg, rgba(123, 255, 199, .05) 0%, rgba(4, 8, 26, .20) 52%),\n var(--surface-1);\n border-color: rgba(123, 255, 199, .24);\n box-shadow:\n inset 0 1px 0 rgba(123, 255, 199, .08),\n 0 20px 46px -30px rgba(123, 255, 199, .55);\n}\n.card.savings:hover {\n border-color: rgba(123, 255, 199, .38);\n}\n.savings-money {\n font-size: 40px;\n text-shadow: 0 0 26px rgba(123, 255, 199, .22);\n}\n.savings-bar { height: 9px; }\n.savings-saved {\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, .18),\n 0 0 12px -2px var(--signal-green);\n}\n\n/* Project dialog: name gets a project-colored accent dot */\n.dialog-name.has-accent {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.dialog-name.has-accent::before {\n content: \"\";\n width: 12px;\n height: 12px;\n border-radius: 4px;\n background: var(--pc, var(--sky));\n box-shadow: 0 0 12px -1px var(--pc, var(--sky));\n flex-shrink: 0;\n}\n\n\n/* ============================================================\n v0.3.1 — date + active project folded into the top nav\n ============================================================ */\n\n/* Let the right cluster shrink so the active path can ellipsize */\n.topnav-right { min-width: 0; }\n\n/* Compact date beside the brand */\n.nav-date {\n display: inline-flex;\n align-items: baseline;\n gap: 6px;\n margin-left: 8px;\n padding-left: 12px;\n border-left: 1px solid var(--rule);\n font-family: var(--font-mono);\n font-size: 11px;\n letter-spacing: 0.10em;\n text-transform: uppercase;\n white-space: nowrap;\n}\n.nav-date .nd-day { color: var(--mist); font-weight: 500; }\n.nav-date .nd-weekday { color: var(--text-dim); }\n.nav-date .nd-month { color: var(--text-mute); }\n\n/* Active project, compact, tail-truncated */\n.nav-active {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n max-width: 300px;\n padding-right: 12px;\n margin-right: 2px;\n border-right: 1px solid var(--rule);\n cursor: help;\n}\n.na-label {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: var(--text-mute);\n flex-shrink: 0;\n}\n.na-value {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--mist);\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n /* keep the project folder (tail) visible, ellipsize the drive prefix */\n direction: rtl;\n text-align: left;\n}\n\n/* Tighten nav on narrow widths */\n@media (max-width: 1100px) {\n .nav-active { max-width: 200px; }\n .nav-date .nd-month { display: none; }\n}\n\n\n/* Column headers signal they are hover-explainable */\n.turns-table thead th.has-tooltip { cursor: help; }\n.turns-table thead th.has-tooltip:hover { color: var(--text-dim); }\n\n/* ---- Recent-turns pager ---- */\n.turns-pager {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n padding: 8px 2px 0;\n margin-top: 6px;\n border-top: 1px solid var(--rule-2);\n}\n.turns-pager.hidden { display: none; }\n.turns-pager button {\n font-family: var(--font-mono);\n font-size: 11px;\n letter-spacing: 0.04em;\n color: var(--text-dim);\n background: rgba(4, 8, 26, .55);\n border: 1px solid var(--rule);\n border-radius: 7px;\n padding: 4px 10px;\n cursor: pointer;\n transition: background 150ms, border-color 150ms, color 150ms, transform 150ms;\n}\n.turns-pager button:hover:not(:disabled) {\n background: rgba(155, 194, 239, .10);\n border-color: var(--rule-hover);\n color: var(--mist);\n transform: translateY(-1px);\n}\n.turns-pager button:disabled {\n opacity: .35;\n cursor: default;\n}\n#turns-page-label {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-mute);\n letter-spacing: 0.04em;\n font-variant-numeric: tabular-nums;\n min-width: 84px;\n text-align: center;\n}\n","// Writes hook scripts into <project>/.claude/hooks/ and registers them in\n// <project>/.claude/settings.local.json. Idempotent — re-running `syn .`\n// regenerates the scripts and merges hook entries cleanly with any user-added\n// hooks already in the file.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { log } from \"../shared/logger.js\";\nimport type { SynthraPaths } from \"../shared/paths.js\";\n\nimport preCompactPs1 from \"./scripts/pre-compact.ps1\";\nimport preCompactSh from \"./scripts/pre-compact.sh\";\nimport preToolUsePs1 from \"./scripts/pre-tool-use.ps1\";\nimport preToolUseSh from \"./scripts/pre-tool-use.sh\";\nimport primePs1 from \"./scripts/prime.ps1\";\nimport primeSh from \"./scripts/prime.sh\";\nimport stopPs1 from \"./scripts/stop.ps1\";\nimport stopSh from \"./scripts/stop.sh\";\n\nexport interface InstallResult {\n scriptsWritten: string[];\n settingsUpdated: boolean;\n}\n\ninterface ScriptDef {\n /** Hook event name as recognized by Claude Code. */\n event: \"SessionStart\" | \"PreToolUse\" | \"PreCompact\" | \"Stop\";\n /** Tool-name regex for PreToolUse only. */\n matcher?: string;\n /** Base filename written into .claude/hooks/. */\n baseName: string;\n ps1: string;\n sh: string;\n}\n\nconst SCRIPTS: ScriptDef[] = [\n { event: \"SessionStart\", baseName: \"synthra-prime\", ps1: primePs1, sh: primeSh },\n { event: \"PreToolUse\", matcher: \"Grep|Glob\", baseName: \"synthra-pre-tool-use\", ps1: preToolUsePs1, sh: preToolUseSh },\n { event: \"PreCompact\", baseName: \"synthra-pre-compact\", ps1: preCompactPs1, sh: preCompactSh },\n { event: \"Stop\", baseName: \"synthra-stop\", ps1: stopPs1, sh: stopSh },\n];\n\nconst SYNTHRA_HOOK_MARKER = \"synthra-hook=true\";\n\nfunction commandFor(scriptPath: string): string {\n if (process.platform === \"win32\") {\n // PowerShell on Windows; -ExecutionPolicy Bypass so the script always runs.\n return `powershell.exe -ExecutionPolicy Bypass -NoProfile -File \"${scriptPath}\"`;\n }\n return `bash \"${scriptPath}\"`;\n}\n\nfunction chosenScriptBody(s: ScriptDef): string {\n return process.platform === \"win32\" ? s.ps1 : s.sh;\n}\n\nfunction chosenScriptExt(): string {\n return process.platform === \"win32\" ? \".ps1\" : \".sh\";\n}\n\ninterface HooksConfig {\n hooks?: {\n [event: string]: Array<{\n matcher?: string;\n hooks?: Array<{ type: string; command: string; meta?: string }>;\n }>;\n };\n [k: string]: unknown;\n}\n\nasync function readSettings(path: string): Promise<HooksConfig> {\n try {\n const raw = await readFile(path, \"utf8\");\n return JSON.parse(raw) as HooksConfig;\n } catch {\n return {};\n }\n}\n\nfunction stripOurHooks(config: HooksConfig): HooksConfig {\n if (!config.hooks) return config;\n const next: HooksConfig[\"hooks\"] = {};\n for (const [event, entries] of Object.entries(config.hooks)) {\n const filtered = entries\n .map((entry) => ({\n ...entry,\n hooks: (entry.hooks ?? []).filter((h) => h.meta !== SYNTHRA_HOOK_MARKER),\n }))\n .filter((entry) => (entry.hooks?.length ?? 0) > 0);\n if (filtered.length) next[event] = filtered;\n }\n config.hooks = next;\n return config;\n}\n\nfunction mergeOurHooks(config: HooksConfig, paths: SynthraPaths): HooksConfig {\n const hooks = (config.hooks = config.hooks ?? {});\n for (const s of SCRIPTS) {\n const scriptPath = join(paths.claudeHooksDir, `${s.baseName}${chosenScriptExt()}`);\n const entry = {\n ...(s.matcher ? { matcher: s.matcher } : {}),\n hooks: [\n {\n type: \"command\",\n command: commandFor(scriptPath),\n meta: SYNTHRA_HOOK_MARKER,\n },\n ],\n };\n const list = (hooks[s.event] = hooks[s.event] ?? []);\n list.push(entry);\n }\n return config;\n}\n\nexport async function installHooks(paths: SynthraPaths): Promise<InstallResult> {\n await mkdir(paths.claudeHooksDir, { recursive: true });\n\n const scriptsWritten: string[] = [];\n for (const s of SCRIPTS) {\n const target = join(paths.claudeHooksDir, `${s.baseName}${chosenScriptExt()}`);\n await writeFile(target, chosenScriptBody(s), \"utf8\");\n scriptsWritten.push(target);\n }\n\n await mkdir(dirname(paths.claudeSettings), { recursive: true });\n const existing = await readSettings(paths.claudeSettings);\n const stripped = stripOurHooks(existing);\n const merged = mergeOurHooks(stripped, paths);\n\n await writeFile(paths.claudeSettings, JSON.stringify(merged, null, 2) + \"\\n\", \"utf8\");\n\n log.debug(`installed ${scriptsWritten.length} hook script(s) into ${paths.claudeHooksDir}`);\n\n return { scriptsWritten, settingsUpdated: true };\n}\n","# PreCompact hook — Windows PowerShell.\n# Re-injects the primer after Claude auto-compacts. Same logic as prime.ps1.\n\n$ErrorActionPreference = \"SilentlyContinue\"\n\n$portFile = Join-Path $PWD \".synthra-graph\\mcp_port\"\nif (-not (Test-Path $portFile)) { exit 0 }\n$port = (Get-Content -Path $portFile -Raw).Trim()\nif (-not $port) { exit 0 }\n\ntry {\n $resp = Invoke-RestMethod -Uri \"http://127.0.0.1:$port/prime\" -Method GET -TimeoutSec 3\n if ($resp.primer) { Write-Output $resp.primer }\n} catch {\n # silent\n}\nexit 0\n","#!/usr/bin/env bash\n# PreCompact hook — bash. Re-injects the primer after Claude auto-compacts.\n\nset +e\n\nPORT_FILE=\"$PWD/.synthra-graph/mcp_port\"\nif [ ! -f \"$PORT_FILE\" ]; then exit 0; fi\nPORT=$(cat \"$PORT_FILE\" 2>/dev/null | tr -d '[:space:]')\nif [ -z \"$PORT\" ]; then exit 0; fi\n\nPRIMER=$(curl -sS --max-time 3 \"http://127.0.0.1:$PORT/prime\" 2>/dev/null \\\n | sed -n 's/.*\"primer\"[[:space:]]*:[[:space:]]*\"\\(.*\\)\".*/\\1/p' \\\n | head -c 8000)\n\nif [ -n \"$PRIMER\" ]; then\n printf '%b\\n' \"$PRIMER\"\nfi\nexit 0\n","# PreToolUse hook — Windows PowerShell.\n# THE MOAT (improvement #1). Reads the tool call from stdin (JSON), POSTs it\n# to /gate, and if the server says \"block\" emits a JSON deny-decision to\n# stdout. Claude Code reads stdout JSON to enforce the decision.\n# Always exits 0; failure-to-reach-server leaves Claude untouched.\n\n$ErrorActionPreference = \"SilentlyContinue\"\n\n$raw = [Console]::In.ReadToEnd()\nif (-not $raw) { exit 0 }\n\ntry {\n $hookInput = $raw | ConvertFrom-Json -ErrorAction Stop\n} catch {\n exit 0\n}\n\n$portFile = Join-Path $PWD \".synthra-graph\\mcp_port\"\nif (-not (Test-Path $portFile)) { exit 0 }\n$port = (Get-Content -Path $portFile -Raw).Trim()\nif (-not $port) { exit 0 }\n\n$payload = @{\n tool_name = $hookInput.tool_name\n tool_input = $hookInput.tool_input\n} | ConvertTo-Json -Depth 10 -Compress\n\ntry {\n $resp = Invoke-RestMethod -Uri \"http://127.0.0.1:$port/gate\" -Method POST `\n -Body $payload -ContentType \"application/json\" -TimeoutSec 3\n} catch {\n exit 0\n}\n\nif ($resp.decision -eq \"block\") {\n $denyJson = @{\n hookSpecificOutput = @{\n hookEventName = \"PreToolUse\"\n permissionDecision = \"deny\"\n permissionDecisionReason = $resp.reason\n }\n } | ConvertTo-Json -Depth 5 -Compress\n Write-Output $denyJson\n}\nexit 0\n","#!/usr/bin/env bash\n# PreToolUse hook — bash. POSTs the tool call to /gate; if server returns\n# \"block\", emits the deny-decision JSON to stdout for Claude Code to enforce.\n# Always exits 0; server failures leave Claude untouched.\n\nset +e\n\nPORT_FILE=\"$PWD/.synthra-graph/mcp_port\"\nif [ ! -f \"$PORT_FILE\" ]; then exit 0; fi\nPORT=$(cat \"$PORT_FILE\" 2>/dev/null | tr -d '[:space:]')\nif [ -z \"$PORT\" ]; then exit 0; fi\n\nINPUT=$(cat 2>/dev/null)\nif [ -z \"$INPUT\" ]; then exit 0; fi\n\nRESP=$(curl -sS --max-time 3 -X POST -H \"Content-Type: application/json\" \\\n --data \"$INPUT\" \"http://127.0.0.1:$PORT/gate\" 2>/dev/null)\n\ncase \"$RESP\" in\n *'\"decision\":\"block\"'*)\n REASON=$(printf '%s' \"$RESP\" | sed -n 's/.*\"reason\"[[:space:]]*:[[:space:]]*\"\\(.*\\)\".*/\\1/p')\n cat <<EOF\n{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"${REASON}\"}}\nEOF\n ;;\nesac\nexit 0\n","# SessionStart + PreCompact hook — Windows PowerShell.\n# Reads .synthra-graph/mcp_port, calls GET /prime, prints the primer to stdout\n# (Claude Code appends stdout to the session's system prompt). Always exits 0;\n# any failure leaves Claude with the prompt it would have gotten without Synthra.\n\n$ErrorActionPreference = \"SilentlyContinue\"\n\n$portFile = Join-Path $PWD \".synthra-graph\\mcp_port\"\nif (-not (Test-Path $portFile)) { exit 0 }\n$port = (Get-Content -Path $portFile -Raw).Trim()\nif (-not $port) { exit 0 }\n\ntry {\n $resp = Invoke-RestMethod -Uri \"http://127.0.0.1:$port/prime\" -Method GET -TimeoutSec 3\n if ($resp.primer) { Write-Output $resp.primer }\n} catch {\n # silent on failure — Claude continues without the primer\n}\nexit 0\n","#!/usr/bin/env bash\n# SessionStart + PreCompact hook — bash.\n# Reads .synthra-graph/mcp_port, calls GET /prime, prints the primer to stdout.\n# Always exits 0; any failure leaves Claude with the prompt it would have had\n# without Synthra.\n\nset +e\n\nPORT_FILE=\"$PWD/.synthra-graph/mcp_port\"\nif [ ! -f \"$PORT_FILE\" ]; then exit 0; fi\nPORT=$(cat \"$PORT_FILE\" 2>/dev/null | tr -d '[:space:]')\nif [ -z \"$PORT\" ]; then exit 0; fi\n\nPRIMER=$(curl -sS --max-time 3 \"http://127.0.0.1:$PORT/prime\" 2>/dev/null \\\n | sed -n 's/.*\"primer\"[[:space:]]*:[[:space:]]*\"\\(.*\\)\".*/\\1/p' \\\n | head -c 8000)\n\nif [ -n \"$PRIMER\" ]; then\n printf '%b\\n' \"$PRIMER\"\nfi\nexit 0\n","# Stop hook — Windows PowerShell.\r\n# Reads Claude's transcript JSONL from $hookInput.transcript_path, sums\r\n# usage.* token counts across all assistant turns since the last offset, and\r\n# POSTs the totals to /log. Uses a per-transcript .stopoffset file to avoid\r\n# double-counting on session resume.\r\n\r\n$ErrorActionPreference = \"SilentlyContinue\"\r\n\r\n$raw = [Console]::In.ReadToEnd()\r\nif (-not $raw) { exit 0 }\r\ntry { $hookInput = $raw | ConvertFrom-Json -ErrorAction Stop } catch { exit 0 }\r\n\r\n$transcript = $hookInput.transcript_path\r\nif (-not $transcript -or -not (Test-Path $transcript)) { exit 0 }\r\n\r\n$portFile = Join-Path $PWD \".synthra-graph\\mcp_port\"\r\nif (-not (Test-Path $portFile)) { exit 0 }\r\n$port = (Get-Content -Path $portFile -Raw).Trim()\r\nif (-not $port) { exit 0 }\r\n\r\n$offsetFile = \"$transcript.stopoffset\"\r\n$startOffset = 0\r\nif (Test-Path $offsetFile) {\r\n $val = (Get-Content -Path $offsetFile -Raw).Trim()\r\n if ($val -match '^\\d+$') { $startOffset = [int]$val }\r\n}\r\n\r\n$lines = Get-Content -Path $transcript\r\n$inT = 0; $outT = 0; $cc = 0; $cr = 0; $model = \"\"\r\n$lineNum = 0\r\nforeach ($line in $lines) {\r\n $lineNum++\r\n if ($lineNum -le $startOffset) { continue }\r\n if (-not $line) { continue }\r\n try { $e = $line | ConvertFrom-Json -ErrorAction Stop } catch { continue }\r\n $usage = $e.message.usage\r\n if (-not $usage) { continue }\r\n $inT += [int]($usage.input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })\r\n $outT += [int]($usage.output_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })\r\n $cc += [int]($usage.cache_creation_input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })\r\n $cr += [int]($usage.cache_read_input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })\r\n if ($e.message.model) { $model = $e.message.model }\r\n}\r\n\r\nSet-Content -Path $offsetFile -Value $lineNum -Encoding ASCII\r\n\r\nif ($inT -eq 0 -and $outT -eq 0) { exit 0 }\r\n\r\n$payload = @{\r\n input_tokens = $inT\r\n output_tokens = $outT\r\n cache_creation_input_tokens = $cc\r\n cache_read_input_tokens = $cr\r\n model = $model\r\n description = \"synthra-stop-hook\"\r\n project = $PWD.Path\r\n} | ConvertTo-Json -Compress\r\n\r\ntry {\r\n Invoke-RestMethod -Uri \"http://127.0.0.1:$port/log\" -Method POST `\r\n -Body $payload -ContentType \"application/json\" -TimeoutSec 3 | Out-Null\r\n} catch {\r\n # silent\r\n}\r\n\r\n# Refresh CONTEXT.md from the branch-scoped store.\r\n$ctxPayload = @{ transcript_path = $transcript } | ConvertTo-Json -Compress\r\ntry {\r\n Invoke-RestMethod -Uri \"http://127.0.0.1:$port/context-update\" -Method POST `\r\n -Body $ctxPayload -ContentType \"application/json\" -TimeoutSec 3 | Out-Null\r\n} catch {\r\n # silent\r\n}\r\nexit 0\r\n","#!/usr/bin/env bash\r\n# Stop hook — bash. Reads transcript JSONL, sums usage.* across new lines,\r\n# POSTs totals to /log. Uses a .stopoffset file to avoid double-counting.\r\n# Requires `jq` for robust JSON parsing; falls back to silent no-op if absent.\r\n\r\nset +e\r\n\r\nINPUT=$(cat 2>/dev/null)\r\nif [ -z \"$INPUT\" ]; then exit 0; fi\r\n\r\nTRANSCRIPT=$(printf '%s' \"$INPUT\" | sed -n 's/.*\"transcript_path\"[[:space:]]*:[[:space:]]*\"\\(.*\\)\".*/\\1/p')\r\nif [ -z \"$TRANSCRIPT\" ] || [ ! -f \"$TRANSCRIPT\" ]; then exit 0; fi\r\n\r\nPORT_FILE=\"$PWD/.synthra-graph/mcp_port\"\r\nif [ ! -f \"$PORT_FILE\" ]; then exit 0; fi\r\nPORT=$(cat \"$PORT_FILE\" 2>/dev/null | tr -d '[:space:]')\r\nif [ -z \"$PORT\" ]; then exit 0; fi\r\n\r\nif ! command -v jq >/dev/null 2>&1; then exit 0; fi\r\n\r\nOFFSET_FILE=\"${TRANSCRIPT}.stopoffset\"\r\nSTART_OFFSET=0\r\nif [ -f \"$OFFSET_FILE\" ]; then\r\n START_OFFSET=$(cat \"$OFFSET_FILE\" 2>/dev/null | tr -d '[:space:]')\r\n case \"$START_OFFSET\" in ''|*[!0-9]*) START_OFFSET=0 ;; esac\r\nfi\r\n\r\nTOTAL_LINES=$(wc -l < \"$TRANSCRIPT\" 2>/dev/null | tr -d ' ')\r\nTOTAL_LINES=${TOTAL_LINES:-0}\r\n\r\nif [ \"$TOTAL_LINES\" -le \"$START_OFFSET\" ]; then exit 0; fi\r\n\r\nUSAGE=$(tail -n +$((START_OFFSET + 1)) \"$TRANSCRIPT\" 2>/dev/null \\\r\n | jq -s '\r\n map(select(.message.usage != null) | .message)\r\n | reduce .[] as $m (\r\n {in:0, out:0, cc:0, cr:0, model:\"\"};\r\n .in += ($m.usage.input_tokens // 0)\r\n | .out += ($m.usage.output_tokens // 0)\r\n | .cc += ($m.usage.cache_creation_input_tokens // 0)\r\n | .cr += ($m.usage.cache_read_input_tokens // 0)\r\n | .model = ($m.model // .model)\r\n )\r\n ' 2>/dev/null)\r\n\r\nprintf '%s' \"$TOTAL_LINES\" > \"$OFFSET_FILE\"\r\n\r\nIN=$(printf '%s' \"$USAGE\" | jq -r '.in // 0')\r\nOUT=$(printf '%s' \"$USAGE\" | jq -r '.out // 0')\r\nCC=$(printf '%s' \"$USAGE\" | jq -r '.cc // 0')\r\nCR=$(printf '%s' \"$USAGE\" | jq -r '.cr // 0')\r\nMODEL=$(printf '%s' \"$USAGE\" | jq -r '.model // \"\"')\r\n\r\nif [ \"$IN\" = \"0\" ] && [ \"$OUT\" = \"0\" ]; then exit 0; fi\r\n\r\ncurl -sS --max-time 3 -X POST -H \"Content-Type: application/json\" \\\r\n --data \"$(jq -nc --argjson i \"$IN\" --argjson o \"$OUT\" --argjson cc \"$CC\" --argjson cr \"$CR\" --arg m \"$MODEL\" --arg p \"$PWD\" \\\r\n '{input_tokens:$i, output_tokens:$o, cache_creation_input_tokens:$cc, cache_read_input_tokens:$cr, model:$m, description:\"synthra-stop-hook\", project:$p}')\" \\\r\n \"http://127.0.0.1:$PORT/log\" >/dev/null 2>&1\r\n\r\n# Refresh CONTEXT.md from the branch-scoped store.\r\ncurl -sS --max-time 3 -X POST -H \"Content-Type: application/json\" \\\r\n --data \"$(jq -nc --arg t \"$TRANSCRIPT\" '{transcript_path:$t}')\" \\\r\n \"http://127.0.0.1:$PORT/context-update\" >/dev/null 2>&1\r\n\r\nexit 0\r\n","// HTTP server (Hono). Hosts the routes hooks need (/prime, /pack, /log,\n// /gate, /activity) and serves the loaded graph from memory. The MCP-protocol\n// envelope (/mcp endpoint, JSON-RPC) is wired in M3.\n\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport { ActivityStore } from \"../activity/activity-log.js\";\nimport { createFileWatcher, type FileWatcher } from \"../activity/file-watcher.js\";\nimport { createGitWatcher, type GitWatcher } from \"../activity/git-watcher.js\";\nimport { scanProject } from \"../cli/scan-command.js\";\nimport { readGraph, readSymbolIndex } from \"../graph/store.js\";\nimport { log } from \"../shared/logger.js\";\nimport type { SynthraPaths } from \"../shared/paths.js\";\nimport type { ServerContext } from \"./context.js\";\nimport { handleMcpRequest } from \"./mcp.js\";\nimport { findFreePort } from \"./port.js\";\nimport { handleActivity } from \"./routes/activity.js\";\nimport { handleContextUpdate } from \"./routes/context-update.js\";\nimport { handleGate } from \"./routes/gate.js\";\nimport { handleLog } from \"./routes/log.js\";\nimport { handlePack } from \"./routes/pack.js\";\nimport { handlePrime } from \"./routes/prime.js\";\n\nexport interface ServerHandle {\n port: number;\n url: string;\n stop(): Promise<void>;\n}\n\nexport interface StartOptions {\n /** Override the port range search. */\n port?: number;\n}\n\nasync function loadContext(paths: SynthraPaths): Promise<ServerContext> {\n try {\n const [graph, symbolIndex] = await Promise.all([\n readGraph(paths.infoGraph),\n readSymbolIndex(paths.symbolIndex),\n ]);\n const activity = new ActivityStore(paths.activityLog);\n return { paths, graph, symbolIndex, activity };\n } catch (err) {\n throw new Error(\n `failed to load graph from ${paths.infoGraph}: ${(err as Error).message}. ` +\n `Run \\`syn scan\\` first.`,\n );\n }\n}\n\nfunction buildApp(ctx: ServerContext, port: number): Hono {\n const app = new Hono();\n\n app.get(\"/\", (c) =>\n c.json({\n service: \"synthra\",\n version: \"0.0.1\",\n port,\n file_count: ctx.graph.file_count,\n symbol_count: ctx.graph.symbol_count,\n generated_at: ctx.graph.generated_at,\n }),\n );\n\n app.get(\"/health\", (c) => c.json({ ok: true }));\n\n app.get(\"/prime\", async (c) => c.json(await handlePrime(ctx, port)));\n\n app.post(\"/pack\", async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json(await handlePack(body, ctx));\n });\n\n app.post(\"/log\", async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json(await handleLog(body, ctx));\n });\n\n app.post(\"/gate\", async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json(await handleGate(body, ctx));\n });\n\n app.get(\"/activity\", async (c) => {\n const sinceParam = c.req.query(\"since\");\n const sinceMs = sinceParam ? Number(sinceParam) : undefined;\n return c.json(\n await handleActivity(Number.isFinite(sinceMs) ? sinceMs : undefined, ctx),\n );\n });\n\n app.post(\"/context-update\", async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json(await handleContextUpdate(body, ctx));\n });\n\n app.post(\"/mcp\", async (c) => {\n const body = await c.req.json().catch(() => null);\n return c.json(await handleMcpRequest(body, ctx));\n });\n\n app.onError((err, c) => {\n log.error(\"route error:\", err.message);\n return c.json({ error: err.message }, 400);\n });\n\n return app;\n}\n\nexport async function startServer(\n paths: SynthraPaths,\n options: StartOptions = {},\n): Promise<ServerHandle> {\n const ctx = await loadContext(paths);\n const port = options.port ?? (await findFreePort());\n\n const app = buildApp(ctx, port);\n const nodeServer = serve({ fetch: app.fetch, port, hostname: \"127.0.0.1\" });\n\n await writeFile(paths.mcpPort, String(port), \"utf8\");\n\n // Spin up the human-activity watchers. Both are best-effort — if chokidar\n // can't watch (e.g. unsupported FS) or .git is missing, they no-op silently.\n const fileWatcher: FileWatcher = createFileWatcher(paths.projectRoot, (e) =>\n ctx.activity.add(e),\n );\n const gitWatcher: GitWatcher = createGitWatcher(paths.projectRoot, async (e) => {\n await ctx.activity.add(e);\n // Per-branch graph: rebuild on branch switch so the in-memory graph\n // matches whichever branch is currently checked out.\n if (e.kind === \"branch-switch\") {\n try {\n const to = (e.details as { to?: string } | undefined)?.to ?? \"unknown\";\n log.info(`branch switched to '${to}' — rebuilding graph…`);\n await scanProject(paths.projectRoot, { silent: true });\n const [g, idx] = await Promise.all([\n readGraph(paths.infoGraph),\n readSymbolIndex(paths.symbolIndex),\n ]);\n ctx.graph = g;\n ctx.symbolIndex = idx;\n log.info(`graph rebuilt for '${to}' (${g.symbol_count} symbols).`);\n } catch (err) {\n log.warn(`branch rescan failed: ${(err as Error).message}`);\n }\n }\n });\n try {\n await fileWatcher.start();\n } catch (err) {\n log.warn(`file watcher failed to start: ${(err as Error).message}`);\n }\n try {\n await gitWatcher.start();\n } catch (err) {\n log.warn(`git watcher failed to start: ${(err as Error).message}`);\n }\n\n const url = `http://127.0.0.1:${port}`;\n\n return {\n port,\n url,\n async stop() {\n await fileWatcher.stop().catch(() => undefined);\n await gitWatcher.stop().catch(() => undefined);\n await new Promise<void>((resolve, reject) => {\n nodeServer.close((err) => (err ? reject(err) : resolve()));\n });\n },\n };\n}\n","// Rolling JSONL log of human activity, written to .synthra-graph/activity.jsonl.\n// In-memory ring buffer for fast queries; disk append for durability.\n//\n// The buffer is bounded (defaults to 100 events) so we don't unbounded-grow\n// memory in long sessions. Disk gets every event so the dashboard / future\n// audit tooling can replay history.\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nexport interface FileEvent {\n kind: \"save\" | \"create\" | \"delete\";\n path: string;\n ts: string;\n}\n\nexport interface GitEvent {\n kind: \"branch-switch\" | \"stage\" | \"unstage\" | \"diff-change\";\n details: Record<string, unknown>;\n ts: string;\n}\n\nexport type ActivityEvent = FileEvent | GitEvent;\n\nconst DEFAULT_RING_SIZE = 100;\n\nexport class ActivityStore {\n private ring: ActivityEvent[] = [];\n private readonly maxRingSize: number;\n private readonly persistPath: string;\n\n constructor(persistPath: string, maxRingSize = DEFAULT_RING_SIZE) {\n this.persistPath = persistPath;\n this.maxRingSize = maxRingSize;\n }\n\n async add(event: ActivityEvent): Promise<void> {\n this.ring.push(event);\n while (this.ring.length > this.maxRingSize) this.ring.shift();\n await this.persist(event);\n }\n\n /** Get events newer than `sinceMs` (epoch ms). If omitted, returns the full ring. */\n getEvents(sinceMs?: number): ActivityEvent[] {\n if (!sinceMs || !Number.isFinite(sinceMs)) return this.ring.slice();\n const cutoff = new Date(sinceMs).toISOString();\n return this.ring.filter((e) => e.ts >= cutoff);\n }\n\n /** Project-relative file paths that have a save/create event newer than `maxAgeMs` ms ago. */\n recentFilePaths(maxAgeMs: number): string[] {\n const cutoff = new Date(Date.now() - maxAgeMs).toISOString();\n const out = new Set<string>();\n for (const e of this.ring) {\n if (\"path\" in e && (e.kind === \"save\" || e.kind === \"create\") && e.ts >= cutoff) {\n out.add(e.path);\n }\n }\n return Array.from(out);\n }\n\n size(): number {\n return this.ring.length;\n }\n\n private async persist(event: ActivityEvent): Promise<void> {\n try {\n await mkdir(dirname(this.persistPath), { recursive: true });\n await appendFile(this.persistPath, JSON.stringify(event) + \"\\n\", \"utf8\");\n } catch {\n // Durability is best-effort; an unwritable disk shouldn't crash the server.\n }\n }\n}\n","// chokidar-based file watcher. Emits save/create/delete events for human\n// edits inside the project. Respects .gitignore + .synthraignore plus a\n// hard-coded list of always-ignored directories (.git, .synthra*, .claude,\n// node_modules, dist, build, coverage).\n\nimport chokidar, { type FSWatcher } from \"chokidar\";\nimport { readFile } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\nimport ignore, { type Ignore } from \"ignore\";\n\nimport { log } from \"../shared/logger.js\";\nimport type { FileEvent } from \"./activity-log.js\";\n\nconst ALWAYS_IGNORE = [\n \".git\",\n \".synthra\",\n \".synthra-graph\",\n \".claude\",\n \"node_modules\",\n \"dist\",\n \"build\",\n \"out\",\n \"coverage\",\n \".next\",\n \".nuxt\",\n \".svelte-kit\",\n \".turbo\",\n \".cache\",\n \".vscode\",\n \".idea\",\n];\n\nexport interface FileWatcher {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\nexport type FileEventHandler = (e: FileEvent) => void | Promise<void>;\n\nasync function readIgnoreFile(path: string): Promise<string[]> {\n try {\n const text = await readFile(path, \"utf8\");\n return text\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter((l) => l.length > 0 && !l.startsWith(\"#\"));\n } catch {\n return [];\n }\n}\n\nasync function buildMatcher(root: string): Promise<Ignore> {\n const ig = ignore();\n ig.add(ALWAYS_IGNORE.map((d) => `${d}/`));\n ig.add(await readIgnoreFile(join(root, \".gitignore\")));\n ig.add(await readIgnoreFile(join(root, \".synthraignore\")));\n return ig;\n}\n\nfunction toPosixRel(root: string, abs: string): string {\n const rel = relative(root, abs);\n return sep === \"/\" ? rel : rel.split(sep).join(\"/\");\n}\n\nexport function createFileWatcher(root: string, onEvent: FileEventHandler): FileWatcher {\n let watcher: FSWatcher | null = null;\n let ig: Ignore | null = null;\n\n const emit = async (kind: FileEvent[\"kind\"], abs: string) => {\n if (!ig) return;\n const rel = toPosixRel(root, abs);\n if (!rel || rel.startsWith(\"..\")) return;\n if (ig.ignores(rel)) return;\n try {\n await onEvent({ kind, path: rel, ts: new Date().toISOString() });\n } catch {\n // swallow handler errors — watcher must keep going\n }\n };\n\n return {\n async start() {\n ig = await buildMatcher(root);\n watcher = chokidar.watch(root, {\n // Cross-platform glob ignore. We match both the directory itself and\n // anything inside it. picomatch (chokidar's matcher) normalizes path\n // separators so a single set of forward-slash globs handles\n // Windows + POSIX. Function-based ignore was unreliable on Windows\n // and let chokidar descend into .git/, which crashed on transient\n // index.lock files held exclusively by git.\n ignored: ALWAYS_IGNORE.flatMap((d) => [`**/${d}`, `**/${d}/**`]),\n ignoreInitial: true,\n persistent: true,\n awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 },\n });\n\n // Chokidar emits \"error\" for transient OS-level issues — most commonly\n // EPERM/ENOENT on rapidly created+deleted files. We never want one of\n // these to crash the syn process. Log + swallow.\n watcher.on(\"error\", (err) => {\n const e = err as NodeJS.ErrnoException;\n log.debug(`file watcher error (swallowed): ${e?.code ?? \"\"} ${e?.message ?? String(err)}`);\n });\n\n watcher.on(\"add\", (path) => emit(\"create\", path));\n watcher.on(\"change\", (path) => emit(\"save\", path));\n watcher.on(\"unlink\", (path) => emit(\"delete\", path));\n },\n\n async stop() {\n if (watcher) {\n await watcher.close();\n watcher = null;\n }\n },\n };\n}\n","// Watches `.git/HEAD` for branch switches (via fs.watch) and polls\n// `git status --porcelain` every ~2s to surface uncommitted-diff changes.\n// Always best-effort: in a non-git directory or when git is missing, the\n// watcher simply emits nothing.\n\nimport { execFile } from \"node:child_process\";\nimport { watch, type FSWatcher } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nimport type { GitEvent } from \"./activity-log.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst POLL_MS = 2000;\n\nexport interface GitWatcher {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\nexport type GitEventHandler = (e: GitEvent) => void | Promise<void>;\n\nasync function readHeadBranch(projectRoot: string): Promise<string | null> {\n try {\n const head = await readFile(join(projectRoot, \".git\", \"HEAD\"), \"utf8\");\n const m = head.trim().match(/^ref:\\s+refs\\/heads\\/(.+)$/);\n return m?.[1] ?? null;\n } catch {\n return null;\n }\n}\n\nasync function readStatusPorcelain(projectRoot: string): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync(\"git\", [\"status\", \"--porcelain\"], {\n cwd: projectRoot,\n });\n return stdout;\n } catch {\n return null;\n }\n}\n\nexport function createGitWatcher(root: string, onEvent: GitEventHandler): GitWatcher {\n let headWatcher: FSWatcher | null = null;\n let pollTimer: NodeJS.Timeout | null = null;\n let lastBranch: string | null = null;\n let lastStatus: string | null = null;\n\n const emitSafe = async (event: GitEvent) => {\n try {\n await onEvent(event);\n } catch {\n // swallow\n }\n };\n\n const checkHead = async () => {\n const branch = await readHeadBranch(root);\n if (branch && branch !== lastBranch) {\n const prev = lastBranch;\n lastBranch = branch;\n if (prev !== null) {\n await emitSafe({\n kind: \"branch-switch\",\n details: { from: prev, to: branch },\n ts: new Date().toISOString(),\n });\n }\n }\n };\n\n const pollStatus = async () => {\n const status = await readStatusPorcelain(root);\n if (status === null) return;\n if (lastStatus !== null && status !== lastStatus) {\n const prevFiles = parseStatusFiles(lastStatus);\n const nowFiles = parseStatusFiles(status);\n const added = nowFiles.filter((f) => !prevFiles.includes(f));\n const removed = prevFiles.filter((f) => !nowFiles.includes(f));\n await emitSafe({\n kind: \"diff-change\",\n details: {\n changed_count: nowFiles.length,\n newly_dirty: added,\n newly_clean: removed,\n },\n ts: new Date().toISOString(),\n });\n }\n lastStatus = status;\n };\n\n return {\n async start() {\n // Seed initial branch + status so the first real change emits an event\n // rather than a stale \"from null\".\n lastBranch = await readHeadBranch(root);\n lastStatus = await readStatusPorcelain(root);\n\n try {\n headWatcher = watch(join(root, \".git\", \"HEAD\"), () => {\n void checkHead();\n });\n // fs.watch emits \"error\" for transient OS issues (EPERM on lock\n // files, ENOENT when refs get rewritten). Swallow them — we never\n // want a transient FS event to crash the syn process.\n headWatcher.on(\"error\", () => {\n // silent — branch-switch detection is best-effort\n });\n } catch {\n // .git/HEAD not present — silently no-op\n }\n\n pollTimer = setInterval(() => {\n void pollStatus();\n }, POLL_MS);\n pollTimer.unref?.();\n },\n\n async stop() {\n if (headWatcher) {\n headWatcher.close();\n headWatcher = null;\n }\n if (pollTimer) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n },\n };\n}\n\nfunction parseStatusFiles(porcelain: string): string[] {\n return porcelain\n .split(/\\r?\\n/)\n .map((l) => l.slice(3).trim())\n .filter((l) => l.length > 0);\n}\n","// `syn scan [path]` — bootstrap then walk + parse + write graph.\n// Also invoked by the default `syn .` flow (M3 will chain start-claude after).\n\nimport { resolve } from \"node:path\";\n\nimport { buildGraph, buildSymbolIndex } from \"../scanner/extract.js\";\nimport { parseFile, type ParsedFile } from \"../scanner/parser.js\";\nimport { walk, type WalkedFile } from \"../scanner/walker.js\";\nimport { writeGraph, writeSymbolIndex } from \"../graph/store.js\";\nimport { log } from \"../shared/logger.js\";\nimport { resolvePaths } from \"../shared/paths.js\";\nimport { bootstrap } from \"./bootstrap.js\";\n\nconst PARSABLE_EXTS = new Set([\n \".ts\", \".tsx\", \".cts\", \".mts\",\n \".js\", \".jsx\", \".cjs\", \".mjs\",\n \".py\", \".pyi\",\n \".svelte\",\n \".vue\",\n \".go\",\n \".rs\",\n \".java\",\n \".kt\", \".kts\",\n \".php\",\n \".rb\",\n \".c\", \".h\",\n \".cpp\", \".cc\", \".cxx\", \".hpp\", \".hh\", \".hxx\",\n \".dart\",\n \".cs\",\n]);\n\nexport interface ScanResult {\n walked: number;\n parsed: number;\n symbolCount: number;\n edgeCount: number;\n durationMs: number;\n}\n\nexport interface ScanOptions {\n /** Suppress per-step log output (used for branch-switch rescans). */\n silent?: boolean;\n}\n\n/**\n * Core scan pipeline — bootstrap + walk + parse + write graph. Importable\n * from anywhere (server, CLI, tests). `scanCommand` is just a logging wrapper\n * around this. Pass `silent: true` to skip the chatty progress output.\n */\nexport async function scanProject(projectRootRaw: string, opts: ScanOptions = {}): Promise<ScanResult> {\n const projectRoot = resolve(projectRootRaw);\n const paths = resolvePaths(projectRoot);\n const start = Date.now();\n const verbose = !opts.silent;\n\n if (verbose) log.info(`scanning ${projectRoot}`);\n\n const boot = await bootstrap(paths);\n if (verbose) {\n if (boot.graphCreated) log.info(\" created .synthra-graph/\");\n if (boot.contextCreated) log.info(\" created .synthra/\");\n if (boot.gitignoreUpdated) log.info(\" updated .gitignore\");\n if (boot.claudeMdCreated) log.info(\" created CLAUDE.md\");\n else if (boot.claudeMdUpdated) log.info(\" updated CLAUDE.md\");\n }\n\n const walked: WalkedFile[] = [];\n for await (const file of walk(projectRoot)) walked.push(file);\n if (verbose) log.info(` walked ${walked.length} files`);\n\n const parsable = walked.filter((f) => PARSABLE_EXTS.has(f.ext));\n const parsed: ParsedFile[] = [];\n let parseErrors = 0;\n for (const file of parsable) {\n try {\n parsed.push(await parseFile(file));\n } catch (err) {\n parseErrors += 1;\n if (verbose) log.debug(` parse failed: ${file.relPath} — ${(err as Error).message}`);\n }\n }\n if (verbose) {\n log.info(\n ` parsed ${parsed.length} files (${walked.length - parsable.length} skipped` +\n (parseErrors ? `, ${parseErrors} errored` : \"\") +\n \")\",\n );\n }\n\n const graph = await buildGraph(projectRoot, parsed);\n const symbolIndex = buildSymbolIndex(graph);\n\n await writeGraph(paths.infoGraph, graph);\n await writeSymbolIndex(paths.symbolIndex, symbolIndex);\n\n if (verbose) {\n log.info(\n ` wrote ${paths.infoGraph} — ${graph.symbol_count} symbols, ${graph.edge_count} edges`,\n );\n log.info(` wrote ${paths.symbolIndex} — ${Object.keys(symbolIndex).length} names`);\n }\n\n const durationMs = Date.now() - start;\n if (verbose) log.info(`done in ${(durationMs / 1000).toFixed(2)}s`);\n\n return {\n walked: walked.length,\n parsed: parsed.length,\n symbolCount: graph.symbol_count,\n edgeCount: graph.edge_count,\n durationMs,\n };\n}\n\n// Thin alias so the CLI command keeps its current name. Drop in v0.2 if we\n// settle on a single public function.\nexport async function scanCommand(rawPath: string): Promise<ScanResult> {\n return scanProject(rawPath);\n}\n","// Turns ParsedFile[] into a GraphSchema (nodes + edges).\n//\n// Edges produced in M1:\n// defines : file → symbol\n// imports : file → file (when the import target resolves inside the project)\n// tests : test-file → source-file (for foo.test.ts ↔ foo.ts)\n\nimport { dirname, join, posix } from \"node:path\";\n\nimport type { Edge, FileNode, GraphSchema, SymbolIndex, SymbolNode } from \"../graph/types.js\";\nimport { fileHash } from \"./hash.js\";\nimport { extractKeywords } from \"./keywords.js\";\nimport type { ParsedFile, ParsedSymbol } from \"./parser.js\";\n\nconst RESOLVE_EXTS = [\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\", \".py\", \".svelte\", \".vue\", \".dart\"];\nconst INDEX_FILES = [\"index.ts\", \"index.tsx\", \"index.js\", \"index.jsx\", \"__init__.py\"];\n\nfunction fileId(relPath: string): string {\n return `file:${relPath}`;\n}\n\nfunction symbolId(relPath: string, sym: ParsedSymbol): string {\n return `symbol:${relPath}::${sym.name}:${sym.startLine}`;\n}\n\nfunction toFileNode(parsed: ParsedFile): FileNode {\n const content = parsed.source;\n return {\n id: fileId(parsed.file.relPath),\n kind: \"file\",\n path: parsed.file.relPath,\n ext: parsed.file.ext,\n size: parsed.file.size,\n keywords: extractKeywords(content, parsed.file.ext),\n content,\n summary: extractSummary(content),\n file_hash: fileHash(content),\n };\n}\n\nfunction extractSummary(content: string): string {\n // First leading comment block (// ... or /** ... */ or # ...), trimmed to ~200 chars.\n const trimmed = content.replace(/^\\s+/, \"\");\n const slashMatch = trimmed.match(/^\\/\\/\\s?(.*(?:\\r?\\n\\/\\/\\s?.*)*)/);\n if (slashMatch?.[1]) return slashMatch[1].split(/\\r?\\n/).join(\" \").trim().slice(0, 200);\n const blockMatch = trimmed.match(/^\\/\\*\\*?([\\s\\S]*?)\\*\\//);\n if (blockMatch?.[1]) {\n return blockMatch[1]\n .split(/\\r?\\n/)\n .map((l) => l.replace(/^\\s*\\*\\s?/, \"\"))\n .join(\" \")\n .trim()\n .slice(0, 200);\n }\n const hashMatch = trimmed.match(/^#\\s?(.*(?:\\r?\\n#\\s?.*)*)/);\n if (hashMatch?.[1]) return hashMatch[1].split(/\\r?\\n/).join(\" \").trim().slice(0, 200);\n return \"\";\n}\n\nfunction toSymbolNode(parsed: ParsedFile, sym: ParsedSymbol): SymbolNode {\n return {\n id: symbolId(parsed.file.relPath, sym),\n kind: \"symbol\",\n symbol_kind: sym.kind,\n name: sym.name,\n file: parsed.file.relPath,\n start_line: sym.startLine,\n end_line: sym.endLine,\n signature: sym.signature,\n };\n}\n\n/**\n * Resolve an import specifier to a project-relative path if it refers to a\n * file inside the project. Returns `null` for external packages (no leading\n * dot) or specifiers that don't match any known file.\n */\n// Strip a trailing JS-family extension so a spec like \"./crypto.js\" can\n// resolve to \"crypto.ts\". TypeScript-style `.js` imports are common.\nconst REWRITE_EXT_RE = /\\.(js|jsx|mjs|cjs)$/;\n\nfunction resolveImport(\n fromRelPath: string,\n spec: string,\n filesByPath: Map<string, true>,\n): string | null {\n if (!spec.startsWith(\".\")) return null;\n const fromDir = posix.dirname(toPosix(fromRelPath));\n const base = posix.normalize(posix.join(fromDir, toPosix(spec)));\n\n const candidates = [base];\n const rewritten = base.replace(REWRITE_EXT_RE, \"\");\n if (rewritten !== base) candidates.push(rewritten);\n\n for (const c of candidates) {\n if (filesByPath.has(c)) return c;\n for (const ext of RESOLVE_EXTS) {\n if (filesByPath.has(c + ext)) return c + ext;\n }\n for (const idx of INDEX_FILES) {\n const candidate = posix.join(c, idx);\n if (filesByPath.has(candidate)) return candidate;\n }\n }\n return null;\n}\n\nfunction toPosix(p: string): string {\n return p.split(/[\\\\/]/).join(\"/\");\n}\n\nconst TEST_RE = /^(?<base>.+?)\\.(test|spec)\\.(?<ext>[tj]sx?|py)$/;\n\nfunction testTarget(relPath: string, filesByPath: Map<string, true>): string | null {\n const fileName = relPath.split(\"/\").pop() ?? relPath;\n const match = TEST_RE.exec(fileName);\n if (!match) return null;\n const dir = relPath.includes(\"/\") ? relPath.slice(0, relPath.lastIndexOf(\"/\") + 1) : \"\";\n const base = match.groups?.base ?? \"\";\n const ext = match.groups?.ext ?? \"\";\n if (!base || !ext) return null;\n const candidate = `${dir}${base}.${ext}`;\n if (filesByPath.has(candidate)) return candidate;\n // Try sibling extensions (e.g. foo.test.ts → foo.tsx)\n for (const e of RESOLVE_EXTS) {\n const alt = `${dir}${base}${e}`;\n if (filesByPath.has(alt)) return alt;\n }\n return null;\n}\n\nexport async function buildGraph(root: string, parsed: ParsedFile[]): Promise<GraphSchema> {\n const filesByPath = new Map<string, true>();\n for (const p of parsed) filesByPath.set(p.file.relPath, true);\n\n const nodes: (FileNode | SymbolNode)[] = [];\n const edges: Edge[] = [];\n\n for (const p of parsed) {\n const fileNode = toFileNode(p);\n nodes.push(fileNode);\n\n for (const sym of p.symbols) {\n const symNode = toSymbolNode(p, sym);\n nodes.push(symNode);\n edges.push({ from: fileNode.id, to: symNode.id, kind: \"defines\" });\n }\n\n const importEdges = new Set<string>();\n for (const spec of p.imports) {\n const target = resolveImport(p.file.relPath, spec, filesByPath);\n if (!target) continue;\n const key = `${fileNode.id}->${fileId(target)}`;\n if (importEdges.has(key)) continue;\n importEdges.add(key);\n edges.push({ from: fileNode.id, to: fileId(target), kind: \"imports\" });\n }\n\n const testTargetPath = testTarget(p.file.relPath, filesByPath);\n if (testTargetPath && testTargetPath !== p.file.relPath) {\n edges.push({ from: fileNode.id, to: fileId(testTargetPath), kind: \"tests\" });\n }\n }\n\n const symbolCount = nodes.filter((n) => n.kind === \"symbol\").length;\n const fileCount = nodes.length - symbolCount;\n\n return {\n root,\n node_count: nodes.length,\n edge_count: edges.length,\n file_count: fileCount,\n symbol_count: symbolCount,\n nodes,\n edges,\n generated_at: new Date().toISOString(),\n schema_version: 1,\n };\n}\n\nexport function buildSymbolIndex(graph: GraphSchema): SymbolIndex {\n // Null-prototype map: symbol names like \"toString\" or \"constructor\" (common\n // in Dart, where every class overrides toString) would otherwise resolve to\n // an inherited Object.prototype member and crash on the .push below.\n const out: SymbolIndex = Object.create(null);\n for (const node of graph.nodes) {\n if (node.kind !== \"symbol\") continue;\n const list = out[node.name] ?? (out[node.name] = []);\n list.push({ file: node.file, line: node.start_line, kind: node.symbol_kind });\n }\n return out;\n}\n\n// Re-export node path helpers in case downstream wants the canonical id format\nexport { fileId, symbolId };\n// Suppress unused-import lint for dirname/join from node:path — kept reserved for incremental updates.\nvoid dirname;\nvoid join;\n","// Stable, short content hash for files. Used to detect changed files\n// during incremental rescans (post-v0.1; M1 does full re-parse).\n// TODO: M1 (minimal); post-v0.1 (incremental)\n\nimport { createHash } from \"node:crypto\";\n\nexport function fileHash(content: string): string {\n return createHash(\"sha1\").update(content).digest(\"hex\").slice(0, 8);\n}\n","// Per-file keyword extraction. Used for query-time relevance ranking.\n// Tokenizes identifiers + comment words, splits camelCase/snake_case, filters\n// stopwords, and returns the top-N rare tokens scored by inverse frequency\n// against a small built-in english/code corpus.\n\nconst STOPWORDS = new Set([\n \"a\", \"an\", \"and\", \"are\", \"as\", \"at\", \"be\", \"but\", \"by\", \"do\", \"for\", \"from\",\n \"has\", \"have\", \"he\", \"if\", \"in\", \"is\", \"it\", \"its\", \"not\", \"of\", \"on\", \"or\",\n \"she\", \"that\", \"the\", \"they\", \"this\", \"to\", \"was\", \"we\", \"were\", \"will\", \"with\",\n \"you\", \"your\", \"i\", \"me\", \"my\", \"our\", \"us\", \"their\", \"them\", \"his\", \"her\",\n // common code words that add no signal\n \"function\", \"const\", \"let\", \"var\", \"class\", \"interface\", \"type\", \"enum\",\n \"import\", \"export\", \"from\", \"default\", \"return\", \"if\", \"else\", \"for\", \"while\",\n \"do\", \"switch\", \"case\", \"break\", \"continue\", \"new\", \"this\", \"super\", \"throw\",\n \"try\", \"catch\", \"finally\", \"async\", \"await\", \"yield\", \"true\", \"false\", \"null\",\n \"undefined\", \"void\", \"any\", \"string\", \"number\", \"boolean\", \"object\", \"array\",\n \"self\", \"cls\", \"def\", \"lambda\", \"pass\", \"raise\", \"with\", \"as\", \"in\",\n \"todo\", \"fixme\", \"note\",\n]);\n\nconst COMMON_CODE = new Set([\n \"value\", \"data\", \"result\", \"args\", \"kwargs\", \"options\", \"config\", \"params\",\n \"name\", \"id\", \"key\", \"index\", \"item\", \"items\", \"list\", \"map\", \"set\", \"get\",\n \"set\", \"add\", \"remove\", \"delete\", \"create\", \"update\", \"find\", \"fetch\", \"load\",\n \"save\", \"init\", \"main\", \"run\", \"start\", \"stop\", \"test\", \"check\", \"validate\",\n \"error\", \"err\", \"warn\", \"info\", \"debug\", \"log\", \"trace\", \"msg\", \"message\",\n \"path\", \"file\", \"dir\", \"url\", \"host\", \"port\", \"size\", \"length\", \"count\",\n \"input\", \"output\", \"source\", \"target\", \"callback\", \"handler\", \"listener\",\n \"props\", \"state\", \"context\", \"render\", \"component\", \"node\", \"tree\", \"root\",\n]);\n\n// Frequency weight — common-code words count for less than rare identifiers\nfunction score(token: string): number {\n if (STOPWORDS.has(token)) return 0;\n if (COMMON_CODE.has(token)) return 0.2;\n if (token.length <= 2) return 0.1;\n return 1;\n}\n\nfunction splitIdentifier(id: string): string[] {\n // snake_case + kebab-case → words\n const partsRaw = id.split(/[_\\-./]+/).filter(Boolean);\n const out: string[] = [];\n for (const part of partsRaw) {\n // camelCase / PascalCase → words. Handles \"XMLHttp\" → [\"XML\", \"Http\"]\n const camelParts = part.match(/[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|[0-9]+/g);\n if (camelParts) out.push(...camelParts);\n else out.push(part);\n }\n return out.map((w) => w.toLowerCase()).filter((w) => /[a-z]/.test(w));\n}\n\nexport function extractKeywords(content: string, _ext: string): string[] {\n // Identifiers + alphanumeric words. Picks up both code and comment text.\n const tokens = content.match(/[A-Za-z_][A-Za-z0-9_]{1,40}/g) ?? [];\n const counts = new Map<string, number>();\n for (const tok of tokens) {\n for (const word of splitIdentifier(tok)) {\n const w = score(word);\n if (w === 0) continue;\n counts.set(word, (counts.get(word) ?? 0) + w);\n }\n }\n return Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 32)\n .map(([w]) => w);\n}\n","// Dispatches a file to its language-specific parser based on extension.\n// Tree-sitter WASM grammars are loaded lazily via tree-sitter-wasms and\n// cached per language for the lifetime of the process.\n\nimport { readFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { Language, Parser } from \"web-tree-sitter\";\n\nimport type { SymbolKind } from \"../graph/types.js\";\nimport { parseC } from \"./parsers/c.js\";\nimport { parseCpp } from \"./parsers/cpp.js\";\nimport { parseCSharp } from \"./parsers/csharp.js\";\nimport { parseDart } from \"./parsers/dart.js\";\nimport { parseGo } from \"./parsers/go.js\";\nimport { parseJava } from \"./parsers/java.js\";\nimport { parseKotlin } from \"./parsers/kotlin.js\";\nimport { parsePhp } from \"./parsers/php.js\";\nimport { parsePython } from \"./parsers/python.js\";\nimport { parseRuby } from \"./parsers/ruby.js\";\nimport { parseRust } from \"./parsers/rust.js\";\nimport { parseSvelte } from \"./parsers/svelte.js\";\nimport { parseTypeScript } from \"./parsers/typescript.js\";\nimport { parseVue } from \"./parsers/vue.js\";\nimport type { WalkedFile } from \"./walker.js\";\n\nexport interface ParsedSymbol {\n name: string;\n kind: SymbolKind;\n startLine: number;\n endLine: number;\n signature: string;\n}\n\nexport interface ParsedFile {\n file: WalkedFile;\n source: string;\n symbols: ParsedSymbol[];\n imports: string[];\n calls: Array<{ from: string; to: string }>;\n}\n\nconst require = createRequire(import.meta.url);\n\nexport type GrammarName =\n | \"typescript\"\n | \"tsx\"\n | \"javascript\"\n | \"python\"\n | \"go\"\n | \"rust\"\n | \"java\"\n | \"kotlin\"\n | \"php\"\n | \"ruby\"\n | \"c\"\n | \"cpp\"\n | \"dart\"\n | \"csharp\";\n\nconst GRAMMAR_FILES: Record<GrammarName, string> = {\n typescript: \"tree-sitter-wasms/out/tree-sitter-typescript.wasm\",\n tsx: \"tree-sitter-wasms/out/tree-sitter-tsx.wasm\",\n javascript: \"tree-sitter-wasms/out/tree-sitter-javascript.wasm\",\n python: \"tree-sitter-wasms/out/tree-sitter-python.wasm\",\n go: \"tree-sitter-wasms/out/tree-sitter-go.wasm\",\n rust: \"tree-sitter-wasms/out/tree-sitter-rust.wasm\",\n java: \"tree-sitter-wasms/out/tree-sitter-java.wasm\",\n kotlin: \"tree-sitter-wasms/out/tree-sitter-kotlin.wasm\",\n php: \"tree-sitter-wasms/out/tree-sitter-php.wasm\",\n ruby: \"tree-sitter-wasms/out/tree-sitter-ruby.wasm\",\n c: \"tree-sitter-wasms/out/tree-sitter-c.wasm\",\n cpp: \"tree-sitter-wasms/out/tree-sitter-cpp.wasm\",\n dart: \"tree-sitter-wasms/out/tree-sitter-dart.wasm\",\n csharp: \"tree-sitter-wasms/out/tree-sitter-c_sharp.wasm\",\n};\n\nlet parserInit: Promise<void> | null = null;\nconst languageCache = new Map<GrammarName, Language>();\n\nasync function ensureParserInit(): Promise<void> {\n if (!parserInit) {\n parserInit = Parser.init();\n }\n return parserInit;\n}\n\nexport async function loadGrammar(name: GrammarName): Promise<Language> {\n await ensureParserInit();\n const cached = languageCache.get(name);\n if (cached) return cached;\n const wasmPath = require.resolve(GRAMMAR_FILES[name]);\n const lang = await Language.load(wasmPath);\n languageCache.set(name, lang);\n return lang;\n}\n\nexport interface LoadedParser {\n parser: Parser;\n language: Language;\n}\n\nexport async function createParser(name: GrammarName): Promise<LoadedParser> {\n const language = await loadGrammar(name);\n const parser = new Parser();\n parser.setLanguage(language);\n return { parser, language };\n}\n\nfunction emptyParsed(file: WalkedFile, source: string): ParsedFile {\n return { file, source, symbols: [], imports: [], calls: [] };\n}\n\nexport async function parseFile(f: WalkedFile): Promise<ParsedFile> {\n let source: string;\n try {\n source = await readFile(f.absPath, \"utf8\");\n } catch {\n return emptyParsed(f, \"\");\n }\n\n switch (f.ext) {\n case \".ts\":\n case \".tsx\":\n case \".cts\":\n case \".mts\":\n case \".js\":\n case \".jsx\":\n case \".cjs\":\n case \".mjs\":\n return parseTypeScript(f, source);\n case \".py\":\n case \".pyi\":\n return parsePython(f, source);\n case \".svelte\":\n return parseSvelte(f, source);\n case \".vue\":\n return parseVue(f, source);\n case \".go\":\n return parseGo(f, source);\n case \".rs\":\n return parseRust(f, source);\n case \".java\":\n return parseJava(f, source);\n case \".kt\":\n case \".kts\":\n return parseKotlin(f, source);\n case \".php\":\n return parsePhp(f, source);\n case \".rb\":\n return parseRuby(f, source);\n case \".c\":\n case \".h\":\n return parseC(f, source);\n case \".cpp\":\n case \".cc\":\n case \".cxx\":\n case \".hpp\":\n case \".hh\":\n case \".hxx\":\n return parseCpp(f, source);\n case \".dart\":\n return parseDart(f, source);\n case \".cs\":\n return parseCSharp(f, source);\n default:\n return emptyParsed(f, source);\n }\n}\n","// Generic tree-sitter parser used by the simpler-grammar languages\n// (Go, Rust, Java, Kotlin, PHP, Ruby, C, C++, Dart, C#).\n//\n// Each language file defines:\n// - which tree-sitter grammar to load\n// - a query string with capture names like `@function`, `@function.name`\n// - a `decls` table mapping declaration-capture pairs to SymbolKind\n// - optional `importCapture` for collecting import edges\n// Everything else (parser init, error handling, dedupe) lives here.\n\nimport { Query, type Node } from \"web-tree-sitter\";\n\nimport type { SymbolKind } from \"../../graph/types.js\";\nimport { createParser, type GrammarName, type ParsedFile, type ParsedSymbol } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nexport interface DeclCapture {\n /** Capture name for the declaration node, e.g. \"function\". */\n declCapture: string;\n /** Capture name for the symbol's name node, e.g. \"function.name\". */\n nameCapture: string;\n /** SymbolKind to assign. */\n kind: SymbolKind;\n}\n\nexport interface GenericParserConfig {\n grammar: GrammarName;\n query: string;\n decls: DeclCapture[];\n /** Capture name for import-source nodes. Skipped when omitted. */\n importCapture?: string;\n}\n\nexport function firstLine(text: string, max = 200): string {\n const line = text.split(/\\r?\\n/, 1)[0] ?? \"\";\n return line.length > max ? line.slice(0, max) + \"…\" : line;\n}\n\nfunction cleanImport(s: string): string {\n // Strip surrounding string-literal quotes (used by Go, Dart, C/C++).\n // Strip angle brackets used by C/C++ system includes.\n return s.replace(/^[\"'`<]+|[\"'`>]+$/g, \"\").trim();\n}\n\nexport async function runGenericParser(\n config: GenericParserConfig,\n f: WalkedFile,\n source: string,\n): Promise<ParsedFile> {\n let symbols: ParsedSymbol[] = [];\n let imports: string[] = [];\n\n try {\n const { parser, language } = await createParser(config.grammar);\n const tree = parser.parse(source);\n if (!tree) return { file: f, source, symbols, imports, calls: [] };\n\n const query = new Query(language, config.query);\n const matches = query.matches(tree.rootNode);\n\n for (const match of matches) {\n const byName = new Map<string, Node>();\n for (const cap of match.captures) byName.set(cap.name, cap.node);\n\n let matched: DeclCapture | null = null;\n for (const d of config.decls) {\n if (byName.has(d.declCapture) && byName.has(d.nameCapture)) {\n matched = d;\n break;\n }\n }\n\n if (matched) {\n const declNode = byName.get(matched.declCapture)!;\n const nameNode = byName.get(matched.nameCapture)!;\n symbols.push({\n name: nameNode.text,\n kind: matched.kind,\n startLine: declNode.startPosition.row + 1,\n endLine: declNode.endPosition.row + 1,\n signature: firstLine(declNode.text),\n });\n continue;\n }\n\n if (config.importCapture) {\n const imp = byName.get(config.importCapture);\n if (imp) imports.push(cleanImport(imp.text));\n }\n }\n\n const seen = new Set<string>();\n symbols = symbols.filter((s) => {\n const k = `${s.name}:${s.startLine}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n imports = Array.from(new Set(imports)).filter((s) => s.length > 0);\n } catch {\n // Query compile or parse failure — return what we have. Silent so a single\n // bad file doesn't abort the whole scan.\n }\n\n return { file: f, source, symbols, imports, calls: [] };\n}\n","// C parser. Function definitions, structs, enums, typedefs, #include directives.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_definition declarator: (function_declarator declarator: (identifier) @function.name)) @function\n(struct_specifier name: (type_identifier) @struct.name) @struct\n(enum_specifier name: (type_identifier) @enum.name) @enum\n(type_definition declarator: (type_identifier) @type.name) @type\n(preproc_include path: (string_literal) @import)\n(preproc_include path: (system_lib_string) @import)\n`;\n\nexport async function parseC(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"c\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"struct\", nameCapture: \"struct.name\", kind: \"class\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n { declCapture: \"type\", nameCapture: \"type.name\", kind: \"type\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// C++ parser. Functions, classes, structs, enums, namespaces, #includes.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_definition declarator: (function_declarator declarator: (identifier) @function.name)) @function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier) @method.name)) @method\n(class_specifier name: (type_identifier) @class.name) @class\n(struct_specifier name: (type_identifier) @struct.name) @struct\n(enum_specifier name: (type_identifier) @enum.name) @enum\n(namespace_definition name: (namespace_identifier) @namespace.name) @namespace\n(preproc_include path: (string_literal) @import)\n(preproc_include path: (system_lib_string) @import)\n`;\n\nexport async function parseCpp(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"cpp\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"struct\", nameCapture: \"struct.name\", kind: \"class\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n { declCapture: \"namespace\", nameCapture: \"namespace.name\", kind: \"class\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// C# (.NET) parser. Classes, interfaces, structs, methods, namespaces.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(class_declaration name: (identifier) @class.name) @class\n(interface_declaration name: (identifier) @interface.name) @interface\n(struct_declaration name: (identifier) @struct.name) @struct\n(enum_declaration name: (identifier) @enum.name) @enum\n(method_declaration name: (identifier) @method.name) @method\n(namespace_declaration name: (_) @namespace.name) @namespace\n(using_directive (_) @import)\n`;\n\nexport async function parseCSharp(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"csharp\",\n query: QUERY,\n decls: [\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"interface\", nameCapture: \"interface.name\", kind: \"interface\" },\n { declCapture: \"struct\", nameCapture: \"struct.name\", kind: \"class\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"namespace\", nameCapture: \"namespace.name\", kind: \"class\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// Dart parser. v0.1.11 — real symbol extraction + import parsing on top of the\n// ABI-v15 grammar that ships in tree-sitter-wasms.\n//\n// Distinguishes top-level function_signature (kind: function) from\n// function_signature nested under method_signature (kind: method) by\n// anchoring the top-level pattern under `program`.\n//\n// Imports: `package:foo/bar.dart` and `dart:async` are stripped — they cross\n// the project boundary. Bare `foo.dart` is normalized to `./foo.dart` so the\n// shared resolveImport() (which requires a leading `.`) treats it as a\n// same-directory relative import.\n\nimport { Query, type Node } from \"web-tree-sitter\";\nimport type { SymbolKind } from \"../../graph/types.js\";\nimport { createParser, type ParsedFile, type ParsedSymbol } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nconst QUERY = `\n(class_definition name: (identifier) @class.name) @class\n(mixin_declaration (identifier) @mixin.name) @mixin\n(extension_declaration name: (identifier) @ext.name) @ext\n(enum_declaration name: (identifier) @enum.name) @enum\n(type_alias (type_identifier) @typedef.name) @typedef\n\n(program (function_signature name: (identifier) @function.name) @function)\n\n(method_signature (function_signature name: (identifier) @method.name)) @method\n(method_signature (getter_signature name: (identifier) @getter.name)) @getter\n(method_signature (setter_signature name: (identifier) @setter.name)) @setter\n(constructor_signature name: (identifier) @ctor.name) @ctor\n\n(import_or_export (library_import (import_specification (configurable_uri (uri (string_literal) @import)))))\n`;\n\ninterface DeclShape {\n declCap: string;\n nameCap: string;\n kind: SymbolKind;\n}\n\nconst DECLS: DeclShape[] = [\n { declCap: \"class\", nameCap: \"class.name\", kind: \"class\" },\n { declCap: \"mixin\", nameCap: \"mixin.name\", kind: \"class\" },\n { declCap: \"ext\", nameCap: \"ext.name\", kind: \"class\" },\n { declCap: \"enum\", nameCap: \"enum.name\", kind: \"enum\" },\n { declCap: \"typedef\", nameCap: \"typedef.name\", kind: \"type\" },\n { declCap: \"function\", nameCap: \"function.name\", kind: \"function\" },\n { declCap: \"method\", nameCap: \"method.name\", kind: \"method\" },\n { declCap: \"getter\", nameCap: \"getter.name\", kind: \"method\" },\n { declCap: \"setter\", nameCap: \"setter.name\", kind: \"method\" },\n { declCap: \"ctor\", nameCap: \"ctor.name\", kind: \"method\" },\n];\n\nfunction firstLine(text: string, max = 200): string {\n const line = text.split(/\\r?\\n/, 1)[0] ?? \"\";\n return line.length > max ? line.slice(0, max) + \"…\" : line;\n}\n\n// Strip surrounding string-literal quotes and normalize bare same-directory\n// imports (Dart allows `import 'foo.dart';` without a leading `./`) so\n// resolveImport() — which keys off a leading dot — can match them.\nfunction normalizeDartImport(raw: string): string | null {\n const stripped = raw.replace(/^['\"]|['\"]$/g, \"\");\n if (!stripped) return null;\n if (stripped.startsWith(\"package:\")) return null;\n if (stripped.startsWith(\"dart:\")) return null;\n if (stripped.startsWith(\".\") || stripped.startsWith(\"/\")) return stripped;\n return `./${stripped}`;\n}\n\nexport async function parseDart(f: WalkedFile, source: string): Promise<ParsedFile> {\n let symbols: ParsedSymbol[] = [];\n let imports: string[] = [];\n\n try {\n const { parser, language } = await createParser(\"dart\");\n const tree = parser.parse(source);\n if (!tree) return { file: f, source, symbols, imports, calls: [] };\n\n const query = new Query(language, QUERY);\n const matches = query.matches(tree.rootNode);\n\n for (const match of matches) {\n const byName = new Map<string, Node>();\n for (const cap of match.captures) byName.set(cap.name, cap.node);\n\n let matched: DeclShape | null = null;\n for (const d of DECLS) {\n if (byName.has(d.declCap) && byName.has(d.nameCap)) {\n matched = d;\n break;\n }\n }\n\n if (matched) {\n const declNode = byName.get(matched.declCap)!;\n const nameNode = byName.get(matched.nameCap)!;\n symbols.push({\n name: nameNode.text,\n kind: matched.kind,\n startLine: declNode.startPosition.row + 1,\n endLine: declNode.endPosition.row + 1,\n signature: firstLine(declNode.text),\n });\n continue;\n }\n\n const importNode = byName.get(\"import\");\n if (importNode) {\n const norm = normalizeDartImport(importNode.text);\n if (norm) imports.push(norm);\n }\n }\n\n const seen = new Set<string>();\n symbols = symbols.filter((s) => {\n const k = `${s.name}:${s.startLine}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n imports = Array.from(new Set(imports));\n } catch {\n // swallow — see _generic.ts for the rationale (single bad file shouldn't\n // abort the whole scan).\n }\n\n return { file: f, source, symbols, imports, calls: [] };\n}\n","// Go parser. Functions, methods, type declarations, imports.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_declaration name: (identifier) @function.name) @function\n(method_declaration name: (field_identifier) @method.name) @method\n(type_spec name: (type_identifier) @type.name) @type\n(import_spec path: (interpreted_string_literal) @import)\n`;\n\nexport async function parseGo(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"go\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"type\", nameCapture: \"type.name\", kind: \"type\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// Java parser. Classes, interfaces, methods, imports.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(class_declaration name: (identifier) @class.name) @class\n(interface_declaration name: (identifier) @interface.name) @interface\n(method_declaration name: (identifier) @method.name) @method\n(enum_declaration name: (identifier) @enum.name) @enum\n(import_declaration (scoped_identifier) @import)\n`;\n\nexport async function parseJava(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"java\",\n query: QUERY,\n decls: [\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"interface\", nameCapture: \"interface.name\", kind: \"interface\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// Kotlin parser. Functions, classes, objects, interfaces, imports.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_declaration (simple_identifier) @function.name) @function\n(class_declaration (type_identifier) @class.name) @class\n(object_declaration (type_identifier) @object.name) @object\n(import_header (identifier) @import)\n`;\n\nexport async function parseKotlin(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"kotlin\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"object\", nameCapture: \"object.name\", kind: \"class\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// PHP parser. Functions, classes, interfaces, methods, traits.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_definition name: (name) @function.name) @function\n(class_declaration name: (name) @class.name) @class\n(interface_declaration name: (name) @interface.name) @interface\n(trait_declaration name: (name) @trait.name) @trait\n(method_declaration name: (name) @method.name) @method\n`;\n\nexport async function parsePhp(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"php\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"interface\", nameCapture: \"interface.name\", kind: \"interface\" },\n { declCapture: \"trait\", nameCapture: \"trait.name\", kind: \"class\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n ],\n },\n f,\n source,\n );\n}\n","// Python parser using tree-sitter-python WASM.\n// Extracts: function/class definitions, methods, and import statements.\n\nimport { Query, type Node } from \"web-tree-sitter\";\nimport { createParser, type ParsedFile, type ParsedSymbol } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nconst QUERY = `\n(function_definition name: (identifier) @function.name) @function\n(class_definition name: (identifier) @class.name) @class\n(import_statement name: (dotted_name) @import.module)\n(import_from_statement module_name: (dotted_name) @import.from)\n(import_from_statement module_name: (relative_import) @import.from)\n`;\n\nfunction firstLine(text: string, max = 200): string {\n const line = text.split(/\\r?\\n/, 1)[0] ?? \"\";\n return line.length > max ? line.slice(0, max) + \"…\" : line;\n}\n\nexport async function parsePython(f: WalkedFile, source: string): Promise<ParsedFile> {\n let symbols: ParsedSymbol[] = [];\n let imports: string[] = [];\n\n try {\n const { parser, language } = await createParser(\"python\");\n const tree = parser.parse(source);\n if (!tree) return { file: f, source, symbols, imports, calls: [] };\n\n const query = new Query(language, QUERY);\n const matches = query.matches(tree.rootNode);\n\n for (const match of matches) {\n const byName = new Map<string, Node>();\n for (const cap of match.captures) byName.set(cap.name, cap.node);\n\n const funcDecl = byName.get(\"function\");\n const funcName = byName.get(\"function.name\");\n if (funcDecl && funcName) {\n const parentType = funcDecl.parent?.parent?.type;\n const isMethod = parentType === \"class_definition\";\n symbols.push({\n name: funcName.text,\n kind: isMethod ? \"method\" : \"function\",\n startLine: funcDecl.startPosition.row + 1,\n endLine: funcDecl.endPosition.row + 1,\n signature: firstLine(funcDecl.text),\n });\n continue;\n }\n\n const classDecl = byName.get(\"class\");\n const className = byName.get(\"class.name\");\n if (classDecl && className) {\n symbols.push({\n name: className.text,\n kind: \"class\",\n startLine: classDecl.startPosition.row + 1,\n endLine: classDecl.endPosition.row + 1,\n signature: firstLine(classDecl.text),\n });\n continue;\n }\n\n const importNode = byName.get(\"import.module\") ?? byName.get(\"import.from\");\n if (importNode) imports.push(importNode.text);\n }\n\n const seen = new Set<string>();\n symbols = symbols.filter((s) => {\n const key = `${s.name}:${s.startLine}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n imports = Array.from(new Set(imports));\n } catch {\n // swallow parse errors\n }\n\n return { file: f, source, symbols, imports, calls: [] };\n}\n","// Ruby parser. Methods, classes, modules.\n// Imports omitted — Ruby's `require` is dynamic and hard to capture cleanly;\n// keyword indexing still surfaces dependencies.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(method name: (identifier) @function.name) @function\n(singleton_method name: (identifier) @method.name) @method\n(class name: (constant) @class.name) @class\n(module name: (constant) @module.name) @module\n`;\n\nexport async function parseRuby(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"ruby\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"module\", nameCapture: \"module.name\", kind: \"class\" },\n ],\n },\n f,\n source,\n );\n}\n","// Rust parser. Functions, structs, enums, traits, impls.\n// Import capture is omitted — `use` paths are nested and complex; the file\n// will still be walked + keyword-indexed.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_item name: (identifier) @function.name) @function\n(struct_item name: (type_identifier) @struct.name) @struct\n(enum_item name: (type_identifier) @enum.name) @enum\n(trait_item name: (type_identifier) @trait.name) @trait\n(impl_item type: (type_identifier) @impl.name) @impl\n`;\n\nexport async function parseRust(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"rust\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"struct\", nameCapture: \"struct.name\", kind: \"class\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n { declCapture: \"trait\", nameCapture: \"trait.name\", kind: \"interface\" },\n { declCapture: \"impl\", nameCapture: \"impl.name\", kind: \"class\" },\n ],\n },\n f,\n source,\n );\n}\n","// TS/JS parser using tree-sitter-typescript / -tsx WASM grammars.\r\n// Extracts: function/class/interface/type/enum declarations, exported consts,\r\n// arrow functions assigned to const, and import sources.\r\n\r\nimport { Query, type Node } from \"web-tree-sitter\";\r\nimport type { SymbolKind } from \"../../graph/types.js\";\r\nimport { createParser, type GrammarName, type ParsedFile, type ParsedSymbol } from \"../parser.js\";\r\nimport type { WalkedFile } from \"../walker.js\";\r\n\r\n// TS / TSX query — uses the type-identifier node type for class names, includes\r\n// interface / type-alias / enum declarations that don't exist in plain JS.\r\nconst TS_QUERY = `\r\n(function_declaration name: (identifier) @function.name) @function\r\n(class_declaration name: (type_identifier) @class.name) @class\r\n(interface_declaration name: (type_identifier) @interface.name) @interface\r\n(type_alias_declaration name: (type_identifier) @type.name) @type\r\n(enum_declaration name: (identifier) @enum.name) @enum\r\n(method_definition name: (property_identifier) @method.name) @method\r\n(lexical_declaration (variable_declarator name: (identifier) @const-fn.name value: [(arrow_function) (function_expression)])) @const-fn\r\n(import_statement source: (string) @import)\r\n`;\r\n\r\n// JS query — class names are plain identifiers (JS grammar has no\r\n// type_identifier node). No interface / type_alias / enum since JS lacks them.\r\n// Adds a call_expression capture for CommonJS require('x'); filtered in the\r\n// matching loop by checking the function identifier text equals \"require\".\r\nconst JS_QUERY = `\r\n(function_declaration name: (identifier) @function.name) @function\r\n(class_declaration name: (identifier) @class.name) @class\r\n(method_definition name: (property_identifier) @method.name) @method\r\n(lexical_declaration (variable_declarator name: (identifier) @const-fn.name value: [(arrow_function) (function_expression)])) @const-fn\r\n(import_statement source: (string) @import)\r\n(call_expression function: (identifier) @_require_fn arguments: (arguments . (string) @require_source))\r\n`;\r\n\r\nfunction grammarFor(ext: string): GrammarName {\r\n if (ext === \".tsx\" || ext === \".jsx\") return \"tsx\";\r\n if (ext === \".js\" || ext === \".cjs\" || ext === \".mjs\") return \"javascript\";\r\n return \"typescript\";\r\n}\r\n\r\nfunction queryFor(grammar: GrammarName): string {\r\n return grammar === \"javascript\" ? JS_QUERY : TS_QUERY;\r\n}\r\n\r\nfunction unquote(s: string): string {\r\n return s.replace(/^[\"'`]|[\"'`]$/g, \"\");\r\n}\r\n\r\nfunction firstLine(text: string, max = 200): string {\r\n const line = text.split(/\\r?\\n/, 1)[0] ?? \"\";\r\n return line.length > max ? line.slice(0, max) + \"…\" : line;\r\n}\r\n\r\ninterface DeclShape {\r\n decl: Node;\r\n name: Node;\r\n kind: SymbolKind;\r\n}\r\n\r\nfunction shapeFromCaptures(captures: Map<string, Node>): DeclShape | null {\r\n const findDecl = (k: string, sk: SymbolKind): DeclShape | null => {\r\n const decl = captures.get(k);\r\n const name = captures.get(`${k}.name`);\r\n return decl && name ? { decl, name, kind: sk } : null;\r\n };\r\n\r\n return (\r\n findDecl(\"function\", \"function\") ??\r\n findDecl(\"class\", \"class\") ??\r\n findDecl(\"interface\", \"interface\") ??\r\n findDecl(\"type\", \"type\") ??\r\n findDecl(\"enum\", \"enum\") ??\r\n findDecl(\"method\", \"method\") ??\r\n findDecl(\"const-fn\", \"function\")\r\n );\r\n}\r\n\r\nexport async function parseTypeScript(f: WalkedFile, source: string): Promise<ParsedFile> {\r\n const grammar = grammarFor(f.ext);\r\n let symbols: ParsedSymbol[] = [];\r\n let imports: string[] = [];\r\n\r\n try {\r\n const { parser, language } = await createParser(grammar);\r\n const tree = parser.parse(source);\r\n if (!tree) return { file: f, source, symbols, imports, calls: [] };\r\n\r\n const query = new Query(language, queryFor(grammar));\r\n const matches = query.matches(tree.rootNode);\r\n\r\n for (const match of matches) {\r\n const byName = new Map<string, Node>();\r\n for (const cap of match.captures) byName.set(cap.name, cap.node);\r\n\r\n const shape = shapeFromCaptures(byName);\r\n if (shape) {\r\n symbols.push({\r\n name: shape.name.text,\r\n kind: shape.kind,\r\n startLine: shape.decl.startPosition.row + 1,\r\n endLine: shape.decl.endPosition.row + 1,\r\n signature: firstLine(shape.decl.text),\r\n });\r\n continue;\r\n }\r\n const importNode = byName.get(\"import\");\r\n if (importNode) {\r\n imports.push(unquote(importNode.text));\r\n continue;\r\n }\r\n // CommonJS require('x') — only captured by JS_QUERY. The identifier\r\n // must literally be \"require\" (not setTimeout, console, etc).\r\n const requireFn = byName.get(\"_require_fn\");\r\n const requireSource = byName.get(\"require_source\");\r\n if (requireFn && requireSource && requireFn.text === \"require\") {\r\n imports.push(unquote(requireSource.text));\r\n }\r\n }\r\n\r\n const seen = new Set<string>();\r\n symbols = symbols.filter((s) => {\r\n const key = `${s.name}:${s.startLine}`;\r\n if (seen.has(key)) return false;\r\n seen.add(key);\r\n return true;\r\n });\r\n imports = Array.from(new Set(imports));\r\n } catch {\r\n // Parse failure shouldn't abort the whole scan — return what we have.\r\n }\r\n\r\n return { file: f, source, symbols, imports, calls: [] };\r\n}\r\n","// Svelte parser. Extracts <script> and <script lang=\"ts\"> blocks and parses\n// their contents with the TypeScript parser. Tracks the original line offset\n// so reported symbol positions match the .svelte source.\n\nimport { parseTypeScript } from \"./typescript.js\";\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nconst SCRIPT_RE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script>/gi;\n\ninterface ScriptBlock {\n source: string;\n startLine: number; // 1-based line number where the script content begins\n isTsx: boolean;\n}\n\nfunction extractScripts(source: string): ScriptBlock[] {\n const out: ScriptBlock[] = [];\n for (const match of source.matchAll(SCRIPT_RE)) {\n const full = match[0];\n const inner = match[1] ?? \"\";\n const openTag = full.slice(0, full.indexOf(\">\") + 1);\n const tagStart = match.index ?? 0;\n const contentStart = tagStart + openTag.length;\n const startLine = source.slice(0, contentStart).split(/\\r?\\n/).length;\n const isTsx = /\\blang\\s*=\\s*[\"']?(ts|tsx|typescript)[\"']?/i.test(openTag);\n out.push({ source: inner, startLine, isTsx });\n }\n return out;\n}\n\nexport async function parseSvelte(f: WalkedFile, source: string): Promise<ParsedFile> {\n const blocks = extractScripts(source);\n const out: ParsedFile = { file: f, source, symbols: [], imports: [], calls: [] };\n\n for (const block of blocks) {\n const virtual: WalkedFile = { ...f, ext: block.isTsx ? \".ts\" : \".js\" };\n const parsed = await parseTypeScript(virtual, block.source);\n const offset = block.startLine - 1;\n for (const sym of parsed.symbols) {\n out.symbols.push({\n ...sym,\n startLine: sym.startLine + offset,\n endLine: sym.endLine + offset,\n });\n }\n for (const imp of parsed.imports) out.imports.push(imp);\n }\n\n // The .svelte file itself is treated as a component.\n out.symbols.push({\n name: f.relPath.split(\"/\").pop()?.replace(/\\.svelte$/i, \"\") ?? f.relPath,\n kind: \"component\",\n startLine: 1,\n endLine: source.split(/\\r?\\n/).length,\n signature: f.relPath,\n });\n out.imports = Array.from(new Set(out.imports));\n return out;\n}\n","// Vue SFC parser. Extracts <script> / <script setup> / <script lang=\"ts\">\n// blocks and parses them with the TypeScript parser, preserving line offsets.\n\nimport { parseTypeScript } from \"./typescript.js\";\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nconst SCRIPT_RE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script>/gi;\n\ninterface ScriptBlock {\n source: string;\n startLine: number;\n isTs: boolean;\n}\n\nfunction extractScripts(source: string): ScriptBlock[] {\n const out: ScriptBlock[] = [];\n for (const match of source.matchAll(SCRIPT_RE)) {\n const full = match[0];\n const inner = match[1] ?? \"\";\n const openTag = full.slice(0, full.indexOf(\">\") + 1);\n const tagStart = match.index ?? 0;\n const contentStart = tagStart + openTag.length;\n const startLine = source.slice(0, contentStart).split(/\\r?\\n/).length;\n const isTs = /\\blang\\s*=\\s*[\"']?(ts|tsx|typescript)[\"']?/i.test(openTag);\n out.push({ source: inner, startLine, isTs });\n }\n return out;\n}\n\nexport async function parseVue(f: WalkedFile, source: string): Promise<ParsedFile> {\n const blocks = extractScripts(source);\n const out: ParsedFile = { file: f, source, symbols: [], imports: [], calls: [] };\n\n for (const block of blocks) {\n const virtual: WalkedFile = { ...f, ext: block.isTs ? \".ts\" : \".js\" };\n const parsed = await parseTypeScript(virtual, block.source);\n const offset = block.startLine - 1;\n for (const sym of parsed.symbols) {\n out.symbols.push({\n ...sym,\n startLine: sym.startLine + offset,\n endLine: sym.endLine + offset,\n });\n }\n for (const imp of parsed.imports) out.imports.push(imp);\n }\n\n out.symbols.push({\n name: f.relPath.split(\"/\").pop()?.replace(/\\.vue$/i, \"\") ?? f.relPath,\n kind: \"component\",\n startLine: 1,\n endLine: source.split(/\\r?\\n/).length,\n signature: f.relPath,\n });\n out.imports = Array.from(new Set(out.imports));\n return out;\n}\n","// Walks project root, yields files to parse.\n// Honors .gitignore + .synthraignore (additive — entries in either are ignored).\n// Defensive defaults skip VCS, build, and dependency directories even if absent\n// from .gitignore.\n\nimport { readFile, readdir, stat } from \"node:fs/promises\";\nimport { extname, join, relative, sep } from \"node:path\";\nimport ignore, { type Ignore } from \"ignore\";\n\nexport interface WalkedFile {\n absPath: string;\n relPath: string;\n ext: string;\n size: number;\n}\n\nexport interface WalkOptions {\n /** Maximum file size to yield (bytes). Defaults to 2 MB. */\n maxFileSize?: number;\n /** Additional ignore patterns layered on top of .gitignore + .synthraignore. */\n extraIgnore?: string[];\n}\n\nconst DEFAULT_IGNORE = [\n \".git/\",\n \".synthra/\",\n \".synthra-graph/\",\n \".claude/\",\n \"node_modules/\",\n \"dist/\",\n \"build/\",\n \"out/\",\n \"coverage/\",\n \".next/\",\n \".nuxt/\",\n \".svelte-kit/\",\n \".turbo/\",\n \".cache/\",\n \".vscode/\",\n \".idea/\",\n \".vs/\",\n // Flutter / Dart build caches — IDE-rehydrated, contain third-party\n // type stubs (typescript.d.ts, babylon.js etc.) that contaminate the graph.\n \".dart_tool/\",\n \".flutter-plugins\",\n \".flutter-plugins-dependencies\",\n // Android / Java / Kotlin / Rust\n \".gradle/\",\n \"target/\",\n // iOS / Xcode\n \"Pods/\",\n \"DerivedData/\",\n // Python\n \"__pycache__/\",\n \".venv/\",\n \"venv/\",\n \".tox/\",\n \".pytest_cache/\",\n \".mypy_cache/\",\n \".ruff_cache/\",\n // .NET\n \"obj/\",\n];\n\nconst BINARY_EXTS = new Set([\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\", \".ico\", \".bmp\",\n \".pdf\", \".zip\", \".tar\", \".gz\", \".7z\", \".rar\",\n \".mp3\", \".mp4\", \".mov\", \".avi\", \".webm\", \".wav\", \".ogg\",\n \".ttf\", \".otf\", \".woff\", \".woff2\", \".eot\",\n \".exe\", \".dll\", \".so\", \".dylib\", \".bin\", \".wasm\",\n \".lock\", \".lockb\",\n]);\n\nasync function readIgnoreFile(path: string): Promise<string[]> {\n try {\n const text = await readFile(path, \"utf8\");\n return text\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter((l) => l.length > 0 && !l.startsWith(\"#\"));\n } catch {\n return [];\n }\n}\n\nasync function buildMatcher(root: string, extra: string[]): Promise<Ignore> {\n const ig = ignore();\n ig.add(DEFAULT_IGNORE);\n ig.add(await readIgnoreFile(join(root, \".gitignore\")));\n ig.add(await readIgnoreFile(join(root, \".synthraignore\")));\n if (extra.length) ig.add(extra);\n return ig;\n}\n\nfunction toPosix(p: string): string {\n return sep === \"/\" ? p : p.split(sep).join(\"/\");\n}\n\nexport async function* walk(\n root: string,\n options: WalkOptions = {},\n): AsyncGenerator<WalkedFile> {\n const maxFileSize = options.maxFileSize ?? 2_000_000;\n const ig = await buildMatcher(root, options.extraIgnore ?? []);\n\n async function* recurse(dir: string): AsyncGenerator<WalkedFile> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n const rel = relative(root, abs);\n if (!rel) continue;\n const relPosix = toPosix(rel);\n const matchPath = entry.isDirectory() ? `${relPosix}/` : relPosix;\n if (ig.ignores(matchPath)) continue;\n\n if (entry.isDirectory()) {\n yield* recurse(abs);\n } else if (entry.isFile()) {\n const ext = extname(entry.name).toLowerCase();\n if (BINARY_EXTS.has(ext)) continue;\n let size: number;\n try {\n const s = await stat(abs);\n size = s.size;\n } catch {\n continue;\n }\n if (size > maxFileSize) continue;\n yield { absPath: abs, relPath: relPosix, ext, size };\n }\n }\n }\n\n yield* recurse(root);\n}\n","// Reads/writes info_graph.json and symbol_index.json.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport type { GraphSchema, SymbolIndex } from \"./types.js\";\n\nasync function writeJson(path: string, data: unknown, pretty: boolean): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const text = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);\n await writeFile(path, text + \"\\n\", \"utf8\");\n}\n\nasync function readJson<T>(path: string): Promise<T> {\n const text = await readFile(path, \"utf8\");\n return JSON.parse(text) as T;\n}\n\nexport async function writeGraph(path: string, graph: GraphSchema): Promise<void> {\n // Pretty-printing a graph with full file contents balloons disk size and\n // the JSON is only ever read by machines; keep it compact.\n await writeJson(path, graph, false);\n}\n\nexport async function readGraph(path: string): Promise<GraphSchema> {\n return readJson<GraphSchema>(path);\n}\n\nexport async function writeSymbolIndex(path: string, index: SymbolIndex): Promise<void> {\n await writeJson(path, index, true);\n}\n\nexport async function readSymbolIndex(path: string): Promise<SymbolIndex> {\n // Re-home onto a null prototype so name lookups (e.g. index[\"toString\"])\n // never resolve to an inherited Object.prototype member. Mirrors\n // buildSymbolIndex, which builds the index the same way.\n const parsed = await readJson<SymbolIndex>(path);\n return Object.assign(Object.create(null), parsed);\n}\n","// Project bootstrap: creates .synthra-graph/, .synthra/, updates .gitignore,\r\n// patches CLAUDE.md with the versioned policy block.\r\n\r\nimport { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\r\n\r\nimport { patchClaudeMd } from \"../hooks/claude-md.js\";\r\nimport type { SynthraPaths } from \"../shared/paths.js\";\r\n\r\nexport interface BootstrapResult {\r\n graphCreated: boolean;\r\n contextCreated: boolean;\r\n gitignoreUpdated: boolean;\r\n claudeMdUpdated: boolean;\r\n claudeMdCreated: boolean;\r\n}\r\n\r\n// Entries Synthra appends to the project .gitignore on bootstrap.\r\n// Each is gated by a check: if the entry is already present (any\r\n// indentation, trimmed match), it's skipped. Comments are per-entry so\r\n// users understand why each line is there and can remove what they don't\r\n// want without breaking the rest.\r\nconst GITIGNORE_ENTRIES: { comment: string; entry: string }[] = [\r\n {\r\n comment: \"added by synthra (heavy generated state — gitignored by design)\",\r\n entry: \".synthra-graph/\",\r\n },\r\n {\r\n comment:\r\n \"added by synthra — MCP registration. Remove this line if you want \" +\r\n \"to share the synthra MCP entry with teammates via committed .mcp.json\",\r\n entry: \".mcp.json\",\r\n },\r\n];\r\n\r\nasync function exists(path: string): Promise<boolean> {\r\n try {\r\n await stat(path);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\nasync function ensureDir(path: string): Promise<boolean> {\r\n const had = await exists(path);\r\n await mkdir(path, { recursive: true });\r\n return !had;\r\n}\r\n\r\nasync function patchGitignore(path: string): Promise<boolean> {\r\n let existing = \"\";\r\n try {\r\n existing = await readFile(path, \"utf8\");\r\n } catch {\r\n /* file may not exist */\r\n }\r\n const trimmed = new Set(existing.split(/\\r?\\n/).map((l) => l.trim()));\r\n const missing = GITIGNORE_ENTRIES.filter((e) => !trimmed.has(e.entry));\r\n if (missing.length === 0) return false;\r\n\r\n const block =\r\n missing.map((m) => `# ${m.comment}\\n${m.entry}`).join(\"\\n\") + \"\\n\";\r\n const appendix =\r\n (existing.length === 0 || existing.endsWith(\"\\n\") ? \"\" : \"\\n\") +\r\n (existing.length ? \"\\n\" : \"\") +\r\n block;\r\n await writeFile(path, existing + appendix, \"utf8\");\r\n return true;\r\n}\r\n\r\nexport async function bootstrap(paths: SynthraPaths): Promise<BootstrapResult> {\r\n const graphCreated = await ensureDir(paths.graphDir);\r\n const contextCreated = await ensureDir(paths.contextDir);\r\n const gitignoreUpdated = await patchGitignore(paths.gitignore);\r\n\r\n const claudeMdExistedBefore = await exists(paths.claudeMd);\r\n const patch = await patchClaudeMd(paths.claudeMd);\r\n\r\n return {\r\n graphCreated,\r\n contextCreated,\r\n gitignoreUpdated,\r\n claudeMdUpdated: patch.updated,\r\n claudeMdCreated: patch.created && !claudeMdExistedBefore,\r\n };\r\n}\r\n","// Idempotent patcher for the project's CLAUDE.md. Manages a single block\r\n// bounded by <!-- synthra-policy v<N> BEGIN --> ... <!-- synthra-policy v<N> END -->.\r\n// On each run, any prior synthra-policy block (any version) is removed and the\r\n// current-version block is appended at the end.\r\n\r\nimport { readFile, writeFile } from \"node:fs/promises\";\r\n\r\nexport const POLICY_VERSION = 3;\r\nexport const POLICY_BEGIN = `<!-- synthra-policy v${POLICY_VERSION} BEGIN -->`;\r\nexport const POLICY_END = `<!-- synthra-policy v${POLICY_VERSION} END -->`;\r\n\r\n// Matches a synthra-policy block of any version, e.g. v1, v2 …\r\nconst ANY_BLOCK_RE = /<!--\\s*synthra-policy\\s+v\\d+\\s+BEGIN\\s*-->[\\s\\S]*?<!--\\s*synthra-policy\\s+v\\d+\\s+END\\s*-->\\s*/g;\r\n\r\nexport interface PatchResult {\r\n created: boolean;\r\n updated: boolean;\r\n skipped: boolean;\r\n}\r\n\r\nexport function policyBlock(): string {\r\n return [\r\n POLICY_BEGIN,\r\n \"## Synthra context policy\",\r\n \"\",\r\n \"Synthra has pre-loaded structured context into this session and exposes\",\r\n \"the project's code graph through MCP tools. **Prefer these tools over\",\r\n \"Grep / Glob / Read** — they are faster, cheaper, and already filtered\",\r\n \"to relevant files.\",\r\n \"\",\r\n \"> **Tool namespace.** Synthra's MCP tools are exposed as\",\r\n \"> `mcp__synthra__graph_continue`, `mcp__synthra__graph_read`, and\",\r\n \"> `mcp__synthra__graph_register_edit`. Below they are referred to by\",\r\n \"> their short names (`graph_continue` etc.) for readability — use the\",\r\n \"> full namespaced form when actually invoking them.\",\r\n \"\",\r\n \"### Tools\",\r\n \"\",\r\n \"- **`graph_continue(query)`** — returns a `Confidence` label, the list\",\r\n \" of relevant `Files`, and signatures + top function bodies for those\",\r\n \" files. Your default first move when you need project context.\",\r\n \"- **`graph_read(target)`** — fetch source. Prefer the\",\r\n \" `\\\"file/path.ts::SymbolName\\\"` form over a bare file path — reading one\",\r\n \" symbol is ~50 tokens, reading a whole file is thousands.\",\r\n \"- **`graph_register_edit(files)`** — after you edit files, call this so\",\r\n \" subsequent turns weight your changes and avoid stale snapshots.\",\r\n \"\",\r\n \"### When to call `graph_continue` — and when to skip\",\r\n \"\",\r\n \"**Call `graph_continue` only when you do NOT already know the relevant\",\r\n \"files.**\",\r\n \"\",\r\n \"Call it when:\",\r\n \"- This is the first message of a new task or conversation\",\r\n \"- The task shifts to a different area of the codebase\",\r\n \"- You need files you haven't seen yet in this session\",\r\n \"\",\r\n \"**Skip `graph_continue` when:**\",\r\n \"- You already identified the relevant files earlier in this conversation\",\r\n \"- You are doing follow-up work on files already read (verify, refactor,\",\r\n \" test, docs, cleanup, commit)\",\r\n \"- The task is pure text (commit message, explanation, summary)\",\r\n \"\",\r\n \"If skipping, go directly to `graph_read(\\\"file.ts::symbol\\\")` on what\",\r\n \"you already know.\",\r\n \"\",\r\n \"### Confidence caps\",\r\n \"\",\r\n \"When `graph_continue` returns:\",\r\n \"\",\r\n \"- **`Confidence: high`** → Stop. Do NOT Grep, Glob, or further explore\",\r\n \" for this query. The graph already has it.\",\r\n \"- **`Confidence: medium`** → Read the listed `Files` directly via\",\r\n \" `graph_read(\\\"file::symbol\\\")` *before* trying Grep. The graph has\",\r\n \" narrowed the search space — use it, don't bypass it.\",\r\n \"- **`Confidence: low`** → You may use Grep / Glob, but the PreToolUse\",\r\n \" hook may still block redundant calls.\",\r\n \"\",\r\n \"### Reading code\",\r\n \"\",\r\n \"- **Always use `file::symbol` notation** with `graph_read`. Whole-file\",\r\n \" reads should be rare — only when you genuinely need the full file.\",\r\n \"- If `graph_continue`'s `Files` list contains a `::` entry, pass it\",\r\n \" verbatim to `graph_read`.\",\r\n \"\",\r\n \"### Don'ts\",\r\n \"\",\r\n \"- Don't Grep / Glob before calling `graph_continue` when required — the\",\r\n \" PreToolUse hook may block it.\",\r\n \"- Don't call `graph_continue` more than once per turn.\",\r\n \"- Don't read whole files when a symbol-level read would suffice.\",\r\n \"\",\r\n \"### Session-end resume note\",\r\n \"\",\r\n \"When the user signals they're done (e.g. \\\"bye\\\", \\\"wrap up\\\", \\\"done\\\"),\",\r\n \"persist the resume state by calling `context_remember` once per bullet.\",\r\n \"Synthra re-renders `.synthra/CONTEXT.md` from those entries at session\",\r\n \"end — do **NOT** write to `CONTEXT.md` directly, it is a derived view\",\r\n \"and direct edits are overwritten by the Stop hook.\",\r\n \"\",\r\n \"Use these `kind` values:\",\r\n \"\",\r\n \"- **`kind: \\\"task\\\"`** — what is being worked on right now (1 entry)\",\r\n \"- **`kind: \\\"decision\\\"`** — non-obvious choices made this session (max 3)\",\r\n \"- **`kind: \\\"next\\\"`** — concrete next steps (max 3)\",\r\n \"\",\r\n \"Tag entries with the relevant area (`tags: [\\\"auth\\\"]`) and the files\",\r\n \"they touch (`files: [\\\"src/auth.ts\\\"]`) so later `context_recall` queries\",\r\n \"can filter. Keep each `text` to 1–2 sentences.\",\r\n \"\",\r\n \"_This block is managed by Synthra. Edits inside the BEGIN/END markers\",\r\n \"are overwritten on every `syn .` run._\",\r\n \"\",\r\n POLICY_END,\r\n ].join(\"\\n\");\r\n}\r\n\r\nexport async function patchClaudeMd(path: string): Promise<PatchResult> {\r\n let existing: string | null;\r\n try {\r\n existing = await readFile(path, \"utf8\");\r\n } catch {\r\n existing = null;\r\n }\r\n\r\n const block = policyBlock();\r\n\r\n if (existing === null) {\r\n await writeFile(path, block + \"\\n\", \"utf8\");\r\n return { created: true, updated: false, skipped: false };\r\n }\r\n\r\n const stripped = existing.replace(ANY_BLOCK_RE, \"\");\r\n const hadBlock = stripped !== existing;\r\n\r\n const desired = stripped.endsWith(\"\\n\")\r\n ? stripped + \"\\n\" + block + \"\\n\"\r\n : (stripped.length ? stripped + \"\\n\\n\" : \"\") + block + \"\\n\";\r\n\r\n if (hadBlock && desired === existing) {\r\n return { created: false, updated: false, skipped: true };\r\n }\r\n\r\n await writeFile(path, desired, \"utf8\");\r\n return { created: false, updated: true, skipped: false };\r\n}\r\n","// Scoring for retrieved files. Combines:\n// - keyword overlap with the (already-tokenized) query\n// - symbol-name overlap (boosted: hits in a file's defined symbols matter more)\n// - import-graph proximity from session-known + recent paths (boost adjacent files)\n// - recency boost from activity log (placeholder — wired in M5)\n\nimport type { Edge, FileNode, GraphSchema, SymbolNode } from \"./types.js\";\n\nexport interface RankInputs {\n candidates: FileNode[];\n query: string;\n graph?: GraphSchema;\n recentlyEditedPaths?: string[];\n sessionKnownPaths?: string[];\n}\n\nexport interface ScoredFile {\n file: FileNode;\n score: number;\n reasons: string[];\n}\n\nconst STOPWORDS = new Set([\n \"a\", \"an\", \"and\", \"are\", \"as\", \"at\", \"be\", \"by\", \"for\", \"from\",\n \"has\", \"have\", \"in\", \"is\", \"it\", \"of\", \"on\", \"or\", \"that\", \"the\",\n \"this\", \"to\", \"was\", \"we\", \"with\", \"what\", \"where\", \"when\", \"why\",\n \"how\", \"do\", \"does\", \"i\", \"me\", \"my\", \"you\", \"your\", \"code\", \"file\",\n]);\n\nexport function tokenizeQuery(query: string): string[] {\n const tokens = query\n .toLowerCase()\n .split(/[^a-z0-9_]+/g)\n .filter((t) => t.length > 1 && !STOPWORDS.has(t));\n // Also split camelCase/snake_case for queries like \"AuthService\"\n const expanded = new Set<string>();\n for (const t of tokens) {\n expanded.add(t);\n const parts = t.match(/[a-z]+|[0-9]+/g) ?? [];\n for (const p of parts) if (p.length > 1) expanded.add(p);\n }\n return Array.from(expanded);\n}\n\nfunction indexSymbolsByFile(graph: GraphSchema | undefined): Map<string, SymbolNode[]> {\n const out = new Map<string, SymbolNode[]>();\n if (!graph) return out;\n for (const n of graph.nodes) {\n if (n.kind !== \"symbol\") continue;\n const list = out.get(n.file) ?? [];\n list.push(n);\n out.set(n.file, list);\n }\n return out;\n}\n\nfunction indexImportEdges(graph: GraphSchema | undefined): Map<string, Set<string>> {\n // file path → set of file paths it imports (1-hop)\n const out = new Map<string, Set<string>>();\n if (!graph) return out;\n const idToPath = new Map<string, string>();\n for (const n of graph.nodes) if (n.kind === \"file\") idToPath.set(n.id, n.path);\n for (const e of graph.edges as Edge[]) {\n if (e.kind !== \"imports\") continue;\n const from = idToPath.get(e.from);\n const to = idToPath.get(e.to);\n if (!from || !to) continue;\n const s = out.get(from) ?? new Set<string>();\n s.add(to);\n out.set(from, s);\n }\n return out;\n}\n\nexport function scoreFiles(inputs: RankInputs): ScoredFile[] {\n const qTokens = new Set(tokenizeQuery(inputs.query));\n const symbolsByFile = indexSymbolsByFile(inputs.graph);\n const importsFrom = indexImportEdges(inputs.graph);\n\n const seeds = new Set<string>(inputs.sessionKnownPaths ?? []);\n for (const p of inputs.recentlyEditedPaths ?? []) seeds.add(p);\n\n // First pass: keyword + symbol score\n const scored: ScoredFile[] = [];\n for (const file of inputs.candidates) {\n const reasons: string[] = [];\n let score = 0;\n\n // Keyword overlap\n let kwHits = 0;\n for (const kw of file.keywords) if (qTokens.has(kw)) kwHits += 1;\n if (kwHits) {\n score += kwHits * 2;\n reasons.push(`kw=${kwHits}`);\n }\n\n // Symbol-name overlap (higher signal than file-level keywords)\n const symbols = symbolsByFile.get(file.path) ?? [];\n let symHits = 0;\n for (const sym of symbols) {\n const name = sym.name.toLowerCase();\n if (qTokens.has(name)) {\n symHits += 3;\n } else {\n // partial match: any query token is a substring of, or contained by, the symbol name\n for (const t of qTokens) {\n if (name.includes(t) || t.includes(name)) {\n symHits += 1;\n break;\n }\n }\n }\n }\n if (symHits) {\n score += symHits;\n reasons.push(`sym=${symHits}`);\n }\n\n // Path match: file path contains a query token\n const pathLower = file.path.toLowerCase();\n let pathHits = 0;\n for (const t of qTokens) if (pathLower.includes(t)) pathHits += 1;\n if (pathHits) {\n score += pathHits;\n reasons.push(`path=${pathHits}`);\n }\n\n if (seeds.has(file.path)) {\n score += 5;\n reasons.push(\"seed\");\n }\n\n scored.push({ file, score, reasons });\n }\n\n // Second pass: 1-hop import-graph boost from any file already scored > 0\n const positivePaths = new Set(scored.filter((s) => s.score > 0).map((s) => s.file.path));\n if (positivePaths.size > 0) {\n for (const s of scored) {\n if (s.score > 0) continue;\n // Does any file that imports this one have a positive score?\n let importBoost = 0;\n for (const [from, tos] of importsFrom) {\n if (!positivePaths.has(from)) continue;\n if (tos.has(s.file.path)) {\n importBoost += 1;\n break;\n }\n }\n if (importBoost) {\n s.score += importBoost * 0.5;\n s.reasons.push(\"imp-adj\");\n }\n }\n }\n\n scored.sort((a, b) => b.score - a.score);\n return scored;\n}\n\nexport function rank(inputs: RankInputs): FileNode[] {\n return scoreFiles(inputs).map((s) => s.file);\n}\n","// Query-time retrieval. Given a natural-language query (or a symbol name),\n// returns a ranked list of FileNodes plus a confidence label.\n\nimport { scoreFiles, tokenizeQuery, type RankInputs } from \"./rank.js\";\nimport type { FileNode, GraphSchema } from \"./types.js\";\n\nexport interface RetrievalResult {\n files: FileNode[];\n confidence: \"high\" | \"medium\" | \"low\";\n reason: string;\n}\n\nexport interface RetrieveOptions {\n topK?: number;\n recentlyEditedPaths?: string[];\n sessionKnownPaths?: string[];\n}\n\nexport async function retrieve(\n graph: GraphSchema,\n query: string,\n options: RetrieveOptions = {},\n): Promise<RetrievalResult> {\n const topK = options.topK ?? 12;\n const qTokens = tokenizeQuery(query);\n\n const allFiles: FileNode[] = graph.nodes.filter(\n (n): n is FileNode => n.kind === \"file\",\n );\n\n if (allFiles.length === 0 || qTokens.length === 0) {\n return {\n files: [],\n confidence: \"low\",\n reason: qTokens.length === 0 ? \"empty query\" : \"empty graph\",\n };\n }\n\n const rankInputs: RankInputs = {\n candidates: allFiles,\n query,\n graph,\n recentlyEditedPaths: options.recentlyEditedPaths,\n sessionKnownPaths: options.sessionKnownPaths,\n };\n const scored = scoreFiles(rankInputs);\n const positive = scored.filter((s) => s.score > 0);\n\n if (positive.length === 0) {\n return {\n files: [],\n confidence: \"low\",\n reason: `no matches for ${JSON.stringify(qTokens)}`,\n };\n }\n\n const top = positive.slice(0, topK).map((s) => s.file);\n const topScore = positive[0]?.score ?? 0;\n const secondScore = positive[1]?.score ?? 0;\n\n // confidence: high = clear top match (2x next or only one hit)\n // medium = several hits but no dominant one\n // low = a few weak hits\n let confidence: \"high\" | \"medium\" | \"low\";\n if (positive.length === 1) confidence = \"high\";\n else if (topScore >= 6 && topScore >= secondScore * 2) confidence = \"high\";\n else if (topScore >= 3) confidence = \"medium\";\n else confidence = \"low\";\n\n const reasons = positive\n .slice(0, Math.min(3, top.length))\n .map((s) => `${s.file.path} (${s.reasons.join(\",\")})`)\n .join(\"; \");\n\n return {\n files: top,\n confidence,\n reason: `top: ${reasons}`,\n };\n}\n","// Branch-aware routing for the context store.\n// On the default branch, reads/writes go to .synthra/context-store.json.\n// On a feature branch, they go to .synthra/branches/<sanitized-branch>/context-store.json.\n\nimport { execFile } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport async function currentBranch(projectRoot: string): Promise<string> {\n // Try .git/HEAD first — avoids the subprocess cost and works for detached\n // worktrees too (.git is a file there).\n try {\n const headPath = join(projectRoot, \".git\", \"HEAD\");\n const head = await readFile(headPath, \"utf8\");\n const trimmed = head.trim();\n const match = trimmed.match(/^ref:\\s+refs\\/heads\\/(.+)$/);\n if (match?.[1]) return match[1];\n // Detached HEAD — fall through\n } catch {\n // .git/HEAD unreadable (worktree file, submodule, or not a git repo)\n }\n\n try {\n const { stdout } = await execFileAsync(\"git\", [\"branch\", \"--show-current\"], {\n cwd: projectRoot,\n });\n const name = stdout.trim();\n if (name) return name;\n } catch {\n // git not on PATH or not a repo\n }\n\n return \"main\";\n}\n\nexport async function defaultBranch(projectRoot: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync(\n \"git\",\n [\"symbolic-ref\", \"refs/remotes/origin/HEAD\", \"--short\"],\n { cwd: projectRoot },\n );\n const trimmed = stdout.trim();\n const match = trimmed.match(/^origin\\/(.+)$/);\n if (match?.[1]) return match[1];\n } catch {\n // No origin/HEAD set — fall back to heuristic\n }\n return \"main\";\n}\n\nexport function sanitizeBranchName(name: string): string {\n return name.replaceAll(\"/\", \"-\").replaceAll(\"\\\\\", \"-\");\n}\n\nexport interface BranchScopedPaths {\n contextStore: string;\n contextMd: string;\n branchDir: string | null;\n}\n\nexport function resolveBranchPaths(\n contextDir: string,\n branch: string,\n isDefault: boolean,\n): BranchScopedPaths {\n if (isDefault) {\n return {\n contextStore: join(contextDir, \"context-store.json\"),\n contextMd: join(contextDir, \"CONTEXT.md\"),\n branchDir: null,\n };\n }\n const branchDir = join(contextDir, \"branches\", sanitizeBranchName(branch));\n return {\n contextStore: join(branchDir, \"context-store.json\"),\n contextMd: join(branchDir, \"CONTEXT.md\"),\n branchDir,\n };\n}\n","// Free-form CONTEXT.md narrative. Updated by Stop hook at session end with:\n// - Current Task (1 sentence)\n// - Key Decisions (max 3 bullets)\n// - Next Steps (max 3 bullets)\n// Capped at ~20 visible content lines.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport type { ContextEntry } from \"./context-store.js\";\n\nexport interface ContextMd {\n branch: string;\n currentTask: string;\n keyDecisions: string[];\n nextSteps: string[];\n date: string;\n}\n\nconst MAX_BULLETS = 3;\n\nexport function deriveContextMd(entries: ContextEntry[], branch: string): ContextMd {\n // Latest pending task drives \"current task\".\n const tasks = entries.filter((e) => e.type === \"task\").reverse();\n const currentTask = tasks[0]?.content ?? \"\";\n\n const keyDecisions = entries\n .filter((e) => e.type === \"decision\")\n .slice(-MAX_BULLETS)\n .map((e) => e.content);\n\n const nextSteps = entries\n .filter((e) => e.type === \"next\")\n .slice(-MAX_BULLETS)\n .map((e) => e.content);\n\n return {\n branch,\n currentTask,\n keyDecisions,\n nextSteps,\n date: new Date().toISOString(),\n };\n}\n\nexport function formatContextMd(ctx: ContextMd): string {\n const lines: string[] = [];\n lines.push(`# Context — ${ctx.branch}`);\n lines.push(\"\");\n lines.push(`_Updated: ${ctx.date}_`);\n lines.push(\"\");\n\n if (ctx.currentTask) {\n lines.push(`## Current task`);\n lines.push(ctx.currentTask);\n lines.push(\"\");\n }\n\n if (ctx.keyDecisions.length) {\n lines.push(`## Key decisions`);\n for (const d of ctx.keyDecisions) lines.push(`- ${d}`);\n lines.push(\"\");\n }\n\n if (ctx.nextSteps.length) {\n lines.push(`## Next steps`);\n for (const n of ctx.nextSteps) lines.push(`- ${n}`);\n lines.push(\"\");\n }\n\n if (!ctx.currentTask && !ctx.keyDecisions.length && !ctx.nextSteps.length) {\n lines.push(\"_(no context entries yet — use `context_remember` to add one)_\");\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\nexport async function writeContextMd(path: string, ctx: ContextMd): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, formatContextMd(ctx), \"utf8\");\n}\n\nexport async function readContextMd(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf8\");\n } catch {\n return null;\n }\n}\n","// Structured decisions/tasks/facts that persist across sessions.\n// Stored in .synthra/ (GIT-TRACKED) so teammates inherit them.\n// Branch-partitioned via branches.ts.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nexport type EntryKind = \"decision\" | \"task\" | \"next\" | \"fact\" | \"blocker\";\n\nexport interface ContextEntry {\n type: EntryKind;\n content: string;\n tags: string[];\n files: string[];\n date: string;\n}\n\ninterface Store {\n schema_version: number;\n entries: ContextEntry[];\n}\n\nconst SCHEMA_VERSION = 1;\n\nexport async function readEntries(path: string): Promise<ContextEntry[]> {\n try {\n const raw = await readFile(path, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<Store>;\n return Array.isArray(parsed.entries) ? parsed.entries : [];\n } catch {\n return [];\n }\n}\n\nexport async function writeEntries(path: string, entries: ContextEntry[]): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const store: Store = { schema_version: SCHEMA_VERSION, entries };\n await writeFile(path, JSON.stringify(store, null, 2) + \"\\n\", \"utf8\");\n}\n\nexport async function appendEntry(path: string, entry: ContextEntry): Promise<void> {\n const entries = await readEntries(path);\n entries.push(entry);\n await writeEntries(path, entries);\n}\n","// High-level orchestration: branch detection + store routing + CONTEXT.md\n// refresh in one call. Used by the MCP tools and the /context-update route.\n\nimport type { SynthraPaths } from \"../shared/paths.js\";\nimport {\n currentBranch,\n defaultBranch,\n resolveBranchPaths,\n type BranchScopedPaths,\n} from \"./branches.js\";\nimport { deriveContextMd, writeContextMd } from \"./context-md.js\";\nimport { appendEntry, readEntries, type ContextEntry, type EntryKind } from \"./context-store.js\";\n\nexport interface ActiveBranch {\n branch: string;\n isDefault: boolean;\n paths: BranchScopedPaths;\n}\n\nexport async function resolveActiveBranch(\n paths: SynthraPaths,\n override?: string,\n): Promise<ActiveBranch> {\n const branch = override ?? (await currentBranch(paths.projectRoot));\n const def = await defaultBranch(paths.projectRoot);\n const isDefault = branch === def;\n return {\n branch,\n isDefault,\n paths: resolveBranchPaths(paths.contextDir, branch, isDefault),\n };\n}\n\nexport interface RememberInput {\n text: string;\n kind: EntryKind;\n tags?: string[];\n files?: string[];\n}\n\nexport interface RememberResult {\n entry: ContextEntry;\n branch: string;\n storePath: string;\n contextMdPath: string;\n}\n\nexport async function rememberEntry(\n paths: SynthraPaths,\n input: RememberInput,\n): Promise<RememberResult> {\n const active = await resolveActiveBranch(paths);\n const entry: ContextEntry = {\n type: input.kind,\n content: input.text,\n tags: input.tags ?? [],\n files: input.files ?? [],\n date: new Date().toISOString(),\n };\n await appendEntry(active.paths.contextStore, entry);\n\n // Refresh CONTEXT.md so the narrative stays in sync with the structured store.\n const entries = await readEntries(active.paths.contextStore);\n const md = deriveContextMd(entries, active.branch);\n await writeContextMd(active.paths.contextMd, md);\n\n return {\n entry,\n branch: active.branch,\n storePath: active.paths.contextStore,\n contextMdPath: active.paths.contextMd,\n };\n}\n\nexport interface RecallInput {\n kind?: EntryKind;\n branch?: string;\n limit?: number;\n}\n\nexport interface RecallResult {\n branch: string;\n entries: ContextEntry[];\n storePath: string;\n}\n\nexport async function recallEntries(\n paths: SynthraPaths,\n input: RecallInput = {},\n): Promise<RecallResult> {\n const active = await resolveActiveBranch(paths, input.branch);\n let entries = await readEntries(active.paths.contextStore);\n if (input.kind) entries = entries.filter((e) => e.type === input.kind);\n if (input.limit && input.limit > 0) entries = entries.slice(-input.limit);\n return {\n branch: active.branch,\n entries,\n storePath: active.paths.contextStore,\n };\n}\n\nexport async function refreshContextMd(paths: SynthraPaths, branchOverride?: string) {\n const active = await resolveActiveBranch(paths, branchOverride);\n const entries = await readEntries(active.paths.contextStore);\n const md = deriveContextMd(entries, active.branch);\n await writeContextMd(active.paths.contextMd, md);\n return {\n branch: active.branch,\n path: active.paths.contextMd,\n entriesSeen: entries.length,\n };\n}\n","// Renders the final context pack as a single Markdown blob for Claude.\n\nexport interface FormatFileSection {\n path: string;\n reason?: string;\n signatures: string[];\n inlineBodies: string;\n associatedTests?: string[];\n}\n\nexport interface FormatInputs {\n query: string;\n files: FormatFileSection[];\n recentActivity?: string;\n truncated?: boolean;\n}\n\nexport function formatPack(inputs: FormatInputs): string {\n const parts: string[] = [];\n parts.push(`# Synthra context — query: ${JSON.stringify(inputs.query)}\\n`);\n\n if (inputs.files.length === 0) {\n parts.push(\"> No matching files found in the graph.\\n\");\n }\n\n for (const f of inputs.files) {\n const heading = f.reason ? `## ${f.path} _(${f.reason})_` : `## ${f.path}`;\n parts.push(heading);\n\n if (f.signatures.length === 0) {\n parts.push(\"_(no symbols extracted)_\");\n } else {\n parts.push(\"**Signatures:**\");\n for (const s of f.signatures) parts.push(`- ${s}`);\n }\n\n if (f.inlineBodies.trim().length > 0) {\n parts.push(\"\");\n parts.push(\"**Bodies:**\");\n parts.push(\"```\");\n parts.push(f.inlineBodies.trimEnd());\n parts.push(\"```\");\n }\n\n if (f.associatedTests?.length) {\n parts.push(\"\");\n parts.push(`**Tests:** ${f.associatedTests.join(\", \")}`);\n }\n\n parts.push(\"\");\n }\n\n if (inputs.recentActivity?.trim()) {\n parts.push(\"---\");\n parts.push(\"## Recent human activity\");\n parts.push(inputs.recentActivity.trim());\n parts.push(\"\");\n }\n\n if (inputs.truncated) {\n parts.push(\"> _(pack truncated to fit budget)_\");\n }\n\n return parts.join(\"\\n\");\n}\n","// Picks the top function bodies from a file (by relevance to the query) and\n// inlines them, respecting a char-based budget. Truncates oversized bodies.\n\nimport { tokenizeQuery } from \"../graph/rank.js\";\nimport type { FileNode, SymbolKind, SymbolNode } from \"../graph/types.js\";\n\nexport interface InlineSelection {\n text: string;\n charsUsed: number;\n functionsInlined: string[];\n}\n\nconst INLINABLE_KINDS = new Set<SymbolKind>([\"function\", \"method\", \"class\"]);\nconst MAX_BODY_CHARS = 1500;\n\nfunction sliceLines(content: string, startLine: number, endLine: number): string {\n const lines = content.split(/\\r?\\n/);\n return lines.slice(Math.max(0, startLine - 1), endLine).join(\"\\n\");\n}\n\nfunction scoreSymbol(name: string, qTokens: Set<string>): number {\n const lower = name.toLowerCase();\n if (qTokens.has(lower)) return 3;\n for (const t of qTokens) {\n if (lower.includes(t) || t.includes(lower)) return 1;\n }\n return 0;\n}\n\nfunction truncate(body: string): string {\n if (body.length <= MAX_BODY_CHARS) return body;\n return body.slice(0, MAX_BODY_CHARS).trimEnd() + \"\\n // … truncated\";\n}\n\nexport function selectInlineBodies(\n file: FileNode,\n symbols: SymbolNode[],\n query: string,\n budgetChars: number,\n): InlineSelection {\n if (budgetChars <= 0) {\n return { text: \"\", charsUsed: 0, functionsInlined: [] };\n }\n\n const qTokens = new Set(tokenizeQuery(query));\n const mine = symbols.filter((s) => s.file === file.path && INLINABLE_KINDS.has(s.symbol_kind));\n\n const scored = mine\n .map((s) => ({ sym: s, score: scoreSymbol(s.name, qTokens) }))\n .sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n // Tie-break: smaller bodies first (so we fit more)\n const aSpan = (a.sym.end_line - a.sym.start_line) || 1;\n const bSpan = (b.sym.end_line - b.sym.start_line) || 1;\n return aSpan - bSpan;\n });\n\n const parts: string[] = [];\n const inlined: string[] = [];\n let used = 0;\n\n for (const { sym, score } of scored) {\n // Skip irrelevant symbols entirely when we have positive hits available;\n // fall back to top-by-size if no query match landed.\n if (score === 0 && inlined.length > 0) break;\n\n const body = truncate(sliceLines(file.content, sym.start_line, sym.end_line));\n const header = `${file.path}::${sym.name} (L${sym.start_line}-${sym.end_line})`;\n const block = `${header}\\n${body}\\n`;\n if (used + block.length > budgetChars) {\n if (inlined.length > 0) break;\n // No room and nothing yet — take a head-only slice.\n const remaining = Math.max(0, budgetChars - used - header.length - 16);\n if (remaining <= 0) break;\n const partial = body.slice(0, remaining).trimEnd() + \"\\n // … truncated\";\n const finalBlock = `${header}\\n${partial}\\n`;\n parts.push(finalBlock);\n inlined.push(sym.name);\n used += finalBlock.length;\n break;\n }\n parts.push(block);\n inlined.push(sym.name);\n used += block.length;\n }\n\n return { text: parts.join(\"\\n\"), charsUsed: used, functionsInlined: inlined };\n}\n","// Extracts function/class signatures from a FileNode (no bodies).\n// Signatures are the first line of each symbol in the file, sorted by line.\n\nimport type { FileNode, SymbolNode } from \"../graph/types.js\";\n\nexport function extractSignatures(file: FileNode, symbols: SymbolNode[]): string[] {\n const mine = symbols\n .filter((s) => s.file === file.path)\n .slice()\n .sort((a, b) => a.start_line - b.start_line);\n\n return mine.map((s) => `L${s.start_line}: ${s.signature.trim()}`);\n}\n","// Test ↔ source co-retrieval. Given a source file, returns the test files\n// linked to it via `tests` edges in the graph (foo.test.ts → foo.ts).\n\nimport type { FileNode, GraphSchema } from \"../graph/types.js\";\n\nexport function findTestsForFile(graph: GraphSchema, file: FileNode): FileNode[] {\n // tests edges run from test file → source file\n const fileNodesById = new Map<string, FileNode>();\n for (const n of graph.nodes) {\n if (n.kind === \"file\") fileNodesById.set(n.id, n);\n }\n\n const out: FileNode[] = [];\n for (const e of graph.edges) {\n if (e.kind !== \"tests\" || e.to !== file.id) continue;\n const testFile = fileNodesById.get(e.from);\n if (testFile && !out.includes(testFile)) out.push(testFile);\n }\n return out;\n}\n","// Compresses a list of retrieved files into a structured context pack:\n// signatures + top function bodies + tests co-retrieved + dependency edges.\n// Budget is enforced in characters (~ tokens × 4) — see SYN_HARD_MAX_READ_CHARS.\n\nimport type { FileNode, GraphSchema, SymbolNode } from \"../graph/types.js\";\nimport { formatPack, type FormatFileSection } from \"./format.js\";\nimport { selectInlineBodies } from \"./inline.js\";\nimport { extractSignatures } from \"./signatures.js\";\nimport { findTestsForFile } from \"./tests.js\";\n\nexport interface PackOptions {\n query: string;\n graph: GraphSchema;\n /** Soft target for total tokens (≈ chars/4). Default: 4000. */\n budgetTokens?: number;\n /** Fraction of remaining budget to spend on a single file's inline bodies. Default: 0.5. */\n inlineBodyRatio?: number;\n /** Co-retrieve linked test files when packing source files. Default: true. */\n includeTests?: boolean;\n /** Optional: file path → reason string from the ranker, surfaced in the pack heading. */\n reasons?: Map<string, string>;\n}\n\nexport interface ContextPack {\n text: string;\n tokenEstimate: number;\n filesUsed: string[];\n testsCoRetrieved: string[];\n truncated: boolean;\n}\n\nconst STATIC_OVERHEAD_PER_FILE = 200; // headers + bullet markdown + spacing\nconst MAX_INLINE_CHARS_PER_FILE = 2500;\n\nfunction indexSymbolsByFile(graph: GraphSchema): SymbolNode[] {\n return graph.nodes.filter((n): n is SymbolNode => n.kind === \"symbol\");\n}\n\nexport async function pack(files: FileNode[], opts: PackOptions): Promise<ContextPack> {\n const budgetTokens = opts.budgetTokens ?? 4000;\n const budgetChars = budgetTokens * 4;\n const inlineRatio = opts.inlineBodyRatio ?? 0.5;\n const includeTests = opts.includeTests ?? true;\n const reasons = opts.reasons ?? new Map<string, string>();\n\n const symbols = indexSymbolsByFile(opts.graph);\n\n const sections: FormatFileSection[] = [];\n const testsCoRetrieved: string[] = [];\n let used = 0;\n let truncated = false;\n\n for (const file of files) {\n const sig = extractSignatures(file, symbols);\n const testFiles = includeTests ? findTestsForFile(opts.graph, file) : [];\n const testPaths = testFiles.map((t) => t.path);\n\n const staticCost =\n file.path.length +\n sig.join(\"\\n\").length +\n testPaths.join(\",\").length +\n STATIC_OVERHEAD_PER_FILE;\n\n if (used + staticCost > budgetChars) {\n truncated = true;\n break;\n }\n\n const remaining = budgetChars - used - staticCost;\n const inlineBudget = Math.min(Math.floor(remaining * inlineRatio), MAX_INLINE_CHARS_PER_FILE);\n\n const inline = selectInlineBodies(file, symbols, opts.query, inlineBudget);\n\n sections.push({\n path: file.path,\n reason: reasons.get(file.path),\n signatures: sig,\n inlineBodies: inline.text,\n associatedTests: testPaths,\n });\n\n used += staticCost + inline.charsUsed;\n for (const t of testPaths) if (!testsCoRetrieved.includes(t)) testsCoRetrieved.push(t);\n\n if (used >= budgetChars) {\n truncated = true;\n break;\n }\n }\n\n if (sections.length < files.length) truncated = true;\n\n const text = formatPack({\n query: opts.query,\n files: sections,\n truncated,\n });\n const tokenEstimate = Math.ceil(text.length / 4);\n\n return {\n text,\n tokenEstimate,\n filesUsed: sections.map((s) => s.path),\n testsCoRetrieved,\n truncated,\n };\n}\n","// MCP-over-HTTP (streamable) protocol handler. Exposes Synthra's graph tools\n// to Claude Code via JSON-RPC 2.0 messages POSTed to /mcp.\n//\n// Tools:\n// graph_continue(query) — retrieve + pack a context bundle\n// graph_read(target) — return source for \"file\" or \"file::symbol\"\n// graph_register_edit(files) — Claude tells Synthra it edited files\n//\n// Spec: https://modelcontextprotocol.io/specification\n\nimport { retrieve } from \"../graph/retrieve.js\";\nimport type { FileNode, SymbolNode } from \"../graph/types.js\";\nimport { recallEntries, rememberEntry } from \"../memory/index.js\";\nimport type { EntryKind } from \"../memory/context-store.js\";\nimport { pack } from \"../packer/index.js\";\nimport type { ServerContext } from \"./context.js\";\n\nconst PROTOCOL_VERSION = \"2024-11-05\";\nconst SERVER_INFO = { name: \"synthra\", version: \"0.0.1\" } as const;\n\ntype JsonRpcId = string | number | null;\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id?: JsonRpcId;\n method: string;\n params?: Record<string, unknown>;\n}\n\nexport interface JsonRpcResponse {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n}\n\nconst ERR = {\n parse: -32700,\n invalidRequest: -32600,\n methodNotFound: -32601,\n invalidParams: -32602,\n internal: -32603,\n} as const;\n\nfunction ok(id: JsonRpcId, result: unknown): JsonRpcResponse {\n return { jsonrpc: \"2.0\", id, result };\n}\n\nfunction err(id: JsonRpcId, code: number, message: string, data?: unknown): JsonRpcResponse {\n return { jsonrpc: \"2.0\", id, error: { code, message, data } };\n}\n\nfunction textContent(text: string) {\n return { content: [{ type: \"text\", text }], isError: false };\n}\n\nfunction errorContent(message: string) {\n return { content: [{ type: \"text\", text: message }], isError: true };\n}\n\nconst TOOLS = [\n {\n name: \"graph_continue\",\n description:\n \"Returns the project context most relevant to a query — function signatures, top function bodies, and linked test files. Use this BEFORE Grep/Glob. If `confidence` is 'high', do not call Grep/Glob for the same query.\",\n inputSchema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Natural-language description of what you're looking for.\" },\n },\n required: [\"query\"],\n },\n },\n {\n name: \"graph_read\",\n description:\n \"Return the source code for a specific file or symbol. Target is either a project-relative file path (e.g. 'src/auth.ts') or 'file::symbol' (e.g. 'src/auth.ts::AuthService').\",\n inputSchema: {\n type: \"object\",\n properties: {\n target: { type: \"string\", description: \"File path or file::symbol notation.\" },\n },\n required: [\"target\"],\n },\n },\n {\n name: \"graph_register_edit\",\n description:\n \"Tell Synthra that you (the AI) have edited these files. Lets Synthra rank them higher in subsequent retrieval and avoid surfacing stale context.\",\n inputSchema: {\n type: \"object\",\n properties: {\n files: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Project-relative file paths that were edited.\",\n },\n },\n required: [\"files\"],\n },\n },\n {\n name: \"context_remember\",\n description:\n \"Persist a decision/task/next-step/fact/blocker into the project's branch-aware context store. Use when the user makes a decision worth keeping, identifies a TODO, or surfaces a key fact. Entries land in `.synthra/context-store.json` on the default branch, or `.synthra/branches/<sanitized>/context-store.json` on a feature branch — git-tracked, so teammates inherit them and they merge naturally.\",\n inputSchema: {\n type: \"object\",\n properties: {\n text: { type: \"string\", description: \"The thing to remember (1–3 sentences).\" },\n kind: {\n type: \"string\",\n enum: [\"decision\", \"task\", \"next\", \"fact\", \"blocker\"],\n description: \"What kind of entry. Required.\",\n },\n tags: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional tags for grouping (e.g. 'auth', 'perf').\",\n },\n files: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional project-relative file paths this entry relates to.\",\n },\n },\n required: [\"text\", \"kind\"],\n },\n },\n {\n name: \"context_recall\",\n description:\n \"Read previously-stored decisions/tasks/facts from the project's branch-aware context store. Defaults to the current branch.\",\n inputSchema: {\n type: \"object\",\n properties: {\n kind: {\n type: \"string\",\n enum: [\"decision\", \"task\", \"next\", \"fact\", \"blocker\"],\n description: \"Filter to a single kind.\",\n },\n branch: { type: \"string\", description: \"Override which branch to read from.\" },\n limit: { type: \"number\", description: \"Return only the most recent N entries.\" },\n },\n },\n },\n {\n name: \"recent_activity\",\n description:\n \"What has the human been doing in the editor recently — file saves, branch switches, and uncommitted-diff changes. Use this to check whether the static context pack may be stale (e.g. before answering a question about a file that was just edited).\",\n inputSchema: {\n type: \"object\",\n properties: {\n since_ms: {\n type: \"number\",\n description:\n \"Epoch milliseconds. Only return events newer than this. Defaults to the last 60 minutes.\",\n },\n limit: { type: \"number\", description: \"Cap on returned events.\" },\n },\n },\n },\n {\n name: \"count_tokens\",\n description:\n \"Estimate token count for a piece of text using a char/4 approximation. Accurate within ~10% for English + code. Useful for budgeting prompt content before sending.\",\n inputSchema: {\n type: \"object\",\n properties: {\n text: { type: \"string\", description: \"The text to estimate tokens for.\" },\n },\n required: [\"text\"],\n },\n },\n {\n name: \"blast_radius\",\n description:\n \"Given a file (or 'file::symbol' target), return all files that depend on it transitively via imports + tests edges. Use BEFORE editing a widely-used file to see what could break. Symbol-level granularity is approximated at the file level (we don't track call edges in v0.1).\",\n inputSchema: {\n type: \"object\",\n properties: {\n target: { type: \"string\", description: \"File path or 'file::symbol' notation.\" },\n depth: { type: \"number\", description: \"Max hops to traverse. Default 3.\" },\n },\n required: [\"target\"],\n },\n },\n {\n name: \"dead_code\",\n description:\n \"Return files in the project that no other file imports and no test file references — strong candidates for unused/orphaned code. File-level granularity (v0.1 limitation — symbol-level needs call-graph edges). Common entry-point patterns (main, index, app, CLI, bin/) are excluded heuristically.\",\n inputSchema: {\n type: \"object\",\n properties: {\n limit: { type: \"number\", description: \"Cap on returned files. Default 50.\" },\n },\n },\n },\n] as const;\n\nasync function callTool(\n name: string,\n args: Record<string, unknown> | undefined,\n ctx: ServerContext,\n) {\n switch (name) {\n case \"graph_continue\":\n return graphContinue(args, ctx);\n case \"graph_read\":\n return graphRead(args, ctx);\n case \"graph_register_edit\":\n return graphRegisterEdit(args, ctx);\n case \"context_remember\":\n return contextRemember(args, ctx);\n case \"context_recall\":\n return contextRecall(args, ctx);\n case \"recent_activity\":\n return recentActivity(args, ctx);\n case \"count_tokens\":\n return countTokens(args);\n case \"blast_radius\":\n return blastRadius(args, ctx);\n case \"dead_code\":\n return deadCode(args, ctx);\n default:\n return errorContent(`Unknown tool: ${name}`);\n }\n}\n\nfunction countTokens(args: Record<string, unknown> | undefined) {\n const text = typeof args?.text === \"string\" ? args.text : \"\";\n if (!text) return errorContent(\"count_tokens: 'text' (string) is required\");\n const tokens = Math.ceil(text.length / 4);\n return textContent(JSON.stringify({ tokens, method: \"chars/4 estimate\", chars: text.length }));\n}\n\nfunction blastRadius(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const targetRaw = typeof args?.target === \"string\" ? args.target.trim() : \"\";\n const maxDepth = typeof args?.depth === \"number\" && args.depth > 0 ? Math.floor(args.depth) : 3;\n if (!targetRaw) return errorContent(\"blast_radius: 'target' (string) is required\");\n\n const filePath = targetRaw.split(\"::\", 1)[0]?.trim() ?? targetRaw;\n const root = ctx.graph.nodes.find(\n (n): n is FileNode => n.kind === \"file\" && n.path === filePath,\n );\n if (!root) return errorContent(`blast_radius: file not in graph: ${filePath}`);\n\n // Index reverse edges (to → [{from, kind}]) once per call.\n const incoming = new Map<string, Array<{ from: string; kind: string }>>();\n for (const e of ctx.graph.edges) {\n if (e.kind !== \"imports\" && e.kind !== \"tests\") continue;\n const list = incoming.get(e.to) ?? [];\n list.push({ from: e.from, kind: e.kind });\n incoming.set(e.to, list);\n }\n\n interface Hit {\n path: string;\n depth: number;\n via: string;\n }\n\n const visited = new Set<string>([root.id]);\n const hits: Hit[] = [];\n const pathById = new Map<string, string>();\n for (const n of ctx.graph.nodes) if (n.kind === \"file\") pathById.set(n.id, n.path);\n\n let frontier = [root.id];\n for (let d = 1; d <= maxDepth; d++) {\n const next: string[] = [];\n for (const cur of frontier) {\n const callers = incoming.get(cur) ?? [];\n for (const c of callers) {\n if (visited.has(c.from)) continue;\n visited.add(c.from);\n next.push(c.from);\n const path = pathById.get(c.from) ?? c.from;\n hits.push({ path, depth: d, via: c.kind });\n }\n }\n frontier = next;\n if (next.length === 0) break;\n }\n\n if (hits.length === 0) {\n return textContent(`# Blast radius for ${filePath}\\n\\n_(no dependents — file is isolated)_`);\n }\n\n hits.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));\n const lines = [`# Blast radius for ${filePath} (depth ≤ ${maxDepth})`, \"\"];\n lines.push(`${hits.length} dependent file(s):`);\n for (const h of hits) {\n lines.push(`- **depth ${h.depth}** \\`${h.path}\\` _(via ${h.via})_`);\n }\n return textContent(lines.join(\"\\n\"));\n}\n\nconst LIKELY_ENTRY_PATTERNS = [\n /(?:^|\\/)main\\.[a-z0-9_]+$/i,\n /(?:^|\\/)index\\.[a-z0-9_]+$/i,\n /(?:^|\\/)app\\.[a-z0-9_]+$/i,\n /(?:^|\\/)entry\\.[a-z0-9_]+$/i,\n /(?:^|\\/)cli[\\/.]/i,\n /(?:^|\\/)bin[\\/.]/i,\n /(?:^|\\/)server\\.[a-z0-9_]+$/i,\n /\\.test\\.[a-z0-9_]+$/i,\n /\\.spec\\.[a-z0-9_]+$/i,\n /(?:^|\\/)tests?\\//i,\n /(?:^|\\/)__tests__\\//i,\n /(?:^|\\/)__init__\\.py$/i,\n];\n\nfunction isLikelyEntry(path: string): boolean {\n return LIKELY_ENTRY_PATTERNS.some((re) => re.test(path));\n}\n\nfunction deadCode(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const limit = typeof args?.limit === \"number\" && args.limit > 0 ? Math.floor(args.limit) : 50;\n\n const hasIncoming = new Set<string>();\n for (const e of ctx.graph.edges) {\n if (e.kind === \"imports\" || e.kind === \"tests\") hasIncoming.add(e.to);\n }\n\n const candidates = ctx.graph.nodes\n .filter((n): n is FileNode => n.kind === \"file\")\n .filter((f) => !hasIncoming.has(f.id))\n .filter((f) => !isLikelyEntry(f.path));\n\n if (candidates.length === 0) {\n return textContent(\n `# Dead code\\n\\n_(no file is unreferenced — every file is either imported by another, has a linked test, or matches an entry-point pattern)_`,\n );\n }\n\n candidates.sort((a, b) => a.path.localeCompare(b.path));\n const shown = candidates.slice(0, limit);\n const lines = [`# Dead code candidates (file-level, v0.1)`, \"\"];\n lines.push(\n `${shown.length} of ${candidates.length} unreferenced file(s) — no other file imports them and no test links them:`,\n );\n lines.push(\"\");\n for (const f of shown) {\n lines.push(`- \\`${f.path}\\``);\n }\n lines.push(\"\");\n lines.push(\n `_v0.1 caveat:_ this is file-level only. Symbol-level dead code (unused exports) needs call-graph edges, which land in v0.2.`,\n );\n return textContent(lines.join(\"\\n\"));\n}\n\nasync function graphContinue(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const query = typeof args?.query === \"string\" ? args.query : \"\";\n if (!query) return errorContent(\"graph_continue: 'query' (string) is required\");\n\n const retrieval = await retrieve(ctx.graph, query);\n const packed = await pack(retrieval.files, { query, graph: ctx.graph });\n\n const header =\n `Confidence: ${retrieval.confidence}\\n` +\n `Files: ${retrieval.files.map((f) => f.path).join(\", \") || \"(none)\"}\\n` +\n `Reason: ${retrieval.reason}\\n`;\n\n // The pack body already starts with a header — keep them concatenated.\n return textContent(`${header}\\n${packed.text}`);\n}\n\nfunction graphRead(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const target = typeof args?.target === \"string\" ? args.target : \"\";\n if (!target) return errorContent(\"graph_read: 'target' (string) is required\");\n\n const [rawFile, symbolName] = target.includes(\"::\") ? target.split(\"::\", 2) : [target, undefined];\n const filePath = (rawFile ?? \"\").trim();\n\n const fileNode = ctx.graph.nodes.find(\n (n): n is FileNode => n.kind === \"file\" && n.path === filePath,\n );\n if (!fileNode) return errorContent(`graph_read: file not found in graph: ${filePath}`);\n\n if (!symbolName) {\n return textContent(`# ${fileNode.path}\\n\\n${fileNode.content}`);\n }\n\n const cleanSym = symbolName.trim();\n const symbol = ctx.graph.nodes.find(\n (n): n is SymbolNode => n.kind === \"symbol\" && n.file === filePath && n.name === cleanSym,\n );\n if (!symbol) {\n return errorContent(`graph_read: symbol '${cleanSym}' not found in ${filePath}`);\n }\n\n const lines = fileNode.content.split(/\\r?\\n/);\n const body = lines.slice(symbol.start_line - 1, symbol.end_line).join(\"\\n\");\n return textContent(\n `# ${fileNode.path}::${symbol.name} (L${symbol.start_line}-${symbol.end_line})\\n\\n${body}`,\n );\n}\n\nconst editedFiles = new Set<string>();\n\nfunction graphRegisterEdit(args: Record<string, unknown> | undefined, _ctx: ServerContext) {\n const files = Array.isArray(args?.files) ? (args.files as unknown[]).filter((f) => typeof f === \"string\") : [];\n for (const f of files) editedFiles.add(f as string);\n return textContent(`Registered ${files.length} edited file(s). Total tracked this session: ${editedFiles.size}.`);\n}\n\nexport function getRegisteredEdits(): string[] {\n return Array.from(editedFiles);\n}\n\nconst VALID_KINDS = new Set<EntryKind>([\"decision\", \"task\", \"next\", \"fact\", \"blocker\"]);\n\nasync function contextRemember(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const text = typeof args?.text === \"string\" ? args.text.trim() : \"\";\n const kindRaw = typeof args?.kind === \"string\" ? args.kind : \"\";\n if (!text) return errorContent(\"context_remember: 'text' (string) is required\");\n if (!VALID_KINDS.has(kindRaw as EntryKind)) {\n return errorContent(\n `context_remember: 'kind' must be one of ${Array.from(VALID_KINDS).join(\", \")}`,\n );\n }\n const tags = Array.isArray(args?.tags)\n ? (args.tags as unknown[]).filter((t): t is string => typeof t === \"string\")\n : [];\n const files = Array.isArray(args?.files)\n ? (args.files as unknown[]).filter((f): f is string => typeof f === \"string\")\n : [];\n\n const result = await rememberEntry(ctx.paths, {\n text,\n kind: kindRaw as EntryKind,\n tags,\n files,\n });\n\n return textContent(\n `Remembered ${result.entry.type} on branch '${result.branch}'.\\n` +\n `Stored: ${result.storePath}\\n` +\n `CONTEXT.md refreshed: ${result.contextMdPath}`,\n );\n}\n\nconst DEFAULT_RECENT_WINDOW_MS = 60 * 60 * 1000;\n\nfunction recentActivity(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const sinceMs =\n typeof args?.since_ms === \"number\" && Number.isFinite(args.since_ms)\n ? args.since_ms\n : Date.now() - DEFAULT_RECENT_WINDOW_MS;\n const limit =\n typeof args?.limit === \"number\" && args.limit > 0 ? Math.floor(args.limit) : undefined;\n\n let events = ctx.activity.getEvents(sinceMs);\n if (limit) events = events.slice(-limit);\n\n if (events.length === 0) {\n return textContent(\n `No human-activity events since ${new Date(sinceMs).toISOString()}.`,\n );\n }\n\n const lines = [`# Recent human activity (${events.length} events)`, \"\"];\n for (const e of events) {\n if (\"path\" in e) {\n lines.push(`- **${e.kind}** ${e.path} _(${e.ts})_`);\n } else {\n const summary = JSON.stringify(e.details);\n lines.push(`- **${e.kind}** ${summary} _(${e.ts})_`);\n }\n }\n return textContent(lines.join(\"\\n\"));\n}\n\nasync function contextRecall(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const kind = typeof args?.kind === \"string\" && VALID_KINDS.has(args.kind as EntryKind)\n ? (args.kind as EntryKind)\n : undefined;\n const branch = typeof args?.branch === \"string\" ? args.branch : undefined;\n const limit = typeof args?.limit === \"number\" && args.limit > 0 ? Math.floor(args.limit) : undefined;\n\n const result = await recallEntries(ctx.paths, { kind, branch, limit });\n\n if (result.entries.length === 0) {\n const filter = kind ? ` of kind '${kind}'` : \"\";\n return textContent(`No context entries${filter} on branch '${result.branch}'.`);\n }\n\n const lines = [`# Context entries — branch: ${result.branch}`, \"\"];\n for (const e of result.entries) {\n const tags = e.tags.length ? ` [${e.tags.join(\", \")}]` : \"\";\n lines.push(`- **${e.type}**${tags} (${e.date}): ${e.content}`);\n if (e.files.length) lines.push(` files: ${e.files.join(\", \")}`);\n }\n return textContent(lines.join(\"\\n\"));\n}\n\nexport async function handleMcpRequest(\n body: unknown,\n ctx: ServerContext,\n): Promise<JsonRpcResponse> {\n if (!body || typeof body !== \"object\") {\n return err(null, ERR.invalidRequest, \"Request body must be a JSON-RPC 2.0 object.\");\n }\n\n const req = body as JsonRpcRequest;\n if (req.jsonrpc !== \"2.0\" || typeof req.method !== \"string\") {\n return err(req.id ?? null, ERR.invalidRequest, \"Invalid JSON-RPC envelope.\");\n }\n\n const id = req.id ?? null;\n\n try {\n switch (req.method) {\n case \"initialize\":\n return ok(id, {\n protocolVersion:\n typeof req.params?.protocolVersion === \"string\"\n ? req.params.protocolVersion\n : PROTOCOL_VERSION,\n capabilities: { tools: {} },\n serverInfo: SERVER_INFO,\n });\n\n case \"notifications/initialized\":\n // Client confirms initialization. No response required for notifications (id===undefined).\n return ok(id, {});\n\n case \"tools/list\":\n return ok(id, { tools: TOOLS });\n\n case \"tools/call\": {\n const params = req.params ?? {};\n const toolName = typeof params.name === \"string\" ? params.name : \"\";\n if (!toolName) return err(id, ERR.invalidParams, \"'name' is required for tools/call.\");\n const args = (params.arguments && typeof params.arguments === \"object\"\n ? (params.arguments as Record<string, unknown>)\n : {});\n const result = await callTool(toolName, args, ctx);\n return ok(id, result);\n }\n\n case \"ping\":\n return ok(id, {});\n\n default:\n return err(id, ERR.methodNotFound, `Method not found: ${req.method}`);\n }\n } catch (e) {\n return err(id, ERR.internal, (e as Error).message);\n }\n}\n\n// Exposed for code that wants to enumerate the tool catalogue without going\n// through JSON-RPC (e.g. CLI introspection in M3).\nexport function listTools(): Array<{ name: string; description: string; inputSchema: unknown }> {\n return TOOLS.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }));\n}\n","// GET /activity?since=<ms> — returns recent human-activity events.\n// Backed by the in-memory ActivityStore (file-watcher + git-watcher feed it).\n// MCP tool `recent_activity` is a thin wrapper.\n\nimport type { ActivityEvent } from \"../../activity/activity-log.js\";\nimport type { ServerContext } from \"../context.js\";\n\nexport interface ActivityResponse {\n events: ActivityEvent[];\n since: string;\n ring_size: number;\n}\n\nexport async function handleActivity(\n sinceMs: number | undefined,\n ctx: ServerContext,\n): Promise<ActivityResponse> {\n const events = ctx.activity.getEvents(sinceMs);\n return {\n events,\n since: new Date(sinceMs ?? Date.now()).toISOString(),\n ring_size: ctx.activity.size(),\n };\n}\n","// POST /context-update — Stop hook calls this at session end.\n// For M4: re-renders CONTEXT.md from the branch-scoped store so the narrative\n// stays in sync with the structured entries that landed during the session.\n// Transcript-mining for new entries (auto \"we decided X\" → store) is v0.2.\n\nimport { refreshContextMd } from \"../../memory/index.js\";\nimport type { ServerContext } from \"../context.js\";\n\nexport interface ContextUpdateRequest {\n transcript_path?: string;\n branch?: string;\n}\n\nexport interface ContextUpdateResponse {\n updated: boolean;\n branch: string;\n path: string;\n entries: number;\n}\n\nexport async function handleContextUpdate(\n req: ContextUpdateRequest,\n ctx: ServerContext,\n): Promise<ContextUpdateResponse> {\n const r = await refreshContextMd(ctx.paths, req?.branch);\n return {\n updated: true,\n branch: r.branch,\n path: r.path,\n entries: r.entriesSeen,\n };\n}\n","// POST /gate — PreToolUse hook calls this with the tool name + arguments.\n// THE MOAT — improvement #1. Strategy:\n// - For Grep/Glob: extract the search pattern, run retrieve().\n// - If recent human activity touches a file matching the query → ALLOW\n// even at high confidence (the user's head is in that file; static\n// context may be stale).\n// - If confidence === \"high\" and no recent overlap → BLOCK with a reason\n// pointing at graph_continue / graph_read.\n// - Otherwise → ALLOW.\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport { retrieve } from \"../../graph/retrieve.js\";\nimport { tokenizeQuery } from \"../../graph/rank.js\";\nimport type { ServerContext } from \"../context.js\";\n\nexport interface GateRequest {\n tool_name: string;\n tool_input: Record<string, unknown>;\n}\n\nexport interface GateResponse {\n decision: \"allow\" | \"block\";\n reason?: string;\n}\n\nconst BLOCKABLE_TOOLS = new Set([\"Grep\", \"Glob\"]);\nconst RECENT_ACTIVITY_WINDOW_MS = 5 * 60 * 1000;\n\nfunction extractQuery(toolName: string, input: Record<string, unknown>): string | null {\n if (toolName === \"Grep\") {\n const pattern = typeof input.pattern === \"string\" ? input.pattern : \"\";\n const query = typeof input.query === \"string\" ? input.query : \"\";\n return (pattern || query).trim() || null;\n }\n if (toolName === \"Glob\") {\n const pattern = typeof input.pattern === \"string\" ? input.pattern : \"\";\n return pattern.replace(/[*?/\\\\.]+/g, \" \").trim() || null;\n }\n return null;\n}\n\nfunction recentlyTouchedMatchesQuery(\n recentPaths: string[],\n queryTokens: Set<string>,\n): string[] {\n const matches: string[] = [];\n for (const path of recentPaths) {\n const lower = path.toLowerCase();\n for (const t of queryTokens) {\n if (lower.includes(t)) {\n matches.push(path);\n break;\n }\n }\n }\n return matches;\n}\n\nasync function logDecision(\n ctx: ServerContext,\n toolName: string,\n query: string | null,\n decision: \"allow\" | \"block\",\n reason: string | undefined,\n): Promise<void> {\n try {\n await mkdir(dirname(ctx.paths.gateLog), { recursive: true });\n const entry = {\n ts: new Date().toISOString(),\n tool: toolName,\n decision,\n query,\n reason,\n };\n await appendFile(ctx.paths.gateLog, JSON.stringify(entry) + \"\\n\", \"utf8\");\n } catch {\n // Durability is best-effort; an unwritable disk shouldn't fail the gate.\n }\n}\n\nexport async function handleGate(req: GateRequest, ctx: ServerContext): Promise<GateResponse> {\n if (!req?.tool_name || typeof req.tool_name !== \"string\") {\n return { decision: \"allow\", reason: \"no tool_name\" };\n }\n\n if (!BLOCKABLE_TOOLS.has(req.tool_name)) {\n return { decision: \"allow\" };\n }\n\n const input = (req.tool_input && typeof req.tool_input === \"object\" ? req.tool_input : {}) as Record<string, unknown>;\n const query = extractQuery(req.tool_name, input);\n if (!query) {\n const res: GateResponse = { decision: \"allow\", reason: \"no extractable query\" };\n await logDecision(ctx, req.tool_name, null, res.decision, res.reason);\n return res;\n }\n\n const retrieval = await retrieve(ctx.graph, query);\n // \"low\" = no real matches → let Grep through; Synthra has nothing useful.\n // \"medium\" + \"high\" = Synthra has structured context for this query →\n // bias toward blocking. The pitch (\"use graph_continue instead of Grep\")\n // holds at medium too — on real codebases of any size, \"high\" is rare\n // because almost every query matches multiple files.\n if (retrieval.confidence === \"low\") {\n const res: GateResponse = {\n decision: \"allow\",\n reason: `confidence=low — no graph context for \"${query}\", letting ${req.tool_name} through`,\n };\n await logDecision(ctx, req.tool_name, query, res.decision, res.reason);\n return res;\n }\n\n // Medium / high — but check if recent activity overlaps the query first.\n // If the user just touched a file matching the query, static context may\n // be stale and they probably want a fresh search.\n const qTokens = new Set(tokenizeQuery(query));\n const recentPaths = ctx.activity.recentFilePaths(RECENT_ACTIVITY_WINDOW_MS);\n const overlap = recentlyTouchedMatchesQuery(recentPaths, qTokens);\n\n if (overlap.length > 0) {\n const res: GateResponse = {\n decision: \"allow\",\n reason:\n `confidence=${retrieval.confidence} but human just touched ${overlap.slice(0, 3).join(\", \")} — ` +\n `static context may be stale, letting ${req.tool_name} through.`,\n };\n await logDecision(ctx, req.tool_name, query, res.decision, res.reason);\n return res;\n }\n\n const top = retrieval.files.slice(0, 3).map((f) => f.path).join(\", \");\n const res: GateResponse = {\n decision: \"block\",\n reason:\n `Synthra has ${retrieval.confidence}-confidence context for \"${query}\" (top files: ${top}). ` +\n `Use the \\`graph_continue\\` MCP tool with this query instead of ${req.tool_name}, ` +\n `or read a specific file/symbol with \\`graph_read\\`.`,\n };\n await logDecision(ctx, req.tool_name, query, res.decision, res.reason);\n return res;\n}\n","// POST /log — Stop hook posts per-turn token usage parsed from Claude's\n// transcript JSONL. Synthra appends each entry as one line to token_log.jsonl.\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport type { ServerContext } from \"../context.js\";\n\nexport interface LogEntry {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n model: string;\n description?: string;\n project: string;\n}\n\nexport interface LogResponse {\n ok: true;\n written_at: string;\n}\n\nexport async function handleLog(entry: LogEntry, ctx: ServerContext): Promise<LogResponse> {\n if (!entry || typeof entry.input_tokens !== \"number\" || typeof entry.output_tokens !== \"number\") {\n throw new Error(\"log: input_tokens and output_tokens (number) are required\");\n }\n\n const written_at = new Date().toISOString();\n const record = { ...entry, written_at };\n await mkdir(dirname(ctx.paths.tokenLog), { recursive: true });\n await appendFile(ctx.paths.tokenLog, JSON.stringify(record) + \"\\n\", \"utf8\");\n\n return { ok: true, written_at };\n}\n","// POST /pack { query, maxTokens? } → ContextPack JSON.\n// Runs retrieve → pack against the in-memory graph.\n\nimport { retrieve } from \"../../graph/retrieve.js\";\nimport { scoreFiles } from \"../../graph/rank.js\";\nimport { pack, type ContextPack } from \"../../packer/index.js\";\nimport type { ServerContext } from \"../context.js\";\n\nexport interface PackRequest {\n query: string;\n maxTokens?: number;\n includeTests?: boolean;\n}\n\nexport interface PackResponse extends ContextPack {\n query: string;\n confidence: \"high\" | \"medium\" | \"low\";\n retrievalReason: string;\n}\n\nexport async function handlePack(req: PackRequest, ctx: ServerContext): Promise<PackResponse> {\n if (!req?.query || typeof req.query !== \"string\") {\n throw new Error(\"pack: 'query' (string) is required\");\n }\n\n const recentlyEditedPaths = ctx.activity.recentFilePaths(15 * 60 * 1000);\n const retrieval = await retrieve(ctx.graph, req.query, { recentlyEditedPaths });\n\n // Surface per-file scoring rationale in the rendered pack.\n const allFiles = ctx.graph.nodes.filter((n) => n.kind === \"file\");\n const scored = scoreFiles({\n candidates: allFiles as Parameters<typeof scoreFiles>[0][\"candidates\"],\n query: req.query,\n graph: ctx.graph,\n recentlyEditedPaths,\n });\n const reasons = new Map<string, string>();\n for (const s of scored) {\n if (s.reasons.length) reasons.set(s.file.path, s.reasons.join(\",\"));\n }\n\n const result = await pack(retrieval.files, {\n query: req.query,\n graph: ctx.graph,\n budgetTokens: req.maxTokens,\n includeTests: req.includeTests,\n reasons,\n });\n\n return {\n ...result,\n query: req.query,\n confidence: retrieval.confidence,\n retrievalReason: retrieval.reason,\n };\n}\n","// GET /prime — SessionStart and PreCompact hooks call this. Returns the\n// priming text Claude sees at session start.\n//\n// For M2 we return a minimal primer derived from the graph (file count + a\n// short top-level summary). M3 layers in CONTEXT.md narrative + recent\n// stored decisions.\n\nimport type { ServerContext } from \"../context.js\";\n\nexport interface PrimeResponse {\n primer: string;\n port: number;\n}\n\nexport async function handlePrime(ctx: ServerContext, port: number): Promise<PrimeResponse> {\n const g = ctx.graph;\n const fileCount = g.file_count;\n const symbolCount = g.symbol_count;\n\n const primer =\n `Synthra context loaded for ${g.root}.\\n` +\n `${fileCount} files indexed, ${symbolCount} symbols. ` +\n `Prefer the graph_* MCP tools over Grep/Glob for navigation.\\n` +\n `(Full primer wired in M3.)`;\n\n return { primer, port };\n}\n","// Environment-variable-driven configuration.\n// All knobs are prefixed SYN_.\n\nexport interface SynthraConfig {\n hardMaxReadChars: number;\n turnReadBudgetChars: number;\n fallbackMaxCallsPerTurn: number;\n retrieveCacheTtlSec: number;\n mcpPort: number | null;\n dashboardPort: number;\n logLevel: \"debug\" | \"info\" | \"warn\" | \"error\";\n claudeBin: string;\n}\n\nfunction num(name: string, fallback: number): number {\n const v = process.env[name];\n if (!v) return fallback;\n const n = Number(v);\n return Number.isFinite(n) ? n : fallback;\n}\n\nfunction str<T extends string>(name: string, fallback: T): T {\n return (process.env[name] as T) ?? fallback;\n}\n\nexport function loadConfig(): SynthraConfig {\n return {\n hardMaxReadChars: num(\"SYN_HARD_MAX_READ_CHARS\", 4000),\n turnReadBudgetChars: num(\"SYN_TURN_READ_BUDGET_CHARS\", 18000),\n fallbackMaxCallsPerTurn: num(\"SYN_FALLBACK_MAX_CALLS_PER_TURN\", 1),\n retrieveCacheTtlSec: num(\"SYN_RETRIEVE_CACHE_TTL_SEC\", 900),\n mcpPort: process.env.SYN_MCP_PORT ? num(\"SYN_MCP_PORT\", 0) : null,\n dashboardPort: num(\"SYN_DASHBOARD_PORT\", 8901),\n logLevel: str(\"SYN_LOG_LEVEL\", \"info\" as const),\n claudeBin: str(\"SYN_CLAUDE_BIN\", \"claude\" as const),\n };\n}\n","// Locate the most recently modified Claude session transcript for a project.\n// Claude Code stores them at: ~/.claude/projects/<encoded-cwd>/<session-uuid>.jsonl\n// where `encoded-cwd` replaces ANY of \\ / : with -.\n\nimport { readdir, stat } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface DiscoveredSession {\n sessionId: string;\n transcriptPath: string;\n modifiedAt: Date;\n}\n\nexport function encodeProjectPath(projectRoot: string): string {\n return projectRoot.replace(/[\\\\/:]/g, \"-\");\n}\n\nexport async function findLatestSession(projectRoot: string): Promise<DiscoveredSession | null> {\n const encoded = encodeProjectPath(projectRoot);\n const dir = join(homedir(), \".claude\", \"projects\", encoded);\n\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return null;\n }\n\n const jsonlFiles = entries.filter((f) => f.endsWith(\".jsonl\"));\n if (jsonlFiles.length === 0) return null;\n\n let latest: DiscoveredSession | null = null;\n for (const file of jsonlFiles) {\n const path = join(dir, file);\n try {\n const s = await stat(path);\n if (!latest || s.mtime > latest.modifiedAt) {\n latest = {\n sessionId: file.replace(/\\.jsonl$/, \"\"),\n transcriptPath: path,\n modifiedAt: s.mtime,\n };\n }\n } catch {\n // skip unreadable file\n }\n }\n return latest;\n}\n","// Graceful shutdown after `claude` exits:\n// - find latest Claude session JSONL → print `syn --resume <id>`\n// MCP-server shutdown is owned by the caller (it has the ServerHandle).\n// CONTEXT.md flushing is M4.\n\nimport { log } from \"../shared/logger.js\";\nimport type { SynthraPaths } from \"../shared/paths.js\";\nimport { findLatestSession } from \"./session-discovery.js\";\n\nexport async function cleanup(paths: SynthraPaths): Promise<void> {\n const session = await findLatestSession(paths.projectRoot);\n if (!session) {\n log.info(\"(no Claude session transcript found — nothing to resume)\");\n return;\n }\n log.info(\"\");\n log.info(`To resume this session: syn --resume ${session.sessionId}`);\n}\n","// `syn dashboard [path]` — starts the standalone token dashboard server on\n// localhost:8901 (or SYN_DASHBOARD_PORT). Reads token_log.jsonl + gate_log.jsonl\n// for the given project. Runs until Ctrl+C.\n\nimport { resolve } from \"node:path\";\n\nimport { startDashboard } from \"../dashboard/server.js\";\nimport { loadConfig } from \"../shared/config.js\";\nimport { log } from \"../shared/logger.js\";\nimport { resolvePaths } from \"../shared/paths.js\";\n\nexport async function dashboardCommand(rawPath: string): Promise<void> {\n const projectRoot = resolve(rawPath);\n const paths = resolvePaths(projectRoot);\n const cfg = loadConfig();\n\n const handle = await startDashboard(paths, cfg.dashboardPort);\n log.info(`Synthra dashboard listening on ${handle.url}`);\n log.info(`project: ${projectRoot}`);\n log.info(`reading: ${paths.tokenLog}`);\n log.info(` ${paths.gateLog}`);\n log.info(\"press Ctrl+C to stop.\");\n\n await new Promise<void>((res) => {\n const shutdown = async (signal: NodeJS.Signals) => {\n log.info(`received ${signal} — shutting down…`);\n try {\n await handle.stop();\n } catch (err) {\n log.warn(`dashboard stop error: ${(err as Error).message}`);\n }\n res();\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n });\n}\n\n// Backwards-compat alias matching the M2-era stub signature.\nexport async function openDashboard(_port = 8901): Promise<void> {\n return dashboardCommand(\".\");\n}\n","// Update flow:\n//\n// 1. At every `syn .` startup, check the npm registry for the latest\n// version. If we're on latest, stay silent. If outdated, prompt the\n// user [y/N]. On 'y', run `npm install -g …@latest` with inherited\n// stdio, print the new version's changelog section from the freshly-\n// installed package, then exit with re-run instructions.\n//\n// 2. On every startup, compare the running binary's version to a\n// persisted \"last seen\" version at ~/.synthra/last-seen-version.json.\n// If running > last-seen, print the changelog for the running version\n// (catches manual `npm install -g …@latest` upgrades that bypassed\n// our prompt). On a fresh install (no last-seen file), set last-seen\n// silently without printing — new users don't need the changelog.\n//\n// No 24h cache — the user explicitly asked for \"always check on every\n// `syn .` run.\" Cost: one ~100–300ms HTTPS round-trip per startup, hard-\n// capped by FETCH_TIMEOUT_MS. SYN_NO_UPDATE_CHECK=1 opts out of (1)\n// only; the local last-seen comparison in (2) still fires.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport spawn from \"cross-spawn\";\n\nimport { log } from \"../shared/logger.js\";\n\nconst PKG_NAME = \"@jefuriiij/synthra\";\nconst SYNTHRA_DIR = join(homedir(), \".synthra\");\nconst LAST_SEEN_PATH = join(SYNTHRA_DIR, \"last-seen-version.json\");\nconst REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}/latest`;\nconst FETCH_TIMEOUT_MS = 2000;\n\ninterface LastSeenFile {\n version: string;\n updated_at: string;\n}\n\nexport interface UpdateCheckResult {\n current: string;\n latest: string | null;\n hasUpdate: boolean;\n}\n\nlet currentVersionCache: string | null = null;\n\nasync function getCurrentVersion(): Promise<string> {\n if (currentVersionCache) return currentVersionCache;\n try {\n // Tsup inlines this import at build time.\n const pkg = (await import(\"../../package.json\", { with: { type: \"json\" } })) as { default: { version: string } } | { version: string };\n const version = \"default\" in pkg ? pkg.default.version : pkg.version;\n currentVersionCache = version;\n return version;\n } catch {\n return \"0.0.0\";\n }\n}\n\n/** Returns true if `candidate` is a higher semver than `baseline`. */\nfunction isNewer(candidate: string, baseline: string): boolean {\n const a = candidate.split(/[.-]/).map((p) => Number(p));\n const b = baseline.split(/[.-]/).map((p) => Number(p));\n const len = Math.max(a.length, b.length);\n for (let i = 0; i < len; i++) {\n const ai = Number.isFinite(a[i]) ? (a[i] as number) : 0;\n const bi = Number.isFinite(b[i]) ? (b[i] as number) : 0;\n if (ai > bi) return true;\n if (ai < bi) return false;\n }\n return false;\n}\n\nasync function fetchLatestFromRegistry(): Promise<string | null> {\n try {\n const res = await fetch(REGISTRY_URL, {\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n headers: { Accept: \"application/json\" },\n });\n if (!res.ok) return null;\n const data = (await res.json()) as { version?: string };\n return typeof data.version === \"string\" ? data.version : null;\n } catch {\n return null;\n }\n}\n\nexport async function checkForUpdate(): Promise<UpdateCheckResult> {\n const current = await getCurrentVersion();\n\n if (process.env.SYN_NO_UPDATE_CHECK === \"1\") {\n return { current, latest: null, hasUpdate: false };\n }\n\n const latest = await fetchLatestFromRegistry();\n const hasUpdate = latest ? isNewer(latest, current) : false;\n return { current, latest, hasUpdate };\n}\n\nasync function readLastSeen(): Promise<string | null> {\n try {\n const raw = await readFile(LAST_SEEN_PATH, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<LastSeenFile>;\n return parsed.version ?? null;\n } catch {\n return null;\n }\n}\n\nasync function writeLastSeen(version: string): Promise<void> {\n try {\n await mkdir(SYNTHRA_DIR, { recursive: true });\n const data: LastSeenFile = { version, updated_at: new Date().toISOString() };\n await writeFile(LAST_SEEN_PATH, JSON.stringify(data, null, 2), \"utf8\");\n } catch {\n // best-effort\n }\n}\n\n/** Find the directory `npm root -g` reports. Used to locate the installed package. */\nfunction npmGlobalRoot(): Promise<string | null> {\n return new Promise((resolve) => {\n const chunks: Buffer[] = [];\n const proc = spawn(\"npm\", [\"root\", \"-g\"], { stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n proc.stdout?.on(\"data\", (c: Buffer) => chunks.push(c));\n proc.on(\"error\", () => resolve(null));\n proc.on(\"exit\", (code) => {\n if (code !== 0) return resolve(null);\n const out = Buffer.concat(chunks).toString(\"utf8\").trim();\n resolve(out || null);\n });\n });\n}\n\n/**\n * Extract the markdown section under `## [version]` (or `## v0.1.11`) from a\n * CHANGELOG body. Returns the bullet/prose content between this version's\n * heading and the next H2, trimmed. Returns null if the version isn't found.\n */\nexport function extractChangelogSection(text: string, version: string): string | null {\n const escapedVersion = version.replace(/\\./g, \"\\\\.\");\n // Match: \"## [0.1.11]\" or \"## v0.1.11\" or \"## 0.1.11\", optionally followed by extra text.\n const headingRe = new RegExp(`^##\\\\s+\\\\[?v?${escapedVersion}\\\\]?.*$`, \"m\");\n const m = headingRe.exec(text);\n if (!m) return null;\n const startBody = m.index + m[0].length;\n const rest = text.slice(startBody);\n const nextHeadingIdx = rest.search(/^##\\s+/m);\n const body = nextHeadingIdx < 0 ? rest : rest.slice(0, nextHeadingIdx);\n // Strip horizontal-rule separator lines.\n return body.replace(/^---\\s*$/gm, \"\").trim() || null;\n}\n\nasync function readInstalledChangelog(): Promise<string | null> {\n const root = await npmGlobalRoot();\n if (!root) return null;\n try {\n return await readFile(join(root, \"@jefuriiij\", \"synthra\", \"CHANGELOG.md\"), \"utf8\");\n } catch {\n return null;\n }\n}\n\nasync function printChangelogForVersion(version: string): Promise<void> {\n const md = await readInstalledChangelog();\n if (!md) return;\n const section = extractChangelogSection(md, version);\n if (!section) return;\n log.info(\"\");\n log.info(`What's new in ${version}:`);\n log.info(\"\");\n for (const line of section.split(/\\r?\\n/)) {\n log.info(` ${line}`);\n }\n log.info(\"\");\n}\n\n/**\n * Compare the running binary's version against the persisted last-seen\n * version. If running > last-seen, print the changelog for the running\n * version and update last-seen. If last-seen is missing (fresh install),\n * silently set it to the current version — new installs don't need a\n * retroactive changelog.\n *\n * Catches users who upgraded via `npm install -g …@latest` outside of\n * Synthra's interactive prompt. Always silent on no-op.\n */\nexport async function runStartupChangelogCheck(): Promise<void> {\n try {\n const current = await getCurrentVersion();\n const lastSeen = await readLastSeen();\n if (!lastSeen) {\n await writeLastSeen(current);\n return;\n }\n if (isNewer(current, lastSeen)) {\n await printChangelogForVersion(current);\n await writeLastSeen(current);\n }\n } catch {\n // silent\n }\n}\n\n/**\n * Ask a yes/no question on stdin/stdout. Returns true only on explicit \"y\" /\n * \"yes\". Empty input or anything else returns false.\n */\nasync function promptYesNo(question: string): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const answer = (await rl.question(question)).trim().toLowerCase();\n return answer === \"y\" || answer === \"yes\";\n } finally {\n rl.close();\n }\n}\n\n/** Run `npm install -g @jefuriiij/synthra@latest`. Inherits stdio. */\nfunction runNpmUpdate(): Promise<boolean> {\n return new Promise((resolve) => {\n const proc = spawn(\"npm\", [\"install\", \"-g\", PKG_NAME + \"@latest\"], {\n stdio: \"inherit\",\n });\n proc.on(\"error\", () => resolve(false));\n proc.on(\"exit\", (code) => resolve(code === 0));\n });\n}\n\n/**\n * Interactive update flow. Always hits the registry — no cache. If a newer\n * version exists AND we're on a TTY, prompts the user [y/N]. On 'y', runs\n * npm install, prints the new version's changelog section, and exits with\n * re-run instructions. On 'n' / non-TTY / no update, returns silently so\n * startup continues.\n */\nexport async function promptForUpdateOrLog(): Promise<void> {\n try {\n const r = await checkForUpdate();\n if (!r.hasUpdate || !r.latest) return;\n\n // Non-interactive (CI, piped stdin) — log a one-line hint but don't prompt.\n if (!process.stdin.isTTY) {\n log.info(\n `Synthra ${r.latest} is available (you have ${r.current}) — run: npm install -g @jefuriiij/synthra@latest`,\n );\n return;\n }\n\n log.info(`Synthra ${r.latest} is available (you have ${r.current}).`);\n const yes = await promptYesNo(\"[syn] Update now? [y/N]: \");\n if (!yes) {\n log.info(\"Skipping update — continuing with current version.\");\n return;\n }\n\n log.info(`Running: npm install -g ${PKG_NAME}@latest`);\n const ok = await runNpmUpdate();\n if (!ok) {\n log.warn(\"npm install failed — continuing with current version.\");\n return;\n }\n log.info(`✓ Updated to ${r.latest}.`);\n await printChangelogForVersion(r.latest);\n await writeLastSeen(r.latest);\n log.info(`Please re-run: syn .`);\n process.exit(0);\n } catch {\n // silent\n }\n}\n","// `syn serve [path]` — starts the HTTP MCP server against an already-scanned\n// project. The graph + symbol index must exist (run `syn scan` first).\n// Traps SIGINT/SIGTERM for a graceful shutdown.\n\nimport { resolve } from \"node:path\";\nimport { stat } from \"node:fs/promises\";\n\nimport { startServer } from \"../server/http.js\";\nimport { log } from \"../shared/logger.js\";\nimport { resolvePaths } from \"../shared/paths.js\";\n\nexport async function serveCommand(rawPath: string): Promise<void> {\n const projectRoot = resolve(rawPath);\n const paths = resolvePaths(projectRoot);\n\n try {\n await stat(paths.infoGraph);\n } catch {\n log.error(`no graph found at ${paths.infoGraph}`);\n log.error(\"run `syn scan` in this project first.\");\n process.exit(2);\n }\n\n const handle = await startServer(paths);\n log.info(`MCP server listening on ${handle.url}`);\n log.info(`port written to ${paths.mcpPort}`);\n log.info(\"press Ctrl+C to stop.\");\n\n const shutdown = async (signal: NodeJS.Signals) => {\n log.info(`received ${signal} — shutting down…`);\n try {\n await handle.stop();\n } catch (err) {\n log.error(\"shutdown error:\", (err as Error).message);\n }\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","// Subroutines for plugging Synthra into Claude Code:\r\n// - registerMcp → `claude mcp add --transport http --scope local`\r\n// - unregisterMcp → `claude mcp remove`\r\n// - spawnClaude → spawn the terminal CLI (used only by `--launch-cli`)\r\n// - startClaude → legacy composite (install hooks + register + spawn + unregister)\r\n// kept for any external callers; cli/index.ts now composes the pieces\r\n// itself so the default flow can skip the CLI spawn.\r\n\r\nimport spawn from \"cross-spawn\";\r\n\r\nimport { installHooks } from \"../hooks/installer.js\";\r\nimport { loadConfig } from \"../shared/config.js\";\r\nimport { log } from \"../shared/logger.js\";\r\nimport type { SynthraPaths } from \"../shared/paths.js\";\r\n\r\nconst MCP_NAME = \"synthra\";\r\n\r\n// We use `cross-spawn` instead of `node:child_process` so Windows .cmd\r\n// shims (e.g. claude.cmd) resolve correctly without setting shell:true.\r\n// shell:true triggers Node's DEP0190 deprecation because args get\r\n// concatenated into a single command line with no escaping. cross-spawn\r\n// handles shim resolution + proper arg escaping internally, so we avoid\r\n// the deprecation and the underlying security concern in one move.\r\nfunction runClaude(\r\n bin: string,\r\n args: string[],\r\n cwd: string,\r\n stdio: \"inherit\" | \"pipe\" = \"pipe\",\r\n): Promise<{ code: number; stdout: string; stderr: string }> {\r\n return new Promise((resolve) => {\r\n const proc = spawn(bin, args, {\r\n cwd,\r\n stdio: stdio === \"inherit\" ? \"inherit\" : [\"ignore\", \"pipe\", \"pipe\"],\r\n });\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n proc.stdout?.on(\"data\", (c) => (stdout += String(c)));\r\n proc.stderr?.on(\"data\", (c) => (stderr += String(c)));\r\n proc.on(\"error\", () => resolve({ code: -1, stdout, stderr: stderr || \"claude not on PATH\" }));\r\n proc.on(\"exit\", (code) => resolve({ code: code ?? 0, stdout, stderr }));\r\n });\r\n}\r\n\r\nexport async function registerMcp(bin: string, mcpPort: number, cwd: string): Promise<boolean> {\r\n const url = `http://127.0.0.1:${mcpPort}/mcp`;\r\n await runClaude(bin, [\"mcp\", \"remove\", MCP_NAME, \"--scope\", \"project\"], cwd).catch(() => undefined);\r\n const reg = await runClaude(\r\n bin,\r\n [\"mcp\", \"add\", MCP_NAME, \"--transport\", \"http\", \"--scope\", \"project\", url],\r\n cwd,\r\n );\r\n if (reg.code !== 0) {\r\n log.warn(`claude mcp add failed (code ${reg.code}). stderr: ${reg.stderr.trim()}`);\r\n log.warn(`Synthra's MCP tools won't be visible to Claude this session.`);\r\n return false;\r\n }\r\n log.info(`registered MCP with Claude: ${MCP_NAME} → ${url}`);\r\n return true;\r\n}\r\n\r\nexport async function unregisterMcp(bin: string, cwd: string): Promise<void> {\r\n const r = await runClaude(bin, [\"mcp\", \"remove\", MCP_NAME, \"--scope\", \"project\"], cwd);\r\n if (r.code === 0) log.debug(\"unregistered MCP server\");\r\n}\r\n\r\nexport interface SpawnClaudeOptions {\r\n cwd: string;\r\n resumeSessionId?: string;\r\n initialPrompt?: string;\r\n}\r\n\r\nexport async function spawnClaude(bin: string, opts: SpawnClaudeOptions): Promise<number> {\r\n const args: string[] = [];\r\n if (opts.resumeSessionId) args.push(\"--resume\", opts.resumeSessionId);\r\n if (opts.initialPrompt) args.push(opts.initialPrompt);\r\n log.info(`launching ${bin} ${args.join(\" \")}`);\r\n const result = await runClaude(bin, args, opts.cwd, \"inherit\");\r\n return result.code;\r\n}\r\n\r\n// Legacy composite — install hooks + register MCP + spawn claude + cleanup.\r\n// cli/index.ts no longer relies on this; it composes the pieces above so the\r\n// new default `syn .` flow can skip the CLI spawn. Kept for compatibility.\r\nexport interface StartClaudeOptions {\r\n paths: SynthraPaths;\r\n mcpPort: number;\r\n resumeSessionId?: string;\r\n initialPrompt?: string;\r\n}\r\n\r\nexport async function startClaude(opts: StartClaudeOptions): Promise<number> {\r\n const bin = loadConfig().claudeBin;\r\n await installHooks(opts.paths);\r\n await registerMcp(bin, opts.mcpPort, opts.paths.projectRoot);\r\n const code = await spawnClaude(bin, {\r\n cwd: opts.paths.projectRoot,\r\n resumeSessionId: opts.resumeSessionId,\r\n initialPrompt: opts.initialPrompt,\r\n });\r\n await unregisterMcp(bin, opts.paths.projectRoot);\r\n return code;\r\n}\r\n"],"mappings":"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F;;;AI3VA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["resolve","resolve","readFile","join","readFile","basename","score","resolve","err","mkdir","readFile","writeFile","dirname","join","pre_compact_default","pre_tool_use_default","prime_default","stop_default","prime_default","pre_tool_use_default","pre_compact_default","stop_default","readFile","join","mkdir","writeFile","dirname","serve","Hono","writeFile","mkdir","dirname","readFile","join","readFile","join","emit","err","readFile","join","dirname","join","readFile","QUERY","QUERY","Query","QUERY","firstLine","Query","QUERY","QUERY","QUERY","QUERY","Query","QUERY","firstLine","Query","QUERY","QUERY","Query","firstLine","Query","SCRIPT_RE","extractScripts","require","readFile","readFile","join","relative","sep","ignore","readIgnoreFile","buildMatcher","toPosix","mkdir","readFile","writeFile","dirname","mkdir","readFile","stat","writeFile","readFile","writeFile","stat","mkdir","readFile","writeFile","err","STOPWORDS","score","execFile","readFile","join","promisify","execFileAsync","mkdir","readFile","writeFile","dirname","mkdir","readFile","writeFile","dirname","SCHEMA_VERSION","score","indexSymbolsByFile","appendFile","mkdir","dirname","mkdir","dirname","appendFile","res","appendFile","mkdir","dirname","err","Hono","serve","writeFile","resolve","readdir","stat","homedir","join","resolve","resolve","err","mkdir","readFile","writeFile","homedir","join","join","homedir","readFile","mkdir","writeFile","resolve","ok","resolve","stat","resolve","stat","err","spawn","resolve","spawn","VERSION","resolve","err"]}
|
|
1
|
+
{"version":3,"sources":["../../package.json","../../src/cli/index.ts","../../src/dashboard/server.ts","../../src/shared/logger.ts","../../src/server/port.ts","../../src/dashboard/delta.ts","../../src/shared/paths.ts","../../src/shared/pricing.ts","../../src/shared/project-registry.ts","../../src/dashboard/public/index.html","../../src/dashboard/public/style.css","../../src/hooks/installer.ts","../../src/hooks/scripts/pre-compact.ps1","../../src/hooks/scripts/pre-compact.sh","../../src/hooks/scripts/pre-tool-use.ps1","../../src/hooks/scripts/pre-tool-use.sh","../../src/hooks/scripts/prime.ps1","../../src/hooks/scripts/prime.sh","../../src/hooks/scripts/stop.ps1","../../src/hooks/scripts/stop.sh","../../src/server/http.ts","../../src/activity/activity-log.ts","../../src/activity/file-watcher.ts","../../src/activity/git-watcher.ts","../../src/cli/scan-command.ts","../../src/scanner/extract.ts","../../src/scanner/hash.ts","../../src/scanner/keywords.ts","../../src/scanner/parser.ts","../../src/scanner/parsers/_generic.ts","../../src/scanner/parsers/c.ts","../../src/scanner/parsers/cpp.ts","../../src/scanner/parsers/csharp.ts","../../src/scanner/parsers/dart.ts","../../src/scanner/parsers/go.ts","../../src/scanner/parsers/java.ts","../../src/scanner/parsers/kotlin.ts","../../src/scanner/parsers/php.ts","../../src/scanner/parsers/python.ts","../../src/scanner/parsers/ruby.ts","../../src/scanner/parsers/rust.ts","../../src/scanner/parsers/typescript.ts","../../src/scanner/parsers/svelte.ts","../../src/scanner/parsers/vue.ts","../../src/scanner/walker.ts","../../src/graph/store.ts","../../src/cli/bootstrap.ts","../../src/hooks/claude-md.ts","../../src/graph/rank.ts","../../src/graph/retrieve.ts","../../src/memory/branches.ts","../../src/memory/context-md.ts","../../src/memory/context-store.ts","../../src/memory/index.ts","../../src/packer/format.ts","../../src/packer/inline.ts","../../src/packer/signatures.ts","../../src/packer/tests.ts","../../src/packer/index.ts","../../src/server/mcp.ts","../../src/server/routes/activity.ts","../../src/server/routes/context-update.ts","../../src/server/routes/gate.ts","../../src/server/routes/log.ts","../../src/server/routes/pack.ts","../../src/server/routes/prime.ts","../../src/shared/config.ts","../../src/cli/session-discovery.ts","../../src/cli/cleanup.ts","../../src/cli/dashboard-command.ts","../../src/cli/self-update.ts","../../src/cli/serve-command.ts","../../src/cli/start-claude.ts"],"sourcesContent":["{\r\n \"name\": \"@jefuriiij/synthra\",\r\n \"version\": \"0.1.17\",\r\n \"publishConfig\": {\r\n \"access\": \"public\"\r\n },\r\n \"description\": \"Local context engine for AI coding assistants — graph-based context, branch-aware memory, real-time human-activity awareness, deterministic Grep/Glob gating, and a live token dashboard.\",\r\n \"type\": \"module\",\r\n \"bin\": {\r\n \"syn\": \"./bin/syn\",\r\n \"synthra\": \"./bin/syn\"\r\n },\r\n \"scripts\": {\r\n \"build\": \"tsup\",\r\n \"dev\": \"tsup --watch\",\r\n \"test\": \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n \"typecheck\": \"tsc --noEmit\"\r\n },\r\n \"files\": [\r\n \"dist\",\r\n \"bin\",\r\n \"README.md\",\r\n \"CHANGELOG.md\",\r\n \"LICENSE\",\r\n \"ROADMAP.md\"\r\n ],\r\n \"keywords\": [\r\n \"claude-code\",\r\n \"mcp\",\r\n \"context-engine\",\r\n \"code-graph\",\r\n \"ai-coding\",\r\n \"token-savings\"\r\n ],\r\n \"author\": \"Jeff (@jefuriiij)\",\r\n \"license\": \"MIT\",\r\n \"homepage\": \"https://github.com/jefuriiij/synthra#readme\",\r\n \"repository\": {\r\n \"type\": \"git\",\r\n \"url\": \"git+https://github.com/jefuriiij/synthra.git\"\r\n },\r\n \"bugs\": {\r\n \"url\": \"https://github.com/jefuriiij/synthra/issues\"\r\n },\r\n \"engines\": {\r\n \"node\": \">=18\"\r\n },\r\n \"dependencies\": {\r\n \"@hono/node-server\": \"^1.18.0\",\r\n \"chokidar\": \"^5.0.0\",\r\n \"cross-spawn\": \"^7.0.6\",\r\n \"hono\": \"^4.12.23\",\r\n \"ignore\": \"^7.0.0\",\r\n \"sade\": \"^1.8.1\",\r\n \"tree-sitter-wasms\": \"^0.1.12\",\r\n \"web-tree-sitter\": \"^0.25.10\"\r\n },\r\n \"devDependencies\": {\r\n \"@types/cross-spawn\": \"^6.0.6\",\r\n \"@types/node\": \"^25.9.1\",\r\n \"tsup\": \"^8.5.1\",\r\n \"typescript\": \"^6.0.3\",\r\n \"vitest\": \"^4.1.7\"\r\n }\r\n}\r\n","// `syn` entry point. Parses args and dispatches to commands.\r\n//\r\n// Commands:\r\n// syn [path] → default: scan + start MCP + dashboard +\r\n// register MCP for IDE extension; block on\r\n// Ctrl+C. NO claude CLI spawn — use the IDE.\r\n// syn . [path] → alias for default\r\n// syn . --launch-cli → also spawn `claude` here (M3 behavior)\r\n// syn scan [path] → scan only\r\n// syn serve [path] → start MCP server only\r\n// syn dashboard [path] → run only the dashboard server\r\n// syn --resume <id> [path] → resume an existing Claude session\r\n// (only meaningful with --launch-cli)\r\n\r\nimport sade from \"sade\";\r\nimport { resolve } from \"node:path\";\r\n\r\n// Inlined at build time by tsup's JSON loader so `syn --version` always\r\n// matches the published package.\r\nimport pkgJson from \"../../package.json\" with { type: \"json\" };\r\n\r\nimport { startDashboard, type DashboardServerHandle } from \"../dashboard/server.js\";\r\nimport { installHooks } from \"../hooks/installer.js\";\r\nimport { startServer, type ServerHandle } from \"../server/http.js\";\r\nimport { loadConfig } from \"../shared/config.js\";\r\nimport { log } from \"../shared/logger.js\";\r\nimport { resolvePaths } from \"../shared/paths.js\";\r\nimport { recordProject } from \"../shared/project-registry.js\";\r\nimport { cleanup } from \"./cleanup.js\";\r\nimport { dashboardCommand } from \"./dashboard-command.js\";\r\nimport { scanCommand, type ScanResult } from \"./scan-command.js\";\r\nimport { promptForUpdateOrLog, runStartupChangelogCheck } from \"./self-update.js\";\r\nimport { serveCommand } from \"./serve-command.js\";\r\nimport { registerMcp, spawnClaude, unregisterMcp } from \"./start-claude.js\";\r\n\r\nconst VERSION = (pkgJson as { version: string }).version;\r\n\r\ninterface DefaultOpts {\r\n resume?: string;\r\n \"launch-cli\"?: boolean;\r\n}\r\n\r\ninterface BannerInfo {\r\n projectRoot: string;\r\n scan: ScanResult;\r\n mcpUrl: string;\r\n dashboardUrl: string | null;\r\n mcpRegistered: boolean;\r\n}\r\n\r\nfunction printReadyBanner(info: BannerInfo): void {\r\n log.info(\"\");\r\n log.info(` ✅ scanned ${info.scan.parsed} files · ${info.scan.symbolCount} symbols · ${info.scan.edgeCount} edges`);\r\n if (info.mcpRegistered) {\r\n log.info(` 🧠 MCP ${info.mcpUrl} → registered as 'synthra'`);\r\n } else {\r\n log.info(` 🧠 MCP ${info.mcpUrl} ⚠ registration with claude failed`);\r\n }\r\n if (info.dashboardUrl) {\r\n log.info(` 📊 Dashboard ${info.dashboardUrl}`);\r\n } else {\r\n log.info(` 📊 Dashboard (failed to start; data is still logged to .synthra-graph/)`);\r\n }\r\n log.info(` 🪝 Hooks installed in .claude/settings.local.json`);\r\n log.info(\"\");\r\n log.info(` 🤖 Ready — open the Claude Code IDE extension (or run \\`claude\\` in another terminal).`);\r\n log.info(` Synthra's tools and gate will be active for that session.`);\r\n log.info(\"\");\r\n log.info(` Press Ctrl+C here when you're done.`);\r\n log.info(\"\");\r\n}\r\n\r\nfunction waitForSignal(): Promise<NodeJS.Signals> {\r\n return new Promise((resolve) => {\r\n const handler = (sig: NodeJS.Signals) => {\r\n process.off(\"SIGINT\", handler);\r\n process.off(\"SIGTERM\", handler);\r\n resolve(sig);\r\n };\r\n process.on(\"SIGINT\", handler);\r\n process.on(\"SIGTERM\", handler);\r\n });\r\n}\r\n\r\nasync function defaultFlow(rawPath: string, opts: DefaultOpts): Promise<void> {\r\n const launchCli = opts[\"launch-cli\"] === true;\r\n const projectRoot = resolve(rawPath);\r\n const paths = resolvePaths(projectRoot);\r\n const cfg = loadConfig();\r\n\r\n // If the running binary is newer than last-seen (e.g. user upgraded via\r\n // `npm install -g …@latest` directly), print the changelog for what they\r\n // just got. Silent if already on latest-seen or on fresh install.\r\n await runStartupChangelogCheck();\r\n\r\n // Always-fresh registry check (no cache). If a newer version is on npm AND\r\n // we're on a TTY, prompts [y/N]. On 'y', runs npm install, prints the new\r\n // version's changelog, and exits with re-run instructions. On 'n' / no\r\n // update / non-TTY, continues silently. SYN_NO_UPDATE_CHECK=1 opts out.\r\n await promptForUpdateOrLog();\r\n\r\n // 1. bootstrap + scan + record in the global registry so the dashboard\r\n // can list this project alongside any others.\r\n await recordProject(projectRoot);\r\n const scan = await scanCommand(rawPath);\r\n\r\n // 2. MCP server (background within this process)\r\n const mcpHandle: ServerHandle = await startServer(paths);\r\n\r\n // 3. Dashboard (optional — non-fatal if it fails)\r\n let dashboardHandle: DashboardServerHandle | null = null;\r\n try {\r\n dashboardHandle = await startDashboard(paths, cfg.dashboardPort);\r\n } catch (err) {\r\n log.warn(`dashboard failed to start on port ${cfg.dashboardPort}: ${(err as Error).message}`);\r\n }\r\n\r\n // 4. Install hooks + register MCP so the IDE / external claude can see it\r\n await installHooks(paths);\r\n const mcpRegistered = await registerMcp(cfg.claudeBin, mcpHandle.port, projectRoot);\r\n\r\n let claudeExitCode = 0;\r\n try {\r\n if (launchCli) {\r\n claudeExitCode = await spawnClaude(cfg.claudeBin, {\r\n cwd: projectRoot,\r\n resumeSessionId: opts.resume,\r\n });\r\n log.info(`claude exited with code ${claudeExitCode}`);\r\n } else {\r\n printReadyBanner({\r\n projectRoot,\r\n scan,\r\n mcpUrl: mcpHandle.url,\r\n dashboardUrl: dashboardHandle?.url ?? null,\r\n mcpRegistered,\r\n });\r\n const sig = await waitForSignal();\r\n log.info(`received ${sig} — shutting down…`);\r\n }\r\n } finally {\r\n await unregisterMcp(cfg.claudeBin, projectRoot).catch(() => undefined);\r\n if (dashboardHandle) {\r\n await dashboardHandle.stop().catch((err) =>\r\n log.warn(`dashboard stop error: ${(err as Error).message}`),\r\n );\r\n }\r\n await mcpHandle.stop().catch((err) =>\r\n log.warn(`MCP server stop error: ${(err as Error).message}`),\r\n );\r\n await cleanup(paths).catch((err) =>\r\n log.warn(`cleanup error: ${(err as Error).message}`),\r\n );\r\n }\r\n}\r\n\r\nexport function buildProgram() {\r\n const prog = sade(\"syn\");\r\n prog\r\n .version(VERSION)\r\n .describe(\"Local context engine for AI coding assistants.\");\r\n\r\n prog\r\n .command(\". [path]\", \"Scan + MCP + dashboard + hooks. Default flow — use with the Claude Code IDE extension.\", {\r\n default: true,\r\n })\r\n .option(\"--resume <id>\", \"Resume an existing Claude session (only with --launch-cli)\")\r\n .option(\"--launch-cli\", \"Also spawn `claude` CLI in this terminal (legacy M3 behavior)\", false)\r\n .action(async (path: string | undefined, opts: DefaultOpts) => {\r\n await defaultFlow(path ?? \".\", opts);\r\n });\r\n\r\n prog\r\n .command(\"scan [path]\", \"Scan only — walk + parse + write graph.\")\r\n .action(async (path: string | undefined) => {\r\n await scanCommand(path ?? \".\");\r\n });\r\n\r\n prog\r\n .command(\"serve [path]\", \"Start the HTTP MCP server against a scanned project.\")\r\n .action(async (path: string | undefined) => {\r\n await serveCommand(path ?? \".\");\r\n });\r\n\r\n prog\r\n .command(\"dashboard [path]\", \"Run the token dashboard server (localhost:8901).\")\r\n .action(async (path: string | undefined) => {\r\n await dashboardCommand(path ?? \".\");\r\n });\r\n\r\n return prog;\r\n}\r\n\r\nexport async function main(argv: string[]): Promise<void> {\r\n const prog = buildProgram();\r\n prog.parse(argv);\r\n}\r\n","// Standalone dashboard server. Default port 8901 (override via\r\n// SYN_DASHBOARD_PORT); falls back through a small range 8901–8910 if the\r\n// preferred port is busy (so we can coexist with other co-installed\r\n// AI-context tools that also expose a dashboard).\r\n// Reads .synthra-graph/token_log.jsonl + .synthra-graph/gate_log.jsonl for the\r\n// given project and renders a live SPA backed by GET /data polled every 2s.\r\n\r\nimport { serve } from \"@hono/node-server\";\r\nimport { Hono } from \"hono\";\r\n\r\n// Tsup inlines this import at build time so `c.html` can echo whatever\r\n// version is running. Replaces the v__SYN_VERSION__ placeholder in the\r\n// dashboard footer on every GET /.\r\nimport pkgJson from \"../../package.json\" with { type: \"json\" };\r\n\r\nimport { log } from \"../shared/logger.js\";\r\nimport type { SynthraPaths } from \"../shared/paths.js\";\r\nimport { findFreePort } from \"../server/port.js\";\r\nimport { computeDashboardData } from \"./delta.js\";\r\n\r\nimport indexHtml from \"./public/index.html\";\r\nimport styleCss from \"./public/style.css\";\r\n\r\nconst FALLBACK_RANGE = 9; // try preferredPort + [0..9]\r\nconst VERSION = (pkgJson as { version: string }).version;\r\n// How many recent turns/gates the /data payload carries. The dashboard\r\n// paginates turns client-side (25/page); the donut uses the uncapped\r\n// per-project model aggregate, so it isn't bounded by this.\r\nconst RECENT_N = Number(process.env.SYN_DASHBOARD_RECENT_N) || 500;\r\n\r\nexport interface DashboardServerHandle {\r\n port: number;\r\n url: string;\r\n stop(): Promise<void>;\r\n}\r\n\r\nexport async function startDashboard(\r\n paths: SynthraPaths,\r\n preferredPort = 8901,\r\n): Promise<DashboardServerHandle> {\r\n const port = await findFreePort(preferredPort, preferredPort + FALLBACK_RANGE);\r\n if (port !== preferredPort) {\r\n log.info(\r\n `dashboard port ${preferredPort} was busy — bound to ${port} instead (likely another dashboard from a coexisting tool).`,\r\n );\r\n }\r\n const app = new Hono();\r\n\r\n app.get(\"/\", (c) => c.html(indexHtml.replaceAll(\"__SYN_VERSION__\", VERSION)));\r\n\r\n app.get(\"/style.css\", (c) => {\r\n c.header(\"Content-Type\", \"text/css; charset=utf-8\");\r\n c.header(\"Cache-Control\", \"no-cache\");\r\n return c.body(styleCss);\r\n });\r\n\r\n app.get(\"/health\", (c) => c.json({ ok: true }));\r\n\r\n app.get(\"/data\", async (c) => {\r\n const data = await computeDashboardData(paths, RECENT_N);\r\n return c.json(data);\r\n });\r\n\r\n const nodeServer = serve({ fetch: app.fetch, port, hostname: \"127.0.0.1\" });\r\n\r\n return {\r\n port,\r\n url: `http://127.0.0.1:${port}`,\r\n async stop() {\r\n await new Promise<void>((resolve, reject) => {\r\n nodeServer.close((err) => (err ? reject(err) : resolve()));\r\n });\r\n },\r\n };\r\n}\r\n","// Minimal logger. Prefixes Synthra output with [syn].\n\ntype Level = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LEVEL_PRIORITY: Record<Level, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nlet activeLevel: Level = (process.env.SYN_LOG_LEVEL as Level) ?? \"info\";\n\nexport function setLevel(level: Level): void {\n activeLevel = level;\n}\n\nfunction shouldLog(level: Level): boolean {\n return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[activeLevel];\n}\n\nfunction emit(level: Level, msg: string, ...args: unknown[]): void {\n if (!shouldLog(level)) return;\n const stream = level === \"error\" || level === \"warn\" ? process.stderr : process.stdout;\n stream.write(`[syn] ${msg}${args.length ? \" \" + args.map(String).join(\" \") : \"\"}\\n`);\n}\n\nexport const log = {\n debug: (m: string, ...a: unknown[]) => emit(\"debug\", m, ...a),\n info: (m: string, ...a: unknown[]) => emit(\"info\", m, ...a),\n warn: (m: string, ...a: unknown[]) => emit(\"warn\", m, ...a),\n error: (m: string, ...a: unknown[]) => emit(\"error\", m, ...a),\n};\n","// Finds a free port in the 8080–8099 range. Writes the chosen port to\n// .synthra-graph/mcp_port so PowerShell/Bash hook scripts can read it.\n// TODO: M2\n\nimport { createServer } from \"node:net\";\n\nexport const PORT_RANGE_START = 8080;\nexport const PORT_RANGE_END = 8099;\n\nexport async function findFreePort(\n start = PORT_RANGE_START,\n end = PORT_RANGE_END,\n): Promise<number> {\n for (let port = start; port <= end; port++) {\n if (await isFree(port)) return port;\n }\n throw new Error(`Synthra: no free port in ${start}-${end}`);\n}\n\nfunction isFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const s = createServer();\n s.once(\"error\", () => resolve(false));\n s.once(\"listening\", () => s.close(() => resolve(true)));\n s.listen(port, \"127.0.0.1\");\n });\n}\n","// Reads token_log.jsonl + gate_log.jsonl for the active project AND every\r\n// project registered in ~/.synthra/projects.json, then computes the\r\n// dashboard's rendered shape: per-project + global aggregate + recent calls\r\n// across all projects.\r\n\r\nimport { readFile } from \"node:fs/promises\";\r\n\r\nimport { resolvePaths, type SynthraPaths } from \"../shared/paths.js\";\r\nimport { estimateCostUsd } from \"../shared/pricing.js\";\r\nimport { listProjects } from \"../shared/project-registry.js\";\r\n\r\nconst AVG_TOKENS_PER_BLOCKED_GREP = 500;\r\n\r\nexport interface TokenLogEntry {\r\n /** Stop-hook-supplied timestamp (preferred). */\r\n ts?: string;\r\n /** Server-side fallback added by handleLog when ts isn't provided. */\r\n written_at?: string;\r\n input_tokens: number;\r\n output_tokens: number;\r\n cache_creation_input_tokens?: number;\r\n cache_read_input_tokens?: number;\r\n model: string;\r\n description?: string;\r\n project: string;\r\n}\r\n\r\nexport interface GateLogEntry {\r\n ts: string;\r\n tool: string;\r\n decision: \"allow\" | \"block\";\r\n query: string | null;\r\n reason?: string;\r\n}\r\n\r\nexport interface ProjectStats {\r\n path: string;\r\n name: string;\r\n last_seen: string | null;\r\n total_turns: number;\r\n total_input_tokens: number;\r\n total_output_tokens: number;\r\n total_cache_read: number;\r\n total_cache_create: number;\r\n total_gate_calls: number;\r\n blocked_count: number;\r\n estimated_tokens_saved: number;\r\n estimated_cost_usd: number;\r\n models: Record<string, number>;\r\n}\r\n\r\nexport interface RecentTurn {\r\n ts: string;\r\n project_name: string;\r\n project_path: string;\r\n input: number;\r\n output: number;\r\n cache_read: number;\r\n cache_create: number;\r\n model: string;\r\n cost_usd: number;\r\n}\r\n\r\nexport interface RecentGate {\r\n ts: string;\r\n project_name: string;\r\n project_path: string;\r\n tool: string;\r\n decision: \"allow\" | \"block\";\r\n query: string | null;\r\n}\r\n\r\nexport interface DashboardData {\r\n active: {\r\n project_root: string;\r\n project_name: string;\r\n stats: ProjectStats;\r\n };\r\n global: {\r\n project_count: number;\r\n total_turns: number;\r\n total_input_tokens: number;\r\n total_output_tokens: number;\r\n total_cache_read: number;\r\n total_cache_create: number;\r\n total_gate_calls: number;\r\n blocked_count: number;\r\n estimated_tokens_saved: number;\r\n saved_percent: number;\r\n estimated_cost_usd: number;\r\n };\r\n projects: ProjectStats[];\r\n recent_turns: RecentTurn[];\r\n recent_gates: RecentGate[];\r\n}\r\n\r\nasync function readJsonl<T>(path: string): Promise<T[]> {\r\n try {\r\n const text = await readFile(path, \"utf8\");\r\n return text\r\n .split(/\\r?\\n/)\r\n .filter((l) => l.length > 0)\r\n .map((l) => {\r\n try {\r\n return JSON.parse(l) as T;\r\n } catch {\r\n return null;\r\n }\r\n })\r\n .filter((v): v is T => v !== null);\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\nfunction basename(p: string): string {\r\n const parts = p.split(/[\\\\/]/);\r\n return parts[parts.length - 1] || p;\r\n}\r\n\r\ninterface ProjectFiles {\r\n path: string;\r\n name: string;\r\n last_seen: string | null;\r\n tokens: TokenLogEntry[];\r\n gates: GateLogEntry[];\r\n}\r\n\r\nfunction summarize(p: ProjectFiles): ProjectStats {\r\n let totalIn = 0;\r\n let totalOut = 0;\r\n let totalCacheRead = 0;\r\n let totalCacheCreate = 0;\r\n let costUsd = 0;\r\n const models: Record<string, number> = {};\r\n\r\n for (const t of p.tokens) {\r\n totalIn += t.input_tokens ?? 0;\r\n totalOut += t.output_tokens ?? 0;\r\n totalCacheRead += t.cache_read_input_tokens ?? 0;\r\n totalCacheCreate += t.cache_creation_input_tokens ?? 0;\r\n costUsd += estimateCostUsd(t);\r\n if (t.model) models[t.model] = (models[t.model] ?? 0) + 1;\r\n }\r\n\r\n const blocked = p.gates.filter((g) => g.decision === \"block\").length;\r\n const saved = blocked * AVG_TOKENS_PER_BLOCKED_GREP;\r\n\r\n return {\r\n path: p.path,\r\n name: p.name,\r\n last_seen: p.last_seen,\r\n total_turns: p.tokens.length,\r\n total_input_tokens: totalIn,\r\n total_output_tokens: totalOut,\r\n total_cache_read: totalCacheRead,\r\n total_cache_create: totalCacheCreate,\r\n total_gate_calls: p.gates.length,\r\n blocked_count: blocked,\r\n estimated_tokens_saved: saved,\r\n estimated_cost_usd: Math.round(costUsd * 100) / 100,\r\n models,\r\n };\r\n}\r\n\r\nasync function loadProjectFiles(\r\n path: string,\r\n name: string,\r\n lastSeen: string | null,\r\n): Promise<ProjectFiles> {\r\n const paths = resolvePaths(path);\r\n const [rawTokens, gates] = await Promise.all([\r\n readJsonl<TokenLogEntry>(paths.tokenLog),\r\n readJsonl<GateLogEntry>(paths.gateLog),\r\n ]);\r\n return { path, name, last_seen: lastSeen, tokens: dedupeTokens(rawTokens), gates };\r\n}\r\n\r\n/**\r\n * Collapse duplicate token-log entries from co-installed AI tools.\r\n *\r\n * Synthra is friendly with other tools that share the .synthra-graph/\r\n * token_log.jsonl shape — if a second tool's Stop hook also writes to\r\n * it, both fire on the same turn and emit nearly-identical entries\r\n * within ~10ms, double-counting every metric in the dashboard.\r\n *\r\n * Strategy: group by (project, usage counts, second-rounded timestamp);\r\n * inside a group, keep the entry with the most credible model field —\r\n * a real Claude model > \"<synthetic>\" > empty.\r\n */\r\nfunction dedupeTokens(entries: TokenLogEntry[]): TokenLogEntry[] {\r\n const score = (model: string | undefined): number => {\r\n if (!model) return 0;\r\n if (model === \"<synthetic>\") return 1;\r\n return 2; // real model name\r\n };\r\n\r\n const groups = new Map<string, TokenLogEntry[]>();\r\n for (const e of entries) {\r\n const ts = e.ts ?? e.written_at ?? \"\";\r\n const second = ts.slice(0, 19); // YYYY-MM-DDTHH:mm:ss\r\n const key = [\r\n e.project ?? \"\",\r\n e.input_tokens ?? 0,\r\n e.output_tokens ?? 0,\r\n e.cache_creation_input_tokens ?? 0,\r\n e.cache_read_input_tokens ?? 0,\r\n second,\r\n ].join(\"|\");\r\n const arr = groups.get(key) ?? [];\r\n arr.push(e);\r\n groups.set(key, arr);\r\n }\r\n\r\n const out: TokenLogEntry[] = [];\r\n for (const arr of groups.values()) {\r\n if (arr.length === 1) {\r\n out.push(arr[0]!);\r\n continue;\r\n }\r\n arr.sort((a, b) => score(b.model) - score(a.model));\r\n out.push(arr[0]!);\r\n }\r\n\r\n // Preserve chronological order in the per-project list.\r\n out.sort((a, b) => {\r\n const at = a.ts ?? a.written_at ?? \"\";\r\n const bt = b.ts ?? b.written_at ?? \"\";\r\n return at.localeCompare(bt);\r\n });\r\n return out;\r\n}\r\n\r\nexport async function computeDashboardData(\r\n activePaths: SynthraPaths,\r\n recentN = 500,\r\n): Promise<DashboardData> {\r\n const registered = await listProjects();\r\n\r\n // Always include the active project, even if not yet in the registry.\r\n const activePath = activePaths.projectRoot;\r\n const activeName = basename(activePath);\r\n const knownPaths = new Set(registered.map((p) => p.path));\r\n const allEntries: Array<{ path: string; name: string; last_seen: string | null }> = [\r\n ...registered.map((p) => ({ path: p.path, name: p.name, last_seen: p.last_seen })),\r\n ];\r\n if (!knownPaths.has(activePath)) {\r\n allEntries.unshift({ path: activePath, name: activeName, last_seen: null });\r\n }\r\n\r\n const loaded = await Promise.all(\r\n allEntries.map((e) => loadProjectFiles(e.path, e.name, e.last_seen)),\r\n );\r\n\r\n const projects = loaded\r\n .map(summarize)\r\n .sort((a, b) => b.total_input_tokens + b.total_output_tokens - (a.total_input_tokens + a.total_output_tokens));\r\n\r\n const activeFiles =\r\n loaded.find((p) => p.path === activePath) ?? {\r\n path: activePath,\r\n name: activeName,\r\n last_seen: null,\r\n tokens: [],\r\n gates: [],\r\n };\r\n const activeStats = summarize(activeFiles);\r\n\r\n // Global aggregates\r\n let g_in = 0,\r\n g_out = 0,\r\n g_cr = 0,\r\n g_cc = 0,\r\n g_gate = 0,\r\n g_block = 0,\r\n g_cost = 0,\r\n g_turns = 0;\r\n for (const s of projects) {\r\n g_turns += s.total_turns;\r\n g_in += s.total_input_tokens;\r\n g_out += s.total_output_tokens;\r\n g_cr += s.total_cache_read;\r\n g_cc += s.total_cache_create;\r\n g_gate += s.total_gate_calls;\r\n g_block += s.blocked_count;\r\n g_cost += s.estimated_cost_usd;\r\n }\r\n const g_saved = g_block * AVG_TOKENS_PER_BLOCKED_GREP;\r\n const g_used = g_in + g_out + g_cc;\r\n const g_saved_pct = g_used + g_saved > 0 ? (g_saved / (g_used + g_saved)) * 100 : 0;\r\n\r\n // Recent turns + gates across all projects, sorted by ts descending\r\n const allTurns: RecentTurn[] = [];\r\n const allGates: RecentGate[] = [];\r\n for (const p of loaded) {\r\n for (const t of p.tokens) {\r\n allTurns.push({\r\n // Fall back to written_at — the Stop hook today posts entries without\r\n // a `ts` field, and the server tags them with written_at on receive.\r\n ts: t.ts ?? t.written_at ?? \"\",\r\n project_name: p.name,\r\n project_path: p.path,\r\n input: t.input_tokens ?? 0,\r\n output: t.output_tokens ?? 0,\r\n cache_read: t.cache_read_input_tokens ?? 0,\r\n cache_create: t.cache_creation_input_tokens ?? 0,\r\n model: t.model ?? \"\",\r\n cost_usd: Math.round(estimateCostUsd(t) * 1000) / 1000,\r\n });\r\n }\r\n for (const gate of p.gates) {\r\n allGates.push({\r\n ts: gate.ts,\r\n project_name: p.name,\r\n project_path: p.path,\r\n tool: gate.tool,\r\n decision: gate.decision,\r\n query: gate.query,\r\n });\r\n }\r\n }\r\n allTurns.sort((a, b) => (a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0));\r\n allGates.sort((a, b) => (a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0));\r\n\r\n return {\r\n active: {\r\n project_root: activePath,\r\n project_name: activeName,\r\n stats: activeStats,\r\n },\r\n global: {\r\n project_count: projects.length,\r\n total_turns: g_turns,\r\n total_input_tokens: g_in,\r\n total_output_tokens: g_out,\r\n total_cache_read: g_cr,\r\n total_cache_create: g_cc,\r\n total_gate_calls: g_gate,\r\n blocked_count: g_block,\r\n estimated_tokens_saved: g_saved,\r\n saved_percent: Math.round(g_saved_pct * 10) / 10,\r\n estimated_cost_usd: Math.round(g_cost * 100) / 100,\r\n },\r\n projects,\r\n recent_turns: allTurns.slice(0, recentN),\r\n recent_gates: allGates.slice(0, recentN),\r\n };\r\n}\r\n\r\n// Legacy shapes from the M2 stub — kept for compat.\r\nexport interface TurnBreakdown {\r\n systemPromptTokens: number;\r\n conversationHistoryTokens: number;\r\n synthraPackTokens: number;\r\n userMessageTokens: number;\r\n responseTokens: number;\r\n totalTokens: number;\r\n costUsd: number;\r\n}\r\n\r\nexport interface SavingsDelta {\r\n withSynthra: TurnBreakdown;\r\n estimatedWithoutSynthra: TurnBreakdown;\r\n savedUsd: number;\r\n savedPercent: number;\r\n}\r\n\r\nexport function computeDelta(breakdown: TurnBreakdown, blockedGreps: number): SavingsDelta {\r\n const savedTokens = blockedGreps * AVG_TOKENS_PER_BLOCKED_GREP;\r\n const without: TurnBreakdown = {\r\n ...breakdown,\r\n conversationHistoryTokens: breakdown.conversationHistoryTokens + savedTokens,\r\n totalTokens: breakdown.totalTokens + savedTokens,\r\n costUsd: breakdown.costUsd + (savedTokens / 1_000_000) * 3,\r\n };\r\n const savedUsd = without.costUsd - breakdown.costUsd;\r\n const savedPercent = without.totalTokens > 0 ? (savedTokens / without.totalTokens) * 100 : 0;\r\n return {\r\n withSynthra: breakdown,\r\n estimatedWithoutSynthra: without,\r\n savedUsd,\r\n savedPercent: Math.round(savedPercent * 10) / 10,\r\n };\r\n}\r\n","// Resolves Synthra's storage locations inside a project root.\n\nimport { join } from \"node:path\";\n\nexport interface SynthraPaths {\n projectRoot: string;\n graphDir: string;\n contextDir: string;\n infoGraph: string;\n symbolIndex: string;\n sessionState: string;\n activityLog: string;\n tokenLog: string;\n gateLog: string;\n mcpPort: string;\n mcpServerLog: string;\n mcpServerErrLog: string;\n contextStore: string;\n contextMd: string;\n branchesDir: string;\n claudeDir: string;\n claudeSettings: string;\n claudeHooksDir: string;\n claudeMd: string;\n gitignore: string;\n}\n\nexport function resolvePaths(projectRoot: string): SynthraPaths {\n const graphDir = join(projectRoot, \".synthra-graph\");\n const contextDir = join(projectRoot, \".synthra\");\n const claudeDir = join(projectRoot, \".claude\");\n\n return {\n projectRoot,\n graphDir,\n contextDir,\n infoGraph: join(graphDir, \"info_graph.json\"),\n symbolIndex: join(graphDir, \"symbol_index.json\"),\n sessionState: join(graphDir, \"session.json\"),\n activityLog: join(graphDir, \"activity.jsonl\"),\n tokenLog: join(graphDir, \"token_log.jsonl\"),\n gateLog: join(graphDir, \"gate_log.jsonl\"),\n mcpPort: join(graphDir, \"mcp_port\"),\n mcpServerLog: join(graphDir, \"mcp_server.log\"),\n mcpServerErrLog: join(graphDir, \"mcp_server.err.log\"),\n contextStore: join(contextDir, \"context-store.json\"),\n contextMd: join(contextDir, \"CONTEXT.md\"),\n branchesDir: join(contextDir, \"branches\"),\n claudeDir,\n claudeSettings: join(claudeDir, \"settings.local.json\"),\n claudeHooksDir: join(claudeDir, \"hooks\"),\n claudeMd: join(projectRoot, \"CLAUDE.md\"),\n gitignore: join(projectRoot, \".gitignore\"),\n };\n}\n","// Approximate per-million-token pricing for Claude models, in USD.\n// Sourced from Anthropic's published rates. Tilde everywhere — these can shift.\n//\n// Used only for the dashboard's \"~$X\" estimate; not for billing.\n\nexport interface ModelPricing {\n /** Cost per 1M raw-input tokens. */\n input: number;\n /** Cost per 1M output tokens. */\n output: number;\n /** Cost per 1M cache-read tokens (typically ~10% of input). */\n cacheRead: number;\n /** Cost per 1M cache-creation tokens (typically input × 1.25). */\n cacheCreate: number;\n}\n\nconst PRICING: Record<string, ModelPricing> = {\n // Opus-class models — premium tier\n \"claude-opus-4-7\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n \"claude-opus-4-6\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n \"claude-opus-4-5\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n // Sonnet-class — workhorse\n \"claude-sonnet-4-6\": { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 },\n \"claude-sonnet-4-5\": { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 },\n // Haiku-class — fast and cheap\n \"claude-haiku-4-5\": { input: 1, output: 5, cacheRead: 0.1, cacheCreate: 1.25 },\n};\n\nconst FALLBACK: ModelPricing = { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 };\n\nexport function pricingFor(model: string | undefined | null): ModelPricing {\n if (!model) return FALLBACK;\n const direct = PRICING[model];\n if (direct) return direct;\n // Loose prefix match: \"claude-opus-…\" / \"claude-sonnet-…\" / \"claude-haiku-…\"\n if (model.includes(\"opus\")) return PRICING[\"claude-opus-4-7\"] ?? FALLBACK;\n if (model.includes(\"sonnet\")) return PRICING[\"claude-sonnet-4-6\"] ?? FALLBACK;\n if (model.includes(\"haiku\")) return PRICING[\"claude-haiku-4-5\"] ?? FALLBACK;\n return FALLBACK;\n}\n\nexport interface UsageRecord {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n model?: string;\n}\n\n/** Approximate USD cost of a single usage record. */\nexport function estimateCostUsd(usage: UsageRecord): number {\n const p = pricingFor(usage.model);\n return (\n (usage.input_tokens / 1_000_000) * p.input +\n (usage.output_tokens / 1_000_000) * p.output +\n ((usage.cache_read_input_tokens ?? 0) / 1_000_000) * p.cacheRead +\n ((usage.cache_creation_input_tokens ?? 0) / 1_000_000) * p.cacheCreate\n );\n}\n","// Global registry of projects that have run `syn .` on this machine.\n// Stored at ~/.synthra/projects.json so the dashboard can enumerate them\n// without walking the filesystem.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\n\nconst REGISTRY_DIR = join(homedir(), \".synthra\");\nconst REGISTRY_PATH = join(REGISTRY_DIR, \"projects.json\");\nconst SCHEMA_VERSION = 1;\n\nexport interface ProjectRegistryEntry {\n path: string; // absolute project root\n name: string; // basename for display\n first_seen: string; // ISO timestamp\n last_seen: string; // ISO timestamp\n}\n\ninterface Registry {\n schema_version: number;\n projects: ProjectRegistryEntry[];\n}\n\nasync function readRegistry(): Promise<Registry> {\n try {\n const raw = await readFile(REGISTRY_PATH, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<Registry>;\n if (!Array.isArray(parsed.projects)) return { schema_version: SCHEMA_VERSION, projects: [] };\n return { schema_version: parsed.schema_version ?? SCHEMA_VERSION, projects: parsed.projects };\n } catch {\n return { schema_version: SCHEMA_VERSION, projects: [] };\n }\n}\n\nasync function writeRegistry(registry: Registry): Promise<void> {\n await mkdir(dirname(REGISTRY_PATH), { recursive: true });\n await writeFile(REGISTRY_PATH, JSON.stringify(registry, null, 2) + \"\\n\", \"utf8\");\n}\n\n/** Upsert this project's entry. Updates `last_seen`; preserves `first_seen`. */\nexport async function recordProject(projectRoot: string): Promise<void> {\n const now = new Date().toISOString();\n const registry = await readRegistry();\n const existing = registry.projects.find((p) => p.path === projectRoot);\n if (existing) {\n existing.last_seen = now;\n existing.name = basename(projectRoot);\n } else {\n registry.projects.push({\n path: projectRoot,\n name: basename(projectRoot),\n first_seen: now,\n last_seen: now,\n });\n }\n try {\n await writeRegistry(registry);\n } catch {\n // Registry is best-effort — a write failure shouldn't block the session.\n }\n}\n\nexport async function listProjects(): Promise<ProjectRegistryEntry[]> {\n const registry = await readRegistry();\n // Sort by last_seen descending so the most active project surfaces first.\n return registry.projects\n .slice()\n .sort((a, b) => (a.last_seen > b.last_seen ? -1 : a.last_seen < b.last_seen ? 1 : 0));\n}\n\nexport { REGISTRY_PATH, REGISTRY_DIR };\n","<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Synthra · Dashboard</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n <link rel=\"stylesheet\" href=\"./style.css\" />\n</head>\n<body>\n\n <!-- ============ Top nav ============ -->\n <header class=\"topnav\">\n <div class=\"brand\">\n <div class=\"brand-mark\"></div>\n <div class=\"brand-name\">Synth<em>ra</em></div>\n <div class=\"brand-eyebrow\">Dashboard</div>\n <div class=\"nav-date\">\n <span class=\"nd-weekday\" id=\"hero-weekday\">—</span>\n <span class=\"nd-day\" id=\"hero-day\">—</span>\n <span class=\"nd-month\" id=\"hero-month\">—</span>\n </div>\n </div>\n <div class=\"top-right\">\n <span class=\"status-pill\">\n <span class=\"dot\" id=\"dot\"></span>\n <span id=\"status\">connecting…</span>\n </span>\n </div>\n <div class=\"topnav-right\">\n <div class=\"nav-active has-tooltip\" data-tooltip=\"The project directory Synthra is currently watching — the most recent `syn .` session on this machine.\">\n <span class=\"na-label\">Active</span>\n <span class=\"na-value\" id=\"active-project\" title=\"—\">—</span>\n </div>\n <span class=\"port-badge\">port <span class=\"mono\" id=\"port-num\">8901</span></span>\n <button class=\"faq-btn has-tooltip\" id=\"faq-btn\" data-tooltip=\"Open the FAQ — explains where every number on this dashboard comes from, how cost is calculated, and what the savings floor actually measures.\" aria-label=\"Open FAQ\">?</button>\n </div>\n </header>\n\n <!-- ============ Main 3-column grid ============ -->\n <main class=\"grid-main\">\n\n <!-- ===== Left ===== -->\n <aside class=\"col-left\">\n <div class=\"card donut-card has-tooltip\" data-tooltip=\"Which Claude models you've been calling, weighted by turn count. Opus = slow and expensive; Sonnet = workhorse; Haiku = cheap and fast. Helps you see where your budget is actually going.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Model usage</div>\n <div class=\"card-meta\">by turns</div>\n </div>\n <div class=\"donut-wrap\">\n <svg viewBox=\"0 0 140 140\" class=\"donut\" id=\"donut-svg\" aria-hidden=\"true\">\n <circle cx=\"70\" cy=\"70\" r=\"52\" class=\"donut-track\"/>\n </svg>\n <div class=\"donut-center\">\n <div class=\"donut-total\" id=\"donut-total\">0</div>\n <div class=\"donut-total-k\">turns</div>\n </div>\n </div>\n <div class=\"donut-legend\" id=\"donut-legend\"></div>\n </div>\n\n <!-- Projects — colored bar chart by turns -->\n <div class=\"card projects-card has-tooltip\" data-tooltip=\"Every project Synthra has tracked on this machine, ranked by how many turns ran there. Each project carries its own color. Click any row to open its full cost & token breakdown.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Projects</div>\n <div class=\"card-meta\">by turns</div>\n </div>\n <div class=\"proj-chart\" id=\"proj-chart\"></div>\n </div>\n </aside>\n\n <!-- ===== Center ===== -->\n <div class=\"col-center\">\n\n <!-- Metric strip — divider-separated, no individual card chrome -->\n <div class=\"metric-strip\">\n <div class=\"metric-item has-tooltip\" data-tooltip=\"Total back-and-forth exchanges with Claude across all projects. One turn = you send a message, Claude responds. Counted from the Stop hook against transcript JSONL files.\">\n <div class=\"m-label\">Turns</div>\n <div class=\"m-value\" id=\"m-turns\">0</div>\n </div>\n <div class=\"metric-item has-tooltip\" data-tooltip=\"↓ Input — new, uncached tokens you sent to Claude. Usually small (a few hundred per turn) because most of the conversation history comes from prompt cache.\">\n <div class=\"m-label\">↓ Input</div>\n <div class=\"m-value\" id=\"m-input\">0</div>\n </div>\n <div class=\"metric-item has-tooltip\" data-tooltip=\"↑ Output — tokens Claude generated in its responses. Most expensive line item per turn (~5× input rate on Opus). High output usually means long code edits.\">\n <div class=\"m-label\">↑ Output</div>\n <div class=\"m-value\" id=\"m-output\">0</div>\n </div>\n <div class=\"metric-item has-tooltip\" data-tooltip=\"⟲ Cache read — tokens reused from the prompt cache (system prompt, conversation history, Synthra's pre-packed context). Cheap, around 10% of the input rate. The bulk of every long session.\">\n <div class=\"m-label\">⟲ Cache R</div>\n <div class=\"m-value\" id=\"m-cache-r\">0</div>\n </div>\n <div class=\"metric-item has-tooltip\" data-tooltip=\"+ Cache write — tokens newly added to the prompt cache so future turns can read them cheaply. Premium-priced (~125% of input rate) but pays back across the session.\">\n <div class=\"m-label\">+ Cache W</div>\n <div class=\"m-value\" id=\"m-cache-w\">0</div>\n </div>\n </div>\n\n <!-- Savings hero -->\n <div class=\"card savings has-tooltip\" data-tooltip=\"What Synthra has saved you, as a deliberately conservative floor estimate. Each time the gate blocks an exploratory Grep/Glob, we credit 500 tokens × $3 per million-token input rate. Real savings are usually higher because the formula ignores cache thrash and follow-up Reads that the block also prevents. The audit line below shows the exact math live.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Synthra savings <span class=\"src-badge estimated\">floor</span></div>\n <div class=\"card-meta\" id=\"savings-pct\">— off</div>\n </div>\n <div class=\"savings-body\">\n <div class=\"savings-figure\">\n <div class=\"savings-money\" id=\"savings-money\">$0.00</div>\n <div class=\"savings-tokens\"><span id=\"savings-tokens\">0</span> tokens avoided</div>\n </div>\n <div class=\"savings-bar\">\n <div class=\"savings-actual\" id=\"savings-actual-bar\" style=\"width:100%\"></div>\n <div class=\"savings-saved\" id=\"savings-saved-bar\" style=\"width:0%\"></div>\n </div>\n <div class=\"savings-legend\">\n <div class=\"sl-row\"><span class=\"sl-dot actual\"></span>You paid <b id=\"savings-actual-amt\">$0.00</b></div>\n <div class=\"sl-row\"><span class=\"sl-dot saved\"></span>Baseline <b id=\"savings-baseline-amt\">$0.00</b></div>\n </div>\n </div>\n <div class=\"savings-audit\">\n <span class=\"audit-formula\">\n <b id=\"audit-blocks\">0</b> blocks × <b>500</b> tokens × <b>$3</b> / M input rate = <b id=\"audit-result\" class=\"audit-result\">$0.00</b>\n </span>\n </div>\n </div>\n\n <!-- Recent turns -->\n <div class=\"card turns-card has-tooltip\" data-tooltip=\"Every conversational turn Synthra has observed across all your projects, newest first. Each row shows when, which project, which model, and how the cost broke down between fresh input, generated output, and cache.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Recent turns</div>\n <div class=\"card-meta\" id=\"turns-count\">— shown</div>\n </div>\n <div class=\"turns-scroll\">\n <table class=\"turns-table\">\n <thead>\n <tr>\n <th class=\"has-tooltip\" data-tooltip=\"When this turn happened, in your local time. Turns from today show as a time; older turns show the date.\">Time</th>\n <th class=\"has-tooltip\" data-tooltip=\"Which project directory the turn ran in. Color-matched to the Projects chart on the left.\">Project</th>\n <th class=\"has-tooltip\" data-tooltip=\"The Claude model used for this turn — Opus, Sonnet, or Haiku — shown in the color-coded pill.\">Model</th>\n <th class=\"num has-tooltip\" data-tooltip=\"↓ Input — raw, uncached tokens sent to Claude this turn. Usually tiny (a few hundred) because conversation history is served from the prompt cache, not re-sent fresh.\">In</th>\n <th class=\"num has-tooltip\" data-tooltip=\"↑ Output — tokens Claude generated in its response. The most expensive line item per turn (~5× the input rate on Opus). Big numbers usually mean long code edits.\">Out</th>\n <th class=\"num has-tooltip\" data-tooltip=\"Cache Read / Cache Write. R = tokens reused from earlier turns (cheap, ~10% of the input rate). W = tokens newly written to the cache so future turns can read them (premium, ~125% of the input rate).\">Cache R/W</th>\n <th class=\"num has-tooltip\" data-tooltip=\"Estimated USD for this turn: input + output + cache read + cache write, each multiplied by the published Anthropic per-model rate.\">Cost</th>\n </tr>\n </thead>\n <tbody id=\"turns-body\"></tbody>\n </table>\n <p class=\"empty hidden\" id=\"turns-empty\">No turns logged yet. Run <code>syn .</code> in any project and chat with Claude.</p>\n </div>\n <div class=\"turns-pager hidden\" id=\"turns-pager\">\n <button type=\"button\" id=\"turns-prev\" aria-label=\"Previous page\">‹ Prev</button>\n <span class=\"mono\" id=\"turns-page-label\">page 1 of 1</span>\n <button type=\"button\" id=\"turns-next\" aria-label=\"Next page\">Next ›</button>\n </div>\n </div>\n\n </div>\n\n <!-- ===== Right ===== -->\n <aside class=\"col-right\">\n\n <!-- Cost hero -->\n <div class=\"card cost-hero has-tooltip\" data-tooltip=\"Your all-time Claude spend across every project Synthra has tracked on this machine. Token counts come from Claude's transcript JSONL files; dollar amounts are computed by multiplying those counts by Anthropic's published per-model rates. See the FAQ for full rate tables.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">Total spend · <em>all time</em></div>\n </div>\n <div class=\"big-money\" id=\"big-cost\">$0.<em>00</em></div>\n <div class=\"cost-sub\">\n <div class=\"cs-row\">\n <span class=\"cs-k\">Tokens (in+out)</span>\n <span class=\"cs-v\" id=\"cs-tokens\">0</span>\n </div>\n <div class=\"cs-row\">\n <span class=\"cs-k\">Avg / turn</span>\n <span class=\"cs-v\" id=\"cs-avg\">$0.00</span>\n </div>\n </div>\n </div>\n\n <!-- The Moat -->\n <div class=\"card moat has-tooltip\" data-tooltip=\"Synthra's PreToolUse hook intercepts. Each block = Synthra recognized the graph already had high-confidence context for the query, so it stopped Claude from running an exploratory Grep or Glob. The list below shows the latest decisions across all projects.\">\n <div class=\"card-head\">\n <div class=\"card-eyebrow\">The <em>Moat</em></div>\n <div class=\"card-meta\">PreToolUse</div>\n </div>\n <div class=\"moat-value\"><span id=\"blocks\">0</span> <em>blocks</em></div>\n <div class=\"gate-mini\" id=\"gate-mini\"></div>\n </div>\n\n </aside>\n </main>\n\n <!-- ============ Project dialog ============ -->\n <div class=\"dialog-backdrop hidden\" id=\"dialog-backdrop\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"dialog\">\n <button class=\"dialog-close\" id=\"dialog-close\" aria-label=\"Close\">×</button>\n <div class=\"dialog-eyebrow\">Project · <em>details</em></div>\n <div class=\"dialog-name\" id=\"d-name\">—</div>\n <div class=\"dialog-path\" id=\"d-path\">—</div>\n <div class=\"dialog-grid\">\n <div class=\"dg-cell\">\n <div class=\"dg-k\">Total cost</div>\n <div class=\"dg-v money\" id=\"d-cost\">$0.00</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">Turns</div>\n <div class=\"dg-v\" id=\"d-turns\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">↓ Raw input</div>\n <div class=\"dg-v\" id=\"d-input\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">↑ Output</div>\n <div class=\"dg-v\" id=\"d-output\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">⟲ Cache read</div>\n <div class=\"dg-v\" id=\"d-cache-r\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">+ Cache write</div>\n <div class=\"dg-v\" id=\"d-cache-w\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">Moat blocks</div>\n <div class=\"dg-v\" id=\"d-blocks\">0</div>\n </div>\n <div class=\"dg-cell\">\n <div class=\"dg-k\">Last active</div>\n <div class=\"dg-v dg-v-sm\" id=\"d-last\">—</div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ============ FAQ dialog ============ -->\n <div class=\"dialog-backdrop hidden\" id=\"faq-backdrop\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Dashboard FAQ\">\n <div class=\"dialog dialog-faq\">\n <button class=\"dialog-close\" id=\"faq-close\" aria-label=\"Close\">×</button>\n <div class=\"dialog-eyebrow\">FAQ · <em>where every number comes from</em></div>\n <div class=\"dialog-name\">Understanding your dashboard</div>\n <div class=\"dialog-path\">Synthra reports what Claude actually used — read from transcripts, not estimated.</div>\n\n <div class=\"faq-content\">\n\n <details open>\n <summary>Where does Synthra get these numbers from?</summary>\n <div class=\"faq-body\">\n <p>Synthra <strong>does not estimate</strong> token counts — it reads them directly from Claude's own log files. Every number on this dashboard is traceable:</p>\n <table>\n <tr><td>Turns, ↓ In, ↑ Out, Cache R/W</td><td>Parsed from Claude's transcript JSONLs at <code>~/.claude/projects/<encoded-cwd>/*.jsonl</code></td></tr>\n <tr><td>Model used per turn</td><td>From each turn's <code>model</code> field in the same transcripts</td></tr>\n <tr><td>Moat blocks + gate decisions</td><td>From <code>.synthra-graph/gate_log.jsonl</code> inside each project</td></tr>\n <tr><td>Active project, project list</td><td>From <code>~/.synthra/projects.json</code> (built up as you run <code>syn .</code>)</td></tr>\n <tr><td>Total spend (USD)</td><td>Above token counts × Anthropic's published per-model rates — see \"How is cost calculated?\" below</td></tr>\n <tr><td>Synthra savings (USD)</td><td><strong>Estimated</strong> — see \"About the Savings (floor) card\" below</td></tr>\n </table>\n <p>The cost-calculation logic lives in <code>src/shared/pricing.ts</code>; the aggregation logic in <code>src/dashboard/delta.ts</code>. Both are linked at the bottom.</p>\n </div>\n </details>\n\n <details>\n <summary>What do the columns in Recent Turns mean?</summary>\n <div class=\"faq-body\">\n <table>\n <tr><td><code>Time</code></td><td>When this turn happened (local time)</td></tr>\n <tr><td><code>Project</code></td><td>Which directory the turn ran in</td></tr>\n <tr><td><code>Model</code></td><td>Claude model used — Opus, Sonnet, or Haiku (color-coded in the model pill)</td></tr>\n <tr><td><code>In</code></td><td>Raw input tokens — brand-new content sent to Claude this turn, not cached</td></tr>\n <tr><td><code>Out</code></td><td>Tokens Claude generated in its response</td></tr>\n <tr><td><code>Cache R/W</code></td><td>Cache <strong>R</strong>ead (reused from prior turns) / Cache <strong>W</strong>rite (newly cached for future turns)</td></tr>\n <tr><td><code>Cost</code></td><td>Per-turn USD estimate using Anthropic's published rates</td></tr>\n </table>\n <p><strong>Why is Raw Input often tiny (e.g. 6 tokens)?</strong> Because Claude Code aggressively caches the system prompt, CLAUDE.md, tool definitions, and conversation history. On each turn, only your brand-new message is \"raw input\" — everything else is a cheap cache read. This is normal and saves significant money.</p>\n </div>\n </details>\n\n <details>\n <summary>How is cost calculated?</summary>\n <div class=\"faq-body\">\n <p>Each token type has a different per-million-token rate. Synthra uses these rates (defined in <code>src/shared/pricing.ts</code>):</p>\n <table>\n <thead><tr><td><strong>Token type</strong></td><td><strong>Haiku 4.5</strong></td><td><strong>Sonnet 4.x</strong></td><td><strong>Opus 4.x</strong></td></tr></thead>\n <tr><td>Raw Input</td><td>$1.00/M</td><td>$3.00/M</td><td>$15.00/M</td></tr>\n <tr><td>Cache Write</td><td>$1.25/M</td><td>$3.75/M</td><td>$18.75/M</td></tr>\n <tr><td>Cache Read</td><td>$0.10/M</td><td>$0.30/M</td><td>$1.50/M</td></tr>\n <tr><td>Output</td><td>$5.00/M</td><td>$15.00/M</td><td>$75.00/M</td></tr>\n </table>\n <p><strong>Cost</strong> = (Input × input rate) + (Output × output rate) + (Cache Read × read rate) + (Cache Write × write rate)</p>\n <p>Cache reads are <strong>10× cheaper</strong> than raw input. Cache writes are <strong>25% more expensive</strong> than raw input. So Claude Code's caching strategy pays for itself quickly across a session.</p>\n <div class=\"warning\"><span class=\"icon\">⚠</span>These are <strong>Anthropic API rates</strong>, not your plan billing. If you're on Claude Pro, Team, Max, or Enterprise, your actual billing is different — the costs shown are estimates of <em>API-equivalent</em> usage, useful for comparing sessions against each other. See <a href=\"https://www.anthropic.com/pricing\" target=\"_blank\" rel=\"noopener noreferrer\">anthropic.com/pricing</a> for the source of these rates.</div>\n </div>\n </details>\n\n <details>\n <summary>What is the total context size per turn?</summary>\n <div class=\"faq-body\">\n <p><code>Total context = Raw Input + Cache Read + Cache Write</code></p>\n <p>Example: if Raw Input is 6, Cache Read is 60K, and Cache Write is 13K, your turn used ~73K tokens of context — but 99.99% was efficiently cached, so you only paid the cache-read rate on most of it. The Recent Turns table lets you scan this row by row.</p>\n <p>Anthropic's prompt-caching mechanics are documented at <a href=\"https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching\" target=\"_blank\" rel=\"noopener noreferrer\">docs.anthropic.com/.../prompt-caching</a>.</p>\n </div>\n </details>\n\n <details>\n <summary>About the \"Savings (floor)\" card</summary>\n <div class=\"faq-body\">\n <p>This is the only number on the dashboard that's <strong>estimated</strong> rather than measured. Here's the math:</p>\n <p class=\"formula-box\">savings = blocks × 500 tokens × $3 per million input rate</p>\n <p>Each time Synthra's PreToolUse hook blocks a <code>Grep</code> or <code>Glob</code> call (because the graph already has high-confidence context), we credit a deliberately conservative 500 tokens at the Sonnet input rate.</p>\n <p>It under-counts on purpose because the formula ignores:</p>\n <ul>\n <li><strong>Cache thrash</strong> — the blocked tool result would have been written to the cache at ~125% of the input rate, which we don't count</li>\n <li><strong>Cascading reads</strong> — Claude usually follows a Grep with several <code>Read</code> calls, which the block also prevents but we don't credit</li>\n <li><strong>Bigger codebases</strong> — actual Grep results often exceed 500 tokens by 3–6× in a real repo</li>\n </ul>\n <p>Real savings are typically <strong>2–5× the floor</strong>. The audit row on the Savings card shows the formula live so you can verify the math.</p>\n </div>\n </details>\n\n <details>\n <summary>What is \"The Moat\"?</summary>\n <div class=\"faq-body\">\n <p>\"The Moat\" is Synthra's <strong>PreToolUse hook</strong>. Every time Claude Code is about to run a <code>Grep</code> or <code>Glob</code>, the hook calls Synthra's local server first and asks: \"do you already have high-confidence context for this query?\"</p>\n <p>If yes, Synthra returns <code>{\"decision\":\"block\"}</code> — Claude can't run the tool. Claude then has to use Synthra's graph tools (<code>graph_continue</code>, <code>graph_read</code>) instead, which return the answer without burning tokens on exploration. This is deterministic enforcement — not prose policy. Claude literally can't disobey.</p>\n <p>The Moat card shows total blocks; the inline list below shows the most recent gate decisions (block vs allow) with the originating query.</p>\n <p>Every decision is logged to <code>.synthra-graph/gate_log.jsonl</code> for the project.</p>\n </div>\n </details>\n\n <details>\n <summary>How does Synthra build the codebase graph?</summary>\n <div class=\"faq-body\">\n <p>When you run <code>syn .</code> in a project, Synthra walks the file tree (respecting <code>.gitignore</code> + <code>.synthraignore</code>) and parses each file with <strong>tree-sitter</strong> WebAssembly grammars. Currently 14 languages are supported:</p>\n <p>TypeScript, JavaScript, JSX/TSX, Python, Svelte, Vue, Go, Rust, Java, Kotlin, PHP, Ruby, C/C++, C#, and Dart.</p>\n <p>For each file we extract: function and class definitions, exports, imports, and test-to-source links. The output is a structured graph stored at <code>.synthra-graph/info_graph.json</code> with a symbol index at <code>.synthra-graph/symbol_index.json</code>.</p>\n <p>The graph is what makes pre-injection and The Moat work — both query it before Claude ever has to Grep.</p>\n </div>\n </details>\n\n <details>\n <summary>Where does Synthra store data on disk?</summary>\n <div class=\"faq-body\">\n <p>Synthra uses two folders per project, intentionally separated:</p>\n <table>\n <tr><td><code>.synthra-graph/</code></td><td><strong>Gitignored</strong>. Heavy generated state — the graph, symbol index, token + gate logs, session info. Rebuilt by <code>syn scan</code>.</td></tr>\n <tr><td><code>.synthra/</code></td><td><strong>Git-tracked.</strong> Decisions, context notes, branch-scoped memory — the part teammates inherit when they clone the repo.</td></tr>\n </table>\n <p>Plus one global file at <code>~/.synthra/projects.json</code> that tracks every project on this machine.</p>\n </div>\n </details>\n\n <details>\n <summary>How does branch-aware memory work?</summary>\n <div class=\"faq-body\">\n <p>Inside <code>.synthra/</code>, context is partitioned by git branch:</p>\n <ul>\n <li><code>.synthra/context-store.json</code> — decisions on the default branch</li>\n <li><code>.synthra/CONTEXT.md</code> — narrative notes on the default branch</li>\n <li><code>.synthra/branches/<sanitized-name>/</code> — overrides on feature branches</li>\n </ul>\n <p>When you switch branches, Synthra's git-watcher (using <code>fs.watch</code> on <code>.git/HEAD</code>) detects the change and reloads the right context. Decisions scoped to a feature branch don't leak back to <code>main</code> until merge.</p>\n </div>\n </details>\n\n <details>\n <summary>How does Synthra actually reduce my Claude bill?</summary>\n <div class=\"faq-body\">\n <p>Three mechanisms, in order of impact:</p>\n <ul>\n <li><strong>Pre-injection</strong> — at session start, Synthra packs ~4K tokens of graph context (function signatures, top inline bodies, file relationships) into Claude's prompt. Claude doesn't have to Grep / Read to discover what's in the codebase — it already knows.</li>\n <li><strong>The Moat</strong> — the PreToolUse hook deterministically blocks exploratory Grep/Glob when the graph already has high-confidence context. Counts on the Moat card.</li>\n <li><strong>Branch-aware memory</strong> — decisions and CONTEXT notes persist in <code>.synthra/</code>, so Claude doesn't have to be re-told what was decided last session.</li>\n </ul>\n <p>The dashboard shows real token counts from Claude's own logs so you can see the effect over time, not just take Synthra's word for it.</p>\n </div>\n </details>\n\n <details>\n <summary>Why do long conversations get expensive?</summary>\n <div class=\"faq-body\">\n <p>Each turn re-sends the entire conversation history as input. Even cached, the cumulative input grows roughly <strong>quadratically</strong>:</p>\n <table>\n <thead><tr><td><strong>Turns</strong></td><td><strong>Per-turn input</strong></td><td><strong>Cumulative input</strong></td></tr></thead>\n <tr><td>10</td><td>~2K (mostly cached)</td><td>~110K</td></tr>\n <tr><td>30</td><td>~2K</td><td>~930K</td></tr>\n <tr><td>50</td><td>~2K</td><td>~2.55M</td></tr>\n </table>\n <p>Prompt caching helps a lot (cache reads are 10× cheaper than fresh input), but context still grows. This is why Synthra's pre-injection matters: starting with the answer already in context means you reach a useful state in fewer turns.</p>\n <p><strong>Tip:</strong> Use <code>/compact</code> in Claude Code or start fresh sessions when a thread feels stale.</p>\n </div>\n </details>\n\n <details>\n <summary>Sources & references</summary>\n <div class=\"faq-body\">\n <p>Synthra is open source. Every number on this dashboard can be cross-checked:</p>\n <ul class=\"link-list\">\n <li><a href=\"https://github.com/jefuriiij/synthra\" target=\"_blank\" rel=\"noopener noreferrer\">github.com/jefuriiij/synthra</a> — source code, issues, roadmap</li>\n <li><a href=\"https://www.npmjs.com/package/@jefuriiij/synthra\" target=\"_blank\" rel=\"noopener noreferrer\">npm: @jefuriiij/synthra</a> — release history, install instructions</li>\n <li><a href=\"https://www.anthropic.com/pricing\" target=\"_blank\" rel=\"noopener noreferrer\">anthropic.com/pricing</a> — official rate table Synthra uses</li>\n <li><a href=\"https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching\" target=\"_blank\" rel=\"noopener noreferrer\">Anthropic docs · prompt caching</a> — explains cache read/write behavior</li>\n <li><code>src/shared/pricing.ts</code> — the file in this repo holding the rate table</li>\n <li><code>src/dashboard/delta.ts</code> — where dashboard aggregates are computed</li>\n <li><code>src/server/routes/gate.ts</code> — the Moat implementation</li>\n </ul>\n </div>\n </details>\n\n </div>\n </div>\n </div>\n\n <!-- ============ Footer ============ -->\n <footer class=\"foot\">\n <div>Synth<em>ra</em> · v__SYN_VERSION__</div>\n <div>Cost figures approximate · @jefuriiij</div>\n </footer>\n\n <script>\n const $ = (sel) => document.querySelector(sel);\n const SAVED_RATE_PER_M = 3.00; // USD per million tokens — conservative input rate\n\n // ----- model classification -----\n function modelFamily(model) {\n if (!model) return 'unknown';\n const m = model.toLowerCase();\n if (m === '<synthetic>') return 'unknown';\n if (m.includes('opus')) return 'opus';\n if (m.includes('sonnet')) return 'sonnet';\n if (m.includes('haiku')) return 'haiku';\n return 'unknown';\n }\n function modelLabel(model) {\n if (!model || model === '<synthetic>') return 'synthetic';\n return model.replace(/^claude-/, '');\n }\n\n // ----- formatting -----\n function fmt(n) {\n if (typeof n !== 'number' || !Number.isFinite(n)) return '0';\n if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + '<em>M</em>';\n if (n >= 1_000) return (n / 1_000).toFixed(1) + '<em>k</em>';\n return n.toLocaleString();\n }\n function fmtPlain(n) {\n if (typeof n !== 'number' || !Number.isFinite(n)) return '0';\n if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';\n if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';\n return n.toLocaleString();\n }\n function fmtCostBig(usd) {\n if (typeof usd !== 'number' || !Number.isFinite(usd)) usd = 0;\n let s;\n if (usd >= 1) s = usd.toFixed(2);\n else if (usd >= 0.01) s = usd.toFixed(3);\n else s = usd.toFixed(4);\n const dot = s.indexOf('.');\n if (dot === -1) return '$' + s;\n return '$' + s.slice(0, dot) + '.<em>' + s.slice(dot + 1) + '</em>';\n }\n function fmtCostFlat(usd) {\n if (typeof usd !== 'number' || !Number.isFinite(usd)) usd = 0;\n if (usd >= 1) return '$' + usd.toFixed(2);\n if (usd >= 0.01) return '$' + usd.toFixed(3);\n return '$' + usd.toFixed(4);\n }\n function fmtTs(iso) {\n try {\n const d = new Date(iso);\n const today = new Date();\n const isToday = d.toDateString() === today.toDateString();\n if (isToday) return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n return d.toLocaleDateString([], { month: 'short', day: 'numeric' });\n } catch { return iso; }\n }\n\n let lastData = null;\n let turnsPage = 1;\n const TURNS_PER_PAGE = 25;\n\n // ----- date in hero -----\n function setHeroDate() {\n const now = new Date();\n $('#hero-day').textContent = now.getDate();\n $('#hero-weekday').textContent = now.toLocaleDateString([], { weekday: 'short' });\n $('#hero-month').textContent = now.toLocaleDateString([], { month: 'long' });\n }\n\n // ----- renderers -----\n function renderSession(turns) {\n const agg = turns.reduce((a, t) => {\n a.turns += 1;\n a.input += t.input || 0;\n a.output += t.output || 0;\n a.cacheR += t.cache_read || 0;\n a.cacheW += t.cache_create || 0;\n return a;\n }, { turns: 0, input: 0, output: 0, cacheR: 0, cacheW: 0 });\n\n $('#m-turns').innerHTML = fmt(agg.turns);\n $('#m-input').innerHTML = fmt(agg.input);\n $('#m-output').innerHTML = fmt(agg.output);\n $('#m-cache-r').innerHTML = fmt(agg.cacheR);\n $('#m-cache-w').innerHTML = fmt(agg.cacheW);\n }\n\n function renderSavings(g) {\n const tokensSaved = g.estimated_tokens_saved || 0;\n const blocks = g.blocked_count || 0;\n const savedUsd = tokensSaved * SAVED_RATE_PER_M / 1_000_000;\n const actualUsd = g.estimated_cost_usd || 0;\n const baselineUsd = actualUsd + savedUsd;\n const savedPct = baselineUsd > 0 ? (savedUsd / baselineUsd) * 100 : 0;\n const actualPct = baselineUsd > 0 ? (actualUsd / baselineUsd) * 100 : 100;\n\n $('#savings-money').textContent = fmtCostFlat(savedUsd);\n $('#savings-pct').textContent = savedPct.toFixed(1) + '% off';\n $('#savings-tokens').textContent = fmtPlain(tokensSaved);\n $('#savings-actual-bar').style.width = actualPct.toFixed(2) + '%';\n $('#savings-saved-bar').style.width = savedPct.toFixed(2) + '%';\n $('#savings-actual-amt').textContent = fmtCostFlat(actualUsd);\n $('#savings-baseline-amt').textContent = fmtCostFlat(baselineUsd);\n\n // Audit row — live formula\n $('#audit-blocks').textContent = blocks.toLocaleString();\n $('#audit-result').textContent = fmtCostFlat(savedUsd);\n }\n\n function renderCostHero(g) {\n $('#big-cost').innerHTML = fmtCostBig(g.estimated_cost_usd);\n const totalTokens = (g.total_input_tokens || 0) + (g.total_output_tokens || 0);\n $('#cs-tokens').textContent = fmtPlain(totalTokens);\n const avg = g.total_turns > 0 ? g.estimated_cost_usd / g.total_turns : 0;\n $('#cs-avg').textContent = fmtCostFlat(avg);\n }\n\n function renderMoat(g) {\n $('#blocks').textContent = fmtPlain(g.blocked_count);\n }\n\n function renderTurns(turns) {\n const tbody = $('#turns-body');\n const empty = $('#turns-empty');\n const pager = $('#turns-pager');\n tbody.innerHTML = '';\n if (!turns.length) {\n empty.classList.remove('hidden');\n pager.classList.add('hidden');\n $('#turns-count').textContent = '0 shown';\n return;\n }\n empty.classList.add('hidden');\n\n const totalPages = Math.max(1, Math.ceil(turns.length / TURNS_PER_PAGE));\n // Clamp in case the list shrank (dedup / data churn) since last render.\n if (turnsPage > totalPages) turnsPage = totalPages;\n if (turnsPage < 1) turnsPage = 1;\n const start = (turnsPage - 1) * TURNS_PER_PAGE;\n const pageItems = turns.slice(start, start + TURNS_PER_PAGE);\n\n $('#turns-count').textContent =\n 'showing ' + (start + 1) + '–' + (start + pageItems.length) + ' of ' + turns.length;\n\n const frag = document.createDocumentFragment();\n for (const t of pageItems) {\n const family = modelFamily(t.model);\n const tr = document.createElement('tr');\n tr.innerHTML =\n '<td class=\"ts\">' + fmtTs(t.ts || t.written_at) + '</td>' +\n '<td class=\"proj\">' + (t.project_name || '—') + '</td>' +\n '<td><span class=\"model-pill ' + family + '\"><span class=\"sq\"></span>' + modelLabel(t.model) + '</span></td>' +\n '<td class=\"num\">' + fmtPlain(t.input || 0) + '</td>' +\n '<td class=\"num\">' + fmtPlain(t.output || 0) + '</td>' +\n '<td class=\"num\">' + fmtPlain(t.cache_read || 0) + ' / ' + fmtPlain(t.cache_create || 0) + '</td>' +\n '<td class=\"num cost\">' + fmtCostFlat(t.cost_usd || 0) + '</td>';\n frag.appendChild(tr);\n }\n tbody.appendChild(frag);\n\n if (totalPages <= 1) {\n pager.classList.add('hidden');\n } else {\n pager.classList.remove('hidden');\n $('#turns-page-label').textContent = 'page ' + turnsPage + ' of ' + totalPages;\n $('#turns-prev').disabled = turnsPage <= 1;\n $('#turns-next').disabled = turnsPage >= totalPages;\n }\n }\n\n function gotoTurnsPage(delta) {\n turnsPage += delta;\n renderTurns((lastData && lastData.recent_turns) || []);\n }\n\n function renderGateMini(gates) {\n const el = $('#gate-mini');\n el.innerHTML = '';\n if (!gates.length) {\n el.innerHTML = '<div class=\"empty\">No gate decisions yet.</div>';\n return;\n }\n const frag = document.createDocumentFragment();\n for (const g of gates.slice(0, 50)) {\n const row = document.createElement('div');\n row.className = 'gate-row';\n const cls = g.decision === 'block' ? 'block' : 'allow';\n row.innerHTML =\n '<span class=\"g-ts\">' + fmtTs(g.ts) + '</span>' +\n '<span class=\"g-decision ' + cls + '\">' + (g.decision || '—').toUpperCase() + '</span>' +\n '<span class=\"g-q\">' + (g.query || g.tool || '—') + '</span>';\n frag.appendChild(row);\n }\n el.appendChild(frag);\n }\n\n // ----- Project colors (stable per name) -----\n const PROJECT_COLORS = [\n 'oklch(78% 0.14 220)', // cyan\n 'oklch(75% 0.14 155)', // green\n 'oklch(78% 0.13 75)', // amber\n 'oklch(72% 0.14 285)', // violet\n 'oklch(72% 0.14 20)', // rose\n 'oklch(74% 0.13 195)', // teal\n 'oklch(80% 0.12 250)', // periwinkle\n 'oklch(76% 0.13 330)', // magenta\n ];\n function projColor(name) {\n let h = 0;\n for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;\n return PROJECT_COLORS[h % PROJECT_COLORS.length];\n }\n\n function renderProjects(projects) {\n const el = $('#proj-chart');\n el.innerHTML = '';\n if (!projects.length) {\n el.innerHTML = '<div class=\"empty\">No projects yet.</div>';\n return;\n }\n const ranked = [...projects].sort((a, b) => (b.total_turns || 0) - (a.total_turns || 0));\n const max = Math.max(1, ...ranked.map((p) => p.total_turns || 0));\n const frag = document.createDocumentFragment();\n for (const p of ranked) {\n const turns = p.total_turns || 0;\n const pct = Math.max(4, Math.round((turns / max) * 100));\n const row = document.createElement('button');\n row.className = 'proj-row';\n row.type = 'button';\n row.style.setProperty('--pc', projColor(p.name));\n row.innerHTML =\n '<span class=\"pr-top\">' +\n '<span class=\"pr-dot\"></span>' +\n '<span class=\"pr-name\" title=\"' + (p.path || p.name) + '\">' + p.name + '</span>' +\n '<span class=\"pr-turns\">' + fmtPlain(turns) + '</span>' +\n '<span class=\"pr-arrow\">›</span>' +\n '</span>' +\n '<span class=\"pr-bar\"><span class=\"pr-fill\" style=\"width:' + pct + '%\"></span></span>';\n row.addEventListener('click', () => openProjectDialog(p.name));\n frag.appendChild(row);\n }\n el.appendChild(frag);\n }\n\n // ----- Project dialog -----\n function lastActiveFor(name) {\n const turns = lastData?.recent_turns || [];\n for (const t of turns) {\n if (t.project_name === name) {\n const ts = t.ts || t.written_at;\n if (!ts) return '—';\n try {\n return new Date(ts).toLocaleString([], {\n year: 'numeric', month: 'short', day: 'numeric',\n hour: '2-digit', minute: '2-digit',\n });\n } catch { return ts; }\n }\n }\n return '—';\n }\n\n function openProjectDialog(name) {\n const p = (lastData?.projects || []).find((x) => x.name === name);\n if (!p) return;\n $('#d-name').textContent = p.name;\n $('#d-name').style.setProperty('--pc', projColor(p.name));\n $('#d-name').classList.add('has-accent');\n $('#d-path').textContent = p.path || '';\n $('#d-cost').textContent = fmtCostFlat(p.estimated_cost_usd);\n $('#d-turns').textContent = fmtPlain(p.total_turns);\n $('#d-input').textContent = fmtPlain(p.total_input_tokens);\n $('#d-output').textContent = fmtPlain(p.total_output_tokens);\n $('#d-cache-r').textContent = fmtPlain(p.total_cache_read);\n $('#d-cache-w').textContent = fmtPlain(p.total_cache_create);\n $('#d-blocks').textContent = fmtPlain(p.blocked_count);\n $('#d-last').textContent = lastActiveFor(p.name);\n $('#dialog-backdrop').classList.remove('hidden');\n }\n\n function closeProjectDialog() {\n $('#dialog-backdrop').classList.add('hidden');\n }\n\n $('#dialog-close').addEventListener('click', closeProjectDialog);\n $('#dialog-backdrop').addEventListener('click', (e) => {\n if (e.target.id === 'dialog-backdrop') closeProjectDialog();\n });\n\n // ----- FAQ dialog -----\n const faqBackdrop = $('#faq-backdrop');\n function openFaq() { faqBackdrop.classList.remove('hidden'); }\n function closeFaq() { faqBackdrop.classList.add('hidden'); }\n $('#faq-btn').addEventListener('click', openFaq);\n $('#faq-close').addEventListener('click', closeFaq);\n faqBackdrop.addEventListener('click', (e) => {\n if (e.target.id === 'faq-backdrop') closeFaq();\n });\n\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') { closeProjectDialog(); closeFaq(); }\n });\n\n $('#turns-prev').addEventListener('click', () => gotoTurnsPage(-1));\n $('#turns-next').addEventListener('click', () => gotoTurnsPage(1));\n\n // Reflect the actual port the dashboard is served on\n const portEl = $('#port-num');\n if (portEl && window.location.port) portEl.textContent = window.location.port;\n\n // ----- Donut chart (all-time model usage by turn count) -----\n // Takes a { model-string -> count } map summed across ALL projects, so it\n // reflects true all-time usage — independent of the recent-turns cap.\n function renderDonut(modelCounts) {\n const counts = { opus: 0, sonnet: 0, haiku: 0, unknown: 0 };\n for (const [model, n] of Object.entries(modelCounts || {})) counts[modelFamily(model)] += n;\n const total = counts.opus + counts.sonnet + counts.haiku + counts.unknown;\n\n const segs = [\n { fam: 'opus', label: 'Opus', n: counts.opus, color: 'var(--c-opus)' },\n { fam: 'sonnet', label: 'Sonnet', n: counts.sonnet, color: 'var(--c-sonnet)' },\n { fam: 'haiku', label: 'Haiku', n: counts.haiku, color: 'var(--c-haiku)' },\n { fam: 'unknown', label: 'Other', n: counts.unknown, color: 'var(--c-unknown)' },\n ].filter((s) => s.n > 0);\n\n const svg = $('#donut-svg');\n svg.querySelectorAll('.donut-seg').forEach((el) => el.remove());\n\n const C = 2 * Math.PI * 52; // ≈ 326.7\n let offset = 0;\n const ns = 'http://www.w3.org/2000/svg';\n if (total === 0) {\n // pleasant empty state — leave just the track\n } else {\n for (const s of segs) {\n const arc = (s.n / total) * C;\n const c = document.createElementNS(ns, 'circle');\n c.setAttribute('cx', '70');\n c.setAttribute('cy', '70');\n c.setAttribute('r', '52');\n c.setAttribute('fill', 'none');\n c.setAttribute('stroke', s.color);\n c.setAttribute('stroke-width', '14');\n c.setAttribute('stroke-dasharray', arc + ' ' + C);\n c.setAttribute('stroke-dashoffset', String(-offset));\n c.setAttribute('transform', 'rotate(-90 70 70)');\n c.setAttribute('stroke-linecap', segs.length === 1 ? 'round' : 'butt');\n c.classList.add('donut-seg');\n svg.appendChild(c);\n offset += arc;\n }\n }\n\n $('#donut-total').textContent = total;\n\n const legend = $('#donut-legend');\n legend.innerHTML = '';\n const lf = document.createDocumentFragment();\n const display = segs.length ? segs : [\n { fam: 'opus', label: 'Opus', n: 0, color: 'var(--c-opus)' },\n { fam: 'sonnet', label: 'Sonnet', n: 0, color: 'var(--c-sonnet)' },\n { fam: 'haiku', label: 'Haiku', n: 0, color: 'var(--c-haiku)' },\n ];\n for (const s of display) {\n const pct = total > 0 ? Math.round((s.n / total) * 100) : 0;\n const row = document.createElement('div');\n row.className = 'dl-row';\n row.innerHTML =\n '<span class=\"dl-dot\" style=\"background:' + s.color + '\"></span>' +\n '<span class=\"dl-name\">' + s.label + '</span>' +\n '<span class=\"dl-count\">' + s.n + '</span>' +\n '<span class=\"dl-pct\">' + pct + '%</span>';\n lf.appendChild(row);\n }\n legend.appendChild(lf);\n }\n\n // ----- master render -----\n function applyData(data) {\n const turns = data.recent_turns || [];\n const gates = data.recent_gates || [];\n\n renderSession(turns);\n renderSavings(data.global);\n renderCostHero(data.global);\n renderMoat(data.global);\n renderTurns(turns);\n renderGateMini(gates);\n\n // Donut: sum per-project model counts (uncapped, full history) so it\n // shows true all-time usage rather than just the recent-turns window.\n const modelCounts = {};\n for (const p of (data.projects || []))\n for (const [m, n] of Object.entries(p.models || {}))\n modelCounts[m] = (modelCounts[m] || 0) + n;\n renderDonut(modelCounts);\n }\n\n // ----- polling -----\n async function tick() {\n try {\n const res = await fetch('/data');\n if (!res.ok) throw new Error('HTTP ' + res.status);\n const data = await res.json();\n lastData = data;\n { const ap = data.active?.project_root || '—'; const el = $('#active-project'); el.textContent = ap; el.title = ap; }\n renderProjects(data.projects || []);\n applyData(data);\n $('#status').textContent = 'live · ' + new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n $('#dot').classList.add('live'); $('#dot').classList.remove('dead');\n } catch (e) {\n $('#status').textContent = 'offline';\n $('#dot').classList.add('dead'); $('#dot').classList.remove('live');\n }\n }\n\n // ----- Viewport-clamped tooltip -----\n const tooltipEl = document.createElement('div');\n tooltipEl.className = 'global-tooltip';\n document.body.appendChild(tooltipEl);\n let activeTooltipTarget = null;\n\n function positionTooltip(target) {\n const rect = target.getBoundingClientRect();\n const ttRect = tooltipEl.getBoundingClientRect();\n const margin = 12;\n let top = rect.top - ttRect.height - 10;\n let left = rect.left + rect.width / 2 - ttRect.width / 2;\n if (left < margin) left = margin;\n if (left + ttRect.width > window.innerWidth - margin) {\n left = window.innerWidth - ttRect.width - margin;\n }\n if (top < margin) {\n top = rect.bottom + 10;\n }\n if (top + ttRect.height > window.innerHeight - margin) {\n top = window.innerHeight - ttRect.height - margin;\n }\n tooltipEl.style.top = top + 'px';\n tooltipEl.style.left = left + 'px';\n }\n\n function showTooltip(target) {\n const text = target.getAttribute('data-tooltip');\n if (!text) return;\n tooltipEl.textContent = text;\n tooltipEl.classList.add('on');\n positionTooltip(target);\n }\n\n function hideTooltip() {\n tooltipEl.classList.remove('on');\n }\n\n document.addEventListener('mouseover', (e) => {\n const t = (e.target instanceof Element) ? e.target.closest('.has-tooltip') : null;\n if (t !== activeTooltipTarget) {\n activeTooltipTarget = t;\n if (t) showTooltip(t);\n else hideTooltip();\n }\n });\n document.addEventListener('scroll', hideTooltip, true);\n document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideTooltip(); });\n\n setHeroDate();\n tick();\n setInterval(tick, 10000);\n </script>\n</body>\n</html>\n","/* Synthra dashboard · v0.2 · Cool Marine\n Darkened surfaces; brand blue reserved for hero elements only.\n Layout: top nav + hero strip + 3-column main, fits 1280×720. */\n\n:root {\n /* Core palette */\n --ink: #04081A;\n --navy: #0A1530;\n --navy-2: #122549;\n --deep-blue: #1B3A78;\n --blue: #2C5DB8;\n --blue-bright: #5C8FE6;\n --sky: #9BC2EF;\n --mist: #D7E6F7;\n --bone: #F4F7FC;\n\n /* Text */\n --text: #ECF2FB;\n --text-dim: #A9BBD6;\n --text-mute: #6D80A0;\n\n /* Rules / dividers */\n --rule: rgba(155, 194, 239, .14);\n --rule-2: rgba(155, 194, 239, .06);\n --rule-hover: rgba(155, 194, 239, .28);\n\n /* Surfaces (darker than v0.1.2) */\n --surface-1: rgba(18, 37, 73, .14);\n --surface-2: rgba(18, 37, 73, .22);\n --surface-3: rgba(4, 8, 26, .55);\n\n /* Signal accents (OKLCH shared chroma) */\n --signal-cyan: oklch(78% 0.14 220);\n --signal-amber: oklch(78% 0.14 75);\n --signal-rose: oklch(70% 0.14 20);\n --signal-green: oklch(75% 0.14 155);\n --signal-violet: oklch(72% 0.14 285);\n\n /* Model family colors */\n --c-opus: #FF6338;\n --c-sonnet: #FFB938;\n --c-haiku: #7438FF;\n --c-unknown: #12CBF5;\n\n /* Money */\n --money: var(--signal-green);\n\n /* Type */\n --font-sans: \"Geist\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n --font-serif: \"Instrument Serif\", \"Times New Roman\", serif;\n --font-mono: \"Geist Mono\", ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;\n}\n\n/* ============================================================\n Reset + base\n ============================================================ */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml,\nbody {\n margin: 0;\n padding: 0;\n}\n\nhtml,\nbody {\n height: 100vh;\n overflow: hidden;\n}\n\nbody {\n background: var(--ink);\n color: var(--text);\n font-family: var(--font-sans);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n display: grid;\n grid-template-rows: auto 1fr auto;\n position: relative;\n}\n\n/* Layered backdrop — quieter */\nbody::before,\nbody::after {\n content: \"\";\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n}\n\nbody::before {\n background-image: radial-gradient(circle, rgba(155, 194, 239, .06) 1px, transparent 1.2px);\n background-size: 22px 22px;\n}\n\nbody::after {\n background:\n radial-gradient(60% 40% at 50% 105%, rgba(44, 93, 184, .16) 0%, rgba(10, 21, 48, 0) 65%),\n radial-gradient(30% 25% at 50% 0%, rgba(92, 143, 230, .06) 0%, transparent 70%);\n}\n\nbody>* {\n position: relative;\n z-index: 1;\n}\n\nbutton {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: transparent;\n color: inherit;\n}\n\na {\n color: inherit;\n text-decoration: none;\n}\n\n/* ============================================================\n Top nav\n ============================================================ */\n.topnav {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n align-items: center;\n height: 52px;\n padding: 0 24px;\n border-bottom: 1px solid var(--rule);\n background: linear-gradient(180deg, rgba(4, 8, 26, .7), rgba(4, 8, 26, .4));\n backdrop-filter: blur(10px);\n}\n\n.brand {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.brand-mark {\n width: 22px;\n height: 22px;\n border-radius: 7px;\n background: radial-gradient(120% 120% at 30% 30%, #6FA6E8 0%, #2C5DB8 45%, #0A1530 100%);\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22), 0 4px 12px -6px #2C5DB8;\n}\n\n.brand-name {\n font-size: 15px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--mist);\n}\n\n.brand-name em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n color: var(--sky);\n letter-spacing: 0;\n}\n\n.brand-eyebrow {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-left: 6px;\n padding-left: 10px;\n border-left: 1px solid var(--rule);\n}\n\n.top-right {\n display: flex;\n align-items: center;\n gap: 12px;\n grid-column: 2;\n justify-self: center;\n}\n\n.topnav-right {\n grid-column: 3;\n justify-self: end;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.port-badge {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-mute);\n padding: 6px 10px;\n border: 1px solid var(--rule);\n border-radius: 999px;\n background: rgba(4, 8, 26, .55);\n}\n\n.port-badge .mono {\n color: var(--text-dim);\n letter-spacing: 0.04em;\n text-transform: none;\n}\n\n.faq-btn {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 1px solid var(--rule);\n background: rgba(4, 8, 26, .55);\n color: var(--text-dim);\n font-family: var(--font-mono);\n font-size: 13px;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: background 180ms, border-color 180ms, color 180ms, transform 180ms;\n}\n\n.faq-btn:hover {\n background: rgba(155, 194, 239, .10);\n border-color: var(--rule-hover);\n color: var(--mist);\n transform: translateY(-1px);\n}\n\n.status-pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n border: 1px solid var(--rule);\n border-radius: 999px;\n background: rgba(4, 8, 26, .55);\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n transition: border-color 240ms ease;\n}\n\n.status-pill:has(.dot.live) {\n border-color: rgba(155, 194, 239, .45);\n color: var(--mist);\n animation: pill-glow 2.4s ease-in-out infinite;\n}\n\n.status-pill:has(.dot.dead) {\n border-color: rgba(220, 90, 90, .40);\n color: oklch(80% 0.10 20);\n}\n\n@keyframes pill-glow {\n\n 0%,\n 100% {\n box-shadow: 0 0 14px -4px rgba(155, 194, 239, .30), inset 0 0 12px -8px rgba(155, 194, 239, .30);\n }\n\n 50% {\n box-shadow: 0 0 26px -2px rgba(155, 194, 239, .55), inset 0 0 18px -6px rgba(155, 194, 239, .45);\n }\n}\n\n.dot {\n width: 7px;\n height: 7px;\n border-radius: 2px;\n background: var(--text-mute);\n transition: background 200ms;\n}\n\n.dot.live {\n background: var(--signal-cyan);\n animation: dot-pulse 1.8s ease-in-out infinite;\n}\n\n.dot.dead {\n background: var(--signal-rose);\n box-shadow: 0 0 0 3px rgba(220, 90, 90, .10);\n}\n\n@keyframes dot-pulse {\n\n 0%,\n 100% {\n box-shadow:\n 0 0 0 3px rgba(155, 194, 239, .10),\n 0 0 6px rgba(155, 194, 239, .50);\n }\n\n 50% {\n box-shadow:\n 0 0 0 6px rgba(155, 194, 239, .05),\n 0 0 14px rgba(155, 194, 239, .90);\n }\n}\n\n/* ============================================================\n Hero strip\n ============================================================ */\n.hero-strip {\n display: flex;\n align-items: center;\n gap: 24px;\n padding: 14px 24px;\n border-bottom: 1px solid var(--rule);\n background: linear-gradient(90deg, rgba(27, 58, 120, .10) 0%, rgba(4, 8, 26, 0) 100%);\n position: relative;\n overflow: hidden;\n}\n\n.hero-spacer {\n flex: 1;\n}\n\n.date-block {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.d-day {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 38px;\n line-height: 1;\n letter-spacing: -0.04em;\n color: var(--mist);\n}\n\n.d-rest {\n display: flex;\n flex-direction: column;\n gap: 2px;\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-dim);\n}\n\n.d-rest .d-mute {\n color: var(--text-mute);\n}\n\n.active-block {\n display: flex;\n flex-direction: column;\n gap: 2px;\n text-align: right;\n max-width: 360px;\n overflow: hidden;\n}\n\n.ab-label {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.ab-value {\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--mist);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 360px;\n}\n\n/* ============================================================\n Main grid\n ============================================================ */\n.grid-main {\n display: grid;\n grid-template-columns: 260px 1fr 340px;\n gap: 16px;\n padding: 16px 24px;\n min-height: 0;\n z-index: 10;\n}\n\n.col-left,\n.col-center,\n.col-right {\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-height: 0;\n}\n\n/* ============================================================\n Panels / cards — darker\n ============================================================ */\n.panel,\n.card {\n position: relative;\n border: 1px solid var(--rule);\n border-radius: 14px;\n background: var(--surface-1);\n padding: 14px 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-height: 0;\n transition: border-color 180ms ease, background 180ms ease;\n}\n\n.card.has-tooltip {\n cursor: help;\n}\n\n.card.has-tooltip:hover {\n border-color: var(--rule-hover);\n background: var(--surface-2);\n}\n\n.card-head {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 12px;\n}\n\n.card-eyebrow {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.card-eyebrow em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n font-size: 12px;\n color: var(--sky);\n letter-spacing: 0;\n text-transform: none;\n}\n\n.card-meta {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n/* Legend panel */\n.panel {\n padding: 14px 14px 16px;\n gap: 14px;\n flex-shrink: 0;\n}\n\n.p-head {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n padding-bottom: 10px;\n border-bottom: 1px solid var(--rule-2);\n}\n\n.p-section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.p-section+.p-section {\n padding-top: 12px;\n border-top: 1px solid var(--rule-2);\n}\n\n.ps-head {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-bottom: 4px;\n}\n\n.check {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 3px 6px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-dim);\n letter-spacing: 0.02em;\n}\n\nbutton.check {\n border: 0;\n background: transparent;\n width: 100%;\n text-align: left;\n}\n\n.check-clickable {\n cursor: pointer;\n border-radius: 6px;\n padding: 5px 6px;\n transition: background 140ms, color 140ms, transform 140ms;\n}\n\n.check-clickable .pf-arrow {\n margin-left: auto;\n color: var(--text-mute);\n font-family: var(--font-mono);\n font-size: 12px;\n transition: color 140ms, transform 140ms;\n}\n\n.check-clickable:hover {\n background: rgba(155, 194, 239, .07);\n color: var(--mist);\n}\n\n.check-clickable:hover .pf-arrow {\n color: var(--sky);\n transform: translateX(2px);\n}\n\n.dot-sq {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n background: var(--text-mute);\n flex-shrink: 0;\n}\n\n.dot-sq.opus {\n background: var(--c-opus);\n}\n\n.dot-sq.sonnet {\n background: var(--c-sonnet);\n}\n\n.dot-sq.haiku {\n background: var(--c-haiku);\n}\n\n.dot-sq.unknown {\n background: var(--c-unknown);\n}\n\n.proj-filter {\n display: flex;\n flex-direction: column;\n gap: 1px;\n max-height: 90px;\n overflow-y: auto;\n}\n\n.pf-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 140px;\n}\n\n/* ============================================================\n Donut card (model usage)\n ============================================================ */\n.donut-card {\n flex: 1;\n gap: 10px;\n}\n\n.donut-wrap {\n position: relative;\n width: 140px;\n height: 140px;\n margin: 4px auto 0;\n}\n\n.donut {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.donut-track {\n fill: none;\n stroke: rgba(155, 194, 239, .07);\n stroke-width: 14;\n}\n\n.donut-seg {\n transition: stroke-dashoffset 400ms ease, stroke-dasharray 400ms ease;\n}\n\n.donut-center {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n pointer-events: none;\n}\n\n.donut-total {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 26px;\n letter-spacing: -0.02em;\n color: var(--mist);\n line-height: 1;\n}\n\n.donut-total-k {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-top: 4px;\n}\n\n.donut-legend {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 6px;\n padding-top: 10px;\n border-top: 1px solid var(--rule-2);\n}\n\n.dl-row {\n display: grid;\n grid-template-columns: auto 1fr auto;\n align-items: center;\n gap: 8px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-dim);\n}\n\n.dl-dot {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n}\n\n.dl-name {\n color: var(--text-dim);\n}\n\n.dl-pct {\n color: var(--mist);\n font-weight: 500;\n}\n\n/* ============================================================\n Center column — Metric strip (no card chrome, divider-separated)\n ============================================================ */\n.metric-strip {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n border: 1px solid var(--rule);\n border-radius: 14px;\n background: var(--surface-1);\n overflow: hidden;\n flex-shrink: 0;\n}\n\n.metric-item {\n padding: 14px 18px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n cursor: help;\n border-right: 1px solid var(--rule-2);\n transition: background 200ms ease;\n min-width: 0;\n}\n.metric-item:last-child { border-right: 0; }\n.metric-item:hover { background: var(--surface-2); }\n\n.m-label {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.m-value {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 26px;\n letter-spacing: -0.025em;\n color: var(--mist);\n line-height: 1;\n}\n\n.m-value em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n color: var(--sky);\n letter-spacing: -0.005em;\n}\n\n/* ============================================================\n Savings card\n ============================================================ */\n.card.savings {\n flex-shrink: 0;\n gap: 12px;\n background:\n linear-gradient(180deg, rgba(123, 255, 199, .05) 0%, rgba(4, 8, 26, .20) 50%),\n var(--surface-1);\n border-color: rgba(123, 255, 199, .18);\n}\n\n.card.savings:hover {\n border-color: rgba(123, 255, 199, .32);\n}\n\n.savings-body {\n display: grid;\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 18px;\n}\n\n.savings-figure {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.savings-money {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 34px;\n letter-spacing: -0.035em;\n line-height: 1;\n color: var(--money);\n}\n\n.savings-tokens {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.10em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-top: 4px;\n}\n\n.savings-bar {\n position: relative;\n height: 8px;\n border-radius: 999px;\n overflow: hidden;\n background: var(--surface-3);\n display: flex;\n}\n\n.savings-actual {\n height: 100%;\n background: rgba(215, 230, 247, .55);\n transition: width 500ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.savings-saved {\n height: 100%;\n background: var(--signal-green);\n transition: width 500ms cubic-bezier(0.16, 1, 0.3, 1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);\n}\n\n.savings-legend {\n grid-column: 2;\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 24px;\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.08em;\n color: var(--text-mute);\n margin-top: 8px;\n}\n\n.sl-row {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.sl-row b {\n color: var(--mist);\n font-weight: 500;\n letter-spacing: 0.04em;\n}\n\n.sl-dot {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n}\n\n.sl-dot.actual {\n background: var(--mist);\n}\n\n.sl-dot.saved {\n background: var(--signal-green);\n}\n\n/* ============================================================\n Recent turns table\n ============================================================ */\n.turns-card {\n flex: 1;\n padding: 0;\n overflow: hidden;\n}\n\n.turns-card .card-head {\n padding: 14px 16px 10px;\n border-bottom: 1px solid var(--rule-2);\n}\n\n.turns-scroll {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n}\n\n.turns-table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.turns-table thead th {\n position: sticky;\n top: 0;\n background: var(--ink);\n padding: 9px 16px;\n text-align: left;\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n font-weight: 500;\n border-bottom: 1px solid var(--rule);\n z-index: 1;\n}\n\n.turns-table thead th.num {\n text-align: right;\n}\n\n.turns-table tbody td {\n padding: 8px 16px;\n border-bottom: 1px solid var(--rule-2);\n color: var(--text-dim);\n font-size: 12px;\n}\n\n.turns-table tbody td.num {\n text-align: right;\n font-family: var(--font-mono);\n}\n\n.turns-table tbody td.cost {\n color: var(--money);\n font-family: var(--font-mono);\n font-weight: 500;\n}\n\n.turns-table tbody td.ts {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-mute);\n}\n\n.turns-table tbody td.proj {\n color: var(--mist);\n}\n\n.turns-table tbody tr:hover {\n background: rgba(155, 194, 239, .03);\n}\n\n.turns-table tbody tr:last-child td {\n border-bottom: 0;\n}\n\n/* Model pills */\n.model-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 2px 8px;\n border-radius: 999px;\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.04em;\n border: 1px solid var(--rule);\n background: rgba(4, 8, 26, .5);\n color: var(--mist);\n}\n\n.model-pill .sq {\n width: 6px;\n height: 6px;\n border-radius: 2px;\n background: var(--text-mute);\n}\n\n.model-pill.opus {\n color: #FF8A66;\n border-color: rgba(255, 99, 56, .32);\n background: rgba(255, 99, 56, .07);\n}\n\n.model-pill.opus .sq {\n background: var(--c-opus);\n}\n\n.model-pill.sonnet {\n color: #FFC766;\n border-color: rgba(255, 185, 56, .32);\n background: rgba(255, 185, 56, .07);\n}\n\n.model-pill.sonnet .sq {\n background: var(--c-sonnet);\n}\n\n.model-pill.haiku {\n color: #A878FF;\n border-color: rgba(116, 56, 255, .42);\n background: rgba(116, 56, 255, .10);\n}\n\n.model-pill.haiku .sq {\n background: var(--c-haiku);\n}\n\n.model-pill.unknown {\n color: #5BDDF7;\n border-color: rgba(18, 203, 245, .32);\n background: rgba(18, 203, 245, .07);\n font-style: italic;\n}\n\n.model-pill.unknown .sq {\n background: var(--c-unknown);\n}\n\n/* ============================================================\n Right column — Cost hero\n ============================================================ */\n.cost-hero {\n position: relative;\n overflow: hidden;\n background:\n radial-gradient(120% 80% at 50% 110%, rgba(44, 93, 184, .18) 0%, rgba(4, 8, 26, .20) 60%),\n var(--surface-1);\n padding: 16px 18px 18px;\n gap: 10px;\n flex-shrink: 0;\n}\n\n.big-money {\n position: relative;\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 42px;\n letter-spacing: -0.035em;\n line-height: 1;\n color: var(--money);\n margin-top: 2px;\n}\n\n.big-money em {\n font-family: inherit;\n font-style: normal;\n font-weight: inherit;\n color: inherit;\n letter-spacing: inherit;\n opacity: 1;\n}\n\n.cost-sub {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 4px;\n padding-top: 10px;\n border-top: 1px solid var(--rule-2);\n}\n\n.cs-row {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n font-family: var(--font-mono);\n font-size: 11px;\n}\n\n.cs-k {\n font-size: 10px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.cs-v {\n color: var(--mist);\n}\n\n/* ============================================================\n Moat card\n ============================================================ */\n.moat {\n flex: 1;\n gap: 8px;\n}\n\n.moat-value {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 34px;\n letter-spacing: -0.03em;\n line-height: 1;\n color: var(--mist);\n margin-top: 2px;\n}\n\n.moat-value em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n font-size: 18px;\n color: var(--sky);\n letter-spacing: 0;\n margin-left: 6px;\n}\n\n.gate-mini {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 6px;\n padding-top: 10px;\n border-top: 1px solid var(--rule-2);\n overflow-y: auto;\n flex: 1;\n min-height: 0;\n}\n\n.gate-row {\n display: grid;\n grid-template-columns: auto auto 1fr;\n align-items: center;\n gap: 8px;\n font-family: var(--font-mono);\n font-size: 10px;\n color: var(--text-dim);\n padding: 3px 0;\n}\n\n.gate-row .g-ts {\n color: var(--text-mute);\n font-size: 9px;\n min-width: 38px;\n}\n\n.gate-row .g-decision {\n font-size: 9px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n padding: 2px 6px;\n border-radius: 999px;\n}\n\n.gate-row .g-decision.block {\n color: var(--signal-rose);\n background: rgba(220, 90, 90, .06);\n}\n\n.gate-row .g-decision.allow {\n color: var(--text-mute);\n background: rgba(155, 194, 239, .03);\n}\n\n.gate-row .g-q {\n color: var(--mist);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ============================================================\n Tooltips\n ============================================================ */\n.has-tooltip {\n position: relative;\n}\n\n/* Global JS-positioned tooltip — viewport-clamped */\n.global-tooltip {\n position: fixed;\n top: 0;\n left: 0;\n background: linear-gradient(180deg, rgba(18, 37, 73, .98), rgba(10, 21, 48, .98));\n color: var(--mist);\n border: 1px solid var(--rule-hover);\n border-radius: 12px;\n padding: 14px 16px;\n font-family: var(--font-sans);\n font-size: 15px;\n font-weight: 400;\n text-transform: none;\n letter-spacing: 0;\n white-space: normal;\n width: 320px;\n max-width: calc(100vw - 24px);\n text-align: left;\n line-height: 1.55;\n box-shadow: 0 16px 36px rgba(0, 0, 0, .7);\n backdrop-filter: blur(10px);\n z-index: 99999;\n opacity: 0;\n pointer-events: none;\n transform: translateY(6px);\n transition: opacity 180ms ease, transform 180ms ease;\n}\n\n.global-tooltip.on {\n opacity: 1;\n transform: translateY(0);\n}\n\n/* ============================================================\n Footer\n ============================================================ */\n.foot {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 24px;\n border-top: 1px solid var(--rule);\n background: linear-gradient(0deg, rgba(4, 8, 26, .7), rgba(4, 8, 26, .4));\n backdrop-filter: blur(10px);\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.foot em {\n font-family: var(--font-serif);\n font-style: italic;\n text-transform: none;\n letter-spacing: 0;\n color: var(--sky);\n font-size: 12px;\n}\n\n.foot .mono {\n color: var(--text-dim);\n text-transform: none;\n letter-spacing: 0.04em;\n}\n\n/* ============================================================\n Empty state\n ============================================================ */\n.empty {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.06em;\n color: var(--text-mute);\n text-align: center;\n padding: 16px 8px;\n font-style: italic;\n text-transform: none;\n}\n\n/* Scrollbar styling */\n.turns-scroll::-webkit-scrollbar,\n.proj-chart::-webkit-scrollbar,\n.gate-mini::-webkit-scrollbar {\n width: 6px;\n}\n\n.turns-scroll::-webkit-scrollbar-thumb,\n.proj-chart::-webkit-scrollbar-thumb,\n.gate-mini::-webkit-scrollbar-thumb {\n background: var(--rule);\n border-radius: 999px;\n}\n\n.turns-scroll::-webkit-scrollbar-track,\n.proj-chart::-webkit-scrollbar-track,\n.gate-mini::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.hidden {\n display: none !important;\n}\n\n/* ============================================================\n Staggered cascade on first paint (one-time, MOTION 6)\n ============================================================ */\n@keyframes cascade-in {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .col-left > *,\n .col-center > *,\n .col-right > * {\n opacity: 0;\n animation: cascade-in 520ms cubic-bezier(0.16, 1, 0.3, 1) forwards;\n will-change: transform, opacity;\n }\n .col-left > *:nth-child(1) { animation-delay: 0ms; }\n .col-left > *:nth-child(2) { animation-delay: 120ms; }\n .col-center > *:nth-child(1) { animation-delay: 40ms; }\n .col-center > *:nth-child(2) { animation-delay: 140ms; }\n .col-center > *:nth-child(3) { animation-delay: 240ms; }\n .col-right > *:nth-child(1) { animation-delay: 80ms; }\n .col-right > *:nth-child(2) { animation-delay: 200ms; }\n\n /* Clear will-change after animation completes */\n .col-left > *,\n .col-center > *,\n .col-right > * {\n animation-fill-mode: forwards;\n }\n}\n\n/* ============================================================\n Source / basis annotations\n ============================================================ */\n.card-source {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.08em;\n text-transform: lowercase;\n color: var(--text-mute);\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-top: auto;\n padding-top: 8px;\n border-top: 1px solid var(--rule-2);\n width: 100%;\n}\n\n.src-badge {\n font-family: var(--font-mono);\n font-size: 8px;\n font-weight: 500;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n padding: 2px 6px;\n border-radius: 4px;\n flex-shrink: 0;\n}\n\n.src-badge.verified {\n color: var(--signal-green);\n background: rgba(123, 255, 199, .08);\n border: 1px solid rgba(123, 255, 199, .25);\n}\n\n.src-badge.estimated,\n.src-badge.estimated.floor {\n color: var(--signal-amber);\n background: rgba(255, 185, 56, .10);\n border: 1px solid rgba(255, 185, 56, .30);\n}\n\n.src-badge.priced {\n color: var(--signal-cyan);\n background: rgba(155, 194, 239, .08);\n border: 1px solid rgba(155, 194, 239, .25);\n}\n\n/* Eyebrow that contains a badge */\n.card-eyebrow .src-badge {\n margin-left: 4px;\n}\n\n/* Savings audit row — live formula reveal */\n.savings-audit {\n margin-top: 10px;\n padding: 10px 12px;\n border: 1px dashed rgba(255, 185, 56, .25);\n border-radius: 8px;\n background: rgba(255, 185, 56, .04);\n font-family: var(--font-mono);\n font-size: 10.5px;\n letter-spacing: 0.04em;\n color: var(--text-mute);\n text-align: center;\n}\n\n.savings-audit b {\n color: var(--text-dim);\n font-weight: 500;\n}\n\n.savings-audit .audit-result {\n color: var(--money);\n}\n\n/* ============================================================\n FAQ dialog\n ============================================================ */\n.dialog.dialog-faq {\n max-width: min(80vw, 1100px);\n width: 100%;\n max-height: 86vh;\n display: flex;\n flex-direction: column;\n padding: 28px 32px 24px;\n gap: 6px;\n}\n\n.dialog.dialog-faq .dialog-path {\n margin-bottom: 4px;\n word-break: normal;\n overflow-wrap: anywhere;\n}\n\n.faq-content {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n margin-top: 18px;\n padding-right: 8px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.faq-content::-webkit-scrollbar {\n width: 6px;\n}\n\n.faq-content::-webkit-scrollbar-thumb {\n background: var(--rule);\n border-radius: 999px;\n}\n\n.faq-content::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.faq-content details {\n border: 1px solid var(--rule);\n border-radius: 12px;\n background: var(--surface-1);\n overflow: hidden;\n transition: background 180ms, border-color 180ms;\n flex-shrink: 0;\n}\n\n.faq-content details:hover {\n border-color: rgba(155, 194, 239, .22);\n}\n\n.faq-content details[open] {\n background: var(--surface-2);\n border-color: var(--rule-hover);\n}\n\n.faq-content summary {\n cursor: pointer;\n padding: 14px 20px;\n font-family: var(--font-sans);\n font-size: 14px;\n font-weight: 500;\n color: var(--mist);\n list-style: none;\n display: flex;\n align-items: center;\n gap: 12px;\n user-select: none;\n}\n\n.faq-content summary::-webkit-details-marker {\n display: none;\n}\n\n.faq-content summary::before {\n content: \"›\";\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n color: var(--text-mute);\n font-family: var(--font-mono);\n font-size: 14px;\n transition: transform 220ms ease, color 220ms ease;\n}\n\n.faq-content details[open] summary::before {\n transform: rotate(90deg);\n color: var(--sky);\n}\n\n.faq-content .faq-body {\n padding: 0 22px 20px 46px;\n color: var(--text-dim);\n font-size: 13.5px;\n line-height: 1.7;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.faq-content .faq-body p {\n margin: 0;\n}\n\n.faq-content .faq-body ul {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.faq-content .faq-body li {\n margin: 0;\n}\n\n.faq-content .faq-body b,\n.faq-content .faq-body strong {\n color: var(--mist);\n font-weight: 500;\n}\n\n.faq-content .faq-body code {\n font-family: var(--font-mono);\n font-size: 12px;\n background: rgba(155, 194, 239, .08);\n padding: 2px 6px;\n border-radius: 4px;\n color: var(--mist);\n border: 1px solid rgba(155, 194, 239, .12);\n word-break: break-word;\n}\n\n.faq-content .faq-body a {\n color: var(--blue-bright);\n text-decoration: underline;\n text-decoration-color: rgba(92, 143, 230, .40);\n text-underline-offset: 3px;\n transition: color 140ms, text-decoration-color 140ms;\n}\n\n.faq-content .faq-body a:hover {\n color: var(--mist);\n text-decoration-color: var(--sky);\n}\n\n.faq-content .faq-body table {\n width: 100%;\n border-collapse: collapse;\n margin: 4px 0;\n font-size: 13px;\n table-layout: fixed;\n}\n\n.faq-content .faq-body thead td {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.10em;\n text-transform: uppercase;\n color: var(--text-mute);\n padding-bottom: 6px;\n border-bottom: 1px solid var(--rule);\n font-weight: 500;\n}\n\n.faq-content .faq-body td {\n padding: 9px 10px;\n border-bottom: 1px solid var(--rule-2);\n vertical-align: top;\n word-break: break-word;\n}\n\n.faq-content .faq-body tr:last-child td {\n border-bottom: 0;\n}\n\n.faq-content .faq-body td:first-child {\n color: var(--text-dim);\n width: 38%;\n}\n\n.faq-content .faq-body td:first-child code {\n font-size: 11.5px;\n}\n\n.faq-content .faq-body .formula-box {\n font-family: var(--font-mono);\n font-size: 12.5px;\n background: rgba(255, 185, 56, .06);\n padding: 12px 14px;\n border-radius: 8px;\n border: 1px dashed rgba(255, 185, 56, .30);\n color: var(--mist);\n letter-spacing: 0.02em;\n}\n\n.faq-content .faq-body .link-list {\n list-style: none;\n padding-left: 0;\n}\n\n.faq-content .faq-body .link-list li {\n padding-left: 18px;\n position: relative;\n}\n\n.faq-content .faq-body .link-list li::before {\n content: \"›\";\n position: absolute;\n left: 0;\n color: var(--sky);\n font-family: var(--font-mono);\n}\n\n.faq-content .faq-body .warning {\n margin-top: 14px;\n padding: 12px 14px;\n background: rgba(255, 185, 56, .06);\n border: 1px solid rgba(255, 185, 56, .25);\n border-left: 3px solid var(--signal-amber);\n border-radius: 8px;\n font-size: 12.5px;\n color: var(--text-dim);\n}\n\n.faq-content .faq-body .warning .icon {\n color: var(--signal-amber);\n margin-right: 8px;\n font-weight: 500;\n}\n\n/* ============================================================\n Project dialog\n ============================================================ */\n.dialog-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(4, 8, 26, .78);\n backdrop-filter: blur(10px);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n animation: dlg-fade 180ms ease;\n}\n\n@keyframes dlg-fade {\n from {\n opacity: 0;\n }\n\n to {\n opacity: 1;\n }\n}\n\n.dialog {\n position: relative;\n width: 100%;\n max-width: 520px;\n background:\n radial-gradient(120% 80% at 50% 0%, rgba(44, 93, 184, .22) 0%, rgba(4, 8, 26, .20) 60%),\n linear-gradient(180deg, rgba(18, 37, 73, .88) 0%, rgba(10, 21, 48, .96) 100%);\n border: 1px solid var(--rule-hover);\n border-radius: 18px;\n padding: 28px 32px 32px;\n box-shadow:\n 0 30px 80px -20px rgba(0, 0, 0, .7),\n inset 0 1px 0 rgba(255, 255, 255, .04);\n animation: dlg-rise 220ms cubic-bezier(.2, .7, .2, 1);\n}\n\n@keyframes dlg-rise {\n from {\n opacity: 0;\n transform: translateY(8px) scale(.98);\n }\n\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.dialog-close {\n position: absolute;\n top: 14px;\n right: 14px;\n width: 30px;\n height: 30px;\n border-radius: 50%;\n color: var(--text-mute);\n font-size: 22px;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 180ms, color 180ms;\n}\n\n.dialog-close:hover {\n background: rgba(155, 194, 239, .10);\n color: var(--mist);\n}\n\n.dialog-eyebrow {\n font-family: var(--font-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-mute);\n margin-bottom: 10px;\n}\n\n.dialog-eyebrow em {\n font-family: var(--font-serif);\n font-style: italic;\n font-weight: 400;\n font-size: 12px;\n color: var(--sky);\n letter-spacing: 0;\n text-transform: none;\n}\n\n.dialog-name {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 28px;\n letter-spacing: -0.025em;\n color: var(--mist);\n line-height: 1.1;\n}\n\n.dialog-path {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-mute);\n margin-top: 6px;\n word-break: break-all;\n}\n\n.dialog-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 18px 24px;\n margin-top: 22px;\n padding-top: 20px;\n border-top: 1px solid var(--rule-2);\n}\n\n.dg-cell {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.dg-k {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: var(--text-mute);\n}\n\n.dg-v {\n font-family: var(--font-sans);\n font-weight: 500;\n font-size: 22px;\n letter-spacing: -0.02em;\n color: var(--mist);\n line-height: 1;\n}\n\n.dg-v.money {\n color: var(--money);\n}\n\n.dg-v-sm {\n font-size: 13px;\n font-family: var(--font-mono);\n font-weight: 400;\n color: var(--text-dim);\n letter-spacing: 0;\n}\n\n/* ============================================================\n v0.3 visual refresh\n - merged model-family legend into donut (count column)\n - Projects -> colored bar chart\n - elevated Savings card\n ============================================================ */\n\n/* Left column sizing: donut natural height, projects fills + scrolls */\n.donut-card { flex: 0 0 auto; }\n\n/* Donut legend now carries a count column */\n.dl-row {\n grid-template-columns: auto 1fr auto auto;\n gap: 8px;\n padding: 1px 0;\n}\n.dl-count {\n font-family: var(--font-mono);\n font-size: 10px;\n color: var(--text-mute);\n letter-spacing: 0.04em;\n font-variant-numeric: tabular-nums;\n}\n.dl-pct {\n min-width: 30px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n}\n\n/* ---- Projects bar chart ---- */\n.projects-card {\n flex: 1 1 auto;\n min-height: 0;\n gap: 10px;\n}\n.proj-chart {\n display: flex;\n flex-direction: column;\n gap: 9px;\n overflow-y: auto;\n min-height: 0;\n flex: 1;\n padding-right: 2px;\n}\n.proj-row {\n display: flex;\n flex-direction: column;\n gap: 7px;\n width: 100%;\n text-align: left;\n padding: 8px;\n border-radius: 9px;\n background: transparent;\n border: 0;\n cursor: pointer;\n transition: background 150ms ease;\n}\n.proj-row:hover { background: rgba(155, 194, 239, .055); }\n.pr-top {\n display: grid;\n grid-template-columns: auto 1fr auto auto;\n align-items: center;\n gap: 9px;\n}\n.pr-dot {\n width: 9px;\n height: 9px;\n border-radius: 3px;\n background: var(--pc, var(--sky));\n box-shadow: 0 0 9px -1px var(--pc, var(--sky));\n flex-shrink: 0;\n}\n.pr-name {\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--text-dim);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n letter-spacing: 0.01em;\n transition: color 150ms ease;\n}\n.proj-row:hover .pr-name { color: var(--mist); }\n.pr-turns {\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--mist);\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.02em;\n}\n.pr-arrow {\n font-family: var(--font-mono);\n font-size: 13px;\n color: var(--text-mute);\n transition: color 150ms ease, transform 150ms ease;\n}\n.proj-row:hover .pr-arrow {\n color: var(--pc, var(--sky));\n transform: translateX(2px);\n}\n.pr-bar {\n position: relative;\n height: 5px;\n border-radius: 999px;\n background: var(--surface-3);\n overflow: hidden;\n}\n.pr-fill {\n display: block;\n height: 100%;\n border-radius: 999px;\n background: linear-gradient(90deg,\n color-mix(in oklch, var(--pc, var(--sky)) 45%, transparent) 0%,\n var(--pc, var(--sky)) 100%);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18);\n transition: width 640ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n/* ---- Elevated Savings card (priority focus) ---- */\n.card.savings {\n background:\n radial-gradient(120% 140% at 10% -10%, rgba(123, 255, 199, .14) 0%, rgba(4, 8, 26, .08) 44%),\n linear-gradient(180deg, rgba(123, 255, 199, .05) 0%, rgba(4, 8, 26, .20) 52%),\n var(--surface-1);\n border-color: rgba(123, 255, 199, .24);\n box-shadow:\n inset 0 1px 0 rgba(123, 255, 199, .08),\n 0 20px 46px -30px rgba(123, 255, 199, .55);\n}\n.card.savings:hover {\n border-color: rgba(123, 255, 199, .38);\n}\n.savings-money {\n font-size: 40px;\n text-shadow: 0 0 26px rgba(123, 255, 199, .22);\n}\n.savings-bar { height: 9px; }\n.savings-saved {\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, .18),\n 0 0 12px -2px var(--signal-green);\n}\n\n/* Project dialog: name gets a project-colored accent dot */\n.dialog-name.has-accent {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.dialog-name.has-accent::before {\n content: \"\";\n width: 12px;\n height: 12px;\n border-radius: 4px;\n background: var(--pc, var(--sky));\n box-shadow: 0 0 12px -1px var(--pc, var(--sky));\n flex-shrink: 0;\n}\n\n\n/* ============================================================\n v0.3.1 — date + active project folded into the top nav\n ============================================================ */\n\n/* Let the right cluster shrink so the active path can ellipsize */\n.topnav-right { min-width: 0; }\n\n/* Compact date beside the brand */\n.nav-date {\n display: inline-flex;\n align-items: baseline;\n gap: 6px;\n margin-left: 8px;\n padding-left: 12px;\n border-left: 1px solid var(--rule);\n font-family: var(--font-mono);\n font-size: 11px;\n letter-spacing: 0.10em;\n text-transform: uppercase;\n white-space: nowrap;\n}\n.nav-date .nd-day { color: var(--mist); font-weight: 500; }\n.nav-date .nd-weekday { color: var(--text-dim); }\n.nav-date .nd-month { color: var(--text-mute); }\n\n/* Active project, compact, tail-truncated */\n.nav-active {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n max-width: 300px;\n padding-right: 12px;\n margin-right: 2px;\n border-right: 1px solid var(--rule);\n cursor: help;\n}\n.na-label {\n font-family: var(--font-mono);\n font-size: 9px;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: var(--text-mute);\n flex-shrink: 0;\n}\n.na-value {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--mist);\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n /* keep the project folder (tail) visible, ellipsize the drive prefix */\n direction: rtl;\n text-align: left;\n}\n\n/* Tighten nav on narrow widths */\n@media (max-width: 1100px) {\n .nav-active { max-width: 200px; }\n .nav-date .nd-month { display: none; }\n}\n\n\n/* Column headers signal they are hover-explainable */\n.turns-table thead th.has-tooltip { cursor: help; }\n.turns-table thead th.has-tooltip:hover { color: var(--text-dim); }\n\n/* ---- Recent-turns pager ---- */\n.turns-pager {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n padding: 8px 2px 0;\n margin-top: 6px;\n border-top: 1px solid var(--rule-2);\n}\n.turns-pager.hidden { display: none; }\n.turns-pager button {\n font-family: var(--font-mono);\n font-size: 11px;\n letter-spacing: 0.04em;\n color: var(--text-dim);\n background: rgba(4, 8, 26, .55);\n border: 1px solid var(--rule);\n border-radius: 7px;\n padding: 4px 10px;\n cursor: pointer;\n transition: background 150ms, border-color 150ms, color 150ms, transform 150ms;\n}\n.turns-pager button:hover:not(:disabled) {\n background: rgba(155, 194, 239, .10);\n border-color: var(--rule-hover);\n color: var(--mist);\n transform: translateY(-1px);\n}\n.turns-pager button:disabled {\n opacity: .35;\n cursor: default;\n}\n#turns-page-label {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-mute);\n letter-spacing: 0.04em;\n font-variant-numeric: tabular-nums;\n min-width: 84px;\n text-align: center;\n}\n","// Writes hook scripts into <project>/.claude/hooks/ and registers them in\n// <project>/.claude/settings.local.json. Idempotent — re-running `syn .`\n// regenerates the scripts and merges hook entries cleanly with any user-added\n// hooks already in the file.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { log } from \"../shared/logger.js\";\nimport type { SynthraPaths } from \"../shared/paths.js\";\n\nimport preCompactPs1 from \"./scripts/pre-compact.ps1\";\nimport preCompactSh from \"./scripts/pre-compact.sh\";\nimport preToolUsePs1 from \"./scripts/pre-tool-use.ps1\";\nimport preToolUseSh from \"./scripts/pre-tool-use.sh\";\nimport primePs1 from \"./scripts/prime.ps1\";\nimport primeSh from \"./scripts/prime.sh\";\nimport stopPs1 from \"./scripts/stop.ps1\";\nimport stopSh from \"./scripts/stop.sh\";\n\nexport interface InstallResult {\n scriptsWritten: string[];\n settingsUpdated: boolean;\n}\n\ninterface ScriptDef {\n /** Hook event name as recognized by Claude Code. */\n event: \"SessionStart\" | \"PreToolUse\" | \"PreCompact\" | \"Stop\";\n /** Tool-name regex for PreToolUse only. */\n matcher?: string;\n /** Base filename written into .claude/hooks/. */\n baseName: string;\n ps1: string;\n sh: string;\n}\n\nconst SCRIPTS: ScriptDef[] = [\n { event: \"SessionStart\", baseName: \"synthra-prime\", ps1: primePs1, sh: primeSh },\n { event: \"PreToolUse\", matcher: \"Grep|Glob\", baseName: \"synthra-pre-tool-use\", ps1: preToolUsePs1, sh: preToolUseSh },\n { event: \"PreCompact\", baseName: \"synthra-pre-compact\", ps1: preCompactPs1, sh: preCompactSh },\n { event: \"Stop\", baseName: \"synthra-stop\", ps1: stopPs1, sh: stopSh },\n];\n\nconst SYNTHRA_HOOK_MARKER = \"synthra-hook=true\";\n\nfunction commandFor(scriptPath: string): string {\n if (process.platform === \"win32\") {\n // PowerShell on Windows; -ExecutionPolicy Bypass so the script always runs.\n return `powershell.exe -ExecutionPolicy Bypass -NoProfile -File \"${scriptPath}\"`;\n }\n return `bash \"${scriptPath}\"`;\n}\n\nfunction chosenScriptBody(s: ScriptDef): string {\n return process.platform === \"win32\" ? s.ps1 : s.sh;\n}\n\nfunction chosenScriptExt(): string {\n return process.platform === \"win32\" ? \".ps1\" : \".sh\";\n}\n\ninterface HooksConfig {\n hooks?: {\n [event: string]: Array<{\n matcher?: string;\n hooks?: Array<{ type: string; command: string; meta?: string }>;\n }>;\n };\n [k: string]: unknown;\n}\n\nasync function readSettings(path: string): Promise<HooksConfig> {\n try {\n const raw = await readFile(path, \"utf8\");\n return JSON.parse(raw) as HooksConfig;\n } catch {\n return {};\n }\n}\n\nfunction stripOurHooks(config: HooksConfig): HooksConfig {\n if (!config.hooks) return config;\n const next: HooksConfig[\"hooks\"] = {};\n for (const [event, entries] of Object.entries(config.hooks)) {\n const filtered = entries\n .map((entry) => ({\n ...entry,\n hooks: (entry.hooks ?? []).filter((h) => h.meta !== SYNTHRA_HOOK_MARKER),\n }))\n .filter((entry) => (entry.hooks?.length ?? 0) > 0);\n if (filtered.length) next[event] = filtered;\n }\n config.hooks = next;\n return config;\n}\n\nfunction mergeOurHooks(config: HooksConfig, paths: SynthraPaths): HooksConfig {\n const hooks = (config.hooks = config.hooks ?? {});\n for (const s of SCRIPTS) {\n const scriptPath = join(paths.claudeHooksDir, `${s.baseName}${chosenScriptExt()}`);\n const entry = {\n ...(s.matcher ? { matcher: s.matcher } : {}),\n hooks: [\n {\n type: \"command\",\n command: commandFor(scriptPath),\n meta: SYNTHRA_HOOK_MARKER,\n },\n ],\n };\n const list = (hooks[s.event] = hooks[s.event] ?? []);\n list.push(entry);\n }\n return config;\n}\n\nexport async function installHooks(paths: SynthraPaths): Promise<InstallResult> {\n await mkdir(paths.claudeHooksDir, { recursive: true });\n\n const scriptsWritten: string[] = [];\n for (const s of SCRIPTS) {\n const target = join(paths.claudeHooksDir, `${s.baseName}${chosenScriptExt()}`);\n await writeFile(target, chosenScriptBody(s), \"utf8\");\n scriptsWritten.push(target);\n }\n\n await mkdir(dirname(paths.claudeSettings), { recursive: true });\n const existing = await readSettings(paths.claudeSettings);\n const stripped = stripOurHooks(existing);\n const merged = mergeOurHooks(stripped, paths);\n\n await writeFile(paths.claudeSettings, JSON.stringify(merged, null, 2) + \"\\n\", \"utf8\");\n\n log.debug(`installed ${scriptsWritten.length} hook script(s) into ${paths.claudeHooksDir}`);\n\n return { scriptsWritten, settingsUpdated: true };\n}\n","# PreCompact hook — Windows PowerShell.\n# Re-injects the primer after Claude auto-compacts. Same logic as prime.ps1.\n\n$ErrorActionPreference = \"SilentlyContinue\"\n\n$portFile = Join-Path $PWD \".synthra-graph\\mcp_port\"\nif (-not (Test-Path $portFile)) { exit 0 }\n$port = (Get-Content -Path $portFile -Raw).Trim()\nif (-not $port) { exit 0 }\n\ntry {\n $resp = Invoke-RestMethod -Uri \"http://127.0.0.1:$port/prime\" -Method GET -TimeoutSec 3\n if ($resp.primer) { Write-Output $resp.primer }\n} catch {\n # silent\n}\nexit 0\n","#!/usr/bin/env bash\n# PreCompact hook — bash. Re-injects the primer after Claude auto-compacts.\n\nset +e\n\nPORT_FILE=\"$PWD/.synthra-graph/mcp_port\"\nif [ ! -f \"$PORT_FILE\" ]; then exit 0; fi\nPORT=$(cat \"$PORT_FILE\" 2>/dev/null | tr -d '[:space:]')\nif [ -z \"$PORT\" ]; then exit 0; fi\n\nPRIMER=$(curl -sS --max-time 3 \"http://127.0.0.1:$PORT/prime\" 2>/dev/null \\\n | sed -n 's/.*\"primer\"[[:space:]]*:[[:space:]]*\"\\(.*\\)\".*/\\1/p' \\\n | head -c 8000)\n\nif [ -n \"$PRIMER\" ]; then\n printf '%b\\n' \"$PRIMER\"\nfi\nexit 0\n","# PreToolUse hook — Windows PowerShell.\n# THE MOAT (improvement #1). Reads the tool call from stdin (JSON), POSTs it\n# to /gate, and if the server says \"block\" emits a JSON deny-decision to\n# stdout. Claude Code reads stdout JSON to enforce the decision.\n# Always exits 0; failure-to-reach-server leaves Claude untouched.\n\n$ErrorActionPreference = \"SilentlyContinue\"\n\n$raw = [Console]::In.ReadToEnd()\nif (-not $raw) { exit 0 }\n\ntry {\n $hookInput = $raw | ConvertFrom-Json -ErrorAction Stop\n} catch {\n exit 0\n}\n\n$portFile = Join-Path $PWD \".synthra-graph\\mcp_port\"\nif (-not (Test-Path $portFile)) { exit 0 }\n$port = (Get-Content -Path $portFile -Raw).Trim()\nif (-not $port) { exit 0 }\n\n$payload = @{\n tool_name = $hookInput.tool_name\n tool_input = $hookInput.tool_input\n} | ConvertTo-Json -Depth 10 -Compress\n\ntry {\n $resp = Invoke-RestMethod -Uri \"http://127.0.0.1:$port/gate\" -Method POST `\n -Body $payload -ContentType \"application/json\" -TimeoutSec 3\n} catch {\n exit 0\n}\n\nif ($resp.decision -eq \"block\") {\n $denyJson = @{\n hookSpecificOutput = @{\n hookEventName = \"PreToolUse\"\n permissionDecision = \"deny\"\n permissionDecisionReason = $resp.reason\n }\n } | ConvertTo-Json -Depth 5 -Compress\n Write-Output $denyJson\n}\nexit 0\n","#!/usr/bin/env bash\n# PreToolUse hook — bash. POSTs the tool call to /gate; if server returns\n# \"block\", emits the deny-decision JSON to stdout for Claude Code to enforce.\n# Always exits 0; server failures leave Claude untouched.\n\nset +e\n\nPORT_FILE=\"$PWD/.synthra-graph/mcp_port\"\nif [ ! -f \"$PORT_FILE\" ]; then exit 0; fi\nPORT=$(cat \"$PORT_FILE\" 2>/dev/null | tr -d '[:space:]')\nif [ -z \"$PORT\" ]; then exit 0; fi\n\nINPUT=$(cat 2>/dev/null)\nif [ -z \"$INPUT\" ]; then exit 0; fi\n\nRESP=$(curl -sS --max-time 3 -X POST -H \"Content-Type: application/json\" \\\n --data \"$INPUT\" \"http://127.0.0.1:$PORT/gate\" 2>/dev/null)\n\ncase \"$RESP\" in\n *'\"decision\":\"block\"'*)\n REASON=$(printf '%s' \"$RESP\" | sed -n 's/.*\"reason\"[[:space:]]*:[[:space:]]*\"\\(.*\\)\".*/\\1/p')\n cat <<EOF\n{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"${REASON}\"}}\nEOF\n ;;\nesac\nexit 0\n","# SessionStart + PreCompact hook — Windows PowerShell.\n# Reads .synthra-graph/mcp_port, calls GET /prime, prints the primer to stdout\n# (Claude Code appends stdout to the session's system prompt). Always exits 0;\n# any failure leaves Claude with the prompt it would have gotten without Synthra.\n\n$ErrorActionPreference = \"SilentlyContinue\"\n\n$portFile = Join-Path $PWD \".synthra-graph\\mcp_port\"\nif (-not (Test-Path $portFile)) { exit 0 }\n$port = (Get-Content -Path $portFile -Raw).Trim()\nif (-not $port) { exit 0 }\n\ntry {\n $resp = Invoke-RestMethod -Uri \"http://127.0.0.1:$port/prime\" -Method GET -TimeoutSec 3\n if ($resp.primer) { Write-Output $resp.primer }\n} catch {\n # silent on failure — Claude continues without the primer\n}\nexit 0\n","#!/usr/bin/env bash\n# SessionStart + PreCompact hook — bash.\n# Reads .synthra-graph/mcp_port, calls GET /prime, prints the primer to stdout.\n# Always exits 0; any failure leaves Claude with the prompt it would have had\n# without Synthra.\n\nset +e\n\nPORT_FILE=\"$PWD/.synthra-graph/mcp_port\"\nif [ ! -f \"$PORT_FILE\" ]; then exit 0; fi\nPORT=$(cat \"$PORT_FILE\" 2>/dev/null | tr -d '[:space:]')\nif [ -z \"$PORT\" ]; then exit 0; fi\n\nPRIMER=$(curl -sS --max-time 3 \"http://127.0.0.1:$PORT/prime\" 2>/dev/null \\\n | sed -n 's/.*\"primer\"[[:space:]]*:[[:space:]]*\"\\(.*\\)\".*/\\1/p' \\\n | head -c 8000)\n\nif [ -n \"$PRIMER\" ]; then\n printf '%b\\n' \"$PRIMER\"\nfi\nexit 0\n","# Stop hook — Windows PowerShell.\r\n# Reads Claude's transcript JSONL from $hookInput.transcript_path, sums\r\n# usage.* token counts across all assistant turns since the last offset, and\r\n# POSTs the totals to /log. Uses a per-transcript .stopoffset file to avoid\r\n# double-counting on session resume.\r\n\r\n$ErrorActionPreference = \"SilentlyContinue\"\r\n\r\n$raw = [Console]::In.ReadToEnd()\r\nif (-not $raw) { exit 0 }\r\ntry { $hookInput = $raw | ConvertFrom-Json -ErrorAction Stop } catch { exit 0 }\r\n\r\n$transcript = $hookInput.transcript_path\r\nif (-not $transcript -or -not (Test-Path $transcript)) { exit 0 }\r\n\r\n$portFile = Join-Path $PWD \".synthra-graph\\mcp_port\"\r\nif (-not (Test-Path $portFile)) { exit 0 }\r\n$port = (Get-Content -Path $portFile -Raw).Trim()\r\nif (-not $port) { exit 0 }\r\n\r\n$offsetFile = \"$transcript.stopoffset\"\r\n$startOffset = 0\r\nif (Test-Path $offsetFile) {\r\n $val = (Get-Content -Path $offsetFile -Raw).Trim()\r\n if ($val -match '^\\d+$') { $startOffset = [int]$val }\r\n}\r\n\r\n$lines = Get-Content -Path $transcript\r\n$inT = 0; $outT = 0; $cc = 0; $cr = 0; $model = \"\"\r\n$lineNum = 0\r\nforeach ($line in $lines) {\r\n $lineNum++\r\n if ($lineNum -le $startOffset) { continue }\r\n if (-not $line) { continue }\r\n try { $e = $line | ConvertFrom-Json -ErrorAction Stop } catch { continue }\r\n $usage = $e.message.usage\r\n if (-not $usage) { continue }\r\n $inT += [int]($usage.input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })\r\n $outT += [int]($usage.output_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })\r\n $cc += [int]($usage.cache_creation_input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })\r\n $cr += [int]($usage.cache_read_input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })\r\n if ($e.message.model) { $model = $e.message.model }\r\n}\r\n\r\nSet-Content -Path $offsetFile -Value $lineNum -Encoding ASCII\r\n\r\nif ($inT -eq 0 -and $outT -eq 0) { exit 0 }\r\n\r\n$payload = @{\r\n input_tokens = $inT\r\n output_tokens = $outT\r\n cache_creation_input_tokens = $cc\r\n cache_read_input_tokens = $cr\r\n model = $model\r\n description = \"synthra-stop-hook\"\r\n project = $PWD.Path\r\n} | ConvertTo-Json -Compress\r\n\r\ntry {\r\n Invoke-RestMethod -Uri \"http://127.0.0.1:$port/log\" -Method POST `\r\n -Body $payload -ContentType \"application/json\" -TimeoutSec 3 | Out-Null\r\n} catch {\r\n # silent\r\n}\r\n\r\n# Refresh CONTEXT.md from the branch-scoped store.\r\n$ctxPayload = @{ transcript_path = $transcript } | ConvertTo-Json -Compress\r\ntry {\r\n Invoke-RestMethod -Uri \"http://127.0.0.1:$port/context-update\" -Method POST `\r\n -Body $ctxPayload -ContentType \"application/json\" -TimeoutSec 3 | Out-Null\r\n} catch {\r\n # silent\r\n}\r\nexit 0\r\n","#!/usr/bin/env bash\r\n# Stop hook — bash. Reads transcript JSONL, sums usage.* across new lines,\r\n# POSTs totals to /log. Uses a .stopoffset file to avoid double-counting.\r\n# Requires `jq` for robust JSON parsing; falls back to silent no-op if absent.\r\n\r\nset +e\r\n\r\nINPUT=$(cat 2>/dev/null)\r\nif [ -z \"$INPUT\" ]; then exit 0; fi\r\n\r\nTRANSCRIPT=$(printf '%s' \"$INPUT\" | sed -n 's/.*\"transcript_path\"[[:space:]]*:[[:space:]]*\"\\(.*\\)\".*/\\1/p')\r\nif [ -z \"$TRANSCRIPT\" ] || [ ! -f \"$TRANSCRIPT\" ]; then exit 0; fi\r\n\r\nPORT_FILE=\"$PWD/.synthra-graph/mcp_port\"\r\nif [ ! -f \"$PORT_FILE\" ]; then exit 0; fi\r\nPORT=$(cat \"$PORT_FILE\" 2>/dev/null | tr -d '[:space:]')\r\nif [ -z \"$PORT\" ]; then exit 0; fi\r\n\r\nif ! command -v jq >/dev/null 2>&1; then exit 0; fi\r\n\r\nOFFSET_FILE=\"${TRANSCRIPT}.stopoffset\"\r\nSTART_OFFSET=0\r\nif [ -f \"$OFFSET_FILE\" ]; then\r\n START_OFFSET=$(cat \"$OFFSET_FILE\" 2>/dev/null | tr -d '[:space:]')\r\n case \"$START_OFFSET\" in ''|*[!0-9]*) START_OFFSET=0 ;; esac\r\nfi\r\n\r\nTOTAL_LINES=$(wc -l < \"$TRANSCRIPT\" 2>/dev/null | tr -d ' ')\r\nTOTAL_LINES=${TOTAL_LINES:-0}\r\n\r\nif [ \"$TOTAL_LINES\" -le \"$START_OFFSET\" ]; then exit 0; fi\r\n\r\nUSAGE=$(tail -n +$((START_OFFSET + 1)) \"$TRANSCRIPT\" 2>/dev/null \\\r\n | jq -s '\r\n map(select(.message.usage != null) | .message)\r\n | reduce .[] as $m (\r\n {in:0, out:0, cc:0, cr:0, model:\"\"};\r\n .in += ($m.usage.input_tokens // 0)\r\n | .out += ($m.usage.output_tokens // 0)\r\n | .cc += ($m.usage.cache_creation_input_tokens // 0)\r\n | .cr += ($m.usage.cache_read_input_tokens // 0)\r\n | .model = ($m.model // .model)\r\n )\r\n ' 2>/dev/null)\r\n\r\nprintf '%s' \"$TOTAL_LINES\" > \"$OFFSET_FILE\"\r\n\r\nIN=$(printf '%s' \"$USAGE\" | jq -r '.in // 0')\r\nOUT=$(printf '%s' \"$USAGE\" | jq -r '.out // 0')\r\nCC=$(printf '%s' \"$USAGE\" | jq -r '.cc // 0')\r\nCR=$(printf '%s' \"$USAGE\" | jq -r '.cr // 0')\r\nMODEL=$(printf '%s' \"$USAGE\" | jq -r '.model // \"\"')\r\n\r\nif [ \"$IN\" = \"0\" ] && [ \"$OUT\" = \"0\" ]; then exit 0; fi\r\n\r\ncurl -sS --max-time 3 -X POST -H \"Content-Type: application/json\" \\\r\n --data \"$(jq -nc --argjson i \"$IN\" --argjson o \"$OUT\" --argjson cc \"$CC\" --argjson cr \"$CR\" --arg m \"$MODEL\" --arg p \"$PWD\" \\\r\n '{input_tokens:$i, output_tokens:$o, cache_creation_input_tokens:$cc, cache_read_input_tokens:$cr, model:$m, description:\"synthra-stop-hook\", project:$p}')\" \\\r\n \"http://127.0.0.1:$PORT/log\" >/dev/null 2>&1\r\n\r\n# Refresh CONTEXT.md from the branch-scoped store.\r\ncurl -sS --max-time 3 -X POST -H \"Content-Type: application/json\" \\\r\n --data \"$(jq -nc --arg t \"$TRANSCRIPT\" '{transcript_path:$t}')\" \\\r\n \"http://127.0.0.1:$PORT/context-update\" >/dev/null 2>&1\r\n\r\nexit 0\r\n","// HTTP server (Hono). Hosts the routes hooks need (/prime, /pack, /log,\n// /gate, /activity) and serves the loaded graph from memory. The MCP-protocol\n// envelope (/mcp endpoint, JSON-RPC) is wired in M3.\n\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport { ActivityStore } from \"../activity/activity-log.js\";\nimport { createFileWatcher, type FileWatcher } from \"../activity/file-watcher.js\";\nimport { createGitWatcher, type GitWatcher } from \"../activity/git-watcher.js\";\nimport { scanProject } from \"../cli/scan-command.js\";\nimport { readGraph, readSymbolIndex } from \"../graph/store.js\";\nimport { log } from \"../shared/logger.js\";\nimport type { SynthraPaths } from \"../shared/paths.js\";\nimport type { ServerContext } from \"./context.js\";\nimport { handleMcpRequest } from \"./mcp.js\";\nimport { findFreePort } from \"./port.js\";\nimport { handleActivity } from \"./routes/activity.js\";\nimport { handleContextUpdate } from \"./routes/context-update.js\";\nimport { handleGate } from \"./routes/gate.js\";\nimport { handleLog } from \"./routes/log.js\";\nimport { handlePack } from \"./routes/pack.js\";\nimport { handlePrime } from \"./routes/prime.js\";\n\nexport interface ServerHandle {\n port: number;\n url: string;\n stop(): Promise<void>;\n}\n\nexport interface StartOptions {\n /** Override the port range search. */\n port?: number;\n}\n\nasync function loadContext(paths: SynthraPaths): Promise<ServerContext> {\n try {\n const [graph, symbolIndex] = await Promise.all([\n readGraph(paths.infoGraph),\n readSymbolIndex(paths.symbolIndex),\n ]);\n const activity = new ActivityStore(paths.activityLog);\n return { paths, graph, symbolIndex, activity };\n } catch (err) {\n throw new Error(\n `failed to load graph from ${paths.infoGraph}: ${(err as Error).message}. ` +\n `Run \\`syn scan\\` first.`,\n );\n }\n}\n\nfunction buildApp(ctx: ServerContext, port: number): Hono {\n const app = new Hono();\n\n app.get(\"/\", (c) =>\n c.json({\n service: \"synthra\",\n version: \"0.0.1\",\n port,\n file_count: ctx.graph.file_count,\n symbol_count: ctx.graph.symbol_count,\n generated_at: ctx.graph.generated_at,\n }),\n );\n\n app.get(\"/health\", (c) => c.json({ ok: true }));\n\n app.get(\"/prime\", async (c) => c.json(await handlePrime(ctx, port)));\n\n app.post(\"/pack\", async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json(await handlePack(body, ctx));\n });\n\n app.post(\"/log\", async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json(await handleLog(body, ctx));\n });\n\n app.post(\"/gate\", async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json(await handleGate(body, ctx));\n });\n\n app.get(\"/activity\", async (c) => {\n const sinceParam = c.req.query(\"since\");\n const sinceMs = sinceParam ? Number(sinceParam) : undefined;\n return c.json(\n await handleActivity(Number.isFinite(sinceMs) ? sinceMs : undefined, ctx),\n );\n });\n\n app.post(\"/context-update\", async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json(await handleContextUpdate(body, ctx));\n });\n\n app.post(\"/mcp\", async (c) => {\n const body = await c.req.json().catch(() => null);\n return c.json(await handleMcpRequest(body, ctx));\n });\n\n app.onError((err, c) => {\n log.error(\"route error:\", err.message);\n return c.json({ error: err.message }, 400);\n });\n\n return app;\n}\n\nexport async function startServer(\n paths: SynthraPaths,\n options: StartOptions = {},\n): Promise<ServerHandle> {\n const ctx = await loadContext(paths);\n const port = options.port ?? (await findFreePort());\n\n const app = buildApp(ctx, port);\n const nodeServer = serve({ fetch: app.fetch, port, hostname: \"127.0.0.1\" });\n\n await writeFile(paths.mcpPort, String(port), \"utf8\");\n\n // Spin up the human-activity watchers. Both are best-effort — if chokidar\n // can't watch (e.g. unsupported FS) or .git is missing, they no-op silently.\n const fileWatcher: FileWatcher = createFileWatcher(paths.projectRoot, (e) =>\n ctx.activity.add(e),\n );\n const gitWatcher: GitWatcher = createGitWatcher(paths.projectRoot, async (e) => {\n await ctx.activity.add(e);\n // Per-branch graph: rebuild on branch switch so the in-memory graph\n // matches whichever branch is currently checked out.\n if (e.kind === \"branch-switch\") {\n try {\n const to = (e.details as { to?: string } | undefined)?.to ?? \"unknown\";\n log.info(`branch switched to '${to}' — rebuilding graph…`);\n await scanProject(paths.projectRoot, { silent: true });\n const [g, idx] = await Promise.all([\n readGraph(paths.infoGraph),\n readSymbolIndex(paths.symbolIndex),\n ]);\n ctx.graph = g;\n ctx.symbolIndex = idx;\n log.info(`graph rebuilt for '${to}' (${g.symbol_count} symbols).`);\n } catch (err) {\n log.warn(`branch rescan failed: ${(err as Error).message}`);\n }\n }\n });\n try {\n await fileWatcher.start();\n } catch (err) {\n log.warn(`file watcher failed to start: ${(err as Error).message}`);\n }\n try {\n await gitWatcher.start();\n } catch (err) {\n log.warn(`git watcher failed to start: ${(err as Error).message}`);\n }\n\n const url = `http://127.0.0.1:${port}`;\n\n return {\n port,\n url,\n async stop() {\n await fileWatcher.stop().catch(() => undefined);\n await gitWatcher.stop().catch(() => undefined);\n await new Promise<void>((resolve, reject) => {\n nodeServer.close((err) => (err ? reject(err) : resolve()));\n });\n },\n };\n}\n","// Rolling JSONL log of human activity, written to .synthra-graph/activity.jsonl.\n// In-memory ring buffer for fast queries; disk append for durability.\n//\n// The buffer is bounded (defaults to 100 events) so we don't unbounded-grow\n// memory in long sessions. Disk gets every event so the dashboard / future\n// audit tooling can replay history.\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nexport interface FileEvent {\n kind: \"save\" | \"create\" | \"delete\";\n path: string;\n ts: string;\n}\n\nexport interface GitEvent {\n kind: \"branch-switch\" | \"stage\" | \"unstage\" | \"diff-change\";\n details: Record<string, unknown>;\n ts: string;\n}\n\nexport type ActivityEvent = FileEvent | GitEvent;\n\nconst DEFAULT_RING_SIZE = 100;\n\nexport class ActivityStore {\n private ring: ActivityEvent[] = [];\n private readonly maxRingSize: number;\n private readonly persistPath: string;\n\n constructor(persistPath: string, maxRingSize = DEFAULT_RING_SIZE) {\n this.persistPath = persistPath;\n this.maxRingSize = maxRingSize;\n }\n\n async add(event: ActivityEvent): Promise<void> {\n this.ring.push(event);\n while (this.ring.length > this.maxRingSize) this.ring.shift();\n await this.persist(event);\n }\n\n /** Get events newer than `sinceMs` (epoch ms). If omitted, returns the full ring. */\n getEvents(sinceMs?: number): ActivityEvent[] {\n if (!sinceMs || !Number.isFinite(sinceMs)) return this.ring.slice();\n const cutoff = new Date(sinceMs).toISOString();\n return this.ring.filter((e) => e.ts >= cutoff);\n }\n\n /** Project-relative file paths that have a save/create event newer than `maxAgeMs` ms ago. */\n recentFilePaths(maxAgeMs: number): string[] {\n const cutoff = new Date(Date.now() - maxAgeMs).toISOString();\n const out = new Set<string>();\n for (const e of this.ring) {\n if (\"path\" in e && (e.kind === \"save\" || e.kind === \"create\") && e.ts >= cutoff) {\n out.add(e.path);\n }\n }\n return Array.from(out);\n }\n\n size(): number {\n return this.ring.length;\n }\n\n private async persist(event: ActivityEvent): Promise<void> {\n try {\n await mkdir(dirname(this.persistPath), { recursive: true });\n await appendFile(this.persistPath, JSON.stringify(event) + \"\\n\", \"utf8\");\n } catch {\n // Durability is best-effort; an unwritable disk shouldn't crash the server.\n }\n }\n}\n","// chokidar-based file watcher. Emits save/create/delete events for human\n// edits inside the project. Respects .gitignore + .synthraignore plus a\n// hard-coded list of always-ignored directories (.git, .synthra*, .claude,\n// node_modules, dist, build, coverage).\n\nimport chokidar, { type FSWatcher } from \"chokidar\";\nimport { readFile } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\nimport ignore, { type Ignore } from \"ignore\";\n\nimport { log } from \"../shared/logger.js\";\nimport type { FileEvent } from \"./activity-log.js\";\n\nconst ALWAYS_IGNORE = [\n \".git\",\n \".synthra\",\n \".synthra-graph\",\n \".claude\",\n \"node_modules\",\n \"dist\",\n \"build\",\n \"out\",\n \"coverage\",\n \".next\",\n \".nuxt\",\n \".svelte-kit\",\n \".turbo\",\n \".cache\",\n \".vscode\",\n \".idea\",\n];\n\nexport interface FileWatcher {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\nexport type FileEventHandler = (e: FileEvent) => void | Promise<void>;\n\nasync function readIgnoreFile(path: string): Promise<string[]> {\n try {\n const text = await readFile(path, \"utf8\");\n return text\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter((l) => l.length > 0 && !l.startsWith(\"#\"));\n } catch {\n return [];\n }\n}\n\nasync function buildMatcher(root: string): Promise<Ignore> {\n const ig = ignore();\n ig.add(ALWAYS_IGNORE.map((d) => `${d}/`));\n ig.add(await readIgnoreFile(join(root, \".gitignore\")));\n ig.add(await readIgnoreFile(join(root, \".synthraignore\")));\n return ig;\n}\n\nfunction toPosixRel(root: string, abs: string): string {\n const rel = relative(root, abs);\n return sep === \"/\" ? rel : rel.split(sep).join(\"/\");\n}\n\nexport function createFileWatcher(root: string, onEvent: FileEventHandler): FileWatcher {\n let watcher: FSWatcher | null = null;\n let ig: Ignore | null = null;\n\n const emit = async (kind: FileEvent[\"kind\"], abs: string) => {\n if (!ig) return;\n const rel = toPosixRel(root, abs);\n if (!rel || rel.startsWith(\"..\")) return;\n if (ig.ignores(rel)) return;\n try {\n await onEvent({ kind, path: rel, ts: new Date().toISOString() });\n } catch {\n // swallow handler errors — watcher must keep going\n }\n };\n\n return {\n async start() {\n ig = await buildMatcher(root);\n watcher = chokidar.watch(root, {\n // Cross-platform glob ignore. We match both the directory itself and\n // anything inside it. picomatch (chokidar's matcher) normalizes path\n // separators so a single set of forward-slash globs handles\n // Windows + POSIX. Function-based ignore was unreliable on Windows\n // and let chokidar descend into .git/, which crashed on transient\n // index.lock files held exclusively by git.\n ignored: ALWAYS_IGNORE.flatMap((d) => [`**/${d}`, `**/${d}/**`]),\n ignoreInitial: true,\n persistent: true,\n awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 },\n });\n\n // Chokidar emits \"error\" for transient OS-level issues — most commonly\n // EPERM/ENOENT on rapidly created+deleted files. We never want one of\n // these to crash the syn process. Log + swallow.\n watcher.on(\"error\", (err) => {\n const e = err as NodeJS.ErrnoException;\n log.debug(`file watcher error (swallowed): ${e?.code ?? \"\"} ${e?.message ?? String(err)}`);\n });\n\n watcher.on(\"add\", (path) => emit(\"create\", path));\n watcher.on(\"change\", (path) => emit(\"save\", path));\n watcher.on(\"unlink\", (path) => emit(\"delete\", path));\n },\n\n async stop() {\n if (watcher) {\n await watcher.close();\n watcher = null;\n }\n },\n };\n}\n","// Watches `.git/HEAD` for branch switches (via fs.watch) and polls\n// `git status --porcelain` every ~2s to surface uncommitted-diff changes.\n// Always best-effort: in a non-git directory or when git is missing, the\n// watcher simply emits nothing.\n\nimport { execFile } from \"node:child_process\";\nimport { watch, type FSWatcher } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nimport type { GitEvent } from \"./activity-log.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst POLL_MS = 2000;\n\nexport interface GitWatcher {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\nexport type GitEventHandler = (e: GitEvent) => void | Promise<void>;\n\nasync function readHeadBranch(projectRoot: string): Promise<string | null> {\n try {\n const head = await readFile(join(projectRoot, \".git\", \"HEAD\"), \"utf8\");\n const m = head.trim().match(/^ref:\\s+refs\\/heads\\/(.+)$/);\n return m?.[1] ?? null;\n } catch {\n return null;\n }\n}\n\nasync function readStatusPorcelain(projectRoot: string): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync(\"git\", [\"status\", \"--porcelain\"], {\n cwd: projectRoot,\n });\n return stdout;\n } catch {\n return null;\n }\n}\n\nexport function createGitWatcher(root: string, onEvent: GitEventHandler): GitWatcher {\n let headWatcher: FSWatcher | null = null;\n let pollTimer: NodeJS.Timeout | null = null;\n let lastBranch: string | null = null;\n let lastStatus: string | null = null;\n\n const emitSafe = async (event: GitEvent) => {\n try {\n await onEvent(event);\n } catch {\n // swallow\n }\n };\n\n const checkHead = async () => {\n const branch = await readHeadBranch(root);\n if (branch && branch !== lastBranch) {\n const prev = lastBranch;\n lastBranch = branch;\n if (prev !== null) {\n await emitSafe({\n kind: \"branch-switch\",\n details: { from: prev, to: branch },\n ts: new Date().toISOString(),\n });\n }\n }\n };\n\n const pollStatus = async () => {\n const status = await readStatusPorcelain(root);\n if (status === null) return;\n if (lastStatus !== null && status !== lastStatus) {\n const prevFiles = parseStatusFiles(lastStatus);\n const nowFiles = parseStatusFiles(status);\n const added = nowFiles.filter((f) => !prevFiles.includes(f));\n const removed = prevFiles.filter((f) => !nowFiles.includes(f));\n await emitSafe({\n kind: \"diff-change\",\n details: {\n changed_count: nowFiles.length,\n newly_dirty: added,\n newly_clean: removed,\n },\n ts: new Date().toISOString(),\n });\n }\n lastStatus = status;\n };\n\n return {\n async start() {\n // Seed initial branch + status so the first real change emits an event\n // rather than a stale \"from null\".\n lastBranch = await readHeadBranch(root);\n lastStatus = await readStatusPorcelain(root);\n\n try {\n headWatcher = watch(join(root, \".git\", \"HEAD\"), () => {\n void checkHead();\n });\n // fs.watch emits \"error\" for transient OS issues (EPERM on lock\n // files, ENOENT when refs get rewritten). Swallow them — we never\n // want a transient FS event to crash the syn process.\n headWatcher.on(\"error\", () => {\n // silent — branch-switch detection is best-effort\n });\n } catch {\n // .git/HEAD not present — silently no-op\n }\n\n pollTimer = setInterval(() => {\n void pollStatus();\n }, POLL_MS);\n pollTimer.unref?.();\n },\n\n async stop() {\n if (headWatcher) {\n headWatcher.close();\n headWatcher = null;\n }\n if (pollTimer) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n },\n };\n}\n\nfunction parseStatusFiles(porcelain: string): string[] {\n return porcelain\n .split(/\\r?\\n/)\n .map((l) => l.slice(3).trim())\n .filter((l) => l.length > 0);\n}\n","// `syn scan [path]` — bootstrap then walk + parse + write graph.\n// Also invoked by the default `syn .` flow (M3 will chain start-claude after).\n\nimport { resolve } from \"node:path\";\n\nimport { buildGraph, buildSymbolIndex } from \"../scanner/extract.js\";\nimport { parseFile, type ParsedFile } from \"../scanner/parser.js\";\nimport { walk, type WalkedFile } from \"../scanner/walker.js\";\nimport { writeGraph, writeSymbolIndex } from \"../graph/store.js\";\nimport { log } from \"../shared/logger.js\";\nimport { resolvePaths } from \"../shared/paths.js\";\nimport { bootstrap } from \"./bootstrap.js\";\n\nconst PARSABLE_EXTS = new Set([\n \".ts\", \".tsx\", \".cts\", \".mts\",\n \".js\", \".jsx\", \".cjs\", \".mjs\",\n \".py\", \".pyi\",\n \".svelte\",\n \".vue\",\n \".go\",\n \".rs\",\n \".java\",\n \".kt\", \".kts\",\n \".php\",\n \".rb\",\n \".c\", \".h\",\n \".cpp\", \".cc\", \".cxx\", \".hpp\", \".hh\", \".hxx\",\n \".dart\",\n \".cs\",\n]);\n\nexport interface ScanResult {\n walked: number;\n parsed: number;\n symbolCount: number;\n edgeCount: number;\n durationMs: number;\n}\n\nexport interface ScanOptions {\n /** Suppress per-step log output (used for branch-switch rescans). */\n silent?: boolean;\n}\n\n/**\n * Core scan pipeline — bootstrap + walk + parse + write graph. Importable\n * from anywhere (server, CLI, tests). `scanCommand` is just a logging wrapper\n * around this. Pass `silent: true` to skip the chatty progress output.\n */\nexport async function scanProject(projectRootRaw: string, opts: ScanOptions = {}): Promise<ScanResult> {\n const projectRoot = resolve(projectRootRaw);\n const paths = resolvePaths(projectRoot);\n const start = Date.now();\n const verbose = !opts.silent;\n\n if (verbose) log.info(`scanning ${projectRoot}`);\n\n const boot = await bootstrap(paths);\n if (verbose) {\n if (boot.graphCreated) log.info(\" created .synthra-graph/\");\n if (boot.contextCreated) log.info(\" created .synthra/\");\n if (boot.gitignoreUpdated) log.info(\" updated .gitignore\");\n if (boot.claudeMdCreated) {\n log.info(\" created CLAUDE.md — onboarding skeleton for the agent\");\n log.info(\" ↳ fill in Build / Conventions / Decisions (or run /init in Claude to auto-draft)\");\n } else if (boot.claudeMdUpdated) {\n log.info(\" updated CLAUDE.md\");\n }\n }\n\n const walked: WalkedFile[] = [];\n for await (const file of walk(projectRoot)) walked.push(file);\n if (verbose) log.info(` walked ${walked.length} files`);\n\n const parsable = walked.filter((f) => PARSABLE_EXTS.has(f.ext));\n const parsed: ParsedFile[] = [];\n let parseErrors = 0;\n for (const file of parsable) {\n try {\n parsed.push(await parseFile(file));\n } catch (err) {\n parseErrors += 1;\n if (verbose) log.debug(` parse failed: ${file.relPath} — ${(err as Error).message}`);\n }\n }\n if (verbose) {\n log.info(\n ` parsed ${parsed.length} files (${walked.length - parsable.length} skipped` +\n (parseErrors ? `, ${parseErrors} errored` : \"\") +\n \")\",\n );\n }\n\n const graph = await buildGraph(projectRoot, parsed);\n const symbolIndex = buildSymbolIndex(graph);\n\n await writeGraph(paths.infoGraph, graph);\n await writeSymbolIndex(paths.symbolIndex, symbolIndex);\n\n if (verbose) {\n log.info(\n ` wrote ${paths.infoGraph} — ${graph.symbol_count} symbols, ${graph.edge_count} edges`,\n );\n log.info(` wrote ${paths.symbolIndex} — ${Object.keys(symbolIndex).length} names`);\n }\n\n const durationMs = Date.now() - start;\n if (verbose) log.info(`done in ${(durationMs / 1000).toFixed(2)}s`);\n\n return {\n walked: walked.length,\n parsed: parsed.length,\n symbolCount: graph.symbol_count,\n edgeCount: graph.edge_count,\n durationMs,\n };\n}\n\n// Thin alias so the CLI command keeps its current name. Drop in v0.2 if we\n// settle on a single public function.\nexport async function scanCommand(rawPath: string): Promise<ScanResult> {\n return scanProject(rawPath);\n}\n","// Turns ParsedFile[] into a GraphSchema (nodes + edges).\n//\n// Edges produced in M1:\n// defines : file → symbol\n// imports : file → file (when the import target resolves inside the project)\n// tests : test-file → source-file (for foo.test.ts ↔ foo.ts)\n\nimport { dirname, join, posix } from \"node:path\";\n\nimport type { Edge, FileNode, GraphSchema, SymbolIndex, SymbolNode } from \"../graph/types.js\";\nimport { fileHash } from \"./hash.js\";\nimport { extractKeywords } from \"./keywords.js\";\nimport type { ParsedFile, ParsedSymbol } from \"./parser.js\";\n\nconst RESOLVE_EXTS = [\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\", \".py\", \".svelte\", \".vue\", \".dart\"];\nconst INDEX_FILES = [\"index.ts\", \"index.tsx\", \"index.js\", \"index.jsx\", \"__init__.py\"];\n\nfunction fileId(relPath: string): string {\n return `file:${relPath}`;\n}\n\nfunction symbolId(relPath: string, sym: ParsedSymbol): string {\n return `symbol:${relPath}::${sym.name}:${sym.startLine}`;\n}\n\nfunction toFileNode(parsed: ParsedFile): FileNode {\n const content = parsed.source;\n return {\n id: fileId(parsed.file.relPath),\n kind: \"file\",\n path: parsed.file.relPath,\n ext: parsed.file.ext,\n size: parsed.file.size,\n keywords: extractKeywords(content, parsed.file.ext),\n content,\n summary: extractSummary(content),\n file_hash: fileHash(content),\n };\n}\n\nfunction extractSummary(content: string): string {\n // First leading comment block (// ... or /** ... */ or # ...), trimmed to ~200 chars.\n const trimmed = content.replace(/^\\s+/, \"\");\n const slashMatch = trimmed.match(/^\\/\\/\\s?(.*(?:\\r?\\n\\/\\/\\s?.*)*)/);\n if (slashMatch?.[1]) return slashMatch[1].split(/\\r?\\n/).join(\" \").trim().slice(0, 200);\n const blockMatch = trimmed.match(/^\\/\\*\\*?([\\s\\S]*?)\\*\\//);\n if (blockMatch?.[1]) {\n return blockMatch[1]\n .split(/\\r?\\n/)\n .map((l) => l.replace(/^\\s*\\*\\s?/, \"\"))\n .join(\" \")\n .trim()\n .slice(0, 200);\n }\n const hashMatch = trimmed.match(/^#\\s?(.*(?:\\r?\\n#\\s?.*)*)/);\n if (hashMatch?.[1]) return hashMatch[1].split(/\\r?\\n/).join(\" \").trim().slice(0, 200);\n return \"\";\n}\n\nfunction toSymbolNode(parsed: ParsedFile, sym: ParsedSymbol): SymbolNode {\n return {\n id: symbolId(parsed.file.relPath, sym),\n kind: \"symbol\",\n symbol_kind: sym.kind,\n name: sym.name,\n file: parsed.file.relPath,\n start_line: sym.startLine,\n end_line: sym.endLine,\n signature: sym.signature,\n };\n}\n\n/**\n * Resolve an import specifier to a project-relative path if it refers to a\n * file inside the project. Returns `null` for external packages (no leading\n * dot) or specifiers that don't match any known file.\n */\n// Strip a trailing JS-family extension so a spec like \"./crypto.js\" can\n// resolve to \"crypto.ts\". TypeScript-style `.js` imports are common.\nconst REWRITE_EXT_RE = /\\.(js|jsx|mjs|cjs)$/;\n\nfunction resolveImport(\n fromRelPath: string,\n spec: string,\n filesByPath: Map<string, true>,\n): string | null {\n if (!spec.startsWith(\".\")) return null;\n const fromDir = posix.dirname(toPosix(fromRelPath));\n const base = posix.normalize(posix.join(fromDir, toPosix(spec)));\n\n const candidates = [base];\n const rewritten = base.replace(REWRITE_EXT_RE, \"\");\n if (rewritten !== base) candidates.push(rewritten);\n\n for (const c of candidates) {\n if (filesByPath.has(c)) return c;\n for (const ext of RESOLVE_EXTS) {\n if (filesByPath.has(c + ext)) return c + ext;\n }\n for (const idx of INDEX_FILES) {\n const candidate = posix.join(c, idx);\n if (filesByPath.has(candidate)) return candidate;\n }\n }\n return null;\n}\n\nfunction toPosix(p: string): string {\n return p.split(/[\\\\/]/).join(\"/\");\n}\n\nconst TEST_RE = /^(?<base>.+?)\\.(test|spec)\\.(?<ext>[tj]sx?|py)$/;\n\nfunction testTarget(relPath: string, filesByPath: Map<string, true>): string | null {\n const fileName = relPath.split(\"/\").pop() ?? relPath;\n const match = TEST_RE.exec(fileName);\n if (!match) return null;\n const dir = relPath.includes(\"/\") ? relPath.slice(0, relPath.lastIndexOf(\"/\") + 1) : \"\";\n const base = match.groups?.base ?? \"\";\n const ext = match.groups?.ext ?? \"\";\n if (!base || !ext) return null;\n const candidate = `${dir}${base}.${ext}`;\n if (filesByPath.has(candidate)) return candidate;\n // Try sibling extensions (e.g. foo.test.ts → foo.tsx)\n for (const e of RESOLVE_EXTS) {\n const alt = `${dir}${base}${e}`;\n if (filesByPath.has(alt)) return alt;\n }\n return null;\n}\n\nexport async function buildGraph(root: string, parsed: ParsedFile[]): Promise<GraphSchema> {\n const filesByPath = new Map<string, true>();\n for (const p of parsed) filesByPath.set(p.file.relPath, true);\n\n const nodes: (FileNode | SymbolNode)[] = [];\n const edges: Edge[] = [];\n\n for (const p of parsed) {\n const fileNode = toFileNode(p);\n nodes.push(fileNode);\n\n for (const sym of p.symbols) {\n const symNode = toSymbolNode(p, sym);\n nodes.push(symNode);\n edges.push({ from: fileNode.id, to: symNode.id, kind: \"defines\" });\n }\n\n const importEdges = new Set<string>();\n for (const spec of p.imports) {\n const target = resolveImport(p.file.relPath, spec, filesByPath);\n if (!target) continue;\n const key = `${fileNode.id}->${fileId(target)}`;\n if (importEdges.has(key)) continue;\n importEdges.add(key);\n edges.push({ from: fileNode.id, to: fileId(target), kind: \"imports\" });\n }\n\n const testTargetPath = testTarget(p.file.relPath, filesByPath);\n if (testTargetPath && testTargetPath !== p.file.relPath) {\n edges.push({ from: fileNode.id, to: fileId(testTargetPath), kind: \"tests\" });\n }\n }\n\n const symbolCount = nodes.filter((n) => n.kind === \"symbol\").length;\n const fileCount = nodes.length - symbolCount;\n\n return {\n root,\n node_count: nodes.length,\n edge_count: edges.length,\n file_count: fileCount,\n symbol_count: symbolCount,\n nodes,\n edges,\n generated_at: new Date().toISOString(),\n schema_version: 1,\n };\n}\n\nexport function buildSymbolIndex(graph: GraphSchema): SymbolIndex {\n // Null-prototype map: symbol names like \"toString\" or \"constructor\" (common\n // in Dart, where every class overrides toString) would otherwise resolve to\n // an inherited Object.prototype member and crash on the .push below.\n const out: SymbolIndex = Object.create(null);\n for (const node of graph.nodes) {\n if (node.kind !== \"symbol\") continue;\n const list = out[node.name] ?? (out[node.name] = []);\n list.push({ file: node.file, line: node.start_line, kind: node.symbol_kind });\n }\n return out;\n}\n\n// Re-export node path helpers in case downstream wants the canonical id format\nexport { fileId, symbolId };\n// Suppress unused-import lint for dirname/join from node:path — kept reserved for incremental updates.\nvoid dirname;\nvoid join;\n","// Stable, short content hash for files. Used to detect changed files\n// during incremental rescans (post-v0.1; M1 does full re-parse).\n// TODO: M1 (minimal); post-v0.1 (incremental)\n\nimport { createHash } from \"node:crypto\";\n\nexport function fileHash(content: string): string {\n return createHash(\"sha1\").update(content).digest(\"hex\").slice(0, 8);\n}\n","// Per-file keyword extraction. Used for query-time relevance ranking.\n// Tokenizes identifiers + comment words, splits camelCase/snake_case, filters\n// stopwords, and returns the top-N rare tokens scored by inverse frequency\n// against a small built-in english/code corpus.\n\nconst STOPWORDS = new Set([\n \"a\", \"an\", \"and\", \"are\", \"as\", \"at\", \"be\", \"but\", \"by\", \"do\", \"for\", \"from\",\n \"has\", \"have\", \"he\", \"if\", \"in\", \"is\", \"it\", \"its\", \"not\", \"of\", \"on\", \"or\",\n \"she\", \"that\", \"the\", \"they\", \"this\", \"to\", \"was\", \"we\", \"were\", \"will\", \"with\",\n \"you\", \"your\", \"i\", \"me\", \"my\", \"our\", \"us\", \"their\", \"them\", \"his\", \"her\",\n // common code words that add no signal\n \"function\", \"const\", \"let\", \"var\", \"class\", \"interface\", \"type\", \"enum\",\n \"import\", \"export\", \"from\", \"default\", \"return\", \"if\", \"else\", \"for\", \"while\",\n \"do\", \"switch\", \"case\", \"break\", \"continue\", \"new\", \"this\", \"super\", \"throw\",\n \"try\", \"catch\", \"finally\", \"async\", \"await\", \"yield\", \"true\", \"false\", \"null\",\n \"undefined\", \"void\", \"any\", \"string\", \"number\", \"boolean\", \"object\", \"array\",\n \"self\", \"cls\", \"def\", \"lambda\", \"pass\", \"raise\", \"with\", \"as\", \"in\",\n \"todo\", \"fixme\", \"note\",\n]);\n\nconst COMMON_CODE = new Set([\n \"value\", \"data\", \"result\", \"args\", \"kwargs\", \"options\", \"config\", \"params\",\n \"name\", \"id\", \"key\", \"index\", \"item\", \"items\", \"list\", \"map\", \"set\", \"get\",\n \"set\", \"add\", \"remove\", \"delete\", \"create\", \"update\", \"find\", \"fetch\", \"load\",\n \"save\", \"init\", \"main\", \"run\", \"start\", \"stop\", \"test\", \"check\", \"validate\",\n \"error\", \"err\", \"warn\", \"info\", \"debug\", \"log\", \"trace\", \"msg\", \"message\",\n \"path\", \"file\", \"dir\", \"url\", \"host\", \"port\", \"size\", \"length\", \"count\",\n \"input\", \"output\", \"source\", \"target\", \"callback\", \"handler\", \"listener\",\n \"props\", \"state\", \"context\", \"render\", \"component\", \"node\", \"tree\", \"root\",\n]);\n\n// Frequency weight — common-code words count for less than rare identifiers\nfunction score(token: string): number {\n if (STOPWORDS.has(token)) return 0;\n if (COMMON_CODE.has(token)) return 0.2;\n if (token.length <= 2) return 0.1;\n return 1;\n}\n\nfunction splitIdentifier(id: string): string[] {\n // snake_case + kebab-case → words\n const partsRaw = id.split(/[_\\-./]+/).filter(Boolean);\n const out: string[] = [];\n for (const part of partsRaw) {\n // camelCase / PascalCase → words. Handles \"XMLHttp\" → [\"XML\", \"Http\"]\n const camelParts = part.match(/[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|[0-9]+/g);\n if (camelParts) out.push(...camelParts);\n else out.push(part);\n }\n return out.map((w) => w.toLowerCase()).filter((w) => /[a-z]/.test(w));\n}\n\nexport function extractKeywords(content: string, _ext: string): string[] {\n // Identifiers + alphanumeric words. Picks up both code and comment text.\n const tokens = content.match(/[A-Za-z_][A-Za-z0-9_]{1,40}/g) ?? [];\n const counts = new Map<string, number>();\n for (const tok of tokens) {\n for (const word of splitIdentifier(tok)) {\n const w = score(word);\n if (w === 0) continue;\n counts.set(word, (counts.get(word) ?? 0) + w);\n }\n }\n return Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 32)\n .map(([w]) => w);\n}\n","// Dispatches a file to its language-specific parser based on extension.\n// Tree-sitter WASM grammars are loaded lazily via tree-sitter-wasms and\n// cached per language for the lifetime of the process.\n\nimport { readFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { Language, Parser } from \"web-tree-sitter\";\n\nimport type { SymbolKind } from \"../graph/types.js\";\nimport { parseC } from \"./parsers/c.js\";\nimport { parseCpp } from \"./parsers/cpp.js\";\nimport { parseCSharp } from \"./parsers/csharp.js\";\nimport { parseDart } from \"./parsers/dart.js\";\nimport { parseGo } from \"./parsers/go.js\";\nimport { parseJava } from \"./parsers/java.js\";\nimport { parseKotlin } from \"./parsers/kotlin.js\";\nimport { parsePhp } from \"./parsers/php.js\";\nimport { parsePython } from \"./parsers/python.js\";\nimport { parseRuby } from \"./parsers/ruby.js\";\nimport { parseRust } from \"./parsers/rust.js\";\nimport { parseSvelte } from \"./parsers/svelte.js\";\nimport { parseTypeScript } from \"./parsers/typescript.js\";\nimport { parseVue } from \"./parsers/vue.js\";\nimport type { WalkedFile } from \"./walker.js\";\n\nexport interface ParsedSymbol {\n name: string;\n kind: SymbolKind;\n startLine: number;\n endLine: number;\n signature: string;\n}\n\nexport interface ParsedFile {\n file: WalkedFile;\n source: string;\n symbols: ParsedSymbol[];\n imports: string[];\n calls: Array<{ from: string; to: string }>;\n}\n\nconst require = createRequire(import.meta.url);\n\nexport type GrammarName =\n | \"typescript\"\n | \"tsx\"\n | \"javascript\"\n | \"python\"\n | \"go\"\n | \"rust\"\n | \"java\"\n | \"kotlin\"\n | \"php\"\n | \"ruby\"\n | \"c\"\n | \"cpp\"\n | \"dart\"\n | \"csharp\";\n\nconst GRAMMAR_FILES: Record<GrammarName, string> = {\n typescript: \"tree-sitter-wasms/out/tree-sitter-typescript.wasm\",\n tsx: \"tree-sitter-wasms/out/tree-sitter-tsx.wasm\",\n javascript: \"tree-sitter-wasms/out/tree-sitter-javascript.wasm\",\n python: \"tree-sitter-wasms/out/tree-sitter-python.wasm\",\n go: \"tree-sitter-wasms/out/tree-sitter-go.wasm\",\n rust: \"tree-sitter-wasms/out/tree-sitter-rust.wasm\",\n java: \"tree-sitter-wasms/out/tree-sitter-java.wasm\",\n kotlin: \"tree-sitter-wasms/out/tree-sitter-kotlin.wasm\",\n php: \"tree-sitter-wasms/out/tree-sitter-php.wasm\",\n ruby: \"tree-sitter-wasms/out/tree-sitter-ruby.wasm\",\n c: \"tree-sitter-wasms/out/tree-sitter-c.wasm\",\n cpp: \"tree-sitter-wasms/out/tree-sitter-cpp.wasm\",\n dart: \"tree-sitter-wasms/out/tree-sitter-dart.wasm\",\n csharp: \"tree-sitter-wasms/out/tree-sitter-c_sharp.wasm\",\n};\n\nlet parserInit: Promise<void> | null = null;\nconst languageCache = new Map<GrammarName, Language>();\n\nasync function ensureParserInit(): Promise<void> {\n if (!parserInit) {\n parserInit = Parser.init();\n }\n return parserInit;\n}\n\nexport async function loadGrammar(name: GrammarName): Promise<Language> {\n await ensureParserInit();\n const cached = languageCache.get(name);\n if (cached) return cached;\n const wasmPath = require.resolve(GRAMMAR_FILES[name]);\n const lang = await Language.load(wasmPath);\n languageCache.set(name, lang);\n return lang;\n}\n\nexport interface LoadedParser {\n parser: Parser;\n language: Language;\n}\n\nexport async function createParser(name: GrammarName): Promise<LoadedParser> {\n const language = await loadGrammar(name);\n const parser = new Parser();\n parser.setLanguage(language);\n return { parser, language };\n}\n\nfunction emptyParsed(file: WalkedFile, source: string): ParsedFile {\n return { file, source, symbols: [], imports: [], calls: [] };\n}\n\nexport async function parseFile(f: WalkedFile): Promise<ParsedFile> {\n let source: string;\n try {\n source = await readFile(f.absPath, \"utf8\");\n } catch {\n return emptyParsed(f, \"\");\n }\n\n switch (f.ext) {\n case \".ts\":\n case \".tsx\":\n case \".cts\":\n case \".mts\":\n case \".js\":\n case \".jsx\":\n case \".cjs\":\n case \".mjs\":\n return parseTypeScript(f, source);\n case \".py\":\n case \".pyi\":\n return parsePython(f, source);\n case \".svelte\":\n return parseSvelte(f, source);\n case \".vue\":\n return parseVue(f, source);\n case \".go\":\n return parseGo(f, source);\n case \".rs\":\n return parseRust(f, source);\n case \".java\":\n return parseJava(f, source);\n case \".kt\":\n case \".kts\":\n return parseKotlin(f, source);\n case \".php\":\n return parsePhp(f, source);\n case \".rb\":\n return parseRuby(f, source);\n case \".c\":\n case \".h\":\n return parseC(f, source);\n case \".cpp\":\n case \".cc\":\n case \".cxx\":\n case \".hpp\":\n case \".hh\":\n case \".hxx\":\n return parseCpp(f, source);\n case \".dart\":\n return parseDart(f, source);\n case \".cs\":\n return parseCSharp(f, source);\n default:\n return emptyParsed(f, source);\n }\n}\n","// Generic tree-sitter parser used by the simpler-grammar languages\n// (Go, Rust, Java, Kotlin, PHP, Ruby, C, C++, Dart, C#).\n//\n// Each language file defines:\n// - which tree-sitter grammar to load\n// - a query string with capture names like `@function`, `@function.name`\n// - a `decls` table mapping declaration-capture pairs to SymbolKind\n// - optional `importCapture` for collecting import edges\n// Everything else (parser init, error handling, dedupe) lives here.\n\nimport { Query, type Node } from \"web-tree-sitter\";\n\nimport type { SymbolKind } from \"../../graph/types.js\";\nimport { createParser, type GrammarName, type ParsedFile, type ParsedSymbol } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nexport interface DeclCapture {\n /** Capture name for the declaration node, e.g. \"function\". */\n declCapture: string;\n /** Capture name for the symbol's name node, e.g. \"function.name\". */\n nameCapture: string;\n /** SymbolKind to assign. */\n kind: SymbolKind;\n}\n\nexport interface GenericParserConfig {\n grammar: GrammarName;\n query: string;\n decls: DeclCapture[];\n /** Capture name for import-source nodes. Skipped when omitted. */\n importCapture?: string;\n}\n\nexport function firstLine(text: string, max = 200): string {\n const line = text.split(/\\r?\\n/, 1)[0] ?? \"\";\n return line.length > max ? line.slice(0, max) + \"…\" : line;\n}\n\nfunction cleanImport(s: string): string {\n // Strip surrounding string-literal quotes (used by Go, Dart, C/C++).\n // Strip angle brackets used by C/C++ system includes.\n return s.replace(/^[\"'`<]+|[\"'`>]+$/g, \"\").trim();\n}\n\nexport async function runGenericParser(\n config: GenericParserConfig,\n f: WalkedFile,\n source: string,\n): Promise<ParsedFile> {\n let symbols: ParsedSymbol[] = [];\n let imports: string[] = [];\n\n try {\n const { parser, language } = await createParser(config.grammar);\n const tree = parser.parse(source);\n if (!tree) return { file: f, source, symbols, imports, calls: [] };\n\n const query = new Query(language, config.query);\n const matches = query.matches(tree.rootNode);\n\n for (const match of matches) {\n const byName = new Map<string, Node>();\n for (const cap of match.captures) byName.set(cap.name, cap.node);\n\n let matched: DeclCapture | null = null;\n for (const d of config.decls) {\n if (byName.has(d.declCapture) && byName.has(d.nameCapture)) {\n matched = d;\n break;\n }\n }\n\n if (matched) {\n const declNode = byName.get(matched.declCapture)!;\n const nameNode = byName.get(matched.nameCapture)!;\n symbols.push({\n name: nameNode.text,\n kind: matched.kind,\n startLine: declNode.startPosition.row + 1,\n endLine: declNode.endPosition.row + 1,\n signature: firstLine(declNode.text),\n });\n continue;\n }\n\n if (config.importCapture) {\n const imp = byName.get(config.importCapture);\n if (imp) imports.push(cleanImport(imp.text));\n }\n }\n\n const seen = new Set<string>();\n symbols = symbols.filter((s) => {\n const k = `${s.name}:${s.startLine}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n imports = Array.from(new Set(imports)).filter((s) => s.length > 0);\n } catch {\n // Query compile or parse failure — return what we have. Silent so a single\n // bad file doesn't abort the whole scan.\n }\n\n return { file: f, source, symbols, imports, calls: [] };\n}\n","// C parser. Function definitions, structs, enums, typedefs, #include directives.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_definition declarator: (function_declarator declarator: (identifier) @function.name)) @function\n(struct_specifier name: (type_identifier) @struct.name) @struct\n(enum_specifier name: (type_identifier) @enum.name) @enum\n(type_definition declarator: (type_identifier) @type.name) @type\n(preproc_include path: (string_literal) @import)\n(preproc_include path: (system_lib_string) @import)\n`;\n\nexport async function parseC(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"c\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"struct\", nameCapture: \"struct.name\", kind: \"class\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n { declCapture: \"type\", nameCapture: \"type.name\", kind: \"type\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// C++ parser. Functions, classes, structs, enums, namespaces, #includes.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_definition declarator: (function_declarator declarator: (identifier) @function.name)) @function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier) @method.name)) @method\n(class_specifier name: (type_identifier) @class.name) @class\n(struct_specifier name: (type_identifier) @struct.name) @struct\n(enum_specifier name: (type_identifier) @enum.name) @enum\n(namespace_definition name: (namespace_identifier) @namespace.name) @namespace\n(preproc_include path: (string_literal) @import)\n(preproc_include path: (system_lib_string) @import)\n`;\n\nexport async function parseCpp(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"cpp\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"struct\", nameCapture: \"struct.name\", kind: \"class\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n { declCapture: \"namespace\", nameCapture: \"namespace.name\", kind: \"class\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// C# (.NET) parser. Classes, interfaces, structs, methods, namespaces.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(class_declaration name: (identifier) @class.name) @class\n(interface_declaration name: (identifier) @interface.name) @interface\n(struct_declaration name: (identifier) @struct.name) @struct\n(enum_declaration name: (identifier) @enum.name) @enum\n(method_declaration name: (identifier) @method.name) @method\n(namespace_declaration name: (_) @namespace.name) @namespace\n(using_directive (_) @import)\n`;\n\nexport async function parseCSharp(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"csharp\",\n query: QUERY,\n decls: [\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"interface\", nameCapture: \"interface.name\", kind: \"interface\" },\n { declCapture: \"struct\", nameCapture: \"struct.name\", kind: \"class\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"namespace\", nameCapture: \"namespace.name\", kind: \"class\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// Dart parser. v0.1.11 — real symbol extraction + import parsing on top of the\n// ABI-v15 grammar that ships in tree-sitter-wasms.\n//\n// Distinguishes top-level function_signature (kind: function) from\n// function_signature nested under method_signature (kind: method) by\n// anchoring the top-level pattern under `program`.\n//\n// Imports: `package:foo/bar.dart` and `dart:async` are stripped — they cross\n// the project boundary. Bare `foo.dart` is normalized to `./foo.dart` so the\n// shared resolveImport() (which requires a leading `.`) treats it as a\n// same-directory relative import.\n\nimport { Query, type Node } from \"web-tree-sitter\";\nimport type { SymbolKind } from \"../../graph/types.js\";\nimport { createParser, type ParsedFile, type ParsedSymbol } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nconst QUERY = `\n(class_definition name: (identifier) @class.name) @class\n(mixin_declaration (identifier) @mixin.name) @mixin\n(extension_declaration name: (identifier) @ext.name) @ext\n(enum_declaration name: (identifier) @enum.name) @enum\n(type_alias (type_identifier) @typedef.name) @typedef\n\n(program (function_signature name: (identifier) @function.name) @function)\n\n(method_signature (function_signature name: (identifier) @method.name)) @method\n(method_signature (getter_signature name: (identifier) @getter.name)) @getter\n(method_signature (setter_signature name: (identifier) @setter.name)) @setter\n(constructor_signature name: (identifier) @ctor.name) @ctor\n\n(import_or_export (library_import (import_specification (configurable_uri (uri (string_literal) @import)))))\n`;\n\ninterface DeclShape {\n declCap: string;\n nameCap: string;\n kind: SymbolKind;\n}\n\nconst DECLS: DeclShape[] = [\n { declCap: \"class\", nameCap: \"class.name\", kind: \"class\" },\n { declCap: \"mixin\", nameCap: \"mixin.name\", kind: \"class\" },\n { declCap: \"ext\", nameCap: \"ext.name\", kind: \"class\" },\n { declCap: \"enum\", nameCap: \"enum.name\", kind: \"enum\" },\n { declCap: \"typedef\", nameCap: \"typedef.name\", kind: \"type\" },\n { declCap: \"function\", nameCap: \"function.name\", kind: \"function\" },\n { declCap: \"method\", nameCap: \"method.name\", kind: \"method\" },\n { declCap: \"getter\", nameCap: \"getter.name\", kind: \"method\" },\n { declCap: \"setter\", nameCap: \"setter.name\", kind: \"method\" },\n { declCap: \"ctor\", nameCap: \"ctor.name\", kind: \"method\" },\n];\n\nfunction firstLine(text: string, max = 200): string {\n const line = text.split(/\\r?\\n/, 1)[0] ?? \"\";\n return line.length > max ? line.slice(0, max) + \"…\" : line;\n}\n\n// Strip surrounding string-literal quotes and normalize bare same-directory\n// imports (Dart allows `import 'foo.dart';` without a leading `./`) so\n// resolveImport() — which keys off a leading dot — can match them.\nfunction normalizeDartImport(raw: string): string | null {\n const stripped = raw.replace(/^['\"]|['\"]$/g, \"\");\n if (!stripped) return null;\n if (stripped.startsWith(\"package:\")) return null;\n if (stripped.startsWith(\"dart:\")) return null;\n if (stripped.startsWith(\".\") || stripped.startsWith(\"/\")) return stripped;\n return `./${stripped}`;\n}\n\nexport async function parseDart(f: WalkedFile, source: string): Promise<ParsedFile> {\n let symbols: ParsedSymbol[] = [];\n let imports: string[] = [];\n\n try {\n const { parser, language } = await createParser(\"dart\");\n const tree = parser.parse(source);\n if (!tree) return { file: f, source, symbols, imports, calls: [] };\n\n const query = new Query(language, QUERY);\n const matches = query.matches(tree.rootNode);\n\n for (const match of matches) {\n const byName = new Map<string, Node>();\n for (const cap of match.captures) byName.set(cap.name, cap.node);\n\n let matched: DeclShape | null = null;\n for (const d of DECLS) {\n if (byName.has(d.declCap) && byName.has(d.nameCap)) {\n matched = d;\n break;\n }\n }\n\n if (matched) {\n const declNode = byName.get(matched.declCap)!;\n const nameNode = byName.get(matched.nameCap)!;\n symbols.push({\n name: nameNode.text,\n kind: matched.kind,\n startLine: declNode.startPosition.row + 1,\n endLine: declNode.endPosition.row + 1,\n signature: firstLine(declNode.text),\n });\n continue;\n }\n\n const importNode = byName.get(\"import\");\n if (importNode) {\n const norm = normalizeDartImport(importNode.text);\n if (norm) imports.push(norm);\n }\n }\n\n const seen = new Set<string>();\n symbols = symbols.filter((s) => {\n const k = `${s.name}:${s.startLine}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n imports = Array.from(new Set(imports));\n } catch {\n // swallow — see _generic.ts for the rationale (single bad file shouldn't\n // abort the whole scan).\n }\n\n return { file: f, source, symbols, imports, calls: [] };\n}\n","// Go parser. Functions, methods, type declarations, imports.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_declaration name: (identifier) @function.name) @function\n(method_declaration name: (field_identifier) @method.name) @method\n(type_spec name: (type_identifier) @type.name) @type\n(import_spec path: (interpreted_string_literal) @import)\n`;\n\nexport async function parseGo(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"go\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"type\", nameCapture: \"type.name\", kind: \"type\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// Java parser. Classes, interfaces, methods, imports.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(class_declaration name: (identifier) @class.name) @class\n(interface_declaration name: (identifier) @interface.name) @interface\n(method_declaration name: (identifier) @method.name) @method\n(enum_declaration name: (identifier) @enum.name) @enum\n(import_declaration (scoped_identifier) @import)\n`;\n\nexport async function parseJava(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"java\",\n query: QUERY,\n decls: [\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"interface\", nameCapture: \"interface.name\", kind: \"interface\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// Kotlin parser. Functions, classes, objects, interfaces, imports.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_declaration (simple_identifier) @function.name) @function\n(class_declaration (type_identifier) @class.name) @class\n(object_declaration (type_identifier) @object.name) @object\n(import_header (identifier) @import)\n`;\n\nexport async function parseKotlin(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"kotlin\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"object\", nameCapture: \"object.name\", kind: \"class\" },\n ],\n importCapture: \"import\",\n },\n f,\n source,\n );\n}\n","// PHP parser. Functions, classes, interfaces, methods, traits.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_definition name: (name) @function.name) @function\n(class_declaration name: (name) @class.name) @class\n(interface_declaration name: (name) @interface.name) @interface\n(trait_declaration name: (name) @trait.name) @trait\n(method_declaration name: (name) @method.name) @method\n`;\n\nexport async function parsePhp(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"php\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"interface\", nameCapture: \"interface.name\", kind: \"interface\" },\n { declCapture: \"trait\", nameCapture: \"trait.name\", kind: \"class\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n ],\n },\n f,\n source,\n );\n}\n","// Python parser using tree-sitter-python WASM.\n// Extracts: function/class definitions, methods, and import statements.\n\nimport { Query, type Node } from \"web-tree-sitter\";\nimport { createParser, type ParsedFile, type ParsedSymbol } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nconst QUERY = `\n(function_definition name: (identifier) @function.name) @function\n(class_definition name: (identifier) @class.name) @class\n(import_statement name: (dotted_name) @import.module)\n(import_from_statement module_name: (dotted_name) @import.from)\n(import_from_statement module_name: (relative_import) @import.from)\n`;\n\nfunction firstLine(text: string, max = 200): string {\n const line = text.split(/\\r?\\n/, 1)[0] ?? \"\";\n return line.length > max ? line.slice(0, max) + \"…\" : line;\n}\n\nexport async function parsePython(f: WalkedFile, source: string): Promise<ParsedFile> {\n let symbols: ParsedSymbol[] = [];\n let imports: string[] = [];\n\n try {\n const { parser, language } = await createParser(\"python\");\n const tree = parser.parse(source);\n if (!tree) return { file: f, source, symbols, imports, calls: [] };\n\n const query = new Query(language, QUERY);\n const matches = query.matches(tree.rootNode);\n\n for (const match of matches) {\n const byName = new Map<string, Node>();\n for (const cap of match.captures) byName.set(cap.name, cap.node);\n\n const funcDecl = byName.get(\"function\");\n const funcName = byName.get(\"function.name\");\n if (funcDecl && funcName) {\n const parentType = funcDecl.parent?.parent?.type;\n const isMethod = parentType === \"class_definition\";\n symbols.push({\n name: funcName.text,\n kind: isMethod ? \"method\" : \"function\",\n startLine: funcDecl.startPosition.row + 1,\n endLine: funcDecl.endPosition.row + 1,\n signature: firstLine(funcDecl.text),\n });\n continue;\n }\n\n const classDecl = byName.get(\"class\");\n const className = byName.get(\"class.name\");\n if (classDecl && className) {\n symbols.push({\n name: className.text,\n kind: \"class\",\n startLine: classDecl.startPosition.row + 1,\n endLine: classDecl.endPosition.row + 1,\n signature: firstLine(classDecl.text),\n });\n continue;\n }\n\n const importNode = byName.get(\"import.module\") ?? byName.get(\"import.from\");\n if (importNode) imports.push(importNode.text);\n }\n\n const seen = new Set<string>();\n symbols = symbols.filter((s) => {\n const key = `${s.name}:${s.startLine}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n imports = Array.from(new Set(imports));\n } catch {\n // swallow parse errors\n }\n\n return { file: f, source, symbols, imports, calls: [] };\n}\n","// Ruby parser. Methods, classes, modules.\n// Imports omitted — Ruby's `require` is dynamic and hard to capture cleanly;\n// keyword indexing still surfaces dependencies.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(method name: (identifier) @function.name) @function\n(singleton_method name: (identifier) @method.name) @method\n(class name: (constant) @class.name) @class\n(module name: (constant) @module.name) @module\n`;\n\nexport async function parseRuby(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"ruby\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"method\", nameCapture: \"method.name\", kind: \"method\" },\n { declCapture: \"class\", nameCapture: \"class.name\", kind: \"class\" },\n { declCapture: \"module\", nameCapture: \"module.name\", kind: \"class\" },\n ],\n },\n f,\n source,\n );\n}\n","// Rust parser. Functions, structs, enums, traits, impls.\n// Import capture is omitted — `use` paths are nested and complex; the file\n// will still be walked + keyword-indexed.\n\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\nimport { runGenericParser } from \"./_generic.js\";\n\nconst QUERY = `\n(function_item name: (identifier) @function.name) @function\n(struct_item name: (type_identifier) @struct.name) @struct\n(enum_item name: (type_identifier) @enum.name) @enum\n(trait_item name: (type_identifier) @trait.name) @trait\n(impl_item type: (type_identifier) @impl.name) @impl\n`;\n\nexport async function parseRust(f: WalkedFile, source: string): Promise<ParsedFile> {\n return runGenericParser(\n {\n grammar: \"rust\",\n query: QUERY,\n decls: [\n { declCapture: \"function\", nameCapture: \"function.name\", kind: \"function\" },\n { declCapture: \"struct\", nameCapture: \"struct.name\", kind: \"class\" },\n { declCapture: \"enum\", nameCapture: \"enum.name\", kind: \"enum\" },\n { declCapture: \"trait\", nameCapture: \"trait.name\", kind: \"interface\" },\n { declCapture: \"impl\", nameCapture: \"impl.name\", kind: \"class\" },\n ],\n },\n f,\n source,\n );\n}\n","// TS/JS parser using tree-sitter-typescript / -tsx WASM grammars.\r\n// Extracts: function/class/interface/type/enum declarations, exported consts,\r\n// arrow functions assigned to const, and import sources.\r\n\r\nimport { Query, type Node } from \"web-tree-sitter\";\r\nimport type { SymbolKind } from \"../../graph/types.js\";\r\nimport { createParser, type GrammarName, type ParsedFile, type ParsedSymbol } from \"../parser.js\";\r\nimport type { WalkedFile } from \"../walker.js\";\r\n\r\n// TS / TSX query — uses the type-identifier node type for class names, includes\r\n// interface / type-alias / enum declarations that don't exist in plain JS.\r\nconst TS_QUERY = `\r\n(function_declaration name: (identifier) @function.name) @function\r\n(class_declaration name: (type_identifier) @class.name) @class\r\n(interface_declaration name: (type_identifier) @interface.name) @interface\r\n(type_alias_declaration name: (type_identifier) @type.name) @type\r\n(enum_declaration name: (identifier) @enum.name) @enum\r\n(method_definition name: (property_identifier) @method.name) @method\r\n(lexical_declaration (variable_declarator name: (identifier) @const-fn.name value: [(arrow_function) (function_expression)])) @const-fn\r\n(import_statement source: (string) @import)\r\n`;\r\n\r\n// JS query — class names are plain identifiers (JS grammar has no\r\n// type_identifier node). No interface / type_alias / enum since JS lacks them.\r\n// Adds a call_expression capture for CommonJS require('x'); filtered in the\r\n// matching loop by checking the function identifier text equals \"require\".\r\nconst JS_QUERY = `\r\n(function_declaration name: (identifier) @function.name) @function\r\n(class_declaration name: (identifier) @class.name) @class\r\n(method_definition name: (property_identifier) @method.name) @method\r\n(lexical_declaration (variable_declarator name: (identifier) @const-fn.name value: [(arrow_function) (function_expression)])) @const-fn\r\n(import_statement source: (string) @import)\r\n(call_expression function: (identifier) @_require_fn arguments: (arguments . (string) @require_source))\r\n`;\r\n\r\nfunction grammarFor(ext: string): GrammarName {\r\n if (ext === \".tsx\" || ext === \".jsx\") return \"tsx\";\r\n if (ext === \".js\" || ext === \".cjs\" || ext === \".mjs\") return \"javascript\";\r\n return \"typescript\";\r\n}\r\n\r\nfunction queryFor(grammar: GrammarName): string {\r\n return grammar === \"javascript\" ? JS_QUERY : TS_QUERY;\r\n}\r\n\r\nfunction unquote(s: string): string {\r\n return s.replace(/^[\"'`]|[\"'`]$/g, \"\");\r\n}\r\n\r\nfunction firstLine(text: string, max = 200): string {\r\n const line = text.split(/\\r?\\n/, 1)[0] ?? \"\";\r\n return line.length > max ? line.slice(0, max) + \"…\" : line;\r\n}\r\n\r\ninterface DeclShape {\r\n decl: Node;\r\n name: Node;\r\n kind: SymbolKind;\r\n}\r\n\r\nfunction shapeFromCaptures(captures: Map<string, Node>): DeclShape | null {\r\n const findDecl = (k: string, sk: SymbolKind): DeclShape | null => {\r\n const decl = captures.get(k);\r\n const name = captures.get(`${k}.name`);\r\n return decl && name ? { decl, name, kind: sk } : null;\r\n };\r\n\r\n return (\r\n findDecl(\"function\", \"function\") ??\r\n findDecl(\"class\", \"class\") ??\r\n findDecl(\"interface\", \"interface\") ??\r\n findDecl(\"type\", \"type\") ??\r\n findDecl(\"enum\", \"enum\") ??\r\n findDecl(\"method\", \"method\") ??\r\n findDecl(\"const-fn\", \"function\")\r\n );\r\n}\r\n\r\nexport async function parseTypeScript(f: WalkedFile, source: string): Promise<ParsedFile> {\r\n const grammar = grammarFor(f.ext);\r\n let symbols: ParsedSymbol[] = [];\r\n let imports: string[] = [];\r\n\r\n try {\r\n const { parser, language } = await createParser(grammar);\r\n const tree = parser.parse(source);\r\n if (!tree) return { file: f, source, symbols, imports, calls: [] };\r\n\r\n const query = new Query(language, queryFor(grammar));\r\n const matches = query.matches(tree.rootNode);\r\n\r\n for (const match of matches) {\r\n const byName = new Map<string, Node>();\r\n for (const cap of match.captures) byName.set(cap.name, cap.node);\r\n\r\n const shape = shapeFromCaptures(byName);\r\n if (shape) {\r\n symbols.push({\r\n name: shape.name.text,\r\n kind: shape.kind,\r\n startLine: shape.decl.startPosition.row + 1,\r\n endLine: shape.decl.endPosition.row + 1,\r\n signature: firstLine(shape.decl.text),\r\n });\r\n continue;\r\n }\r\n const importNode = byName.get(\"import\");\r\n if (importNode) {\r\n imports.push(unquote(importNode.text));\r\n continue;\r\n }\r\n // CommonJS require('x') — only captured by JS_QUERY. The identifier\r\n // must literally be \"require\" (not setTimeout, console, etc).\r\n const requireFn = byName.get(\"_require_fn\");\r\n const requireSource = byName.get(\"require_source\");\r\n if (requireFn && requireSource && requireFn.text === \"require\") {\r\n imports.push(unquote(requireSource.text));\r\n }\r\n }\r\n\r\n const seen = new Set<string>();\r\n symbols = symbols.filter((s) => {\r\n const key = `${s.name}:${s.startLine}`;\r\n if (seen.has(key)) return false;\r\n seen.add(key);\r\n return true;\r\n });\r\n imports = Array.from(new Set(imports));\r\n } catch {\r\n // Parse failure shouldn't abort the whole scan — return what we have.\r\n }\r\n\r\n return { file: f, source, symbols, imports, calls: [] };\r\n}\r\n","// Svelte parser. Extracts <script> and <script lang=\"ts\"> blocks and parses\n// their contents with the TypeScript parser. Tracks the original line offset\n// so reported symbol positions match the .svelte source.\n\nimport { parseTypeScript } from \"./typescript.js\";\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nconst SCRIPT_RE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script>/gi;\n\ninterface ScriptBlock {\n source: string;\n startLine: number; // 1-based line number where the script content begins\n isTsx: boolean;\n}\n\nfunction extractScripts(source: string): ScriptBlock[] {\n const out: ScriptBlock[] = [];\n for (const match of source.matchAll(SCRIPT_RE)) {\n const full = match[0];\n const inner = match[1] ?? \"\";\n const openTag = full.slice(0, full.indexOf(\">\") + 1);\n const tagStart = match.index ?? 0;\n const contentStart = tagStart + openTag.length;\n const startLine = source.slice(0, contentStart).split(/\\r?\\n/).length;\n const isTsx = /\\blang\\s*=\\s*[\"']?(ts|tsx|typescript)[\"']?/i.test(openTag);\n out.push({ source: inner, startLine, isTsx });\n }\n return out;\n}\n\nexport async function parseSvelte(f: WalkedFile, source: string): Promise<ParsedFile> {\n const blocks = extractScripts(source);\n const out: ParsedFile = { file: f, source, symbols: [], imports: [], calls: [] };\n\n for (const block of blocks) {\n const virtual: WalkedFile = { ...f, ext: block.isTsx ? \".ts\" : \".js\" };\n const parsed = await parseTypeScript(virtual, block.source);\n const offset = block.startLine - 1;\n for (const sym of parsed.symbols) {\n out.symbols.push({\n ...sym,\n startLine: sym.startLine + offset,\n endLine: sym.endLine + offset,\n });\n }\n for (const imp of parsed.imports) out.imports.push(imp);\n }\n\n // The .svelte file itself is treated as a component.\n out.symbols.push({\n name: f.relPath.split(\"/\").pop()?.replace(/\\.svelte$/i, \"\") ?? f.relPath,\n kind: \"component\",\n startLine: 1,\n endLine: source.split(/\\r?\\n/).length,\n signature: f.relPath,\n });\n out.imports = Array.from(new Set(out.imports));\n return out;\n}\n","// Vue SFC parser. Extracts <script> / <script setup> / <script lang=\"ts\">\n// blocks and parses them with the TypeScript parser, preserving line offsets.\n\nimport { parseTypeScript } from \"./typescript.js\";\nimport type { ParsedFile } from \"../parser.js\";\nimport type { WalkedFile } from \"../walker.js\";\n\nconst SCRIPT_RE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script>/gi;\n\ninterface ScriptBlock {\n source: string;\n startLine: number;\n isTs: boolean;\n}\n\nfunction extractScripts(source: string): ScriptBlock[] {\n const out: ScriptBlock[] = [];\n for (const match of source.matchAll(SCRIPT_RE)) {\n const full = match[0];\n const inner = match[1] ?? \"\";\n const openTag = full.slice(0, full.indexOf(\">\") + 1);\n const tagStart = match.index ?? 0;\n const contentStart = tagStart + openTag.length;\n const startLine = source.slice(0, contentStart).split(/\\r?\\n/).length;\n const isTs = /\\blang\\s*=\\s*[\"']?(ts|tsx|typescript)[\"']?/i.test(openTag);\n out.push({ source: inner, startLine, isTs });\n }\n return out;\n}\n\nexport async function parseVue(f: WalkedFile, source: string): Promise<ParsedFile> {\n const blocks = extractScripts(source);\n const out: ParsedFile = { file: f, source, symbols: [], imports: [], calls: [] };\n\n for (const block of blocks) {\n const virtual: WalkedFile = { ...f, ext: block.isTs ? \".ts\" : \".js\" };\n const parsed = await parseTypeScript(virtual, block.source);\n const offset = block.startLine - 1;\n for (const sym of parsed.symbols) {\n out.symbols.push({\n ...sym,\n startLine: sym.startLine + offset,\n endLine: sym.endLine + offset,\n });\n }\n for (const imp of parsed.imports) out.imports.push(imp);\n }\n\n out.symbols.push({\n name: f.relPath.split(\"/\").pop()?.replace(/\\.vue$/i, \"\") ?? f.relPath,\n kind: \"component\",\n startLine: 1,\n endLine: source.split(/\\r?\\n/).length,\n signature: f.relPath,\n });\n out.imports = Array.from(new Set(out.imports));\n return out;\n}\n","// Walks project root, yields files to parse.\n// Honors .gitignore + .synthraignore (additive — entries in either are ignored).\n// Defensive defaults skip VCS, build, and dependency directories even if absent\n// from .gitignore.\n\nimport { readFile, readdir, stat } from \"node:fs/promises\";\nimport { extname, join, relative, sep } from \"node:path\";\nimport ignore, { type Ignore } from \"ignore\";\n\nexport interface WalkedFile {\n absPath: string;\n relPath: string;\n ext: string;\n size: number;\n}\n\nexport interface WalkOptions {\n /** Maximum file size to yield (bytes). Defaults to 2 MB. */\n maxFileSize?: number;\n /** Additional ignore patterns layered on top of .gitignore + .synthraignore. */\n extraIgnore?: string[];\n}\n\nconst DEFAULT_IGNORE = [\n \".git/\",\n \".synthra/\",\n \".synthra-graph/\",\n \".claude/\",\n \"node_modules/\",\n \"dist/\",\n \"build/\",\n \"out/\",\n \"coverage/\",\n \".next/\",\n \".nuxt/\",\n \".svelte-kit/\",\n \".turbo/\",\n \".cache/\",\n \".vscode/\",\n \".idea/\",\n \".vs/\",\n // Flutter / Dart build caches — IDE-rehydrated, contain third-party\n // type stubs (typescript.d.ts, babylon.js etc.) that contaminate the graph.\n \".dart_tool/\",\n \".flutter-plugins\",\n \".flutter-plugins-dependencies\",\n // Android / Java / Kotlin / Rust\n \".gradle/\",\n \"target/\",\n // iOS / Xcode\n \"Pods/\",\n \"DerivedData/\",\n // Python\n \"__pycache__/\",\n \".venv/\",\n \"venv/\",\n \".tox/\",\n \".pytest_cache/\",\n \".mypy_cache/\",\n \".ruff_cache/\",\n // .NET\n \"obj/\",\n];\n\nconst BINARY_EXTS = new Set([\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\", \".ico\", \".bmp\",\n \".pdf\", \".zip\", \".tar\", \".gz\", \".7z\", \".rar\",\n \".mp3\", \".mp4\", \".mov\", \".avi\", \".webm\", \".wav\", \".ogg\",\n \".ttf\", \".otf\", \".woff\", \".woff2\", \".eot\",\n \".exe\", \".dll\", \".so\", \".dylib\", \".bin\", \".wasm\",\n \".lock\", \".lockb\",\n]);\n\nasync function readIgnoreFile(path: string): Promise<string[]> {\n try {\n const text = await readFile(path, \"utf8\");\n return text\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter((l) => l.length > 0 && !l.startsWith(\"#\"));\n } catch {\n return [];\n }\n}\n\nasync function buildMatcher(root: string, extra: string[]): Promise<Ignore> {\n const ig = ignore();\n ig.add(DEFAULT_IGNORE);\n ig.add(await readIgnoreFile(join(root, \".gitignore\")));\n ig.add(await readIgnoreFile(join(root, \".synthraignore\")));\n if (extra.length) ig.add(extra);\n return ig;\n}\n\nfunction toPosix(p: string): string {\n return sep === \"/\" ? p : p.split(sep).join(\"/\");\n}\n\nexport async function* walk(\n root: string,\n options: WalkOptions = {},\n): AsyncGenerator<WalkedFile> {\n const maxFileSize = options.maxFileSize ?? 2_000_000;\n const ig = await buildMatcher(root, options.extraIgnore ?? []);\n\n async function* recurse(dir: string): AsyncGenerator<WalkedFile> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n const rel = relative(root, abs);\n if (!rel) continue;\n const relPosix = toPosix(rel);\n const matchPath = entry.isDirectory() ? `${relPosix}/` : relPosix;\n if (ig.ignores(matchPath)) continue;\n\n if (entry.isDirectory()) {\n yield* recurse(abs);\n } else if (entry.isFile()) {\n const ext = extname(entry.name).toLowerCase();\n if (BINARY_EXTS.has(ext)) continue;\n let size: number;\n try {\n const s = await stat(abs);\n size = s.size;\n } catch {\n continue;\n }\n if (size > maxFileSize) continue;\n yield { absPath: abs, relPath: relPosix, ext, size };\n }\n }\n }\n\n yield* recurse(root);\n}\n","// Reads/writes info_graph.json and symbol_index.json.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport type { GraphSchema, SymbolIndex } from \"./types.js\";\n\nasync function writeJson(path: string, data: unknown, pretty: boolean): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const text = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);\n await writeFile(path, text + \"\\n\", \"utf8\");\n}\n\nasync function readJson<T>(path: string): Promise<T> {\n const text = await readFile(path, \"utf8\");\n return JSON.parse(text) as T;\n}\n\nexport async function writeGraph(path: string, graph: GraphSchema): Promise<void> {\n // Pretty-printing a graph with full file contents balloons disk size and\n // the JSON is only ever read by machines; keep it compact.\n await writeJson(path, graph, false);\n}\n\nexport async function readGraph(path: string): Promise<GraphSchema> {\n return readJson<GraphSchema>(path);\n}\n\nexport async function writeSymbolIndex(path: string, index: SymbolIndex): Promise<void> {\n await writeJson(path, index, true);\n}\n\nexport async function readSymbolIndex(path: string): Promise<SymbolIndex> {\n // Re-home onto a null prototype so name lookups (e.g. index[\"toString\"])\n // never resolve to an inherited Object.prototype member. Mirrors\n // buildSymbolIndex, which builds the index the same way.\n const parsed = await readJson<SymbolIndex>(path);\n return Object.assign(Object.create(null), parsed);\n}\n","// Project bootstrap: creates .synthra-graph/, .synthra/, updates .gitignore,\r\n// patches CLAUDE.md with the versioned policy block.\r\n\r\nimport { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\r\nimport { basename } from \"node:path\";\r\n\r\nimport { patchClaudeMd } from \"../hooks/claude-md.js\";\r\nimport type { SynthraPaths } from \"../shared/paths.js\";\r\n\r\nexport interface BootstrapResult {\r\n graphCreated: boolean;\r\n contextCreated: boolean;\r\n gitignoreUpdated: boolean;\r\n claudeMdUpdated: boolean;\r\n claudeMdCreated: boolean;\r\n}\r\n\r\n// Entries Synthra appends to the project .gitignore on bootstrap.\r\n// Each is gated by a check: if the entry is already present (any\r\n// indentation, trimmed match), it's skipped. Comments are per-entry so\r\n// users understand why each line is there and can remove what they don't\r\n// want without breaking the rest.\r\nconst GITIGNORE_ENTRIES: { comment: string; entry: string }[] = [\r\n {\r\n comment: \"added by synthra (heavy generated state — gitignored by design)\",\r\n entry: \".synthra-graph/\",\r\n },\r\n {\r\n comment:\r\n \"added by synthra — MCP registration. Remove this line if you want \" +\r\n \"to share the synthra MCP entry with teammates via committed .mcp.json\",\r\n entry: \".mcp.json\",\r\n },\r\n];\r\n\r\nasync function exists(path: string): Promise<boolean> {\r\n try {\r\n await stat(path);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\nasync function ensureDir(path: string): Promise<boolean> {\r\n const had = await exists(path);\r\n await mkdir(path, { recursive: true });\r\n return !had;\r\n}\r\n\r\nasync function patchGitignore(path: string): Promise<boolean> {\r\n let existing = \"\";\r\n try {\r\n existing = await readFile(path, \"utf8\");\r\n } catch {\r\n /* file may not exist */\r\n }\r\n const trimmed = new Set(existing.split(/\\r?\\n/).map((l) => l.trim()));\r\n const missing = GITIGNORE_ENTRIES.filter((e) => !trimmed.has(e.entry));\r\n if (missing.length === 0) return false;\r\n\r\n const block =\r\n missing.map((m) => `# ${m.comment}\\n${m.entry}`).join(\"\\n\") + \"\\n\";\r\n const appendix =\r\n (existing.length === 0 || existing.endsWith(\"\\n\") ? \"\" : \"\\n\") +\r\n (existing.length ? \"\\n\" : \"\") +\r\n block;\r\n await writeFile(path, existing + appendix, \"utf8\");\r\n return true;\r\n}\r\n\r\nexport async function bootstrap(paths: SynthraPaths): Promise<BootstrapResult> {\r\n const graphCreated = await ensureDir(paths.graphDir);\r\n const contextCreated = await ensureDir(paths.contextDir);\r\n const gitignoreUpdated = await patchGitignore(paths.gitignore);\r\n\r\n const claudeMdExistedBefore = await exists(paths.claudeMd);\r\n const patch = await patchClaudeMd(paths.claudeMd, basename(paths.projectRoot));\r\n\r\n return {\r\n graphCreated,\r\n contextCreated,\r\n gitignoreUpdated,\r\n claudeMdUpdated: patch.updated,\r\n claudeMdCreated: patch.created && !claudeMdExistedBefore,\r\n };\r\n}\r\n","// Idempotent patcher for the project's CLAUDE.md. Manages a single block\r\n// bounded by <!-- synthra-policy v<N> BEGIN --> ... <!-- synthra-policy v<N> END -->.\r\n// On each run, any prior synthra-policy block (any version) is removed and the\r\n// current-version block is appended at the end.\r\n\r\nimport { readFile, writeFile } from \"node:fs/promises\";\r\nimport { basename, dirname } from \"node:path\";\r\n\r\nexport const POLICY_VERSION = 3;\r\nexport const POLICY_BEGIN = `<!-- synthra-policy v${POLICY_VERSION} BEGIN -->`;\r\nexport const POLICY_END = `<!-- synthra-policy v${POLICY_VERSION} END -->`;\r\n\r\n// Matches a synthra-policy block of any version, e.g. v1, v2 …\r\nconst ANY_BLOCK_RE = /<!--\\s*synthra-policy\\s+v\\d+\\s+BEGIN\\s*-->[\\s\\S]*?<!--\\s*synthra-policy\\s+v\\d+\\s+END\\s*-->\\s*/g;\r\n\r\nexport interface PatchResult {\r\n created: boolean;\r\n updated: boolean;\r\n skipped: boolean;\r\n}\r\n\r\nexport function policyBlock(): string {\r\n return [\r\n POLICY_BEGIN,\r\n \"## Synthra context policy\",\r\n \"\",\r\n \"Synthra has pre-loaded structured context into this session and exposes\",\r\n \"the project's code graph through MCP tools. **Prefer these tools over\",\r\n \"Grep / Glob / Read** — they are faster, cheaper, and already filtered\",\r\n \"to relevant files.\",\r\n \"\",\r\n \"> **Tool namespace.** Synthra's MCP tools are exposed as\",\r\n \"> `mcp__synthra__graph_continue`, `mcp__synthra__graph_read`, and\",\r\n \"> `mcp__synthra__graph_register_edit`. Below they are referred to by\",\r\n \"> their short names (`graph_continue` etc.) for readability — use the\",\r\n \"> full namespaced form when actually invoking them.\",\r\n \"\",\r\n \"### Tools\",\r\n \"\",\r\n \"- **`graph_continue(query)`** — returns a `Confidence` label, the list\",\r\n \" of relevant `Files`, and signatures + top function bodies for those\",\r\n \" files. Your default first move when you need project context.\",\r\n \"- **`graph_read(target)`** — fetch source. Prefer the\",\r\n \" `\\\"file/path.ts::SymbolName\\\"` form over a bare file path — reading one\",\r\n \" symbol is ~50 tokens, reading a whole file is thousands.\",\r\n \"- **`graph_register_edit(files)`** — after you edit files, call this so\",\r\n \" subsequent turns weight your changes and avoid stale snapshots.\",\r\n \"\",\r\n \"### When to call `graph_continue` — and when to skip\",\r\n \"\",\r\n \"**Call `graph_continue` only when you do NOT already know the relevant\",\r\n \"files.**\",\r\n \"\",\r\n \"Call it when:\",\r\n \"- This is the first message of a new task or conversation\",\r\n \"- The task shifts to a different area of the codebase\",\r\n \"- You need files you haven't seen yet in this session\",\r\n \"\",\r\n \"**Skip `graph_continue` when:**\",\r\n \"- You already identified the relevant files earlier in this conversation\",\r\n \"- You are doing follow-up work on files already read (verify, refactor,\",\r\n \" test, docs, cleanup, commit)\",\r\n \"- The task is pure text (commit message, explanation, summary)\",\r\n \"\",\r\n \"If skipping, go directly to `graph_read(\\\"file.ts::symbol\\\")` on what\",\r\n \"you already know.\",\r\n \"\",\r\n \"### Confidence caps\",\r\n \"\",\r\n \"When `graph_continue` returns:\",\r\n \"\",\r\n \"- **`Confidence: high`** → Stop. Do NOT Grep, Glob, or further explore\",\r\n \" for this query. The graph already has it.\",\r\n \"- **`Confidence: medium`** → Read the listed `Files` directly via\",\r\n \" `graph_read(\\\"file::symbol\\\")` *before* trying Grep. The graph has\",\r\n \" narrowed the search space — use it, don't bypass it.\",\r\n \"- **`Confidence: low`** → You may use Grep / Glob, but the PreToolUse\",\r\n \" hook may still block redundant calls.\",\r\n \"\",\r\n \"### Reading code\",\r\n \"\",\r\n \"- **Always use `file::symbol` notation** with `graph_read`. Whole-file\",\r\n \" reads should be rare — only when you genuinely need the full file.\",\r\n \"- If `graph_continue`'s `Files` list contains a `::` entry, pass it\",\r\n \" verbatim to `graph_read`.\",\r\n \"\",\r\n \"### Don'ts\",\r\n \"\",\r\n \"- Don't Grep / Glob before calling `graph_continue` when required — the\",\r\n \" PreToolUse hook may block it.\",\r\n \"- Don't call `graph_continue` more than once per turn.\",\r\n \"- Don't read whole files when a symbol-level read would suffice.\",\r\n \"\",\r\n \"### Session-end resume note\",\r\n \"\",\r\n \"When the user signals they're done (e.g. \\\"bye\\\", \\\"wrap up\\\", \\\"done\\\"),\",\r\n \"persist the resume state by calling `context_remember` once per bullet.\",\r\n \"Synthra re-renders `.synthra/CONTEXT.md` from those entries at session\",\r\n \"end — do **NOT** write to `CONTEXT.md` directly, it is a derived view\",\r\n \"and direct edits are overwritten by the Stop hook.\",\r\n \"\",\r\n \"Use these `kind` values:\",\r\n \"\",\r\n \"- **`kind: \\\"task\\\"`** — what is being worked on right now (1 entry)\",\r\n \"- **`kind: \\\"decision\\\"`** — non-obvious choices made this session (max 3)\",\r\n \"- **`kind: \\\"next\\\"`** — concrete next steps (max 3)\",\r\n \"\",\r\n \"Tag entries with the relevant area (`tags: [\\\"auth\\\"]`) and the files\",\r\n \"they touch (`files: [\\\"src/auth.ts\\\"]`) so later `context_recall` queries\",\r\n \"can filter. Keep each `text` to 1–2 sentences.\",\r\n \"\",\r\n \"_This block is managed by Synthra. Edits inside the BEGIN/END markers\",\r\n \"are overwritten on every `syn .` run._\",\r\n \"\",\r\n POLICY_END,\r\n ].join(\"\\n\");\r\n}\r\n\r\n// A lean, agent-facing onboarding skeleton written ONLY when a project has no\r\n// CLAUDE.md yet. It captures the durable \"why/how\" the graph can't infer\r\n// (build/test, conventions, decisions, gotchas). It lives OUTSIDE the\r\n// synthra-policy markers, so later `syn .` runs — which strip and re-add the\r\n// policy block — never touch what the user fills in here.\r\nexport function onboardingSkeleton(projectName: string): string {\r\n return [\r\n `# ${projectName}`,\r\n \"\",\r\n \"> Onboarding notes for AI coding agents. Synthra's graph already knows the\",\r\n \"> code's *structure* (files, symbols, imports) — this file is for what the\",\r\n \"> graph can't infer: how to run the project, its conventions, and the\",\r\n \"> decisions behind them. Keep it lean and current; delete prompts you don't need.\",\r\n \"\",\r\n \"## Build & test\",\r\n \"\",\r\n \"- TODO: install deps / build\",\r\n \"- TODO: run tests / lint / typecheck\",\r\n \"- TODO: run the app locally\",\r\n \"\",\r\n \"## Conventions\",\r\n \"\",\r\n \"- TODO: code style, naming, file layout the agent should follow\",\r\n \"\",\r\n \"## Key decisions\",\r\n \"\",\r\n \"- TODO: non-obvious choices and *why* (\\\"we use X not Y because …\\\")\",\r\n \"\",\r\n \"## Gotchas\",\r\n \"\",\r\n \"- TODO: traps, footguns, \\\"don't touch X without Y\\\"\",\r\n \"\",\r\n \"_Tip: run `/init` in Claude Code to auto-draft the sections above, then trim\",\r\n \"to the durable bits. Synthra manages its own block below — leave it._\",\r\n \"\",\r\n ].join(\"\\n\");\r\n}\r\n\r\nexport async function patchClaudeMd(path: string, projectName?: string): Promise<PatchResult> {\r\n let existing: string | null;\r\n try {\r\n existing = await readFile(path, \"utf8\");\r\n } catch {\r\n existing = null;\r\n }\r\n\r\n const block = policyBlock();\r\n\r\n if (existing === null) {\r\n // First creation: scaffold the onboarding skeleton (user-owned, written\r\n // once) followed by Synthra's managed policy block.\r\n const name = projectName || basename(dirname(path)) || \"this project\";\r\n await writeFile(path, onboardingSkeleton(name) + \"\\n\" + block + \"\\n\", \"utf8\");\r\n return { created: true, updated: false, skipped: false };\r\n }\r\n\r\n const stripped = existing.replace(ANY_BLOCK_RE, \"\");\r\n const hadBlock = stripped !== existing;\r\n\r\n const desired = stripped.endsWith(\"\\n\")\r\n ? stripped + \"\\n\" + block + \"\\n\"\r\n : (stripped.length ? stripped + \"\\n\\n\" : \"\") + block + \"\\n\";\r\n\r\n if (hadBlock && desired === existing) {\r\n return { created: false, updated: false, skipped: true };\r\n }\r\n\r\n await writeFile(path, desired, \"utf8\");\r\n return { created: false, updated: true, skipped: false };\r\n}\r\n","// Scoring for retrieved files. Combines:\n// - keyword overlap with the (already-tokenized) query\n// - symbol-name overlap (boosted: hits in a file's defined symbols matter more)\n// - import-graph proximity from session-known + recent paths (boost adjacent files)\n// - recency boost from activity log (placeholder — wired in M5)\n\nimport type { Edge, FileNode, GraphSchema, SymbolNode } from \"./types.js\";\n\nexport interface RankInputs {\n candidates: FileNode[];\n query: string;\n graph?: GraphSchema;\n recentlyEditedPaths?: string[];\n sessionKnownPaths?: string[];\n}\n\nexport interface ScoredFile {\n file: FileNode;\n score: number;\n reasons: string[];\n}\n\nconst STOPWORDS = new Set([\n \"a\", \"an\", \"and\", \"are\", \"as\", \"at\", \"be\", \"by\", \"for\", \"from\",\n \"has\", \"have\", \"in\", \"is\", \"it\", \"of\", \"on\", \"or\", \"that\", \"the\",\n \"this\", \"to\", \"was\", \"we\", \"with\", \"what\", \"where\", \"when\", \"why\",\n \"how\", \"do\", \"does\", \"i\", \"me\", \"my\", \"you\", \"your\", \"code\", \"file\",\n]);\n\nexport function tokenizeQuery(query: string): string[] {\n const tokens = query\n .toLowerCase()\n .split(/[^a-z0-9_]+/g)\n .filter((t) => t.length > 1 && !STOPWORDS.has(t));\n // Also split camelCase/snake_case for queries like \"AuthService\"\n const expanded = new Set<string>();\n for (const t of tokens) {\n expanded.add(t);\n const parts = t.match(/[a-z]+|[0-9]+/g) ?? [];\n for (const p of parts) if (p.length > 1) expanded.add(p);\n }\n return Array.from(expanded);\n}\n\nfunction indexSymbolsByFile(graph: GraphSchema | undefined): Map<string, SymbolNode[]> {\n const out = new Map<string, SymbolNode[]>();\n if (!graph) return out;\n for (const n of graph.nodes) {\n if (n.kind !== \"symbol\") continue;\n const list = out.get(n.file) ?? [];\n list.push(n);\n out.set(n.file, list);\n }\n return out;\n}\n\nfunction indexImportEdges(graph: GraphSchema | undefined): Map<string, Set<string>> {\n // file path → set of file paths it imports (1-hop)\n const out = new Map<string, Set<string>>();\n if (!graph) return out;\n const idToPath = new Map<string, string>();\n for (const n of graph.nodes) if (n.kind === \"file\") idToPath.set(n.id, n.path);\n for (const e of graph.edges as Edge[]) {\n if (e.kind !== \"imports\") continue;\n const from = idToPath.get(e.from);\n const to = idToPath.get(e.to);\n if (!from || !to) continue;\n const s = out.get(from) ?? new Set<string>();\n s.add(to);\n out.set(from, s);\n }\n return out;\n}\n\nexport function scoreFiles(inputs: RankInputs): ScoredFile[] {\n const qTokens = new Set(tokenizeQuery(inputs.query));\n const symbolsByFile = indexSymbolsByFile(inputs.graph);\n const importsFrom = indexImportEdges(inputs.graph);\n\n const seeds = new Set<string>(inputs.sessionKnownPaths ?? []);\n for (const p of inputs.recentlyEditedPaths ?? []) seeds.add(p);\n\n // First pass: keyword + symbol score\n const scored: ScoredFile[] = [];\n for (const file of inputs.candidates) {\n const reasons: string[] = [];\n let score = 0;\n\n // Keyword overlap\n let kwHits = 0;\n for (const kw of file.keywords) if (qTokens.has(kw)) kwHits += 1;\n if (kwHits) {\n score += kwHits * 2;\n reasons.push(`kw=${kwHits}`);\n }\n\n // Symbol-name overlap (higher signal than file-level keywords)\n const symbols = symbolsByFile.get(file.path) ?? [];\n let symHits = 0;\n for (const sym of symbols) {\n const name = sym.name.toLowerCase();\n if (qTokens.has(name)) {\n symHits += 3;\n } else {\n // partial match: any query token is a substring of, or contained by, the symbol name\n for (const t of qTokens) {\n if (name.includes(t) || t.includes(name)) {\n symHits += 1;\n break;\n }\n }\n }\n }\n if (symHits) {\n score += symHits;\n reasons.push(`sym=${symHits}`);\n }\n\n // Path match: file path contains a query token\n const pathLower = file.path.toLowerCase();\n let pathHits = 0;\n for (const t of qTokens) if (pathLower.includes(t)) pathHits += 1;\n if (pathHits) {\n score += pathHits;\n reasons.push(`path=${pathHits}`);\n }\n\n if (seeds.has(file.path)) {\n score += 5;\n reasons.push(\"seed\");\n }\n\n scored.push({ file, score, reasons });\n }\n\n // Second pass: 1-hop import-graph boost from any file already scored > 0\n const positivePaths = new Set(scored.filter((s) => s.score > 0).map((s) => s.file.path));\n if (positivePaths.size > 0) {\n for (const s of scored) {\n if (s.score > 0) continue;\n // Does any file that imports this one have a positive score?\n let importBoost = 0;\n for (const [from, tos] of importsFrom) {\n if (!positivePaths.has(from)) continue;\n if (tos.has(s.file.path)) {\n importBoost += 1;\n break;\n }\n }\n if (importBoost) {\n s.score += importBoost * 0.5;\n s.reasons.push(\"imp-adj\");\n }\n }\n }\n\n scored.sort((a, b) => b.score - a.score);\n return scored;\n}\n\nexport function rank(inputs: RankInputs): FileNode[] {\n return scoreFiles(inputs).map((s) => s.file);\n}\n","// Query-time retrieval. Given a natural-language query (or a symbol name),\n// returns a ranked list of FileNodes plus a confidence label.\n\nimport { scoreFiles, tokenizeQuery, type RankInputs } from \"./rank.js\";\nimport type { FileNode, GraphSchema } from \"./types.js\";\n\nexport interface RetrievalResult {\n files: FileNode[];\n confidence: \"high\" | \"medium\" | \"low\";\n reason: string;\n}\n\nexport interface RetrieveOptions {\n topK?: number;\n recentlyEditedPaths?: string[];\n sessionKnownPaths?: string[];\n}\n\nexport async function retrieve(\n graph: GraphSchema,\n query: string,\n options: RetrieveOptions = {},\n): Promise<RetrievalResult> {\n const topK = options.topK ?? 12;\n const qTokens = tokenizeQuery(query);\n\n const allFiles: FileNode[] = graph.nodes.filter(\n (n): n is FileNode => n.kind === \"file\",\n );\n\n if (allFiles.length === 0 || qTokens.length === 0) {\n return {\n files: [],\n confidence: \"low\",\n reason: qTokens.length === 0 ? \"empty query\" : \"empty graph\",\n };\n }\n\n const rankInputs: RankInputs = {\n candidates: allFiles,\n query,\n graph,\n recentlyEditedPaths: options.recentlyEditedPaths,\n sessionKnownPaths: options.sessionKnownPaths,\n };\n const scored = scoreFiles(rankInputs);\n const positive = scored.filter((s) => s.score > 0);\n\n if (positive.length === 0) {\n return {\n files: [],\n confidence: \"low\",\n reason: `no matches for ${JSON.stringify(qTokens)}`,\n };\n }\n\n const top = positive.slice(0, topK).map((s) => s.file);\n const topScore = positive[0]?.score ?? 0;\n const secondScore = positive[1]?.score ?? 0;\n\n // confidence: high = clear top match (2x next or only one hit)\n // medium = several hits but no dominant one\n // low = a few weak hits\n let confidence: \"high\" | \"medium\" | \"low\";\n if (positive.length === 1) confidence = \"high\";\n else if (topScore >= 6 && topScore >= secondScore * 2) confidence = \"high\";\n else if (topScore >= 3) confidence = \"medium\";\n else confidence = \"low\";\n\n const reasons = positive\n .slice(0, Math.min(3, top.length))\n .map((s) => `${s.file.path} (${s.reasons.join(\",\")})`)\n .join(\"; \");\n\n return {\n files: top,\n confidence,\n reason: `top: ${reasons}`,\n };\n}\n","// Branch-aware routing for the context store.\n// On the default branch, reads/writes go to .synthra/context-store.json.\n// On a feature branch, they go to .synthra/branches/<sanitized-branch>/context-store.json.\n\nimport { execFile } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport async function currentBranch(projectRoot: string): Promise<string> {\n // Try .git/HEAD first — avoids the subprocess cost and works for detached\n // worktrees too (.git is a file there).\n try {\n const headPath = join(projectRoot, \".git\", \"HEAD\");\n const head = await readFile(headPath, \"utf8\");\n const trimmed = head.trim();\n const match = trimmed.match(/^ref:\\s+refs\\/heads\\/(.+)$/);\n if (match?.[1]) return match[1];\n // Detached HEAD — fall through\n } catch {\n // .git/HEAD unreadable (worktree file, submodule, or not a git repo)\n }\n\n try {\n const { stdout } = await execFileAsync(\"git\", [\"branch\", \"--show-current\"], {\n cwd: projectRoot,\n });\n const name = stdout.trim();\n if (name) return name;\n } catch {\n // git not on PATH or not a repo\n }\n\n return \"main\";\n}\n\nexport async function defaultBranch(projectRoot: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync(\n \"git\",\n [\"symbolic-ref\", \"refs/remotes/origin/HEAD\", \"--short\"],\n { cwd: projectRoot },\n );\n const trimmed = stdout.trim();\n const match = trimmed.match(/^origin\\/(.+)$/);\n if (match?.[1]) return match[1];\n } catch {\n // No origin/HEAD set — fall back to heuristic\n }\n return \"main\";\n}\n\nexport function sanitizeBranchName(name: string): string {\n return name.replaceAll(\"/\", \"-\").replaceAll(\"\\\\\", \"-\");\n}\n\nexport interface BranchScopedPaths {\n contextStore: string;\n contextMd: string;\n branchDir: string | null;\n}\n\nexport function resolveBranchPaths(\n contextDir: string,\n branch: string,\n isDefault: boolean,\n): BranchScopedPaths {\n if (isDefault) {\n return {\n contextStore: join(contextDir, \"context-store.json\"),\n contextMd: join(contextDir, \"CONTEXT.md\"),\n branchDir: null,\n };\n }\n const branchDir = join(contextDir, \"branches\", sanitizeBranchName(branch));\n return {\n contextStore: join(branchDir, \"context-store.json\"),\n contextMd: join(branchDir, \"CONTEXT.md\"),\n branchDir,\n };\n}\n","// Free-form CONTEXT.md narrative. Updated by Stop hook at session end with:\n// - Current Task (1 sentence)\n// - Key Decisions (max 3 bullets)\n// - Next Steps (max 3 bullets)\n// Capped at ~20 visible content lines.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport type { ContextEntry } from \"./context-store.js\";\n\nexport interface ContextMd {\n branch: string;\n currentTask: string;\n keyDecisions: string[];\n nextSteps: string[];\n date: string;\n}\n\nconst MAX_BULLETS = 3;\n\nexport function deriveContextMd(entries: ContextEntry[], branch: string): ContextMd {\n // Latest pending task drives \"current task\".\n const tasks = entries.filter((e) => e.type === \"task\").reverse();\n const currentTask = tasks[0]?.content ?? \"\";\n\n const keyDecisions = entries\n .filter((e) => e.type === \"decision\")\n .slice(-MAX_BULLETS)\n .map((e) => e.content);\n\n const nextSteps = entries\n .filter((e) => e.type === \"next\")\n .slice(-MAX_BULLETS)\n .map((e) => e.content);\n\n return {\n branch,\n currentTask,\n keyDecisions,\n nextSteps,\n date: new Date().toISOString(),\n };\n}\n\nexport function formatContextMd(ctx: ContextMd): string {\n const lines: string[] = [];\n lines.push(`# Context — ${ctx.branch}`);\n lines.push(\"\");\n lines.push(`_Updated: ${ctx.date}_`);\n lines.push(\"\");\n\n if (ctx.currentTask) {\n lines.push(`## Current task`);\n lines.push(ctx.currentTask);\n lines.push(\"\");\n }\n\n if (ctx.keyDecisions.length) {\n lines.push(`## Key decisions`);\n for (const d of ctx.keyDecisions) lines.push(`- ${d}`);\n lines.push(\"\");\n }\n\n if (ctx.nextSteps.length) {\n lines.push(`## Next steps`);\n for (const n of ctx.nextSteps) lines.push(`- ${n}`);\n lines.push(\"\");\n }\n\n if (!ctx.currentTask && !ctx.keyDecisions.length && !ctx.nextSteps.length) {\n lines.push(\"_(no context entries yet — use `context_remember` to add one)_\");\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\nexport async function writeContextMd(path: string, ctx: ContextMd): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, formatContextMd(ctx), \"utf8\");\n}\n\nexport async function readContextMd(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf8\");\n } catch {\n return null;\n }\n}\n","// Structured decisions/tasks/facts that persist across sessions.\n// Stored in .synthra/ (GIT-TRACKED) so teammates inherit them.\n// Branch-partitioned via branches.ts.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nexport type EntryKind = \"decision\" | \"task\" | \"next\" | \"fact\" | \"blocker\";\n\nexport interface ContextEntry {\n type: EntryKind;\n content: string;\n tags: string[];\n files: string[];\n date: string;\n}\n\ninterface Store {\n schema_version: number;\n entries: ContextEntry[];\n}\n\nconst SCHEMA_VERSION = 1;\n\nexport async function readEntries(path: string): Promise<ContextEntry[]> {\n try {\n const raw = await readFile(path, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<Store>;\n return Array.isArray(parsed.entries) ? parsed.entries : [];\n } catch {\n return [];\n }\n}\n\nexport async function writeEntries(path: string, entries: ContextEntry[]): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const store: Store = { schema_version: SCHEMA_VERSION, entries };\n await writeFile(path, JSON.stringify(store, null, 2) + \"\\n\", \"utf8\");\n}\n\nexport async function appendEntry(path: string, entry: ContextEntry): Promise<void> {\n const entries = await readEntries(path);\n entries.push(entry);\n await writeEntries(path, entries);\n}\n","// High-level orchestration: branch detection + store routing + CONTEXT.md\n// refresh in one call. Used by the MCP tools and the /context-update route.\n\nimport type { SynthraPaths } from \"../shared/paths.js\";\nimport {\n currentBranch,\n defaultBranch,\n resolveBranchPaths,\n type BranchScopedPaths,\n} from \"./branches.js\";\nimport { deriveContextMd, writeContextMd } from \"./context-md.js\";\nimport { appendEntry, readEntries, type ContextEntry, type EntryKind } from \"./context-store.js\";\n\nexport interface ActiveBranch {\n branch: string;\n isDefault: boolean;\n paths: BranchScopedPaths;\n}\n\nexport async function resolveActiveBranch(\n paths: SynthraPaths,\n override?: string,\n): Promise<ActiveBranch> {\n const branch = override ?? (await currentBranch(paths.projectRoot));\n const def = await defaultBranch(paths.projectRoot);\n const isDefault = branch === def;\n return {\n branch,\n isDefault,\n paths: resolveBranchPaths(paths.contextDir, branch, isDefault),\n };\n}\n\nexport interface RememberInput {\n text: string;\n kind: EntryKind;\n tags?: string[];\n files?: string[];\n}\n\nexport interface RememberResult {\n entry: ContextEntry;\n branch: string;\n storePath: string;\n contextMdPath: string;\n}\n\nexport async function rememberEntry(\n paths: SynthraPaths,\n input: RememberInput,\n): Promise<RememberResult> {\n const active = await resolveActiveBranch(paths);\n const entry: ContextEntry = {\n type: input.kind,\n content: input.text,\n tags: input.tags ?? [],\n files: input.files ?? [],\n date: new Date().toISOString(),\n };\n await appendEntry(active.paths.contextStore, entry);\n\n // Refresh CONTEXT.md so the narrative stays in sync with the structured store.\n const entries = await readEntries(active.paths.contextStore);\n const md = deriveContextMd(entries, active.branch);\n await writeContextMd(active.paths.contextMd, md);\n\n return {\n entry,\n branch: active.branch,\n storePath: active.paths.contextStore,\n contextMdPath: active.paths.contextMd,\n };\n}\n\nexport interface RecallInput {\n kind?: EntryKind;\n branch?: string;\n limit?: number;\n}\n\nexport interface RecallResult {\n branch: string;\n entries: ContextEntry[];\n storePath: string;\n}\n\nexport async function recallEntries(\n paths: SynthraPaths,\n input: RecallInput = {},\n): Promise<RecallResult> {\n const active = await resolveActiveBranch(paths, input.branch);\n let entries = await readEntries(active.paths.contextStore);\n if (input.kind) entries = entries.filter((e) => e.type === input.kind);\n if (input.limit && input.limit > 0) entries = entries.slice(-input.limit);\n return {\n branch: active.branch,\n entries,\n storePath: active.paths.contextStore,\n };\n}\n\nexport async function refreshContextMd(paths: SynthraPaths, branchOverride?: string) {\n const active = await resolveActiveBranch(paths, branchOverride);\n const entries = await readEntries(active.paths.contextStore);\n const md = deriveContextMd(entries, active.branch);\n await writeContextMd(active.paths.contextMd, md);\n return {\n branch: active.branch,\n path: active.paths.contextMd,\n entriesSeen: entries.length,\n };\n}\n","// Renders the final context pack as a single Markdown blob for Claude.\n\nexport interface FormatFileSection {\n path: string;\n reason?: string;\n signatures: string[];\n inlineBodies: string;\n associatedTests?: string[];\n}\n\nexport interface FormatInputs {\n query: string;\n files: FormatFileSection[];\n recentActivity?: string;\n truncated?: boolean;\n}\n\nexport function formatPack(inputs: FormatInputs): string {\n const parts: string[] = [];\n parts.push(`# Synthra context — query: ${JSON.stringify(inputs.query)}\\n`);\n\n if (inputs.files.length === 0) {\n parts.push(\"> No matching files found in the graph.\\n\");\n }\n\n for (const f of inputs.files) {\n const heading = f.reason ? `## ${f.path} _(${f.reason})_` : `## ${f.path}`;\n parts.push(heading);\n\n if (f.signatures.length === 0) {\n parts.push(\"_(no symbols extracted)_\");\n } else {\n parts.push(\"**Signatures:**\");\n for (const s of f.signatures) parts.push(`- ${s}`);\n }\n\n if (f.inlineBodies.trim().length > 0) {\n parts.push(\"\");\n parts.push(\"**Bodies:**\");\n parts.push(\"```\");\n parts.push(f.inlineBodies.trimEnd());\n parts.push(\"```\");\n }\n\n if (f.associatedTests?.length) {\n parts.push(\"\");\n parts.push(`**Tests:** ${f.associatedTests.join(\", \")}`);\n }\n\n parts.push(\"\");\n }\n\n if (inputs.recentActivity?.trim()) {\n parts.push(\"---\");\n parts.push(\"## Recent human activity\");\n parts.push(inputs.recentActivity.trim());\n parts.push(\"\");\n }\n\n if (inputs.truncated) {\n parts.push(\"> _(pack truncated to fit budget)_\");\n }\n\n return parts.join(\"\\n\");\n}\n","// Picks the top function bodies from a file (by relevance to the query) and\n// inlines them, respecting a char-based budget. Truncates oversized bodies.\n\nimport { tokenizeQuery } from \"../graph/rank.js\";\nimport type { FileNode, SymbolKind, SymbolNode } from \"../graph/types.js\";\n\nexport interface InlineSelection {\n text: string;\n charsUsed: number;\n functionsInlined: string[];\n}\n\nconst INLINABLE_KINDS = new Set<SymbolKind>([\"function\", \"method\", \"class\"]);\nconst MAX_BODY_CHARS = 1500;\n\nfunction sliceLines(content: string, startLine: number, endLine: number): string {\n const lines = content.split(/\\r?\\n/);\n return lines.slice(Math.max(0, startLine - 1), endLine).join(\"\\n\");\n}\n\nfunction scoreSymbol(name: string, qTokens: Set<string>): number {\n const lower = name.toLowerCase();\n if (qTokens.has(lower)) return 3;\n for (const t of qTokens) {\n if (lower.includes(t) || t.includes(lower)) return 1;\n }\n return 0;\n}\n\nfunction truncate(body: string): string {\n if (body.length <= MAX_BODY_CHARS) return body;\n return body.slice(0, MAX_BODY_CHARS).trimEnd() + \"\\n // … truncated\";\n}\n\nexport function selectInlineBodies(\n file: FileNode,\n symbols: SymbolNode[],\n query: string,\n budgetChars: number,\n): InlineSelection {\n if (budgetChars <= 0) {\n return { text: \"\", charsUsed: 0, functionsInlined: [] };\n }\n\n const qTokens = new Set(tokenizeQuery(query));\n const mine = symbols.filter((s) => s.file === file.path && INLINABLE_KINDS.has(s.symbol_kind));\n\n const scored = mine\n .map((s) => ({ sym: s, score: scoreSymbol(s.name, qTokens) }))\n .sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n // Tie-break: smaller bodies first (so we fit more)\n const aSpan = (a.sym.end_line - a.sym.start_line) || 1;\n const bSpan = (b.sym.end_line - b.sym.start_line) || 1;\n return aSpan - bSpan;\n });\n\n const parts: string[] = [];\n const inlined: string[] = [];\n let used = 0;\n\n for (const { sym, score } of scored) {\n // Skip irrelevant symbols entirely when we have positive hits available;\n // fall back to top-by-size if no query match landed.\n if (score === 0 && inlined.length > 0) break;\n\n const body = truncate(sliceLines(file.content, sym.start_line, sym.end_line));\n const header = `${file.path}::${sym.name} (L${sym.start_line}-${sym.end_line})`;\n const block = `${header}\\n${body}\\n`;\n if (used + block.length > budgetChars) {\n if (inlined.length > 0) break;\n // No room and nothing yet — take a head-only slice.\n const remaining = Math.max(0, budgetChars - used - header.length - 16);\n if (remaining <= 0) break;\n const partial = body.slice(0, remaining).trimEnd() + \"\\n // … truncated\";\n const finalBlock = `${header}\\n${partial}\\n`;\n parts.push(finalBlock);\n inlined.push(sym.name);\n used += finalBlock.length;\n break;\n }\n parts.push(block);\n inlined.push(sym.name);\n used += block.length;\n }\n\n return { text: parts.join(\"\\n\"), charsUsed: used, functionsInlined: inlined };\n}\n","// Extracts function/class signatures from a FileNode (no bodies).\n// Signatures are the first line of each symbol in the file, sorted by line.\n\nimport type { FileNode, SymbolNode } from \"../graph/types.js\";\n\nexport function extractSignatures(file: FileNode, symbols: SymbolNode[]): string[] {\n const mine = symbols\n .filter((s) => s.file === file.path)\n .slice()\n .sort((a, b) => a.start_line - b.start_line);\n\n return mine.map((s) => `L${s.start_line}: ${s.signature.trim()}`);\n}\n","// Test ↔ source co-retrieval. Given a source file, returns the test files\n// linked to it via `tests` edges in the graph (foo.test.ts → foo.ts).\n\nimport type { FileNode, GraphSchema } from \"../graph/types.js\";\n\nexport function findTestsForFile(graph: GraphSchema, file: FileNode): FileNode[] {\n // tests edges run from test file → source file\n const fileNodesById = new Map<string, FileNode>();\n for (const n of graph.nodes) {\n if (n.kind === \"file\") fileNodesById.set(n.id, n);\n }\n\n const out: FileNode[] = [];\n for (const e of graph.edges) {\n if (e.kind !== \"tests\" || e.to !== file.id) continue;\n const testFile = fileNodesById.get(e.from);\n if (testFile && !out.includes(testFile)) out.push(testFile);\n }\n return out;\n}\n","// Compresses a list of retrieved files into a structured context pack:\n// signatures + top function bodies + tests co-retrieved + dependency edges.\n// Budget is enforced in characters (~ tokens × 4) — see SYN_HARD_MAX_READ_CHARS.\n\nimport type { FileNode, GraphSchema, SymbolNode } from \"../graph/types.js\";\nimport { formatPack, type FormatFileSection } from \"./format.js\";\nimport { selectInlineBodies } from \"./inline.js\";\nimport { extractSignatures } from \"./signatures.js\";\nimport { findTestsForFile } from \"./tests.js\";\n\nexport interface PackOptions {\n query: string;\n graph: GraphSchema;\n /** Soft target for total tokens (≈ chars/4). Default: 4000. */\n budgetTokens?: number;\n /** Fraction of remaining budget to spend on a single file's inline bodies. Default: 0.5. */\n inlineBodyRatio?: number;\n /** Co-retrieve linked test files when packing source files. Default: true. */\n includeTests?: boolean;\n /** Optional: file path → reason string from the ranker, surfaced in the pack heading. */\n reasons?: Map<string, string>;\n}\n\nexport interface ContextPack {\n text: string;\n tokenEstimate: number;\n filesUsed: string[];\n testsCoRetrieved: string[];\n truncated: boolean;\n}\n\nconst STATIC_OVERHEAD_PER_FILE = 200; // headers + bullet markdown + spacing\nconst MAX_INLINE_CHARS_PER_FILE = 2500;\n\nfunction indexSymbolsByFile(graph: GraphSchema): SymbolNode[] {\n return graph.nodes.filter((n): n is SymbolNode => n.kind === \"symbol\");\n}\n\nexport async function pack(files: FileNode[], opts: PackOptions): Promise<ContextPack> {\n const budgetTokens = opts.budgetTokens ?? 4000;\n const budgetChars = budgetTokens * 4;\n const inlineRatio = opts.inlineBodyRatio ?? 0.5;\n const includeTests = opts.includeTests ?? true;\n const reasons = opts.reasons ?? new Map<string, string>();\n\n const symbols = indexSymbolsByFile(opts.graph);\n\n const sections: FormatFileSection[] = [];\n const testsCoRetrieved: string[] = [];\n let used = 0;\n let truncated = false;\n\n for (const file of files) {\n const sig = extractSignatures(file, symbols);\n const testFiles = includeTests ? findTestsForFile(opts.graph, file) : [];\n const testPaths = testFiles.map((t) => t.path);\n\n const staticCost =\n file.path.length +\n sig.join(\"\\n\").length +\n testPaths.join(\",\").length +\n STATIC_OVERHEAD_PER_FILE;\n\n if (used + staticCost > budgetChars) {\n truncated = true;\n break;\n }\n\n const remaining = budgetChars - used - staticCost;\n const inlineBudget = Math.min(Math.floor(remaining * inlineRatio), MAX_INLINE_CHARS_PER_FILE);\n\n const inline = selectInlineBodies(file, symbols, opts.query, inlineBudget);\n\n sections.push({\n path: file.path,\n reason: reasons.get(file.path),\n signatures: sig,\n inlineBodies: inline.text,\n associatedTests: testPaths,\n });\n\n used += staticCost + inline.charsUsed;\n for (const t of testPaths) if (!testsCoRetrieved.includes(t)) testsCoRetrieved.push(t);\n\n if (used >= budgetChars) {\n truncated = true;\n break;\n }\n }\n\n if (sections.length < files.length) truncated = true;\n\n const text = formatPack({\n query: opts.query,\n files: sections,\n truncated,\n });\n const tokenEstimate = Math.ceil(text.length / 4);\n\n return {\n text,\n tokenEstimate,\n filesUsed: sections.map((s) => s.path),\n testsCoRetrieved,\n truncated,\n };\n}\n","// MCP-over-HTTP (streamable) protocol handler. Exposes Synthra's graph tools\n// to Claude Code via JSON-RPC 2.0 messages POSTed to /mcp.\n//\n// Tools:\n// graph_continue(query) — retrieve + pack a context bundle\n// graph_read(target) — return source for \"file\" or \"file::symbol\"\n// graph_register_edit(files) — Claude tells Synthra it edited files\n//\n// Spec: https://modelcontextprotocol.io/specification\n\nimport { retrieve } from \"../graph/retrieve.js\";\nimport type { FileNode, SymbolNode } from \"../graph/types.js\";\nimport { recallEntries, rememberEntry } from \"../memory/index.js\";\nimport type { EntryKind } from \"../memory/context-store.js\";\nimport { pack } from \"../packer/index.js\";\nimport type { ServerContext } from \"./context.js\";\n\nconst PROTOCOL_VERSION = \"2024-11-05\";\nconst SERVER_INFO = { name: \"synthra\", version: \"0.0.1\" } as const;\n\ntype JsonRpcId = string | number | null;\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id?: JsonRpcId;\n method: string;\n params?: Record<string, unknown>;\n}\n\nexport interface JsonRpcResponse {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n}\n\nconst ERR = {\n parse: -32700,\n invalidRequest: -32600,\n methodNotFound: -32601,\n invalidParams: -32602,\n internal: -32603,\n} as const;\n\nfunction ok(id: JsonRpcId, result: unknown): JsonRpcResponse {\n return { jsonrpc: \"2.0\", id, result };\n}\n\nfunction err(id: JsonRpcId, code: number, message: string, data?: unknown): JsonRpcResponse {\n return { jsonrpc: \"2.0\", id, error: { code, message, data } };\n}\n\nfunction textContent(text: string) {\n return { content: [{ type: \"text\", text }], isError: false };\n}\n\nfunction errorContent(message: string) {\n return { content: [{ type: \"text\", text: message }], isError: true };\n}\n\nconst TOOLS = [\n {\n name: \"graph_continue\",\n description:\n \"Returns the project context most relevant to a query — function signatures, top function bodies, and linked test files. Use this BEFORE Grep/Glob. If `confidence` is 'high', do not call Grep/Glob for the same query.\",\n inputSchema: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Natural-language description of what you're looking for.\" },\n },\n required: [\"query\"],\n },\n },\n {\n name: \"graph_read\",\n description:\n \"Return the source code for a specific file or symbol. Target is either a project-relative file path (e.g. 'src/auth.ts') or 'file::symbol' (e.g. 'src/auth.ts::AuthService').\",\n inputSchema: {\n type: \"object\",\n properties: {\n target: { type: \"string\", description: \"File path or file::symbol notation.\" },\n },\n required: [\"target\"],\n },\n },\n {\n name: \"graph_register_edit\",\n description:\n \"Tell Synthra that you (the AI) have edited these files. Lets Synthra rank them higher in subsequent retrieval and avoid surfacing stale context.\",\n inputSchema: {\n type: \"object\",\n properties: {\n files: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Project-relative file paths that were edited.\",\n },\n },\n required: [\"files\"],\n },\n },\n {\n name: \"context_remember\",\n description:\n \"Persist a decision/task/next-step/fact/blocker into the project's branch-aware context store. Use when the user makes a decision worth keeping, identifies a TODO, or surfaces a key fact. Entries land in `.synthra/context-store.json` on the default branch, or `.synthra/branches/<sanitized>/context-store.json` on a feature branch — git-tracked, so teammates inherit them and they merge naturally.\",\n inputSchema: {\n type: \"object\",\n properties: {\n text: { type: \"string\", description: \"The thing to remember (1–3 sentences).\" },\n kind: {\n type: \"string\",\n enum: [\"decision\", \"task\", \"next\", \"fact\", \"blocker\"],\n description: \"What kind of entry. Required.\",\n },\n tags: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional tags for grouping (e.g. 'auth', 'perf').\",\n },\n files: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional project-relative file paths this entry relates to.\",\n },\n },\n required: [\"text\", \"kind\"],\n },\n },\n {\n name: \"context_recall\",\n description:\n \"Read previously-stored decisions/tasks/facts from the project's branch-aware context store. Defaults to the current branch.\",\n inputSchema: {\n type: \"object\",\n properties: {\n kind: {\n type: \"string\",\n enum: [\"decision\", \"task\", \"next\", \"fact\", \"blocker\"],\n description: \"Filter to a single kind.\",\n },\n branch: { type: \"string\", description: \"Override which branch to read from.\" },\n limit: { type: \"number\", description: \"Return only the most recent N entries.\" },\n },\n },\n },\n {\n name: \"recent_activity\",\n description:\n \"What has the human been doing in the editor recently — file saves, branch switches, and uncommitted-diff changes. Use this to check whether the static context pack may be stale (e.g. before answering a question about a file that was just edited).\",\n inputSchema: {\n type: \"object\",\n properties: {\n since_ms: {\n type: \"number\",\n description:\n \"Epoch milliseconds. Only return events newer than this. Defaults to the last 60 minutes.\",\n },\n limit: { type: \"number\", description: \"Cap on returned events.\" },\n },\n },\n },\n {\n name: \"count_tokens\",\n description:\n \"Estimate token count for a piece of text using a char/4 approximation. Accurate within ~10% for English + code. Useful for budgeting prompt content before sending.\",\n inputSchema: {\n type: \"object\",\n properties: {\n text: { type: \"string\", description: \"The text to estimate tokens for.\" },\n },\n required: [\"text\"],\n },\n },\n {\n name: \"blast_radius\",\n description:\n \"Given a file (or 'file::symbol' target), return all files that depend on it transitively via imports + tests edges. Use BEFORE editing a widely-used file to see what could break. Symbol-level granularity is approximated at the file level (we don't track call edges in v0.1).\",\n inputSchema: {\n type: \"object\",\n properties: {\n target: { type: \"string\", description: \"File path or 'file::symbol' notation.\" },\n depth: { type: \"number\", description: \"Max hops to traverse. Default 3.\" },\n },\n required: [\"target\"],\n },\n },\n {\n name: \"dead_code\",\n description:\n \"Return files in the project that no other file imports and no test file references — strong candidates for unused/orphaned code. File-level granularity (v0.1 limitation — symbol-level needs call-graph edges). Common entry-point patterns (main, index, app, CLI, bin/) are excluded heuristically.\",\n inputSchema: {\n type: \"object\",\n properties: {\n limit: { type: \"number\", description: \"Cap on returned files. Default 50.\" },\n },\n },\n },\n] as const;\n\nasync function callTool(\n name: string,\n args: Record<string, unknown> | undefined,\n ctx: ServerContext,\n) {\n switch (name) {\n case \"graph_continue\":\n return graphContinue(args, ctx);\n case \"graph_read\":\n return graphRead(args, ctx);\n case \"graph_register_edit\":\n return graphRegisterEdit(args, ctx);\n case \"context_remember\":\n return contextRemember(args, ctx);\n case \"context_recall\":\n return contextRecall(args, ctx);\n case \"recent_activity\":\n return recentActivity(args, ctx);\n case \"count_tokens\":\n return countTokens(args);\n case \"blast_radius\":\n return blastRadius(args, ctx);\n case \"dead_code\":\n return deadCode(args, ctx);\n default:\n return errorContent(`Unknown tool: ${name}`);\n }\n}\n\nfunction countTokens(args: Record<string, unknown> | undefined) {\n const text = typeof args?.text === \"string\" ? args.text : \"\";\n if (!text) return errorContent(\"count_tokens: 'text' (string) is required\");\n const tokens = Math.ceil(text.length / 4);\n return textContent(JSON.stringify({ tokens, method: \"chars/4 estimate\", chars: text.length }));\n}\n\nfunction blastRadius(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const targetRaw = typeof args?.target === \"string\" ? args.target.trim() : \"\";\n const maxDepth = typeof args?.depth === \"number\" && args.depth > 0 ? Math.floor(args.depth) : 3;\n if (!targetRaw) return errorContent(\"blast_radius: 'target' (string) is required\");\n\n const filePath = targetRaw.split(\"::\", 1)[0]?.trim() ?? targetRaw;\n const root = ctx.graph.nodes.find(\n (n): n is FileNode => n.kind === \"file\" && n.path === filePath,\n );\n if (!root) return errorContent(`blast_radius: file not in graph: ${filePath}`);\n\n // Index reverse edges (to → [{from, kind}]) once per call.\n const incoming = new Map<string, Array<{ from: string; kind: string }>>();\n for (const e of ctx.graph.edges) {\n if (e.kind !== \"imports\" && e.kind !== \"tests\") continue;\n const list = incoming.get(e.to) ?? [];\n list.push({ from: e.from, kind: e.kind });\n incoming.set(e.to, list);\n }\n\n interface Hit {\n path: string;\n depth: number;\n via: string;\n }\n\n const visited = new Set<string>([root.id]);\n const hits: Hit[] = [];\n const pathById = new Map<string, string>();\n for (const n of ctx.graph.nodes) if (n.kind === \"file\") pathById.set(n.id, n.path);\n\n let frontier = [root.id];\n for (let d = 1; d <= maxDepth; d++) {\n const next: string[] = [];\n for (const cur of frontier) {\n const callers = incoming.get(cur) ?? [];\n for (const c of callers) {\n if (visited.has(c.from)) continue;\n visited.add(c.from);\n next.push(c.from);\n const path = pathById.get(c.from) ?? c.from;\n hits.push({ path, depth: d, via: c.kind });\n }\n }\n frontier = next;\n if (next.length === 0) break;\n }\n\n if (hits.length === 0) {\n return textContent(`# Blast radius for ${filePath}\\n\\n_(no dependents — file is isolated)_`);\n }\n\n hits.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));\n const lines = [`# Blast radius for ${filePath} (depth ≤ ${maxDepth})`, \"\"];\n lines.push(`${hits.length} dependent file(s):`);\n for (const h of hits) {\n lines.push(`- **depth ${h.depth}** \\`${h.path}\\` _(via ${h.via})_`);\n }\n return textContent(lines.join(\"\\n\"));\n}\n\nconst LIKELY_ENTRY_PATTERNS = [\n /(?:^|\\/)main\\.[a-z0-9_]+$/i,\n /(?:^|\\/)index\\.[a-z0-9_]+$/i,\n /(?:^|\\/)app\\.[a-z0-9_]+$/i,\n /(?:^|\\/)entry\\.[a-z0-9_]+$/i,\n /(?:^|\\/)cli[\\/.]/i,\n /(?:^|\\/)bin[\\/.]/i,\n /(?:^|\\/)server\\.[a-z0-9_]+$/i,\n /\\.test\\.[a-z0-9_]+$/i,\n /\\.spec\\.[a-z0-9_]+$/i,\n /(?:^|\\/)tests?\\//i,\n /(?:^|\\/)__tests__\\//i,\n /(?:^|\\/)__init__\\.py$/i,\n];\n\nfunction isLikelyEntry(path: string): boolean {\n return LIKELY_ENTRY_PATTERNS.some((re) => re.test(path));\n}\n\nfunction deadCode(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const limit = typeof args?.limit === \"number\" && args.limit > 0 ? Math.floor(args.limit) : 50;\n\n const hasIncoming = new Set<string>();\n for (const e of ctx.graph.edges) {\n if (e.kind === \"imports\" || e.kind === \"tests\") hasIncoming.add(e.to);\n }\n\n const candidates = ctx.graph.nodes\n .filter((n): n is FileNode => n.kind === \"file\")\n .filter((f) => !hasIncoming.has(f.id))\n .filter((f) => !isLikelyEntry(f.path));\n\n if (candidates.length === 0) {\n return textContent(\n `# Dead code\\n\\n_(no file is unreferenced — every file is either imported by another, has a linked test, or matches an entry-point pattern)_`,\n );\n }\n\n candidates.sort((a, b) => a.path.localeCompare(b.path));\n const shown = candidates.slice(0, limit);\n const lines = [`# Dead code candidates (file-level, v0.1)`, \"\"];\n lines.push(\n `${shown.length} of ${candidates.length} unreferenced file(s) — no other file imports them and no test links them:`,\n );\n lines.push(\"\");\n for (const f of shown) {\n lines.push(`- \\`${f.path}\\``);\n }\n lines.push(\"\");\n lines.push(\n `_v0.1 caveat:_ this is file-level only. Symbol-level dead code (unused exports) needs call-graph edges, which land in v0.2.`,\n );\n return textContent(lines.join(\"\\n\"));\n}\n\nasync function graphContinue(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const query = typeof args?.query === \"string\" ? args.query : \"\";\n if (!query) return errorContent(\"graph_continue: 'query' (string) is required\");\n\n const retrieval = await retrieve(ctx.graph, query);\n const packed = await pack(retrieval.files, { query, graph: ctx.graph });\n\n const header =\n `Confidence: ${retrieval.confidence}\\n` +\n `Files: ${retrieval.files.map((f) => f.path).join(\", \") || \"(none)\"}\\n` +\n `Reason: ${retrieval.reason}\\n`;\n\n // The pack body already starts with a header — keep them concatenated.\n return textContent(`${header}\\n${packed.text}`);\n}\n\nfunction graphRead(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const target = typeof args?.target === \"string\" ? args.target : \"\";\n if (!target) return errorContent(\"graph_read: 'target' (string) is required\");\n\n const [rawFile, symbolName] = target.includes(\"::\") ? target.split(\"::\", 2) : [target, undefined];\n const filePath = (rawFile ?? \"\").trim();\n\n const fileNode = ctx.graph.nodes.find(\n (n): n is FileNode => n.kind === \"file\" && n.path === filePath,\n );\n if (!fileNode) return errorContent(`graph_read: file not found in graph: ${filePath}`);\n\n if (!symbolName) {\n return textContent(`# ${fileNode.path}\\n\\n${fileNode.content}`);\n }\n\n const cleanSym = symbolName.trim();\n const symbol = ctx.graph.nodes.find(\n (n): n is SymbolNode => n.kind === \"symbol\" && n.file === filePath && n.name === cleanSym,\n );\n if (!symbol) {\n return errorContent(`graph_read: symbol '${cleanSym}' not found in ${filePath}`);\n }\n\n const lines = fileNode.content.split(/\\r?\\n/);\n const body = lines.slice(symbol.start_line - 1, symbol.end_line).join(\"\\n\");\n return textContent(\n `# ${fileNode.path}::${symbol.name} (L${symbol.start_line}-${symbol.end_line})\\n\\n${body}`,\n );\n}\n\nconst editedFiles = new Set<string>();\n\nfunction graphRegisterEdit(args: Record<string, unknown> | undefined, _ctx: ServerContext) {\n const files = Array.isArray(args?.files) ? (args.files as unknown[]).filter((f) => typeof f === \"string\") : [];\n for (const f of files) editedFiles.add(f as string);\n return textContent(`Registered ${files.length} edited file(s). Total tracked this session: ${editedFiles.size}.`);\n}\n\nexport function getRegisteredEdits(): string[] {\n return Array.from(editedFiles);\n}\n\nconst VALID_KINDS = new Set<EntryKind>([\"decision\", \"task\", \"next\", \"fact\", \"blocker\"]);\n\nasync function contextRemember(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const text = typeof args?.text === \"string\" ? args.text.trim() : \"\";\n const kindRaw = typeof args?.kind === \"string\" ? args.kind : \"\";\n if (!text) return errorContent(\"context_remember: 'text' (string) is required\");\n if (!VALID_KINDS.has(kindRaw as EntryKind)) {\n return errorContent(\n `context_remember: 'kind' must be one of ${Array.from(VALID_KINDS).join(\", \")}`,\n );\n }\n const tags = Array.isArray(args?.tags)\n ? (args.tags as unknown[]).filter((t): t is string => typeof t === \"string\")\n : [];\n const files = Array.isArray(args?.files)\n ? (args.files as unknown[]).filter((f): f is string => typeof f === \"string\")\n : [];\n\n const result = await rememberEntry(ctx.paths, {\n text,\n kind: kindRaw as EntryKind,\n tags,\n files,\n });\n\n return textContent(\n `Remembered ${result.entry.type} on branch '${result.branch}'.\\n` +\n `Stored: ${result.storePath}\\n` +\n `CONTEXT.md refreshed: ${result.contextMdPath}`,\n );\n}\n\nconst DEFAULT_RECENT_WINDOW_MS = 60 * 60 * 1000;\n\nfunction recentActivity(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const sinceMs =\n typeof args?.since_ms === \"number\" && Number.isFinite(args.since_ms)\n ? args.since_ms\n : Date.now() - DEFAULT_RECENT_WINDOW_MS;\n const limit =\n typeof args?.limit === \"number\" && args.limit > 0 ? Math.floor(args.limit) : undefined;\n\n let events = ctx.activity.getEvents(sinceMs);\n if (limit) events = events.slice(-limit);\n\n if (events.length === 0) {\n return textContent(\n `No human-activity events since ${new Date(sinceMs).toISOString()}.`,\n );\n }\n\n const lines = [`# Recent human activity (${events.length} events)`, \"\"];\n for (const e of events) {\n if (\"path\" in e) {\n lines.push(`- **${e.kind}** ${e.path} _(${e.ts})_`);\n } else {\n const summary = JSON.stringify(e.details);\n lines.push(`- **${e.kind}** ${summary} _(${e.ts})_`);\n }\n }\n return textContent(lines.join(\"\\n\"));\n}\n\nasync function contextRecall(args: Record<string, unknown> | undefined, ctx: ServerContext) {\n const kind = typeof args?.kind === \"string\" && VALID_KINDS.has(args.kind as EntryKind)\n ? (args.kind as EntryKind)\n : undefined;\n const branch = typeof args?.branch === \"string\" ? args.branch : undefined;\n const limit = typeof args?.limit === \"number\" && args.limit > 0 ? Math.floor(args.limit) : undefined;\n\n const result = await recallEntries(ctx.paths, { kind, branch, limit });\n\n if (result.entries.length === 0) {\n const filter = kind ? ` of kind '${kind}'` : \"\";\n return textContent(`No context entries${filter} on branch '${result.branch}'.`);\n }\n\n const lines = [`# Context entries — branch: ${result.branch}`, \"\"];\n for (const e of result.entries) {\n const tags = e.tags.length ? ` [${e.tags.join(\", \")}]` : \"\";\n lines.push(`- **${e.type}**${tags} (${e.date}): ${e.content}`);\n if (e.files.length) lines.push(` files: ${e.files.join(\", \")}`);\n }\n return textContent(lines.join(\"\\n\"));\n}\n\nexport async function handleMcpRequest(\n body: unknown,\n ctx: ServerContext,\n): Promise<JsonRpcResponse> {\n if (!body || typeof body !== \"object\") {\n return err(null, ERR.invalidRequest, \"Request body must be a JSON-RPC 2.0 object.\");\n }\n\n const req = body as JsonRpcRequest;\n if (req.jsonrpc !== \"2.0\" || typeof req.method !== \"string\") {\n return err(req.id ?? null, ERR.invalidRequest, \"Invalid JSON-RPC envelope.\");\n }\n\n const id = req.id ?? null;\n\n try {\n switch (req.method) {\n case \"initialize\":\n return ok(id, {\n protocolVersion:\n typeof req.params?.protocolVersion === \"string\"\n ? req.params.protocolVersion\n : PROTOCOL_VERSION,\n capabilities: { tools: {} },\n serverInfo: SERVER_INFO,\n });\n\n case \"notifications/initialized\":\n // Client confirms initialization. No response required for notifications (id===undefined).\n return ok(id, {});\n\n case \"tools/list\":\n return ok(id, { tools: TOOLS });\n\n case \"tools/call\": {\n const params = req.params ?? {};\n const toolName = typeof params.name === \"string\" ? params.name : \"\";\n if (!toolName) return err(id, ERR.invalidParams, \"'name' is required for tools/call.\");\n const args = (params.arguments && typeof params.arguments === \"object\"\n ? (params.arguments as Record<string, unknown>)\n : {});\n const result = await callTool(toolName, args, ctx);\n return ok(id, result);\n }\n\n case \"ping\":\n return ok(id, {});\n\n default:\n return err(id, ERR.methodNotFound, `Method not found: ${req.method}`);\n }\n } catch (e) {\n return err(id, ERR.internal, (e as Error).message);\n }\n}\n\n// Exposed for code that wants to enumerate the tool catalogue without going\n// through JSON-RPC (e.g. CLI introspection in M3).\nexport function listTools(): Array<{ name: string; description: string; inputSchema: unknown }> {\n return TOOLS.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }));\n}\n","// GET /activity?since=<ms> — returns recent human-activity events.\n// Backed by the in-memory ActivityStore (file-watcher + git-watcher feed it).\n// MCP tool `recent_activity` is a thin wrapper.\n\nimport type { ActivityEvent } from \"../../activity/activity-log.js\";\nimport type { ServerContext } from \"../context.js\";\n\nexport interface ActivityResponse {\n events: ActivityEvent[];\n since: string;\n ring_size: number;\n}\n\nexport async function handleActivity(\n sinceMs: number | undefined,\n ctx: ServerContext,\n): Promise<ActivityResponse> {\n const events = ctx.activity.getEvents(sinceMs);\n return {\n events,\n since: new Date(sinceMs ?? Date.now()).toISOString(),\n ring_size: ctx.activity.size(),\n };\n}\n","// POST /context-update — Stop hook calls this at session end.\n// For M4: re-renders CONTEXT.md from the branch-scoped store so the narrative\n// stays in sync with the structured entries that landed during the session.\n// Transcript-mining for new entries (auto \"we decided X\" → store) is v0.2.\n\nimport { refreshContextMd } from \"../../memory/index.js\";\nimport type { ServerContext } from \"../context.js\";\n\nexport interface ContextUpdateRequest {\n transcript_path?: string;\n branch?: string;\n}\n\nexport interface ContextUpdateResponse {\n updated: boolean;\n branch: string;\n path: string;\n entries: number;\n}\n\nexport async function handleContextUpdate(\n req: ContextUpdateRequest,\n ctx: ServerContext,\n): Promise<ContextUpdateResponse> {\n const r = await refreshContextMd(ctx.paths, req?.branch);\n return {\n updated: true,\n branch: r.branch,\n path: r.path,\n entries: r.entriesSeen,\n };\n}\n","// POST /gate — PreToolUse hook calls this with the tool name + arguments.\n// THE MOAT — improvement #1. Strategy:\n// - For Grep/Glob: extract the search pattern, run retrieve().\n// - If recent human activity touches a file matching the query → ALLOW\n// even at high confidence (the user's head is in that file; static\n// context may be stale).\n// - If confidence === \"high\" and no recent overlap → BLOCK with a reason\n// pointing at graph_continue / graph_read.\n// - Otherwise → ALLOW.\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport { retrieve } from \"../../graph/retrieve.js\";\nimport { tokenizeQuery } from \"../../graph/rank.js\";\nimport type { ServerContext } from \"../context.js\";\n\nexport interface GateRequest {\n tool_name: string;\n tool_input: Record<string, unknown>;\n}\n\nexport interface GateResponse {\n decision: \"allow\" | \"block\";\n reason?: string;\n}\n\nconst BLOCKABLE_TOOLS = new Set([\"Grep\", \"Glob\"]);\nconst RECENT_ACTIVITY_WINDOW_MS = 5 * 60 * 1000;\n\nfunction extractQuery(toolName: string, input: Record<string, unknown>): string | null {\n if (toolName === \"Grep\") {\n const pattern = typeof input.pattern === \"string\" ? input.pattern : \"\";\n const query = typeof input.query === \"string\" ? input.query : \"\";\n return (pattern || query).trim() || null;\n }\n if (toolName === \"Glob\") {\n const pattern = typeof input.pattern === \"string\" ? input.pattern : \"\";\n return pattern.replace(/[*?/\\\\.]+/g, \" \").trim() || null;\n }\n return null;\n}\n\nfunction recentlyTouchedMatchesQuery(\n recentPaths: string[],\n queryTokens: Set<string>,\n): string[] {\n const matches: string[] = [];\n for (const path of recentPaths) {\n const lower = path.toLowerCase();\n for (const t of queryTokens) {\n if (lower.includes(t)) {\n matches.push(path);\n break;\n }\n }\n }\n return matches;\n}\n\nasync function logDecision(\n ctx: ServerContext,\n toolName: string,\n query: string | null,\n decision: \"allow\" | \"block\",\n reason: string | undefined,\n): Promise<void> {\n try {\n await mkdir(dirname(ctx.paths.gateLog), { recursive: true });\n const entry = {\n ts: new Date().toISOString(),\n tool: toolName,\n decision,\n query,\n reason,\n };\n await appendFile(ctx.paths.gateLog, JSON.stringify(entry) + \"\\n\", \"utf8\");\n } catch {\n // Durability is best-effort; an unwritable disk shouldn't fail the gate.\n }\n}\n\nexport async function handleGate(req: GateRequest, ctx: ServerContext): Promise<GateResponse> {\n if (!req?.tool_name || typeof req.tool_name !== \"string\") {\n return { decision: \"allow\", reason: \"no tool_name\" };\n }\n\n if (!BLOCKABLE_TOOLS.has(req.tool_name)) {\n return { decision: \"allow\" };\n }\n\n const input = (req.tool_input && typeof req.tool_input === \"object\" ? req.tool_input : {}) as Record<string, unknown>;\n const query = extractQuery(req.tool_name, input);\n if (!query) {\n const res: GateResponse = { decision: \"allow\", reason: \"no extractable query\" };\n await logDecision(ctx, req.tool_name, null, res.decision, res.reason);\n return res;\n }\n\n const retrieval = await retrieve(ctx.graph, query);\n // \"low\" = no real matches → let Grep through; Synthra has nothing useful.\n // \"medium\" + \"high\" = Synthra has structured context for this query →\n // bias toward blocking. The pitch (\"use graph_continue instead of Grep\")\n // holds at medium too — on real codebases of any size, \"high\" is rare\n // because almost every query matches multiple files.\n if (retrieval.confidence === \"low\") {\n const res: GateResponse = {\n decision: \"allow\",\n reason: `confidence=low — no graph context for \"${query}\", letting ${req.tool_name} through`,\n };\n await logDecision(ctx, req.tool_name, query, res.decision, res.reason);\n return res;\n }\n\n // Medium / high — but check if recent activity overlaps the query first.\n // If the user just touched a file matching the query, static context may\n // be stale and they probably want a fresh search.\n const qTokens = new Set(tokenizeQuery(query));\n const recentPaths = ctx.activity.recentFilePaths(RECENT_ACTIVITY_WINDOW_MS);\n const overlap = recentlyTouchedMatchesQuery(recentPaths, qTokens);\n\n if (overlap.length > 0) {\n const res: GateResponse = {\n decision: \"allow\",\n reason:\n `confidence=${retrieval.confidence} but human just touched ${overlap.slice(0, 3).join(\", \")} — ` +\n `static context may be stale, letting ${req.tool_name} through.`,\n };\n await logDecision(ctx, req.tool_name, query, res.decision, res.reason);\n return res;\n }\n\n const top = retrieval.files.slice(0, 3).map((f) => f.path).join(\", \");\n const res: GateResponse = {\n decision: \"block\",\n reason:\n `Synthra has ${retrieval.confidence}-confidence context for \"${query}\" (top files: ${top}). ` +\n `Use the \\`graph_continue\\` MCP tool with this query instead of ${req.tool_name}, ` +\n `or read a specific file/symbol with \\`graph_read\\`.`,\n };\n await logDecision(ctx, req.tool_name, query, res.decision, res.reason);\n return res;\n}\n","// POST /log — Stop hook posts per-turn token usage parsed from Claude's\n// transcript JSONL. Synthra appends each entry as one line to token_log.jsonl.\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport type { ServerContext } from \"../context.js\";\n\nexport interface LogEntry {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n model: string;\n description?: string;\n project: string;\n}\n\nexport interface LogResponse {\n ok: true;\n written_at: string;\n}\n\nexport async function handleLog(entry: LogEntry, ctx: ServerContext): Promise<LogResponse> {\n if (!entry || typeof entry.input_tokens !== \"number\" || typeof entry.output_tokens !== \"number\") {\n throw new Error(\"log: input_tokens and output_tokens (number) are required\");\n }\n\n const written_at = new Date().toISOString();\n const record = { ...entry, written_at };\n await mkdir(dirname(ctx.paths.tokenLog), { recursive: true });\n await appendFile(ctx.paths.tokenLog, JSON.stringify(record) + \"\\n\", \"utf8\");\n\n return { ok: true, written_at };\n}\n","// POST /pack { query, maxTokens? } → ContextPack JSON.\n// Runs retrieve → pack against the in-memory graph.\n\nimport { retrieve } from \"../../graph/retrieve.js\";\nimport { scoreFiles } from \"../../graph/rank.js\";\nimport { pack, type ContextPack } from \"../../packer/index.js\";\nimport type { ServerContext } from \"../context.js\";\n\nexport interface PackRequest {\n query: string;\n maxTokens?: number;\n includeTests?: boolean;\n}\n\nexport interface PackResponse extends ContextPack {\n query: string;\n confidence: \"high\" | \"medium\" | \"low\";\n retrievalReason: string;\n}\n\nexport async function handlePack(req: PackRequest, ctx: ServerContext): Promise<PackResponse> {\n if (!req?.query || typeof req.query !== \"string\") {\n throw new Error(\"pack: 'query' (string) is required\");\n }\n\n const recentlyEditedPaths = ctx.activity.recentFilePaths(15 * 60 * 1000);\n const retrieval = await retrieve(ctx.graph, req.query, { recentlyEditedPaths });\n\n // Surface per-file scoring rationale in the rendered pack.\n const allFiles = ctx.graph.nodes.filter((n) => n.kind === \"file\");\n const scored = scoreFiles({\n candidates: allFiles as Parameters<typeof scoreFiles>[0][\"candidates\"],\n query: req.query,\n graph: ctx.graph,\n recentlyEditedPaths,\n });\n const reasons = new Map<string, string>();\n for (const s of scored) {\n if (s.reasons.length) reasons.set(s.file.path, s.reasons.join(\",\"));\n }\n\n const result = await pack(retrieval.files, {\n query: req.query,\n graph: ctx.graph,\n budgetTokens: req.maxTokens,\n includeTests: req.includeTests,\n reasons,\n });\n\n return {\n ...result,\n query: req.query,\n confidence: retrieval.confidence,\n retrievalReason: retrieval.reason,\n };\n}\n","// GET /prime — SessionStart and PreCompact hooks call this. Returns the\n// priming text Claude sees at session start.\n//\n// For M2 we return a minimal primer derived from the graph (file count + a\n// short top-level summary). M3 layers in CONTEXT.md narrative + recent\n// stored decisions.\n\nimport type { ServerContext } from \"../context.js\";\n\nexport interface PrimeResponse {\n primer: string;\n port: number;\n}\n\nexport async function handlePrime(ctx: ServerContext, port: number): Promise<PrimeResponse> {\n const g = ctx.graph;\n const fileCount = g.file_count;\n const symbolCount = g.symbol_count;\n\n const primer =\n `Synthra context loaded for ${g.root}.\\n` +\n `${fileCount} files indexed, ${symbolCount} symbols. ` +\n `Prefer the graph_* MCP tools over Grep/Glob for navigation.\\n` +\n `(Full primer wired in M3.)`;\n\n return { primer, port };\n}\n","// Environment-variable-driven configuration.\n// All knobs are prefixed SYN_.\n\nexport interface SynthraConfig {\n hardMaxReadChars: number;\n turnReadBudgetChars: number;\n fallbackMaxCallsPerTurn: number;\n retrieveCacheTtlSec: number;\n mcpPort: number | null;\n dashboardPort: number;\n logLevel: \"debug\" | \"info\" | \"warn\" | \"error\";\n claudeBin: string;\n}\n\nfunction num(name: string, fallback: number): number {\n const v = process.env[name];\n if (!v) return fallback;\n const n = Number(v);\n return Number.isFinite(n) ? n : fallback;\n}\n\nfunction str<T extends string>(name: string, fallback: T): T {\n return (process.env[name] as T) ?? fallback;\n}\n\nexport function loadConfig(): SynthraConfig {\n return {\n hardMaxReadChars: num(\"SYN_HARD_MAX_READ_CHARS\", 4000),\n turnReadBudgetChars: num(\"SYN_TURN_READ_BUDGET_CHARS\", 18000),\n fallbackMaxCallsPerTurn: num(\"SYN_FALLBACK_MAX_CALLS_PER_TURN\", 1),\n retrieveCacheTtlSec: num(\"SYN_RETRIEVE_CACHE_TTL_SEC\", 900),\n mcpPort: process.env.SYN_MCP_PORT ? num(\"SYN_MCP_PORT\", 0) : null,\n dashboardPort: num(\"SYN_DASHBOARD_PORT\", 8901),\n logLevel: str(\"SYN_LOG_LEVEL\", \"info\" as const),\n claudeBin: str(\"SYN_CLAUDE_BIN\", \"claude\" as const),\n };\n}\n","// Locate the most recently modified Claude session transcript for a project.\n// Claude Code stores them at: ~/.claude/projects/<encoded-cwd>/<session-uuid>.jsonl\n// where `encoded-cwd` replaces ANY of \\ / : with -.\n\nimport { readdir, stat } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface DiscoveredSession {\n sessionId: string;\n transcriptPath: string;\n modifiedAt: Date;\n}\n\nexport function encodeProjectPath(projectRoot: string): string {\n return projectRoot.replace(/[\\\\/:]/g, \"-\");\n}\n\nexport async function findLatestSession(projectRoot: string): Promise<DiscoveredSession | null> {\n const encoded = encodeProjectPath(projectRoot);\n const dir = join(homedir(), \".claude\", \"projects\", encoded);\n\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return null;\n }\n\n const jsonlFiles = entries.filter((f) => f.endsWith(\".jsonl\"));\n if (jsonlFiles.length === 0) return null;\n\n let latest: DiscoveredSession | null = null;\n for (const file of jsonlFiles) {\n const path = join(dir, file);\n try {\n const s = await stat(path);\n if (!latest || s.mtime > latest.modifiedAt) {\n latest = {\n sessionId: file.replace(/\\.jsonl$/, \"\"),\n transcriptPath: path,\n modifiedAt: s.mtime,\n };\n }\n } catch {\n // skip unreadable file\n }\n }\n return latest;\n}\n","// Graceful shutdown after `claude` exits:\n// - find latest Claude session JSONL → print `syn --resume <id>`\n// MCP-server shutdown is owned by the caller (it has the ServerHandle).\n// CONTEXT.md flushing is M4.\n\nimport { log } from \"../shared/logger.js\";\nimport type { SynthraPaths } from \"../shared/paths.js\";\nimport { findLatestSession } from \"./session-discovery.js\";\n\nexport async function cleanup(paths: SynthraPaths): Promise<void> {\n const session = await findLatestSession(paths.projectRoot);\n if (!session) {\n log.info(\"(no Claude session transcript found — nothing to resume)\");\n return;\n }\n log.info(\"\");\n log.info(`To resume this session: syn --resume ${session.sessionId}`);\n}\n","// `syn dashboard [path]` — starts the standalone token dashboard server on\n// localhost:8901 (or SYN_DASHBOARD_PORT). Reads token_log.jsonl + gate_log.jsonl\n// for the given project. Runs until Ctrl+C.\n\nimport { resolve } from \"node:path\";\n\nimport { startDashboard } from \"../dashboard/server.js\";\nimport { loadConfig } from \"../shared/config.js\";\nimport { log } from \"../shared/logger.js\";\nimport { resolvePaths } from \"../shared/paths.js\";\n\nexport async function dashboardCommand(rawPath: string): Promise<void> {\n const projectRoot = resolve(rawPath);\n const paths = resolvePaths(projectRoot);\n const cfg = loadConfig();\n\n const handle = await startDashboard(paths, cfg.dashboardPort);\n log.info(`Synthra dashboard listening on ${handle.url}`);\n log.info(`project: ${projectRoot}`);\n log.info(`reading: ${paths.tokenLog}`);\n log.info(` ${paths.gateLog}`);\n log.info(\"press Ctrl+C to stop.\");\n\n await new Promise<void>((res) => {\n const shutdown = async (signal: NodeJS.Signals) => {\n log.info(`received ${signal} — shutting down…`);\n try {\n await handle.stop();\n } catch (err) {\n log.warn(`dashboard stop error: ${(err as Error).message}`);\n }\n res();\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n });\n}\n\n// Backwards-compat alias matching the M2-era stub signature.\nexport async function openDashboard(_port = 8901): Promise<void> {\n return dashboardCommand(\".\");\n}\n","// Update flow:\n//\n// 1. At every `syn .` startup, check the npm registry for the latest\n// version. If we're on latest, stay silent. If outdated, prompt the\n// user [y/N]. On 'y', run `npm install -g …@latest` with inherited\n// stdio, print the new version's changelog section from the freshly-\n// installed package, then exit with re-run instructions.\n//\n// 2. On every startup, compare the running binary's version to a\n// persisted \"last seen\" version at ~/.synthra/last-seen-version.json.\n// If running > last-seen, print the changelog for the running version\n// (catches manual `npm install -g …@latest` upgrades that bypassed\n// our prompt). On a fresh install (no last-seen file), set last-seen\n// silently without printing — new users don't need the changelog.\n//\n// No 24h cache — the user explicitly asked for \"always check on every\n// `syn .` run.\" Cost: one ~100–300ms HTTPS round-trip per startup, hard-\n// capped by FETCH_TIMEOUT_MS. SYN_NO_UPDATE_CHECK=1 opts out of (1)\n// only; the local last-seen comparison in (2) still fires.\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport spawn from \"cross-spawn\";\n\nimport { log } from \"../shared/logger.js\";\n\nconst PKG_NAME = \"@jefuriiij/synthra\";\nconst SYNTHRA_DIR = join(homedir(), \".synthra\");\nconst LAST_SEEN_PATH = join(SYNTHRA_DIR, \"last-seen-version.json\");\nconst REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}/latest`;\nconst FETCH_TIMEOUT_MS = 2000;\n\ninterface LastSeenFile {\n version: string;\n updated_at: string;\n}\n\nexport interface UpdateCheckResult {\n current: string;\n latest: string | null;\n hasUpdate: boolean;\n}\n\nlet currentVersionCache: string | null = null;\n\nasync function getCurrentVersion(): Promise<string> {\n if (currentVersionCache) return currentVersionCache;\n try {\n // Tsup inlines this import at build time.\n const pkg = (await import(\"../../package.json\", { with: { type: \"json\" } })) as { default: { version: string } } | { version: string };\n const version = \"default\" in pkg ? pkg.default.version : pkg.version;\n currentVersionCache = version;\n return version;\n } catch {\n return \"0.0.0\";\n }\n}\n\n/** Returns true if `candidate` is a higher semver than `baseline`. */\nfunction isNewer(candidate: string, baseline: string): boolean {\n const a = candidate.split(/[.-]/).map((p) => Number(p));\n const b = baseline.split(/[.-]/).map((p) => Number(p));\n const len = Math.max(a.length, b.length);\n for (let i = 0; i < len; i++) {\n const ai = Number.isFinite(a[i]) ? (a[i] as number) : 0;\n const bi = Number.isFinite(b[i]) ? (b[i] as number) : 0;\n if (ai > bi) return true;\n if (ai < bi) return false;\n }\n return false;\n}\n\nasync function fetchLatestFromRegistry(): Promise<string | null> {\n try {\n const res = await fetch(REGISTRY_URL, {\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n headers: { Accept: \"application/json\" },\n });\n if (!res.ok) return null;\n const data = (await res.json()) as { version?: string };\n return typeof data.version === \"string\" ? data.version : null;\n } catch {\n return null;\n }\n}\n\nexport async function checkForUpdate(): Promise<UpdateCheckResult> {\n const current = await getCurrentVersion();\n\n if (process.env.SYN_NO_UPDATE_CHECK === \"1\") {\n return { current, latest: null, hasUpdate: false };\n }\n\n const latest = await fetchLatestFromRegistry();\n const hasUpdate = latest ? isNewer(latest, current) : false;\n return { current, latest, hasUpdate };\n}\n\nasync function readLastSeen(): Promise<string | null> {\n try {\n const raw = await readFile(LAST_SEEN_PATH, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<LastSeenFile>;\n return parsed.version ?? null;\n } catch {\n return null;\n }\n}\n\nasync function writeLastSeen(version: string): Promise<void> {\n try {\n await mkdir(SYNTHRA_DIR, { recursive: true });\n const data: LastSeenFile = { version, updated_at: new Date().toISOString() };\n await writeFile(LAST_SEEN_PATH, JSON.stringify(data, null, 2), \"utf8\");\n } catch {\n // best-effort\n }\n}\n\n/** Find the directory `npm root -g` reports. Used to locate the installed package. */\nfunction npmGlobalRoot(): Promise<string | null> {\n return new Promise((resolve) => {\n const chunks: Buffer[] = [];\n const proc = spawn(\"npm\", [\"root\", \"-g\"], { stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n proc.stdout?.on(\"data\", (c: Buffer) => chunks.push(c));\n proc.on(\"error\", () => resolve(null));\n proc.on(\"exit\", (code) => {\n if (code !== 0) return resolve(null);\n const out = Buffer.concat(chunks).toString(\"utf8\").trim();\n resolve(out || null);\n });\n });\n}\n\n/**\n * Extract the markdown section under `## [version]` (or `## v0.1.11`) from a\n * CHANGELOG body. Returns the bullet/prose content between this version's\n * heading and the next H2, trimmed. Returns null if the version isn't found.\n */\nexport function extractChangelogSection(text: string, version: string): string | null {\n const escapedVersion = version.replace(/\\./g, \"\\\\.\");\n // Match: \"## [0.1.11]\" or \"## v0.1.11\" or \"## 0.1.11\", optionally followed by extra text.\n const headingRe = new RegExp(`^##\\\\s+\\\\[?v?${escapedVersion}\\\\]?.*$`, \"m\");\n const m = headingRe.exec(text);\n if (!m) return null;\n const startBody = m.index + m[0].length;\n const rest = text.slice(startBody);\n const nextHeadingIdx = rest.search(/^##\\s+/m);\n const body = nextHeadingIdx < 0 ? rest : rest.slice(0, nextHeadingIdx);\n // Strip horizontal-rule separator lines.\n return body.replace(/^---\\s*$/gm, \"\").trim() || null;\n}\n\nasync function readInstalledChangelog(): Promise<string | null> {\n const root = await npmGlobalRoot();\n if (!root) return null;\n try {\n return await readFile(join(root, \"@jefuriiij\", \"synthra\", \"CHANGELOG.md\"), \"utf8\");\n } catch {\n return null;\n }\n}\n\nasync function printChangelogForVersion(version: string): Promise<void> {\n const md = await readInstalledChangelog();\n if (!md) return;\n const section = extractChangelogSection(md, version);\n if (!section) return;\n log.info(\"\");\n log.info(`What's new in ${version}:`);\n log.info(\"\");\n for (const line of section.split(/\\r?\\n/)) {\n log.info(` ${line}`);\n }\n log.info(\"\");\n}\n\n/**\n * Compare the running binary's version against the persisted last-seen\n * version. If running > last-seen, print the changelog for the running\n * version and update last-seen. If last-seen is missing (fresh install),\n * silently set it to the current version — new installs don't need a\n * retroactive changelog.\n *\n * Catches users who upgraded via `npm install -g …@latest` outside of\n * Synthra's interactive prompt. Always silent on no-op.\n */\nexport async function runStartupChangelogCheck(): Promise<void> {\n try {\n const current = await getCurrentVersion();\n const lastSeen = await readLastSeen();\n if (!lastSeen) {\n await writeLastSeen(current);\n return;\n }\n if (isNewer(current, lastSeen)) {\n await printChangelogForVersion(current);\n await writeLastSeen(current);\n }\n } catch {\n // silent\n }\n}\n\n/**\n * Ask a yes/no question on stdin/stdout. Returns true only on explicit \"y\" /\n * \"yes\". Empty input or anything else returns false.\n */\nasync function promptYesNo(question: string): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const answer = (await rl.question(question)).trim().toLowerCase();\n return answer === \"y\" || answer === \"yes\";\n } finally {\n rl.close();\n }\n}\n\n/** Run `npm install -g @jefuriiij/synthra@latest`. Inherits stdio. */\nfunction runNpmUpdate(): Promise<boolean> {\n return new Promise((resolve) => {\n const proc = spawn(\"npm\", [\"install\", \"-g\", PKG_NAME + \"@latest\"], {\n stdio: \"inherit\",\n });\n proc.on(\"error\", () => resolve(false));\n proc.on(\"exit\", (code) => resolve(code === 0));\n });\n}\n\n/**\n * Interactive update flow. Always hits the registry — no cache. If a newer\n * version exists AND we're on a TTY, prompts the user [y/N]. On 'y', runs\n * npm install, prints the new version's changelog section, and exits with\n * re-run instructions. On 'n' / non-TTY / no update, returns silently so\n * startup continues.\n */\nexport async function promptForUpdateOrLog(): Promise<void> {\n try {\n const r = await checkForUpdate();\n if (!r.hasUpdate || !r.latest) return;\n\n // Non-interactive (CI, piped stdin) — log a one-line hint but don't prompt.\n if (!process.stdin.isTTY) {\n log.info(\n `Synthra ${r.latest} is available (you have ${r.current}) — run: npm install -g @jefuriiij/synthra@latest`,\n );\n return;\n }\n\n log.info(`Synthra ${r.latest} is available (you have ${r.current}).`);\n const yes = await promptYesNo(\"[syn] Update now? [y/N]: \");\n if (!yes) {\n log.info(\"Skipping update — continuing with current version.\");\n return;\n }\n\n log.info(`Running: npm install -g ${PKG_NAME}@latest`);\n const ok = await runNpmUpdate();\n if (!ok) {\n log.warn(\"npm install failed — continuing with current version.\");\n return;\n }\n log.info(`✓ Updated to ${r.latest}.`);\n await printChangelogForVersion(r.latest);\n await writeLastSeen(r.latest);\n log.info(`Please re-run: syn .`);\n process.exit(0);\n } catch {\n // silent\n }\n}\n","// `syn serve [path]` — starts the HTTP MCP server against an already-scanned\n// project. The graph + symbol index must exist (run `syn scan` first).\n// Traps SIGINT/SIGTERM for a graceful shutdown.\n\nimport { resolve } from \"node:path\";\nimport { stat } from \"node:fs/promises\";\n\nimport { startServer } from \"../server/http.js\";\nimport { log } from \"../shared/logger.js\";\nimport { resolvePaths } from \"../shared/paths.js\";\n\nexport async function serveCommand(rawPath: string): Promise<void> {\n const projectRoot = resolve(rawPath);\n const paths = resolvePaths(projectRoot);\n\n try {\n await stat(paths.infoGraph);\n } catch {\n log.error(`no graph found at ${paths.infoGraph}`);\n log.error(\"run `syn scan` in this project first.\");\n process.exit(2);\n }\n\n const handle = await startServer(paths);\n log.info(`MCP server listening on ${handle.url}`);\n log.info(`port written to ${paths.mcpPort}`);\n log.info(\"press Ctrl+C to stop.\");\n\n const shutdown = async (signal: NodeJS.Signals) => {\n log.info(`received ${signal} — shutting down…`);\n try {\n await handle.stop();\n } catch (err) {\n log.error(\"shutdown error:\", (err as Error).message);\n }\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","// Subroutines for plugging Synthra into Claude Code:\r\n// - registerMcp → `claude mcp add --transport http --scope local`\r\n// - unregisterMcp → `claude mcp remove`\r\n// - spawnClaude → spawn the terminal CLI (used only by `--launch-cli`)\r\n// - startClaude → legacy composite (install hooks + register + spawn + unregister)\r\n// kept for any external callers; cli/index.ts now composes the pieces\r\n// itself so the default flow can skip the CLI spawn.\r\n\r\nimport spawn from \"cross-spawn\";\r\n\r\nimport { installHooks } from \"../hooks/installer.js\";\r\nimport { loadConfig } from \"../shared/config.js\";\r\nimport { log } from \"../shared/logger.js\";\r\nimport type { SynthraPaths } from \"../shared/paths.js\";\r\n\r\nconst MCP_NAME = \"synthra\";\r\n\r\n// We use `cross-spawn` instead of `node:child_process` so Windows .cmd\r\n// shims (e.g. claude.cmd) resolve correctly without setting shell:true.\r\n// shell:true triggers Node's DEP0190 deprecation because args get\r\n// concatenated into a single command line with no escaping. cross-spawn\r\n// handles shim resolution + proper arg escaping internally, so we avoid\r\n// the deprecation and the underlying security concern in one move.\r\nfunction runClaude(\r\n bin: string,\r\n args: string[],\r\n cwd: string,\r\n stdio: \"inherit\" | \"pipe\" = \"pipe\",\r\n): Promise<{ code: number; stdout: string; stderr: string }> {\r\n return new Promise((resolve) => {\r\n const proc = spawn(bin, args, {\r\n cwd,\r\n stdio: stdio === \"inherit\" ? \"inherit\" : [\"ignore\", \"pipe\", \"pipe\"],\r\n });\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n proc.stdout?.on(\"data\", (c) => (stdout += String(c)));\r\n proc.stderr?.on(\"data\", (c) => (stderr += String(c)));\r\n proc.on(\"error\", () => resolve({ code: -1, stdout, stderr: stderr || \"claude not on PATH\" }));\r\n proc.on(\"exit\", (code) => resolve({ code: code ?? 0, stdout, stderr }));\r\n });\r\n}\r\n\r\nexport async function registerMcp(bin: string, mcpPort: number, cwd: string): Promise<boolean> {\r\n const url = `http://127.0.0.1:${mcpPort}/mcp`;\r\n await runClaude(bin, [\"mcp\", \"remove\", MCP_NAME, \"--scope\", \"project\"], cwd).catch(() => undefined);\r\n const reg = await runClaude(\r\n bin,\r\n [\"mcp\", \"add\", MCP_NAME, \"--transport\", \"http\", \"--scope\", \"project\", url],\r\n cwd,\r\n );\r\n if (reg.code !== 0) {\r\n log.warn(`claude mcp add failed (code ${reg.code}). stderr: ${reg.stderr.trim()}`);\r\n log.warn(`Synthra's MCP tools won't be visible to Claude this session.`);\r\n return false;\r\n }\r\n log.info(`registered MCP with Claude: ${MCP_NAME} → ${url}`);\r\n return true;\r\n}\r\n\r\nexport async function unregisterMcp(bin: string, cwd: string): Promise<void> {\r\n const r = await runClaude(bin, [\"mcp\", \"remove\", MCP_NAME, \"--scope\", \"project\"], cwd);\r\n if (r.code === 0) log.debug(\"unregistered MCP server\");\r\n}\r\n\r\nexport interface SpawnClaudeOptions {\r\n cwd: string;\r\n resumeSessionId?: string;\r\n initialPrompt?: string;\r\n}\r\n\r\nexport async function spawnClaude(bin: string, opts: SpawnClaudeOptions): Promise<number> {\r\n const args: string[] = [];\r\n if (opts.resumeSessionId) args.push(\"--resume\", opts.resumeSessionId);\r\n if (opts.initialPrompt) args.push(opts.initialPrompt);\r\n log.info(`launching ${bin} ${args.join(\" \")}`);\r\n const result = await runClaude(bin, args, opts.cwd, \"inherit\");\r\n return result.code;\r\n}\r\n\r\n// Legacy composite — install hooks + register MCP + spawn claude + cleanup.\r\n// cli/index.ts no longer relies on this; it composes the pieces above so the\r\n// new default `syn .` flow can skip the CLI spawn. Kept for compatibility.\r\nexport interface StartClaudeOptions {\r\n paths: SynthraPaths;\r\n mcpPort: number;\r\n resumeSessionId?: string;\r\n initialPrompt?: string;\r\n}\r\n\r\nexport async function startClaude(opts: StartClaudeOptions): Promise<number> {\r\n const bin = loadConfig().claudeBin;\r\n await installHooks(opts.paths);\r\n await registerMcp(bin, opts.mcpPort, opts.paths.projectRoot);\r\n const code = await spawnClaude(bin, {\r\n cwd: opts.paths.projectRoot,\r\n resumeSessionId: opts.resumeSessionId,\r\n initialPrompt: opts.initialPrompt,\r\n });\r\n await unregisterMcp(bin, opts.paths.projectRoot);\r\n return code;\r\n}\r\n"],"mappings":"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F;;;AI3VA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["resolve","resolve","readFile","join","readFile","basename","score","resolve","err","mkdir","readFile","writeFile","dirname","join","pre_compact_default","pre_tool_use_default","prime_default","stop_default","prime_default","pre_tool_use_default","pre_compact_default","stop_default","readFile","join","mkdir","writeFile","dirname","serve","Hono","writeFile","mkdir","dirname","readFile","join","readFile","join","emit","err","readFile","join","dirname","join","readFile","QUERY","QUERY","Query","QUERY","firstLine","Query","QUERY","QUERY","QUERY","QUERY","Query","QUERY","firstLine","Query","QUERY","QUERY","Query","firstLine","Query","SCRIPT_RE","extractScripts","require","readFile","readFile","join","relative","sep","ignore","readIgnoreFile","buildMatcher","toPosix","mkdir","readFile","writeFile","dirname","mkdir","readFile","stat","writeFile","basename","readFile","writeFile","basename","dirname","stat","mkdir","readFile","writeFile","basename","err","STOPWORDS","score","execFile","readFile","join","promisify","execFileAsync","mkdir","readFile","writeFile","dirname","mkdir","readFile","writeFile","dirname","SCHEMA_VERSION","score","indexSymbolsByFile","appendFile","mkdir","dirname","mkdir","dirname","appendFile","res","appendFile","mkdir","dirname","err","Hono","serve","writeFile","resolve","readdir","stat","homedir","join","resolve","resolve","err","mkdir","readFile","writeFile","homedir","join","join","homedir","readFile","mkdir","writeFile","resolve","ok","resolve","stat","resolve","stat","err","spawn","resolve","spawn","VERSION","resolve","err"]}
|