@sonnechasser/ntrp 0.2.2 β 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/guardrails-smoke.js +19902 -0
- package/dist/ai/guardrails-smoke.js.map +1 -0
- package/dist/conversation/loop-guard-smoke.js +20096 -0
- package/dist/conversation/loop-guard-smoke.js.map +1 -0
- package/dist/demo/whimsy-smoke.js +1 -0
- package/dist/demo/whimsy-smoke.js.map +1 -1
- package/dist/index.js +23465 -19403
- package/dist/index.js.map +1 -1
- package/dist/investigation/verbosity-cli.js +15615 -12482
- package/dist/investigation/verbosity-cli.js.map +1 -1
- package/dist/mcp/server.js +17002 -13929
- package/dist/mcp/server.js.map +1 -1
- package/dist/services/transcript-smoke.js +897 -0
- package/dist/services/transcript-smoke.js.map +1 -0
- package/dist/strategist/strategist-smoke.js +1873 -0
- package/dist/strategist/strategist-smoke.js.map +1 -0
- package/dist/whimsy/time-bank-smoke.js +21749 -18097
- package/dist/whimsy/time-bank-smoke.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/output/formatters.ts","../../src/services/transcript-smoke.ts","../../src/services/terminal-capture.ts","../../src/services/transcript.ts","../../src/cli/context.ts","../../src/services/context-doc.ts"],"sourcesContent":["import type { VitalSign, VitalSignStatus } from \"../types.js\";\n\nexport const VITAL_SIGN_LABELS: Record<VitalSign, string> = {\n freshness: \"Freshness\",\n flow_rate: \"Flow Rate\",\n drop_rate: \"Drop Rate\",\n signal_to_noise: \"Signal:Noise\",\n thread_depth: \"Thread Depth\",\n};\n\nexport function statusEmoji(status: VitalSignStatus): string {\n switch (status) {\n case \"green\": return \"π’\";\n case \"yellow\": return \"π‘\";\n case \"red\": return \"π΄\";\n }\n}\n\nexport function formatDollarImpact(value: number | null | undefined, label: string | null | undefined): string {\n if (value != null && value > 0) return `${formatDollarValue(value)} ${label ?? \"\"}`.trim();\n return \"N/A\";\n}\n\nexport function formatScore(score: number): string {\n return `${Math.round(score)}`;\n}\n\nexport function formatPercent(value: number): string {\n return `${Math.round(value)}%`;\n}\n\nexport function formatNumber(value: number): string {\n return value.toLocaleString();\n}\n\nexport function formatCurrency(value: number): string {\n if (value >= 1_000_000) return `$${(value / 1_000_000).toFixed(1)}M`;\n if (value >= 1_000) return `$${(value / 1_000).toFixed(0)}K`;\n return `$${value.toFixed(0)}`;\n}\n\nexport interface PipelineMetrics {\n total_pipeline_value: number;\n at_risk_value: number;\n at_risk_deal_count: number;\n total_open_deals: number;\n}\n\ninterface VitalSignResultLike {\n vital_sign: string;\n components: Record<string, unknown>;\n}\n\nexport function extractPipelineMetrics(vitals: VitalSignResultLike[]): PipelineMetrics | null {\n const flowRate = vitals.find((v) => v.vital_sign === \"flow_rate\");\n if (!flowRate) return null;\n const openDeals = flowRate.components.open_deals as Record<string, unknown> | undefined;\n if (!openDeals) return null;\n const total = typeof openDeals.total_amount === \"number\" ? openDeals.total_amount : 0;\n if (total === 0) return null;\n return {\n total_pipeline_value: total,\n at_risk_value: typeof openDeals.stuck_total_amount === \"number\" ? openDeals.stuck_total_amount : 0,\n at_risk_deal_count: typeof openDeals.stuck_count === \"number\" ? openDeals.stuck_count : 0,\n total_open_deals: typeof openDeals.count === \"number\" ? openDeals.count : 0,\n };\n}\n\nexport function formatPipelineLine(metrics: PipelineMetrics): string {\n const total = formatCurrency(metrics.total_pipeline_value);\n if (metrics.at_risk_deal_count > 0) {\n const atRisk = formatCurrency(metrics.at_risk_value);\n return `Pipeline: ${total} total \\u00B7 ${atRisk} at risk (${metrics.at_risk_deal_count} deals)`;\n }\n return `Pipeline: ${total} open (${metrics.total_open_deals} deals)`;\n}\n\nexport const DOLLAR_LABELS: Record<VitalSign, string> = {\n freshness: \"pipeline at risk\",\n flow_rate: \"stuck in pipeline\",\n drop_rate: \"est. lost at handoff\",\n signal_to_noise: \"misdirected effort\",\n thread_depth: \"single-threaded\",\n};\n\nexport function formatDollarValue(value: number | null | undefined): string {\n if (value == null || value === 0) return \"N/A\";\n return formatCurrency(value);\n}\n\nexport function severityLabel(severity: string): string {\n switch (severity) {\n case \"critical\": return \"CRITICAL\";\n case \"warning\": return \"WARNING\";\n case \"info\": return \"INFO\";\n default: return severity.toUpperCase();\n }\n}\n","/**\n * Offline smoke for session transcript storage β deterministic, no network,\n * no DB. Run via `npm run test:transcript` (wrapper isolates NTRP_HOME).\n *\n * Covers: the terminal capture emulator (spinner-frame collapse, CR/CRLF,\n * erase-line, cursor-column, screen clear, chunk-split escapes), secret\n * redaction, the context brief builder, and the recorder lifecycle\n * (start β capture β input markers β rebind/continue β discard β stop).\n */\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n TerminalCapture,\n stripAnsi,\n redactSecrets,\n SCREEN_CLEAR_MARKER,\n} from \"./terminal-capture.js\";\nimport {\n startSessionTranscript,\n stopSessionTranscript,\n rebindSessionTranscript,\n discardSessionTranscript,\n pauseTranscriptCapture,\n resumeTranscriptCapture,\n noteTranscriptInput,\n isTranscriptActive,\n} from \"./transcript.js\";\nimport { buildSessionContextDoc, writeSessionContextDoc } from \"./context-doc.js\";\nimport {\n getSessionsDir,\n transcriptPathForSession,\n contextDocPathForSession,\n defaultSessionAnalysis,\n type Context,\n type SessionFile,\n} from \"../cli/context.js\";\nimport type { FullComputeResult } from \"../vitals/health-score.js\";\n\nconst failures: string[] = [];\n\nfunction assert(cond: boolean, msg: string): void {\n if (!cond) failures.push(msg);\n}\n\n// βββ Terminal capture emulator ββββββββββββββββββββββββββββββββββββββββ\n\n{\n const cap = new TerminalCapture();\n cap.feed(\"plain line\\n\");\n cap.feed(\"\\x1b[31mcolored\\x1b[39m line\\n\");\n assert(cap.snapshot()[0] === \"plain line\", \"plain line committed\");\n assert(cap.snapshot()[1] === \"colored line\", \"SGR color codes removed\");\n}\n\n{\n // Spinner frames: cursor-to-col-0 + erase-line rewrites collapse to final state.\n const cap = new TerminalCapture();\n cap.feed(\"β Computing vitalsβ¦\");\n cap.feed(\"\\x1b[1G\\x1b[2Kβ Computing vitalsβ¦\");\n cap.feed(\"\\x1b[1G\\x1b[2Kβ Vitals computed\\n\");\n const lines = cap.snapshot();\n assert(lines.length === 1 && lines[0] === \"β Vitals computed\", `spinner frames collapse (got ${JSON.stringify(lines)})`);\n}\n\n{\n // CR-based overwrite vs CRLF line endings.\n const cap = new TerminalCapture();\n cap.feed(\"abc\\r\\n\");\n cap.feed(\"abc\\rxyz\\n\");\n const lines = cap.snapshot();\n assert(lines[0] === \"abc\", \"CRLF commits the line unchanged\");\n assert(lines[1] === \"xyz\", \"bare CR overwrites from column 0\");\n}\n\n{\n // CR at a chunk boundary β decided by the next chunk's first char.\n const cap = new TerminalCapture();\n cap.feed(\"abc\\r\");\n cap.feed(\"\\n\");\n cap.feed(\"def\\r\");\n cap.feed(\"z\\n\");\n const lines = cap.snapshot();\n assert(lines[0] === \"abc\", \"chunk-split CRLF still one line\");\n assert(lines[1] === \"zef\", \"chunk-split CR overwrite applies at col 0\");\n}\n\n{\n // Escape sequence split across chunks.\n const cap = new TerminalCapture();\n cap.feed(\"temp\");\n cap.feed(\"\\x1b[\");\n cap.feed(\"2K\\x1b[1Gfinal\\n\");\n assert(cap.snapshot()[0] === \"final\", \"chunk-split CSI erase applied\");\n}\n\n{\n // Erase-to-end + partial overwrite (readline-style repaint).\n const cap = new TerminalCapture();\n cap.feed(\"abcdef\\x1b[3G\\x1b[0K123\\n\");\n assert(cap.snapshot()[0] === \"ab123\", \"cursor-column + erase-right repaint\");\n}\n\n{\n // Backspace moves the cursor; spaces blank the tail.\n const cap = new TerminalCapture();\n cap.feed(\"abcd\\b\\b \\n\");\n assert(cap.snapshot()[0] === \"ab\", \"backspace + space erase\");\n}\n\n{\n // Screen clear preserves history with a marker (/clear, /home).\n const cap = new TerminalCapture();\n cap.feed(\"before\\n\\x1b[2J\\x1b[Hafter\\n\");\n const lines = cap.snapshot();\n assert(lines[0] === \"before\", \"history preserved across screen clear\");\n assert(lines[1] === SCREEN_CLEAR_MARKER, \"screen clear marker inserted\");\n assert(lines[2] === \"after\", \"output continues after clear\");\n}\n\n{\n // OSC (terminal title) dropped; bell dropped; in-progress line in snapshot.\n const cap = new TerminalCapture();\n cap.feed(\"\\x1b]0;ntrp\\x07hello\\x07 world\");\n const lines = cap.snapshot();\n assert(lines[0] === \"hello world\", \"OSC + BEL dropped, live line in snapshot\");\n cap.note(\"β― ntrp βΊ use demo data\");\n assert(cap.snapshot().includes(\"β― ntrp βΊ use demo data\"), \"note() appends standalone line\");\n}\n\n{\n // Line cap keeps the file bounded.\n const cap = new TerminalCapture(600);\n for (let i = 0; i < 1200; i++) cap.feed(`line ${i}\\n`);\n assert(cap.droppedLineCount > 0, \"old lines dropped past cap\");\n assert(cap.snapshot().length <= 700, \"snapshot bounded\");\n}\n\n// βββ stripAnsi + redaction ββββββββββββββββββββββββββββββββββββββββββββ\n\nassert(stripAnsi(\"\\x1b[2mdim\\x1b[22m \\x1b]0;t\\x07x\") === \"dim x\", \"stripAnsi removes CSI + OSC\");\nassert(\n redactSecrets(\"key sk-ant-api03-abcdefghijklmnop end\").includes(\"sk-antβ¦[redacted]\"),\n \"anthropic key redacted\",\n);\nassert(!redactSecrets(\"gsk_abcdefghijklmnop\").includes(\"abcdefghijklmnop\"), \"groq key redacted\");\nassert(\n redactSecrets(\"NTRP-AAAA-BBBB-CCCC-DDDD\").includes(\"NTRP-Aβ¦[redacted]\"),\n \"license key redacted\",\n);\nassert(redactSecrets(\"normal text $3.1M at risk\") === \"normal text $3.1M at risk\", \"plain text untouched\");\n\n// βββ Context brief builder ββββββββββββββββββββββββββββββββββββββββββββ\n\nconst sessionFile: SessionFile = {\n id: \"2026-07-29-ab12\",\n created_at: \"2026-07-29T10:00:00.000Z\",\n stage: \"analyzed\",\n name: \"board-prep\",\n dataset: {\n label: \"hidden_crisis demo\",\n source: \"demo:hidden_crisis\",\n counts: { contacts: 4200, opportunities: 310 },\n ingested_at: \"2026-07-29T10:01:00.000Z\",\n },\n scope: {\n intent_summary: \"Is our retention real for the board?\",\n primary_lens: \"revenue_metrics\",\n audience: \"board\",\n },\n analysis: {\n primary: \"revenue_metrics\",\n completed: [\"revenue_metrics\"],\n headline: [\n { metric: \"arr\", label: \"ARR\", formatted: \"$8.5M\" },\n { metric: \"nrr\", label: \"Net Revenue Retention\", formatted: \"95%\" },\n { metric: \"grr\", label: \"Gross Revenue Retention\", formatted: \"76%\" },\n ],\n },\n deliverables: [{ kind: \"board_deck_prompt\", at: \"2026-07-29T10:30:00.000Z\", path: \"/tmp/deck.md\" }],\n messages: [\n { role: \"user\", content: \"is our retention real for the board?\", at: \"2026-07-29T10:02:00.000Z\" },\n { role: \"agent\", content: \"Scope confirmed. Data check complete.\", at: \"2026-07-29T10:02:05.000Z\" },\n { role: \"user\", content: \"what is ARR?\", at: \"2026-07-29T10:03:00.000Z\" },\n { role: \"agent\", content: \"ARR is $12.4M based on the latest close history. \".repeat(30), at: \"2026-07-29T10:03:10.000Z\" },\n ],\n exchange_count: 2,\n};\n\nconst snapshot: FullComputeResult = {\n aggregate: {\n overall_score: 42,\n overall_status: \"red\",\n gating_vital_sign: \"freshness\",\n total_value_at_risk: 3_100_000,\n vital_signs: [\n {\n vital_sign: \"freshness\",\n score: 29,\n status: \"red\",\n components: {},\n entity_details: [],\n dollar_value: 3_100_000,\n dollar_label: \"pipeline at risk\",\n },\n ],\n },\n segments: [],\n};\n\nconst doc = buildSessionContextDoc(sessionFile, { snapshot });\nassert(doc.includes(\"# Session context β 2026-07-29-ab12 (board-prep)\"), \"context doc title + name\");\nassert(doc.includes(\"- Stage: analyzed\"), \"context doc stage\");\nassert(doc.includes(\"hidden_crisis demo\"), \"context doc dataset label\");\nassert(doc.includes(\"4,200 contacts\"), \"context doc entity counts\");\nassert(doc.includes(\"Is our retention real for the board?\"), \"context doc scope intent\");\nassert(doc.includes(\"- Gating vital sign: freshness\"), \"context doc gating vital\");\nassert(doc.includes(\"$3.1M pipeline at risk\"), \"context doc dollar translation\");\nassert(doc.includes(\"### Headline metrics\"), \"context doc headline section\");\nassert(doc.includes(\"- ARR: $8.5M\"), \"context doc headline ARR value\");\nassert(doc.includes(\"- Net Revenue Retention: 95%\"), \"context doc headline NRR value\");\nassert(doc.includes(\"board_deck_prompt\"), \"context doc deliverable\");\nassert(doc.includes(\"β― what is ARR?\"), \"context doc user question\");\nassert(doc.includes(\"β¦\"), \"context doc long agent answer truncated\");\nassert(doc.includes(\"/session ab12\"), \"context doc pickup hint\");\nassert(doc.includes(\".transcript.md\"), \"context doc links transcript\");\n\n// βββ Recorder lifecycle (isolated NTRP_HOME from the wrapper) βββββββββ\n\nfunction makeCtx(sessionId: string): Context {\n return {\n sessionId,\n sessionFile: `${sessionId}.json`,\n oneShot: false,\n execution: { mode: \"interactive\", output: \"terminal\", progress: true, color: false, strictStdout: false, quiet: false },\n snapshot: { computeResult: null, divergences: [] },\n messages: [],\n conversation: [],\n stage: \"new\",\n deliverables: [],\n analysis: defaultSessionAnalysis(),\n wizardDepth: 0,\n attachments: [],\n } as unknown as Context;\n}\n\nconst ctxA = makeCtx(\"2026-07-29-aaaa\");\nconst ctxB = makeCtx(\"2026-07-29-bbbb\");\n\nstartSessionTranscript(ctxA);\nassert(isTranscriptActive(ctxA.sessionId), \"recorder active for session A\");\nconsole.log(\"hello from session A\");\nconsole.error(\"stderr also captured\");\nconsole.log(\"inline key sk-ant-api03-abcdefghijklmnop here\");\npauseTranscriptCapture();\nconsole.log(\"PROMPT-ECHO-NOISE-SHOULD-NOT-APPEAR\");\nresumeTranscriptCapture();\nnoteTranscriptInput(\"ntrp βΊ \", \"use demo data\");\n\n// Sessions that persisted state (JSON on disk) keep their transcript on switch.\nwriteFileSync(join(getSessionsDir(), `${ctxA.sessionId}.json`), \"{}\\n\");\nwriteFileSync(join(getSessionsDir(), `${ctxB.sessionId}.json`), \"{}\\n\");\nrebindSessionTranscript(ctxB);\nconsole.log(\"hello from session B\");\nstopSessionTranscript();\n\nconst fileA = readFileSync(transcriptPathForSession(ctxA.sessionId), \"utf-8\");\nconst fileB = readFileSync(transcriptPathForSession(ctxB.sessionId), \"utf-8\");\nassert(fileA.includes(\"# ntrp transcript β 2026-07-29-aaaa\"), \"transcript A header\");\nassert(fileA.includes(\"hello from session A\"), \"stdout captured in A\");\nassert(fileA.includes(\"stderr also captured\"), \"stderr captured in A\");\nassert(fileA.includes(\"sk-antβ¦[redacted]\"), \"inline key redacted in transcript\");\nassert(!fileA.includes(\"sk-ant-api03-abcdefghijklmnop\"), \"raw key absent from transcript\");\nassert(!fileA.includes(\"PROMPT-ECHO-NOISE-SHOULD-NOT-APPEAR\"), \"paused output not captured\");\nassert(fileA.includes(\"β― ntrp βΊ use demo data\"), \"input marker recorded with prompt label\");\nassert(fileA.includes(\"(switched session)\"), \"A closed with switch note\");\nassert(!fileA.includes(\"hello from session B\"), \"B output not in A\");\nassert(fileB.includes(\"hello from session B\"), \"B transcript captured after rebind\");\nassert(fileB.includes(\"(session closed)\"), \"B closed on stop\");\n\n// Continuing an existing session appends a segment instead of overwriting.\nstartSessionTranscript(ctxA);\nconsole.log(\"picked this back up\");\nstopSessionTranscript();\nconst fileA2 = readFileSync(transcriptPathForSession(ctxA.sessionId), \"utf-8\");\nassert(fileA2.includes(\"hello from session A\"), \"continue keeps prior history\");\nassert(fileA2.includes(\"## Continued β \"), \"continue adds Continued segment\");\nassert(fileA2.includes(\"picked this back up\"), \"continue captures new output\");\n\n// Discard removes the file for empty sessions.\nconst ctxC = makeCtx(\"2026-07-29-cccc\");\nstartSessionTranscript(ctxC);\nconsole.log(\"ephemeral\");\ndiscardSessionTranscript(ctxC.sessionId);\nstopSessionTranscript();\nassert(!existsSync(transcriptPathForSession(ctxC.sessionId)), \"discarded transcript deleted\");\n\n// Switching away from a session that never persisted state (no JSON) drops\n// its welcome-screen-only transcript instead of leaving an orphan file.\nconst ctxE = makeCtx(\"2026-07-29-eeee\");\nconst ctxF = makeCtx(\"2026-07-29-ffff\");\nstartSessionTranscript(ctxE);\nconsole.log(\"throwaway shell before pickup\");\nrebindSessionTranscript(ctxF);\nstopSessionTranscript();\nassert(!existsSync(transcriptPathForSession(ctxE.sessionId)), \"unpersisted session transcript discarded on switch\");\n\n// Context brief writer from a live context.\nconst ctxD = makeCtx(\"2026-07-29-dddd\");\nctxD.messages.push({ role: \"user\", content: \"why is freshness red?\", at: new Date().toISOString() });\nctxD.messages.push({ role: \"agent\", content: \"Because 61% of contacts are stale.\", at: new Date().toISOString() });\nwriteSessionContextDoc(ctxD);\nconst docD = readFileSync(contextDocPathForSession(ctxD.sessionId), \"utf-8\");\nassert(docD.includes(\"# Session context β 2026-07-29-dddd\"), \"live context doc written\");\nassert(docD.includes(\"why is freshness red?\"), \"live context doc includes exchange\");\n\n// βββ Report βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nif (failures.length > 0) {\n console.error(`FAIL transcript-smoke (${failures.length}):`);\n for (const f of failures) console.error(` - ${f}`);\n process.exit(1);\n}\nconsole.log(\n \"PASS transcript-smoke (capture emulator, redaction, context brief, recorder lifecycle)\",\n);\n","/**\n * Terminal capture β reconstructs the visible terminal text from a raw\n * stdout/stderr stream.\n *\n * The REPL paints with ANSI escapes: ora spinners rewrite the same row many\n * times per second, readline repaints the prompt, /clear wipes the screen.\n * Persisting the raw byte stream would be unreadable, so this module runs a\n * tiny single-row terminal emulator: it tracks the current line + cursor\n * column, applies carriage returns / erase-line / cursor-column sequences,\n * and commits a line only when a newline arrives. Spinner frames therefore\n * collapse to their final state β the transcript reads like what the\n * operator actually saw.\n *\n * Pure and side-effect free β the stream tee lives in transcript.ts.\n */\n\nconst MAX_LINES_DEFAULT = 20_000;\nconst DROP_CHUNK = 500;\n\n/** Matches CSI, OSC, and other escape sequences for one-off stripping. */\nconst ANSI_ANY =\n // eslint-disable-next-line no-control-regex\n /\\x1B(?:\\[[0-9;?]*[ -/]*[@-~]|\\][^\\x07\\x1B]*(?:\\x07|\\x1B\\\\)?|[()][0-9A-Za-z]|[@-Z\\\\-_=><])/g;\n\n/** Strip all ANSI escapes + non-newline control chars from a string. */\nexport function stripAnsi(value: string): string {\n // eslint-disable-next-line no-control-regex\n return value.replace(ANSI_ANY, \"\").replace(/[\\x00-\\x08\\x0b-\\x1f\\x7f]/g, \"\");\n}\n\n// ============================================================\n// Secret redaction\n// ============================================================\n\n/**\n * Provider API keys and license keys must never persist in a transcript that\n * is meant to be shared for triage. Masked prompts already print bullets, but\n * keys typed inline (`/connect --key sk-β¦`, `ntrp activate NTRP-β¦`) would\n * otherwise land verbatim.\n */\nconst SECRET_PATTERNS: RegExp[] = [\n /\\bsk-ant-[A-Za-z0-9_-]{8,}/g, // Anthropic\n /\\bsk-or-[A-Za-z0-9_-]{8,}/g, // OpenRouter\n /\\bsk-proj-[A-Za-z0-9_-]{8,}/g, // OpenAI project keys\n /\\bsk-[A-Za-z0-9_-]{20,}/g, // OpenAI / generic sk-\n /\\bgsk_[A-Za-z0-9_-]{8,}/g, // Groq\n /\\bxai-[A-Za-z0-9_-]{8,}/g, // xAI\n /\\bfw_[A-Za-z0-9_-]{8,}/g, // Fireworks\n /\\bAIza[A-Za-z0-9_-]{10,}/g, // Google\n /\\bNTRP-[A-Z0-9][A-Z0-9-]{8,}/g, // license keys\n];\n\n/** Replace key-shaped tokens with a short prefix + redaction marker. */\nexport function redactSecrets(line: string): string {\n let out = line;\n for (const pattern of SECRET_PATTERNS) {\n out = out.replace(pattern, (m) => `${m.slice(0, 6)}β¦[redacted]`);\n }\n return out;\n}\n\n// ============================================================\n// Capture emulator\n// ============================================================\n\nexport const SCREEN_CLEAR_MARKER = \"ββ screen cleared ββ\";\n\nexport class TerminalCapture {\n private lines: string[] = [];\n private cur = \"\";\n private col = 0;\n /** Partial escape sequence held across chunk boundaries. */\n private carry = \"\";\n /** A bare \\r at a chunk boundary β CRLF vs overwrite is decided by the next char. */\n private pendingCr = false;\n private dropped = 0;\n\n constructor(private readonly maxLines = MAX_LINES_DEFAULT) {}\n\n /** Feed a raw chunk of terminal output. */\n feed(chunk: string): void {\n const data = this.carry + chunk;\n this.carry = \"\";\n let i = 0;\n\n while (i < data.length) {\n const c = data[i]!;\n\n if (this.pendingCr) {\n this.pendingCr = false;\n if (c === \"\\n\") {\n this.newline();\n i++;\n continue;\n }\n // Bare CR β cursor returns to column 0; following text overwrites.\n this.col = 0;\n }\n\n if (c === \"\\n\") {\n this.newline();\n i++;\n continue;\n }\n if (c === \"\\r\") {\n this.pendingCr = true;\n i++;\n continue;\n }\n if (c === \"\\x1b\") {\n const consumed = this.consumeEscape(data, i);\n if (consumed === -1) {\n // Incomplete sequence β hold for the next chunk.\n this.carry = data.slice(i);\n return;\n }\n i += consumed;\n continue;\n }\n if (c === \"\\b\") {\n this.col = Math.max(0, this.col - 1);\n i++;\n continue;\n }\n if (c === \"\\t\") {\n const next = Math.floor(this.col / 8) * 8 + 8;\n while (this.col < next) this.writeChar(\" \");\n i++;\n continue;\n }\n if (c < \" \" || c === \"\\x7f\") {\n i++;\n continue; // bell + misc control chars\n }\n\n this.writeChar(c);\n i++;\n }\n }\n\n /** Append a standalone line (operator input markers, section notes). */\n note(line: string): void {\n this.commit(line);\n }\n\n /** Committed lines + the in-progress line (e.g. a live spinner row). */\n snapshot(): string[] {\n const out = [...this.lines];\n if (this.cur.trim().length > 0) out.push(this.cur.trimEnd());\n return out;\n }\n\n /** Lines evicted from the front once maxLines was exceeded. */\n get droppedLineCount(): number {\n return this.dropped;\n }\n\n // ----------------------------------------------------------\n\n private writeChar(c: string): void {\n if (this.col < this.cur.length) {\n this.cur = this.cur.slice(0, this.col) + c + this.cur.slice(this.col + 1);\n } else {\n this.cur = this.cur.padEnd(this.col, \" \") + c;\n }\n this.col++;\n }\n\n private newline(): void {\n this.commit(this.cur.trimEnd());\n this.cur = \"\";\n this.col = 0;\n }\n\n private commit(line: string): void {\n this.lines.push(line);\n if (this.lines.length > this.maxLines) {\n this.lines.splice(0, DROP_CHUNK);\n this.dropped += DROP_CHUNK;\n }\n }\n\n /**\n * Consume one escape sequence starting at data[start] (which is ESC).\n * Returns the number of chars consumed, or -1 if the sequence is\n * incomplete at the end of the chunk.\n */\n private consumeEscape(data: string, start: number): number {\n if (start + 1 >= data.length) return -1;\n const kind = data[start + 1]!;\n\n // CSI β ESC [ params final\n if (kind === \"[\") {\n let i = start + 2;\n while (i < data.length && /[0-9;?]/.test(data[i]!)) i++;\n while (i < data.length && data[i]! >= \" \" && data[i]! <= \"/\") i++;\n if (i >= data.length) return -1;\n const final = data[i]!;\n const params = data.slice(start + 2, i).replace(/[?]/g, \"\");\n this.applyCsi(params, final);\n return i - start + 1;\n }\n\n // OSC β ESC ] ... (BEL | ESC \\)\n if (kind === \"]\") {\n let i = start + 2;\n while (i < data.length) {\n if (data[i] === \"\\x07\") return i - start + 1;\n if (data[i] === \"\\x1b\" && data[i + 1] === \"\\\\\") return i - start + 2;\n i++;\n }\n return -1;\n }\n\n // Charset designators β ESC ( X / ESC ) X\n if (kind === \"(\" || kind === \")\") {\n if (start + 2 >= data.length) return -1;\n return 3;\n }\n\n // Other two-char escapes (ESC =, ESC >, ESC 7, ESC 8, β¦)\n return 2;\n }\n\n private applyCsi(params: string, final: string): void {\n const first = Number.parseInt(params.split(\";\")[0] ?? \"\", 10);\n const n = Number.isFinite(first) ? first : undefined;\n\n switch (final) {\n case \"K\": // erase in line\n if (n === 2) {\n this.cur = \"\";\n } else if (n === 1) {\n const keep = this.cur.slice(this.col);\n this.cur = \" \".repeat(Math.min(this.col, this.cur.length)) + keep;\n } else {\n this.cur = this.cur.slice(0, this.col);\n }\n break;\n case \"G\": // cursor to column\n this.col = Math.max(0, (n ?? 1) - 1);\n break;\n case \"J\": // erase in display\n if (n === 2 || n === 3) {\n if (this.cur.trim().length > 0) this.commit(this.cur.trimEnd());\n this.commit(SCREEN_CLEAR_MARKER);\n this.cur = \"\";\n this.col = 0;\n } else {\n this.cur = this.cur.slice(0, this.col);\n }\n break;\n case \"C\": // cursor right\n this.col += n ?? 1;\n break;\n case \"D\": // cursor left\n this.col = Math.max(0, this.col - (n ?? 1));\n break;\n case \"E\": // next line\n case \"F\": // previous line\n this.col = 0;\n break;\n case \"H\": // cursor home (row ignored β single-row model)\n case \"f\":\n this.col = 0;\n break;\n default:\n // SGR colors, cursor show/hide, scroll regions, β¦ β no text effect.\n break;\n }\n }\n}\n","/**\n * Session transcript recorder β persists the raw terminal session to\n * ~/.ntrp/sessions/<id>.transcript.md so a session can be triaged after the\n * fact (what was computed, what the operator typed, what was printed).\n *\n * How it works:\n * - Tees process.stdout / process.stderr writes into a TerminalCapture\n * (spinner frames collapse, ANSI is resolved to visible text).\n * - Capture pauses while the REPL prompt is idle; the submitted line is\n * recorded as an explicit `β― <prompt><input>` marker instead, so\n * keystroke echo / ghost autocompletion never pollute the file.\n * - The file is fully rewritten on a short throttle so it is valid\n * markdown at all times β a crash loses at most ~1s of output, which is\n * exactly when a transcript matters most.\n * - Session switches (/new, /session <id>, /end) rebind the recorder to\n * the new session's file; picking up an existing session appends a\n * \"Continued\" segment rather than overwriting history.\n *\n * Interactive REPL only β one-shot commands print to the terminal the user\n * already controls and are not session-scoped.\n */\n\nimport { existsSync, readFileSync, writeFileSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Context } from \"../cli/context.js\";\nimport { getSessionsDir, transcriptPathForSession } from \"../cli/context.js\";\nimport { TerminalCapture, stripAnsi, redactSecrets } from \"./terminal-capture.js\";\n\nconst FLUSH_THROTTLE_MS = 250;\nconst FLUSH_MAX_STALENESS_MS = 900;\n\ninterface RecorderState {\n sessionId: string;\n filePath: string;\n /** Prior file content when continuing an existing session's transcript. */\n base: string;\n segmentStartedAt: string;\n capture: TerminalCapture;\n paused: boolean;\n discarded: boolean;\n lastFlushMs: number;\n flushTimer: NodeJS.Timeout | null;\n}\n\nlet state: RecorderState | null = null;\n\ntype WriteFn = typeof process.stdout.write;\nlet originalStdoutWrite: WriteFn | null = null;\nlet originalStderrWrite: WriteFn | null = null;\n\n// ============================================================\n// Lifecycle\n// ============================================================\n\n/** Begin recording the interactive session. No-op in one-shot mode. */\nexport function startSessionTranscript(ctx: Context): void {\n if (ctx.oneShot || state) return;\n installTees();\n state = createState(ctx.sessionId);\n flushNow();\n}\n\n/** Rebind the recorder when the context rotates/picks up another session. */\nexport function rebindSessionTranscript(ctx: Context): void {\n if (!state || state.sessionId === ctx.sessionId) return;\n // A session that never persisted any state (no JSON) has nothing to triage β\n // don't leave a welcome-screen-only transcript behind (mirrors the\n // empty-session cleanup in finalizeSession).\n const priorJson = join(getSessionsDir(), `${state.sessionId}.json`);\n if (existsSync(priorJson)) {\n finalizeCurrentFile(\"switched session\");\n } else {\n discardSessionTranscript(state.sessionId);\n }\n state = createState(ctx.sessionId);\n flushNow();\n}\n\n/** Stop recording and write the final flush. */\nexport function stopSessionTranscript(): void {\n if (!state) return;\n finalizeCurrentFile(\"session closed\");\n state = null;\n removeTees();\n}\n\n/**\n * Delete the transcript of a session that turned out to be empty (no\n * exchanges, no data, no deliverables) β mirrors finalizeSession's policy of\n * not leaving empty session files behind.\n */\nexport function discardSessionTranscript(sessionId: string): void {\n if (state && state.sessionId === sessionId) {\n state.discarded = true;\n clearFlushTimer();\n }\n try {\n rmSync(transcriptPathForSession(sessionId), { force: true });\n } catch {\n // best-effort\n }\n}\n\n/** Suspend capture while the REPL prompt is idle (input echo is noise). */\nexport function pauseTranscriptCapture(): void {\n if (state) state.paused = true;\n}\n\nexport function resumeTranscriptCapture(): void {\n if (state) state.paused = false;\n}\n\n/** Record a submitted input line with its phase prompt, e.g. `β― ask βΊ high what is arr`. */\nexport function noteTranscriptInput(promptLabel: string, input: string): void {\n if (!state || state.discarded) return;\n state.capture.note(\"\");\n state.capture.note(`β― ${stripAnsi(promptLabel)}${input}`.trimEnd());\n flushNow();\n}\n\n/** True when the recorder is active for this session id. */\nexport function isTranscriptActive(sessionId?: string): boolean {\n if (!state || state.discarded) return false;\n return sessionId === undefined || state.sessionId === sessionId;\n}\n\n// ============================================================\n// Stream tees\n// ============================================================\n\nfunction installTees(): void {\n if (originalStdoutWrite) return;\n originalStdoutWrite = process.stdout.write.bind(process.stdout) as WriteFn;\n originalStderrWrite = process.stderr.write.bind(process.stderr) as WriteFn;\n\n const tee =\n (original: WriteFn): WriteFn =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ((chunk: any, encoding?: any, callback?: any) => {\n try {\n if (state && !state.paused && !state.discarded) {\n const text =\n typeof chunk === \"string\"\n ? chunk\n : Buffer.isBuffer(chunk)\n ? chunk.toString(\"utf-8\")\n : String(chunk);\n state.capture.feed(text);\n scheduleFlush();\n }\n } catch {\n // The transcript must never break the live terminal.\n }\n return original(chunk, encoding, callback);\n }) as WriteFn;\n\n process.stdout.write = tee(originalStdoutWrite);\n process.stderr.write = tee(originalStderrWrite);\n}\n\nfunction removeTees(): void {\n if (originalStdoutWrite) {\n process.stdout.write = originalStdoutWrite;\n originalStdoutWrite = null;\n }\n if (originalStderrWrite) {\n process.stderr.write = originalStderrWrite;\n originalStderrWrite = null;\n }\n}\n\n// ============================================================\n// Rendering + flushing\n// ============================================================\n\nfunction createState(sessionId: string): RecorderState {\n const filePath = transcriptPathForSession(sessionId);\n let base = \"\";\n if (existsSync(filePath)) {\n try {\n base = readFileSync(filePath, \"utf-8\").trimEnd() + \"\\n\";\n } catch {\n base = \"\";\n }\n }\n return {\n sessionId,\n filePath,\n base,\n segmentStartedAt: new Date().toISOString(),\n capture: new TerminalCapture(),\n paused: false,\n discarded: false,\n lastFlushMs: 0,\n flushTimer: null,\n };\n}\n\nfunction renderHeader(sessionId: string): string {\n return [\n `# ntrp transcript β ${sessionId}`,\n \"\",\n `- Session data: \\`${sessionId}.json\\` Β· Context brief: \\`${sessionId}.context.md\\``,\n \"- Raw terminal text (ANSI stripped, spinner frames collapsed). Lines starting with `β―` are operator input.\",\n \"\",\n \"\",\n ].join(\"\\n\");\n}\n\nfunction renderSegment(s: RecorderState, closedNote?: string): string {\n const lines = s.capture.snapshot().map(redactSecrets);\n const dropped = s.capture.droppedLineCount;\n\n // Fenced block must survive terminal output that itself contains backticks\n // (handoff prompts print fenced markdown) β grow the fence past the longest\n // backtick run in the content.\n let longestRun = 0;\n for (const line of lines) {\n for (const match of line.matchAll(/`+/g)) {\n if (match[0].length > longestRun) longestRun = match[0].length;\n }\n }\n const fence = \"`\".repeat(Math.max(3, longestRun + 1));\n\n const heading = s.base\n ? `## Continued β ${s.segmentStartedAt}`\n : `## Session start β ${s.segmentStartedAt}`;\n\n const parts: string[] = [heading, \"\"];\n if (dropped > 0) {\n parts.push(`_(${dropped.toLocaleString()} earlier lines dropped to bound file size)_`, \"\");\n }\n parts.push(`${fence}text`, ...lines, fence, \"\");\n parts.push(\n closedNote\n ? `_Closed: ${new Date().toISOString()} (${closedNote})_`\n : `_Last write: ${new Date().toISOString()}_`,\n );\n parts.push(\"\");\n return parts.join(\"\\n\");\n}\n\nfunction render(s: RecorderState, closedNote?: string): string {\n const prefix = s.base ? s.base + \"\\n\" : renderHeader(s.sessionId);\n return prefix + renderSegment(s, closedNote);\n}\n\nfunction flushNow(closedNote?: string): void {\n const s = state;\n if (!s || s.discarded) return;\n clearFlushTimer();\n s.lastFlushMs = Date.now();\n try {\n getSessionsDir(); // ensure the directory exists (e.g. after /scratch)\n writeFileSync(s.filePath, render(s, closedNote));\n } catch {\n // best-effort β never break the session over transcript IO\n }\n}\n\nfunction scheduleFlush(): void {\n const s = state;\n if (!s || s.discarded) return;\n if (Date.now() - s.lastFlushMs >= FLUSH_MAX_STALENESS_MS) {\n flushNow();\n return;\n }\n if (s.flushTimer) return;\n s.flushTimer = setTimeout(() => {\n if (state) state.flushTimer = null;\n flushNow();\n }, FLUSH_THROTTLE_MS);\n s.flushTimer.unref?.();\n}\n\nfunction clearFlushTimer(): void {\n if (state?.flushTimer) {\n clearTimeout(state.flushTimer);\n state.flushTimer = null;\n }\n}\n\nfunction finalizeCurrentFile(reason: string): void {\n if (!state) return;\n clearFlushTimer();\n if (!state.discarded) flushNow(reason);\n}\n","/**\n * Shared execution context passed into every handler + the REPL.\n *\n * Caches:\n * - session ID + session file path (for Last Activity persistence)\n * - lazily-computed FullComputeResult (reused across NL questions)\n * - current config snapshot\n */\n\nimport { basename, join, resolve, sep } from \"node:path\";\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, statSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { randomUUID } from \"node:crypto\";\nimport type { Interface as ReadlineInterface } from \"node:readline/promises\";\nimport type { LlmMessage } from \"../ai/llm/types.js\";\nimport { normalizeThread } from \"../ai/llm/thread-compat.js\";\nimport type { FullComputeResult } from \"../vitals/health-score.js\";\nimport type { Divergence } from \"../pipeline/divergence.js\";\nimport { buildExecutionOptions } from \"../io/context.js\";\nimport type { ExecutionOptions } from \"../io/types.js\";\nimport type { AnalysisLens, LlmSessionOverride, SessionAnalysis } from \"../types.js\";\nimport type { AnalysisScope, ChatAttachment, GapAuditResult } from \"../conversation/types.js\";\nimport { writeSessionContextDoc, writeContextDocForSessionFile } from \"../services/context-doc.js\";\nimport { rebindSessionTranscript, discardSessionTranscript } from \"../services/transcript.js\";\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface SessionMessage {\n role: \"user\" | \"agent\";\n content: string;\n at: string;\n}\n\n/**\n * Lifecycle of a point-in-time analysis:\n * new β session created, no data loaded / not yet diagnosed\n * analyzed β data loaded and diagnosed; ready for questions\n * delivered β an output / action was produced (the session reached action)\n * ended β user explicitly closed without producing an output (/end)\n * \"Unfinished\" work = stage \"analyzed\" (reached insight, never shipped).\n */\nexport type SessionStage = \"new\" | \"analyzed\" | \"delivered\" | \"ended\";\n\n/** What data a session is anchored to β the heart of the point-in-time model. */\nexport interface DatasetMeta {\n /** Human label, e.g. \"Acme Q2 export\" or \"hidden_crisis demo\". */\n label?: string;\n /** Where the data came from: a file path, \"demo:<scenario>\", etc. */\n source?: string;\n /** Entity counts captured at ingest time. */\n counts?: Record<string, number>;\n /** When the data was loaded. */\n ingested_at?: string;\n}\n\n/** A produced output / action taken from the analysis. */\nexport interface Deliverable {\n kind: string;\n at: string;\n path?: string;\n note?: string;\n}\n\n/** Multi-turn strategist flow state β drives the strategize conversation phase. */\nexport interface StrategistFlowState {\n /**\n * awaiting_analysis β strategist requested pre-analysis; auto-resumes after compute\n * objective_confirm β objective card printed, awaiting yes/adjust\n * objective_input β waiting for the user to state the objective in their words\n */\n step: \"awaiting_analysis\" | \"objective_confirm\" | \"objective_input\";\n /** Candidate objective (user's seed text or proposed from the gating vital sign). */\n objective?: string;\n /** Operator-stated constraints captured inline (capacity, deadlines). */\n constraintsNote?: string;\n /** Which door the session came through. */\n origin?: \"command\" | \"nl\" | \"ai\";\n}\n\nexport interface SessionFile {\n id: string;\n created_at: string;\n messages: SessionMessage[];\n ended_at?: string;\n exchange_count?: number;\n summary?: string;\n resumed_from?: string;\n name?: string;\n /** Lifecycle stage of this point-in-time analysis. */\n stage?: SessionStage;\n /** The dataset this session is anchored to. */\n dataset?: DatasetMeta;\n /** Outputs / actions produced from this analysis. */\n deliverables?: Deliverable[];\n /**\n * Compacted Anthropic message thread (text-only Q&A) for true cross-session\n * continuity. Re-seeded into the agent on resume/switch so it remembers the\n * actual prior exchanges, not just an 80-char summary.\n */\n thread?: LlmMessage[];\n /** Primary and completed analysis lenses for this session. */\n analysis?: SessionAnalysis;\n /** Conversation-first analysis scope. */\n scope?: AnalysisScope;\n /** Files ingested via chat. */\n attachments?: ChatAttachment[];\n /** Session-scoped LLM engine overrides (provider, tier, model). */\n llm?: LlmSessionOverride;\n /** In-flight strategist flow (resumes across REPL restarts). */\n strategist?: StrategistFlowState;\n}\n\nexport interface SessionListEntry {\n id: string;\n created_at: string;\n ended_at?: string;\n exchange_count: number;\n summary?: string;\n name?: string;\n stage?: SessionStage;\n dataset?: DatasetMeta;\n deliverables?: Deliverable[];\n analysis?: SessionAnalysis;\n scope?: AnalysisScope;\n mtime: number;\n}\n\n/** In-progress sessions untouched this long group under \"Stale\" in lists. */\nexport const STALE_SESSION_MS = 14 * 24 * 60 * 60 * 1000;\n\n/** True when the session file hasn't been touched in STALE_SESSION_MS. */\nexport function isSessionStale(s: SessionListEntry): boolean {\n return Date.now() - s.mtime > STALE_SESSION_MS;\n}\n\nexport interface Context {\n /** Unique session ID β new one per REPL launch. */\n sessionId: string;\n /** Absolute path to the session file on disk. */\n sessionFile: string;\n /** True when running a single command and exiting. */\n oneShot: boolean;\n /** Output and process behavior for terminal, JSON, and agent use. */\n execution: ExecutionOptions;\n /** Lazily-computed health snapshot. Populated on first NL question. */\n snapshot: {\n computeResult: FullComputeResult | null;\n divergences: Divergence[];\n };\n /** In-memory session message log. Persisted to disk after each exchange. */\n messages: SessionMessage[];\n /**\n * Compacted cross-turn conversation thread (text-only Q&A) fed back into the\n * agent on every turn so it has continuity and never answers from a blank\n * slate. Persisted to the session file and rehydrated on resume/switch.\n */\n conversation: LlmMessage[];\n /**\n * When running inside the REPL, the REPL's readline interface is stored\n * here so interactive commands (wizards, confirms) can reuse it instead\n * of opening a second interface on stdin β two interfaces on the same\n * TTY produces double-echo keystrokes. Undefined in one-shot mode and\n * during first-run onboarding (before the REPL has started).\n */\n rl?: ReadlineInterface;\n /** Summary loaded from a resumed session. */\n resumedSessionSummary?: string;\n /** Session ID that was resumed. */\n resumedFromId?: string;\n /** Human-readable session name set via /name or /switch. */\n sessionName?: string;\n /** Absolute path of this session's dataset DB file (interactive REPL only). */\n datasetPath?: string;\n /** Lifecycle stage of the current point-in-time analysis. */\n stage: SessionStage;\n /** The dataset the current session is anchored to. */\n dataset?: DatasetMeta;\n /** Outputs / actions produced from the current analysis. */\n deliverables: Deliverable[];\n /** The most recent NL question + answer, for lightweight /rate feedback. */\n lastExchange?: { question: string; answer: string };\n /** Primary and completed analysis lenses. */\n analysis: SessionAnalysis;\n /** Active interactive wizard depth (REPL readline shared with prompts). */\n wizardDepth: number;\n /** True while masked secret entry owns stdin β REPL must not echo keypresses. */\n secretInputActive?: boolean;\n /** Conversation-first scope for this analysis. */\n scope?: AnalysisScope;\n /** Files ingested through chat. */\n attachments?: ChatAttachment[];\n /** Cached data gap audit (invalidated on ingest). */\n gapAudit?: GapAuditResult;\n /** User signaled deliverable intent β drives deliver phase. */\n deliverIntent?: boolean;\n /** Transient flag while formula compute runs. */\n computeInProgress?: boolean;\n /** Session-scoped LLM engine overrides β cleared on /new, persisted on resume. */\n llm?: LlmSessionOverride;\n /** In-flight strategist flow β drives the strategize phase. */\n strategistState?: StrategistFlowState;\n /** True once the interactive REPL loop has started (false during first-run onboard). */\n replStarted?: boolean;\n /** Background update check when cache is stale (REPL startup). */\n pendingUpdateCheck?: Promise<import(\"../update/registry.js\").UpdateCheckResult | null>;\n /** Transient β conversation compute credits gap_compute instead of full diagnose. */\n skipTimeBankDiagnoseCredit?: boolean;\n}\n\n/** True when a report has been produced and the user can ask questions. */\nexport function isAnalysisReady(ctx: Context): boolean {\n if (ctx.stage !== \"analyzed\" || ctx.analysis.completed.length === 0) return false;\n if (!ctx.dataset) return false;\n const counts = ctx.dataset.counts ?? {};\n return Object.values(counts).some((n) => n > 0);\n}\n\n// ============================================================\n// Directories\n// ============================================================\n\nconst SESSION_ID_RE = /^\\d{4}-\\d{2}-\\d{2}-[a-f0-9]{4}$/i;\n\nfunction ntrpHomeDir(): string {\n return process.env.NTRP_HOME ? resolve(process.env.NTRP_HOME) : join(homedir(), \".ntrp\");\n}\n\nexport function getSessionsDir(): string {\n const dir = join(ntrpHomeDir(), \"sessions\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\nexport function getDatasetsDir(): string {\n const dir = join(ntrpHomeDir(), \"datasets\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\n/** Absolute path of the per-session dataset DB file for a session id. */\nexport function datasetPathForSession(id: string): string {\n return join(getDatasetsDir(), `${id}.duckdb`);\n}\n\n/** Absolute path of the raw terminal transcript markdown for a session id. */\nexport function transcriptPathForSession(id: string): string {\n return join(getSessionsDir(), `${id}.transcript.md`);\n}\n\n/** Absolute path of the summarized context brief markdown for a session id. */\nexport function contextDocPathForSession(id: string): string {\n return join(getSessionsDir(), `${id}.context.md`);\n}\n\n// ============================================================\n// Session lifecycle\n// ============================================================\n\nexport function makeSessionId(): string {\n const now = new Date();\n const date = now.toISOString().slice(0, 10);\n const uuid = randomUUID().slice(0, 4);\n return `${date}-${uuid}`;\n}\n\nfunction isValidSessionId(id: string): boolean {\n return SESSION_ID_RE.test(id);\n}\n\nfunction sessionPathForId(id: string): string | null {\n if (!isValidSessionId(id)) return null;\n const dir = resolve(getSessionsDir());\n const filePath = resolve(dir, `${id}.json`);\n if (filePath !== dir && !filePath.startsWith(dir + sep)) return null;\n return filePath;\n}\n\nexport function initContext(oneShot: boolean, execution?: Partial<ExecutionOptions>): Context {\n const sessionId = makeSessionId();\n const sessionFile = join(getSessionsDir(), `${sessionId}.json`);\n\n return {\n sessionId,\n sessionFile,\n oneShot,\n execution: buildExecutionOptions({\n mode: oneShot ? \"one_shot\" : \"interactive\",\n ...execution,\n }),\n snapshot: { computeResult: null, divergences: [] },\n messages: [],\n conversation: [],\n stage: \"new\",\n deliverables: [],\n analysis: defaultSessionAnalysis(),\n wizardDepth: 0,\n attachments: [],\n deliverIntent: false,\n computeInProgress: false,\n };\n}\n\n/** Snapshot the live context as a SessionFile (used for persistence + context brief). */\nexport function buildSessionFileSnapshot(ctx: Context): SessionFile {\n const file: SessionFile = {\n id: ctx.sessionId,\n created_at: ctx.messages[0]?.at ?? new Date().toISOString(),\n messages: ctx.messages,\n stage: ctx.stage,\n };\n if (ctx.sessionName) file.name = ctx.sessionName;\n if (ctx.dataset) file.dataset = ctx.dataset;\n if (ctx.deliverables.length > 0) file.deliverables = ctx.deliverables;\n if (ctx.conversation.length > 0) file.thread = ctx.conversation;\n if (ctx.resumedFromId) file.resumed_from = ctx.resumedFromId;\n if (ctx.analysis) file.analysis = ctx.analysis;\n if (ctx.scope) file.scope = ctx.scope;\n if (ctx.attachments && ctx.attachments.length > 0) file.attachments = ctx.attachments;\n if (ctx.llm && Object.keys(ctx.llm).length > 0) file.llm = ctx.llm;\n if (ctx.strategistState) file.strategist = ctx.strategistState;\n return file;\n}\n\nexport function defaultSessionAnalysis(primary: AnalysisLens = \"gtm_health\"): SessionAnalysis {\n return { primary, completed: [] };\n}\n\nexport function setPrimaryLens(ctx: Context, lens: AnalysisLens): void {\n ctx.analysis = { ...ctx.analysis, primary: lens };\n}\n\nexport function markLensCompleted(ctx: Context, lens: AnalysisLens): void {\n const completed = ctx.analysis.completed.includes(lens)\n ? ctx.analysis.completed\n : [...ctx.analysis.completed, lens];\n ctx.analysis = { ...ctx.analysis, completed };\n}\n\nexport function lensBadgeLabel(analysis?: SessionAnalysis): string {\n if (!analysis) return \"health\";\n const hasHealth = analysis.completed.includes(\"gtm_health\") || analysis.primary === \"gtm_health\";\n const hasMetrics = analysis.completed.includes(\"revenue_metrics\") || analysis.primary === \"revenue_metrics\";\n if (hasHealth && hasMetrics) return \"both\";\n if (hasMetrics) return \"metrics\";\n return \"health\";\n}\n\n/** Session lens context injected into NL / ask agent prompts. */\nexport function buildAnalysisBlock(ctx: Context): string {\n return [\n `Primary lens: ${ctx.analysis.primary}`,\n `Completed: ${ctx.analysis.completed.join(\", \") || \"none\"}`,\n `Badge: ${lensBadgeLabel(ctx.analysis)}`,\n ctx.analysis.coverage\n ? `Coverage: ${ctx.analysis.coverage.distinct_months} months, ${ctx.analysis.coverage.recommended_cadence} cadence`\n : null,\n ctx.analysis.data_source_type ? `Data source: ${ctx.analysis.data_source_type}` : null,\n ].filter(Boolean).join(\"\\n\");\n}\n\n/** Metrics-primary session with metrics done but no GTM health run yet. */\nexport function prefersMetricsFirstContext(ctx: Context): boolean {\n return (\n ctx.analysis.primary === \"revenue_metrics\" &&\n ctx.analysis.completed.includes(\"revenue_metrics\") &&\n !ctx.analysis.completed.includes(\"gtm_health\")\n );\n}\n\n/**\n * Rehydrate analysis lens state for headless / MCP paths that skip the REPL.\n * Prefers the most recent persisted session; falls back to DB lane signals.\n */\nexport async function hydrateAnalysisFromPersistedState(ctx: Context): Promise<void> {\n const sessions = listSessions({ limit: 10 });\n const withAnalysis = sessions.find(\n (s) =>\n s.analysis &&\n (s.analysis.completed.length > 0 ||\n s.analysis.primary !== \"gtm_health\" ||\n s.stage === \"analyzed\"),\n );\n if (withAnalysis?.analysis) {\n ctx.analysis = {\n ...defaultSessionAnalysis(withAnalysis.analysis.primary),\n ...withAnalysis.analysis,\n completed: [...withAnalysis.analysis.completed],\n };\n if (withAnalysis.stage) ctx.stage = withAnalysis.stage;\n if (withAnalysis.dataset) ctx.dataset = withAnalysis.dataset;\n return;\n }\n\n const { loadLatestDiagnosis, loadLatestMetricsAnalysis } = await import(\"../db/queries.js\");\n const [diagnosis, metrics] = await Promise.all([\n loadLatestDiagnosis(),\n loadLatestMetricsAnalysis(),\n ]);\n const completed: AnalysisLens[] = [];\n if (diagnosis) completed.push(\"gtm_health\");\n if (metrics?.metrics.length) completed.push(\"revenue_metrics\");\n if (completed.length === 0) return;\n\n let primary = ctx.analysis.primary;\n if (completed.includes(\"revenue_metrics\") && !completed.includes(\"gtm_health\")) {\n primary = \"revenue_metrics\";\n } else if (completed.includes(\"gtm_health\") && !completed.includes(\"revenue_metrics\")) {\n primary = \"gtm_health\";\n }\n ctx.analysis = { ...ctx.analysis, primary, completed };\n if (ctx.stage === \"new\") ctx.stage = \"analyzed\";\n}\n\n/** Headless agent context with session / DB analysis hydration. */\nexport async function initHeadlessAgentContext(): Promise<Context> {\n const ctx = initContext(true, { mode: \"headless\", output: \"json\" });\n await hydrateAnalysisFromPersistedState(ctx);\n return ctx;\n}\n\n/** Append a message to the session and persist to disk. */\nexport function recordMessage(ctx: Context, role: \"user\" | \"agent\", content: string): void {\n const msg: SessionMessage = { role, content, at: new Date().toISOString() };\n ctx.messages.push(msg);\n if (ctx.oneShot) return; // don't persist one-shot noise\n\n try {\n writeFileSync(ctx.sessionFile, JSON.stringify(buildSessionFileSnapshot(ctx), null, 2) + \"\\n\");\n } catch {\n // best-effort; don't crash REPL\n }\n writeSessionContextDoc(ctx);\n}\n\n/**\n * Persist the session's current stage/dataset/deliverables without requiring an\n * NL exchange. Called by /new and /handoff to checkpoint lifecycle progress so\n * the welcome dashboard can surface unfinished work accurately.\n */\nexport function saveSessionState(ctx: Context): void {\n if (ctx.oneShot) return;\n try {\n writeFileSync(ctx.sessionFile, JSON.stringify(buildSessionFileSnapshot(ctx), null, 2) + \"\\n\");\n } catch {\n // best-effort\n }\n writeSessionContextDoc(ctx);\n}\n\n// ============================================================\n// Last-activity lookup for the welcome dashboard\n// ============================================================\n\n/**\n * Find the most recent session file's modification time and return a\n * compact relative-time string (\"2h ago\", \"just now\", \"2026-04-11\").\n * Used on the welcome dashboard β the session mtime is the truest signal\n * of \"when did I last use ntrp\" because every REPL exchange touches it.\n */\nexport function getLastActivityRelative(): string | null {\n const dir = getSessionsDir();\n let mostRecent = 0;\n try {\n for (const name of readdirSync(dir)) {\n if (!name.endsWith(\".json\")) continue;\n const m = statSync(join(dir, name)).mtimeMs;\n if (m > mostRecent) mostRecent = m;\n }\n } catch {\n return null;\n }\n\n if (mostRecent === 0) return null;\n return formatRelativeTime(new Date(mostRecent));\n}\n\nfunction formatRelativeTime(then: Date): string {\n const diffMs = Date.now() - then.getTime();\n if (diffMs < 0) return \"just now\";\n const s = Math.floor(diffMs / 1000);\n if (s < 60) return \"just now\";\n const m = Math.floor(s / 60);\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 7) return `${d}d ago`;\n // Older than a week β show the date\n return then.toISOString().slice(0, 10);\n}\n\n// ============================================================\n// Session close + listing\n// ============================================================\n\n/** Read and parse a session JSON file. Returns null on any error. */\nexport function loadSessionFile(id: string): SessionFile | null {\n const filePath = sessionPathForId(id);\n if (!filePath) return null;\n try {\n const raw = readFileSync(filePath, \"utf-8\");\n const session = JSON.parse(raw) as SessionFile;\n if (session.thread?.length) {\n session.thread = normalizeThread(session.thread as unknown[]);\n }\n return session;\n } catch {\n return null;\n }\n}\n\n/** List all session files, sorted by mtime desc. Optional limit. */\nexport function listSessions(opts?: { limit?: number }): SessionListEntry[] {\n const dir = getSessionsDir();\n const entries: SessionListEntry[] = [];\n try {\n const files = readdirSync(dir)\n .filter((name) => name.endsWith(\".json\"))\n .map((name) => {\n const filePath = join(dir, name);\n return { name, filePath, mtime: statSync(filePath).mtimeMs };\n })\n .sort((a, b) => b.mtime - a.mtime);\n const filesToRead = opts?.limit ? files.slice(0, opts.limit) : files;\n\n for (const { name, filePath, mtime } of filesToRead) {\n const id = basename(name, \".json\");\n if (!isValidSessionId(id)) continue;\n try {\n const raw = readFileSync(filePath, \"utf-8\");\n const session = JSON.parse(raw) as SessionFile;\n entries.push({\n id,\n created_at: session.created_at,\n ended_at: session.ended_at,\n exchange_count: session.exchange_count ?? Math.floor(session.messages.length / 2),\n summary: session.summary,\n name: session.name,\n stage: session.stage,\n dataset: session.dataset,\n deliverables: session.deliverables,\n analysis: session.analysis,\n scope: session.scope,\n mtime,\n });\n } catch {\n // skip malformed files\n }\n }\n } catch {\n return [];\n }\n entries.sort((a, b) => b.mtime - a.mtime);\n if (opts?.limit) return entries.slice(0, opts.limit);\n return entries;\n}\n\n/** Convenience: get the N most recent sessions. */\nexport function getRecentSessions(n: number): SessionListEntry[] {\n return listSessions({ limit: n });\n}\n\n/**\n * Sessions that reached insight but never shipped an output β \"unfinished\"\n * work the welcome flow nudges the user to pick back up. Excludes the active\n * session and delivered/empty ones.\n */\nexport function getUnfinishedSessions(excludeId?: string): SessionListEntry[] {\n return listSessions().filter(\n (s) =>\n s.id !== excludeId &&\n s.stage === \"analyzed\" &&\n (s.deliverables?.length ?? 0) === 0,\n );\n}\n\n/** True when a session has started work but is not closed or delivered. */\nexport function isSessionInProgress(s: SessionListEntry): boolean {\n if (s.stage === \"ended\") return false;\n if ((s.deliverables?.length ?? 0) > 0 || s.stage === \"delivered\") return false;\n if (s.stage === \"analyzed\") return true;\n return (s.exchange_count ?? 0) > 0 || !!s.dataset?.label;\n}\n\n/** Sessions still open β analyzed awaiting handoff, or new with data/exchanges. */\nexport function getActiveSessions(): SessionListEntry[] {\n return listSessions().filter(isSessionInProgress);\n}\n\n/**\n * Mark every in-progress session as ended, then rotate the REPL to a fresh shell.\n * Session JSON and dataset files are preserved on disk.\n */\nexport async function closeAllActiveSessions(\n ctx: Context,\n): Promise<{ closed: string[]; skipped: string[] }> {\n const active = getActiveSessions();\n const closed: string[] = [];\n const skipped: string[] = [];\n const endedAt = new Date().toISOString();\n\n for (const s of active) {\n if (s.id === ctx.sessionId) continue;\n const file = loadSessionFile(s.id);\n if (!file) {\n skipped.push(s.id);\n continue;\n }\n file.stage = \"ended\";\n file.ended_at = endedAt;\n const filePath = sessionPathForId(s.id);\n if (!filePath) {\n skipped.push(s.id);\n continue;\n }\n writeFileSync(filePath, JSON.stringify(file, null, 2) + \"\\n\");\n writeContextDocForSessionFile(file);\n closed.push(s.id);\n }\n\n const currentActive = active.some((s) => s.id === ctx.sessionId);\n if (currentActive) {\n const alreadyClosed = ctx.stage === \"delivered\" || ctx.stage === \"ended\";\n const hasWork =\n ctx.stage === \"analyzed\" ||\n !!ctx.dataset ||\n ctx.messages.length > 0 ||\n ctx.deliverables.length > 0;\n\n if (!alreadyClosed && hasWork) {\n await endSession(ctx);\n if (!closed.includes(ctx.sessionId)) closed.push(ctx.sessionId);\n } else if (!alreadyClosed && (ctx.messages.length > 0 || !!ctx.dataset?.label)) {\n await endSession(ctx);\n if (!closed.includes(ctx.sessionId)) closed.push(ctx.sessionId);\n }\n }\n\n await rotateToFreshSession(ctx);\n const { initSchema } = await import(\"../db/schema.js\");\n await initSchema();\n\n return { closed, skipped };\n}\n\n/**\n * Most recently touched session with real work (skips empty shells).\n * listSessions() is already sorted by mtime desc.\n */\nexport function getLastWorkedSession(): SessionListEntry | null {\n for (const s of listSessions()) {\n if (\n (s.exchange_count ?? 0) > 0 ||\n s.stage === \"analyzed\" ||\n s.stage === \"delivered\" ||\n !!s.dataset?.label\n ) {\n return s;\n }\n }\n return null;\n}\n\n/** Finalize the session: compute exchange_count, set ended_at, generate AI summary, write file. */\nexport async function closeSession(ctx: Context): Promise<string | undefined> {\n return finalizeSession(ctx, ctx.stage);\n}\n\n/**\n * Close the current analysis without a handoff β marks stage \"ended\" so it\n * drops off the unfinished list. Preserves transcript, dataset anchor, and\n * optional AI summary like closeSession.\n */\nexport async function endSession(ctx: Context): Promise<string | undefined> {\n return finalizeSession(ctx, \"ended\");\n}\n\n/** Rotate to a brand-new empty session + dataset file (caller finalizes the prior session first). */\nexport async function rotateToFreshSession(ctx: Context): Promise<void> {\n const { setActiveDbPath } = await import(\"../db/connection.js\");\n const newId = makeSessionId();\n resetContextForSwitch(ctx, {\n sessionId: newId,\n sessionFile: join(getSessionsDir(), `${newId}.json`),\n messages: [],\n stage: \"new\",\n analysis: defaultSessionAnalysis(),\n llm: undefined,\n });\n ctx.datasetPath = datasetPathForSession(newId);\n await setActiveDbPath(ctx.datasetPath);\n}\n\n/** Compact one-line summary for session lists and close β no LLM. */\nexport function buildLightweightSessionSummary(ctx: Context): string {\n const parts: string[] = [];\n const intent = ctx.scope?.intent_summary?.trim();\n if (intent) parts.push(intent.length > 90 ? `${intent.slice(0, 87)}β¦` : intent);\n\n const gating = ctx.snapshot.computeResult?.aggregate.gating_vital_sign;\n if (gating) {\n parts.push(`gated by ${gating.replace(/_/g, \" \")}`);\n } else if (ctx.dataset?.label) {\n parts.push(ctx.dataset.label);\n }\n\n const exchanges = Math.floor(ctx.messages.length / 2);\n if (exchanges > 0) parts.push(`${exchanges} exchange${exchanges === 1 ? \"\" : \"s\"}`);\n if (ctx.deliverables.length > 0) {\n parts.push(`${ctx.deliverables.length} deliverable${ctx.deliverables.length === 1 ? \"\" : \"s\"}`);\n }\n\n return parts.join(\" Β· \") || `Session ${ctx.sessionId.slice(0, 8)}`;\n}\n\nasync function finalizeSession(ctx: Context, stage: SessionStage): Promise<string | undefined> {\n if (ctx.oneShot) return undefined;\n\n const exchangeCount = Math.floor(ctx.messages.length / 2);\n const endedAt = new Date().toISOString();\n\n // Nothing happened in this session β no questions, no data, no output.\n // Don't leave an empty session file or an empty per-session dataset behind.\n if (\n ctx.messages.length === 0 &&\n ctx.stage === \"new\" &&\n !ctx.dataset &&\n ctx.deliverables.length === 0\n ) {\n if (ctx.datasetPath) {\n try {\n const { close } = await import(\"../db/connection.js\");\n await close();\n } catch { /* best-effort */ }\n for (const path of [ctx.datasetPath, `${ctx.datasetPath}.wal`]) {\n try { rmSync(path, { force: true }); } catch { /* best-effort */ }\n }\n }\n discardSessionTranscript(ctx.sessionId);\n try { rmSync(contextDocPathForSession(ctx.sessionId), { force: true }); } catch { /* best-effort */ }\n return undefined;\n }\n\n if (ctx.deliverables.length > 0) {\n const { creditSessionDeliverableWrapup } = await import(\"../whimsy/time-bank.js\");\n creditSessionDeliverableWrapup(ctx);\n }\n\n const { recordSessionClosed } = await import(\"../whimsy/usage-stats.js\");\n recordSessionClosed();\n\n const summary = buildLightweightSessionSummary(ctx);\n\n const file: SessionFile = {\n id: ctx.sessionId,\n created_at: ctx.messages[0]?.at ?? endedAt,\n messages: ctx.messages,\n ended_at: endedAt,\n exchange_count: exchangeCount,\n stage,\n summary,\n };\n\n if (ctx.resumedFromId) {\n file.resumed_from = ctx.resumedFromId;\n }\n if (ctx.sessionName) {\n file.name = ctx.sessionName;\n }\n if (ctx.dataset) {\n file.dataset = ctx.dataset;\n }\n if (ctx.deliverables.length > 0) {\n file.deliverables = ctx.deliverables;\n }\n if (ctx.conversation.length > 0) {\n file.thread = ctx.conversation;\n }\n if (ctx.analysis) {\n file.analysis = ctx.analysis;\n }\n if (ctx.scope) {\n file.scope = ctx.scope;\n }\n if (ctx.attachments && ctx.attachments.length > 0) {\n file.attachments = ctx.attachments;\n }\n if (ctx.llm && Object.keys(ctx.llm).length > 0) {\n file.llm = ctx.llm;\n }\n // Persist in-flight strategist state consistently with buildSessionFileSnapshot\n // β a mid-flow close must not silently drop (or silently keep) the confirm\n // gate depending on the exit path. Pickup announces it.\n if (ctx.strategistState) {\n file.strategist = ctx.strategistState;\n }\n\n try {\n writeFileSync(ctx.sessionFile, JSON.stringify(file, null, 2) + \"\\n\");\n } catch {\n // best-effort\n }\n writeContextDocForSessionFile(file, { snapshot: ctx.snapshot.computeResult });\n\n // Learning loop: distill durable facts β race with a short timeout so close\n // never blocks on a slow LLM; distill continues in background if needed.\n let closeNote = summary;\n if (exchangeCount > 0) {\n try {\n const { distillSessionFactsWithTimeout } = await import(\"../memory/distill.js\");\n const { count } = await distillSessionFactsWithTimeout(ctx, ctx.sessionId);\n if (count > 0) {\n closeNote = `${summary} Β· noted ${count} for memory (/recall)`;\n }\n } catch {\n // never block session close on memory writes\n }\n }\n\n return closeNote;\n}\n\n// ============================================================\n// Named-session helpers (for /name and /switch)\n// ============================================================\n\n/**\n * Resolve a session by full id, 4-char suffix, or name.\n * undefined = no match, null = ambiguous (message printed when printErrors is true).\n */\nexport function resolveSessionByToken(\n idArg: string,\n options?: { printErrors?: boolean },\n): SessionListEntry | null | undefined {\n const printErrors = options?.printErrors !== false;\n const all = listSessions();\n const lower = idArg.toLowerCase();\n let matches = all.filter((s) => s.id === idArg);\n if (matches.length === 0) matches = all.filter((s) => s.name?.toLowerCase() === lower);\n if (matches.length === 0 && idArg.length >= 4) {\n matches = all.filter((s) => s.id.endsWith(idArg));\n }\n if (matches.length === 0) return undefined;\n if (matches.length > 1) {\n if (printErrors) {\n console.log(\n ` Ambiguous \"${idArg}\" β matches ${matches.length} sessions. Use a longer id.`,\n );\n }\n return null;\n }\n return matches[0]!;\n}\n\n/** Find the most recent session with a given name (case-insensitive). */\nexport function findSessionByName(name: string): SessionListEntry | null {\n const lower = name.toLowerCase();\n const all = listSessions();\n return all.find((s) => s.name?.toLowerCase() === lower) ?? null;\n}\n\n/** Build a richer context string for a resumed/switched session: summary + last 3 user messages. */\nexport function buildSwitchContext(session: SessionFile): string {\n const parts: string[] = [];\n if (session.summary) parts.push(session.summary);\n\n const userMsgs = session.messages\n .filter((m) => m.role === \"user\")\n .slice(-3);\n for (const m of userMsgs) {\n parts.push(m.content.slice(0, 300));\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * Mutate ctx in-place for a session switch. Resets session identity\n * and messages but preserves snapshot, rl, and oneShot.\n */\nexport function resetContextForSwitch(\n ctx: Context,\n opts: {\n sessionId: string;\n sessionFile: string;\n sessionName?: string;\n messages: SessionMessage[];\n conversation?: LlmMessage[];\n resumedFromId?: string;\n resumedSessionSummary?: string;\n stage?: SessionStage;\n dataset?: DatasetMeta;\n deliverables?: Deliverable[];\n analysis?: SessionAnalysis;\n scope?: AnalysisScope;\n attachments?: ChatAttachment[];\n llm?: LlmSessionOverride;\n strategistState?: StrategistFlowState;\n },\n): void {\n ctx.sessionId = opts.sessionId;\n ctx.sessionFile = opts.sessionFile;\n ctx.sessionName = opts.sessionName;\n ctx.messages = opts.messages;\n ctx.conversation = opts.conversation ?? [];\n ctx.resumedFromId = opts.resumedFromId;\n ctx.resumedSessionSummary = opts.resumedSessionSummary;\n ctx.stage = opts.stage ?? \"new\";\n ctx.dataset = opts.dataset;\n ctx.deliverables = opts.deliverables ?? [];\n ctx.analysis = opts.analysis ?? defaultSessionAnalysis();\n ctx.scope = opts.scope;\n ctx.attachments = opts.attachments ?? [];\n ctx.llm = opts.llm;\n ctx.strategistState = opts.strategistState;\n ctx.gapAudit = undefined;\n ctx.deliverIntent = false;\n ctx.computeInProgress = false;\n ctx.wizardDepth = 0;\n // The cached health snapshot belongs to the previous dataset β clear it so\n // the next question recomputes against the newly-bound dataset.\n ctx.snapshot = { computeResult: null, divergences: [] };\n // Re-point the transcript recorder at the new session's file.\n rebindSessionTranscript(ctx);\n}\n","/**\n * Session context brief β a human/agent-readable markdown summary written to\n * ~/.ntrp/sessions/<id>.context.md alongside the session JSON and the raw\n * transcript.\n *\n * Purpose: triage and pickup. The JSON is for the program, the transcript is\n * the full terminal record, and this brief is the 1-page \"what happened here\"\n * an operator or coding agent reads first: dataset, scope, what was computed\n * (scores + dollars), what was asked, what was delivered, and how to resume.\n *\n * Deterministic β no LLM required. Regenerated on every session checkpoint\n * (recordMessage / saveSessionState / finalize), so it always reflects the\n * latest state.\n */\n\nimport { writeFileSync } from \"node:fs\";\nimport type { Context, SessionFile } from \"../cli/context.js\";\nimport {\n buildSessionFileSnapshot,\n contextDocPathForSession,\n datasetPathForSession,\n getSessionsDir,\n transcriptPathForSession,\n} from \"../cli/context.js\";\nimport type { FullComputeResult } from \"../vitals/health-score.js\";\nimport { VITAL_SIGN_LABELS, formatCurrency } from \"../output/formatters.js\";\nimport { redactSecrets } from \"./terminal-capture.js\";\n\nconst AGENT_EXCERPT_CHARS = 400;\n\n// ============================================================\n// Builder\n// ============================================================\n\nexport function buildSessionContextDoc(\n file: SessionFile,\n opts: { snapshot?: FullComputeResult | null } = {},\n): string {\n const id = file.id;\n const shortId = id.slice(-4);\n const exchanges = file.exchange_count ?? Math.floor(file.messages.length / 2);\n const lines: string[] = [];\n\n lines.push(`# Session context β ${id}${file.name ? ` (${file.name})` : \"\"}`);\n lines.push(\"\");\n\n // Status\n lines.push(\"## Status\");\n lines.push(\"\");\n lines.push(`- Stage: ${file.stage ?? \"new\"}`);\n lines.push(`- Created: ${file.created_at}`);\n if (file.ended_at) lines.push(`- Ended: ${file.ended_at}`);\n lines.push(`- Updated: ${new Date().toISOString()}`);\n lines.push(`- Exchanges: ${exchanges}`);\n if (file.summary) lines.push(`- Summary: ${file.summary}`);\n if (file.resumed_from) lines.push(`- Resumed from: ${file.resumed_from}`);\n lines.push(\"\");\n\n // Dataset\n lines.push(\"## Dataset\");\n lines.push(\"\");\n if (file.dataset?.label || file.dataset?.source) {\n lines.push(`- Label: ${file.dataset.label ?? \"(unlabeled)\"}`);\n if (file.dataset.source) lines.push(`- Source: ${file.dataset.source}`);\n if (file.dataset.ingested_at) lines.push(`- Ingested: ${file.dataset.ingested_at}`);\n const counts = Object.entries(file.dataset.counts ?? {}).filter(([, n]) => n > 0);\n if (counts.length > 0) {\n lines.push(`- Counts: ${counts.map(([k, n]) => `${n.toLocaleString()} ${k}`).join(\", \")}`);\n }\n } else {\n lines.push(\"- No data loaded.\");\n }\n if (file.attachments && file.attachments.length > 0) {\n for (const a of file.attachments) {\n const detail = [a.entity_type, a.row_count != null ? `${a.row_count} rows` : null]\n .filter(Boolean)\n .join(\", \");\n lines.push(`- Attachment: ${a.path}${detail ? ` (${detail})` : \"\"}`);\n }\n }\n lines.push(\"\");\n\n // Scope\n if (file.scope) {\n lines.push(\"## Scope\");\n lines.push(\"\");\n lines.push(`- Intent: ${file.scope.intent_summary}`);\n lines.push(`- Lens: ${file.scope.primary_lens}`);\n if (file.scope.audience) lines.push(`- Audience: ${file.scope.audience}`);\n if (file.scope.time_horizon) lines.push(`- Time horizon: ${file.scope.time_horizon}`);\n if (file.scope.segments?.length) lines.push(`- Segments: ${file.scope.segments.join(\", \")}`);\n if (file.scope.confirmed_at) lines.push(`- Confirmed: ${file.scope.confirmed_at}`);\n lines.push(\"\");\n }\n\n // Analysis\n lines.push(\"## Analysis\");\n lines.push(\"\");\n if (file.analysis) {\n lines.push(`- Primary lens: ${file.analysis.primary}`);\n lines.push(`- Completed: ${file.analysis.completed.join(\", \") || \"none\"}`);\n if (file.analysis.coverage) {\n lines.push(\n `- Coverage: ${file.analysis.coverage.distinct_months} months Β· recommended cadence ${file.analysis.coverage.recommended_cadence}`,\n );\n }\n if (file.analysis.data_source_type) {\n lines.push(`- Data source type: ${file.analysis.data_source_type}`);\n }\n if (file.analysis.headline?.length) {\n lines.push(\"\");\n lines.push(\"### Headline metrics\");\n lines.push(\"\");\n for (const h of file.analysis.headline) {\n lines.push(`- ${h.label}: ${h.formatted}`);\n }\n }\n } else {\n lines.push(\"- No analysis recorded.\");\n }\n\n const health = opts.snapshot?.aggregate;\n if (health) {\n lines.push(\"\");\n lines.push(\"### GTM health snapshot\");\n lines.push(\"\");\n lines.push(`- Overall: ${Math.round(health.overall_score)} (${health.overall_status})`);\n lines.push(`- Gating vital sign: ${health.gating_vital_sign.replace(/_/g, \" \")}`);\n if (health.total_value_at_risk != null && health.total_value_at_risk > 0) {\n lines.push(`- Total value at risk: ${formatCurrency(health.total_value_at_risk)}`);\n }\n for (const vs of health.vital_signs) {\n const label = VITAL_SIGN_LABELS[vs.vital_sign] ?? vs.vital_sign;\n const dollars =\n vs.dollar_value != null\n ? ` β ${formatCurrency(vs.dollar_value)}${vs.dollar_label ? ` ${vs.dollar_label}` : \"\"}`\n : \"\";\n lines.push(`- ${label}: ${Math.round(vs.score)} (${vs.status})${dollars}`);\n }\n }\n lines.push(\"\");\n\n // Strategist\n if (file.strategist) {\n lines.push(\"## Strategist (in flight)\");\n lines.push(\"\");\n lines.push(`- Step: ${file.strategist.step}`);\n if (file.strategist.objective) lines.push(`- Objective: ${file.strategist.objective}`);\n if (file.strategist.constraintsNote) {\n lines.push(`- Constraints: ${file.strategist.constraintsNote}`);\n }\n if (file.strategist.origin) lines.push(`- Origin: ${file.strategist.origin}`);\n lines.push(\"\");\n }\n\n // Deliverables\n lines.push(\"## Deliverables\");\n lines.push(\"\");\n if (file.deliverables && file.deliverables.length > 0) {\n for (const d of file.deliverables) {\n const detail = [d.path, d.note].filter(Boolean).join(\" β \");\n lines.push(`- ${d.kind} (${d.at})${detail ? `: ${detail}` : \"\"}`);\n }\n } else {\n lines.push(\"- None yet.\");\n }\n lines.push(\"\");\n\n // Conversation\n lines.push(`## Conversation (${exchanges} exchange${exchanges === 1 ? \"\" : \"s\"})`);\n lines.push(\"\");\n if (file.messages.length === 0) {\n lines.push(\"- No exchanges yet.\");\n } else {\n let n = 0;\n for (const msg of file.messages) {\n if (msg.role === \"user\") {\n n++;\n lines.push(`${n}. β― ${excerpt(msg.content, AGENT_EXCERPT_CHARS)}`);\n } else {\n lines.push(` β³ ${excerpt(msg.content, AGENT_EXCERPT_CHARS)}`);\n }\n }\n }\n lines.push(\"\");\n\n // Files + pickup\n lines.push(\"## Files\");\n lines.push(\"\");\n lines.push(`- Transcript (raw terminal): \\`${transcriptPathForSession(id)}\\``);\n lines.push(`- Session data (JSON): \\`${sessionJsonPath(id)}\\``);\n lines.push(`- Dataset (DuckDB): \\`${datasetPathForSession(id)}\\``);\n lines.push(\"\");\n lines.push(\"## Pick up this session\");\n lines.push(\"\");\n lines.push(`Run \\`ntrp\\`, then \\`/session ${shortId}\\` β rebinds the dataset and reloads the`);\n lines.push(\"conversation thread in place. Read the transcript above for the full terminal\");\n lines.push(\"history before continuing.\");\n lines.push(\"\");\n\n return lines.map(redactSecrets).join(\"\\n\");\n}\n\nfunction excerpt(content: string, max: number): string {\n const flat = content.replace(/\\s+/g, \" \").trim();\n return flat.length > max ? `${flat.slice(0, max - 1)}β¦` : flat;\n}\n\nfunction sessionJsonPath(id: string): string {\n return `${getSessionsDir()}/${id}.json`;\n}\n\n// ============================================================\n// Writers (best-effort β never break the session over doc IO)\n// ============================================================\n\n/** Write the context brief for the live context. Skipped in one-shot mode. */\nexport function writeSessionContextDoc(ctx: Context): void {\n if (ctx.oneShot) return;\n try {\n const file = buildSessionFileSnapshot(ctx);\n const doc = buildSessionContextDoc(file, { snapshot: ctx.snapshot.computeResult });\n writeFileSync(contextDocPathForSession(ctx.sessionId), doc);\n } catch {\n // best-effort\n }\n}\n\n/** Write the context brief from an already-built session file (close paths). */\nexport function writeContextDocForSessionFile(\n file: SessionFile,\n opts: { snapshot?: FullComputeResult | null } = {},\n): void {\n try {\n writeFileSync(contextDocPathForSession(file.id), buildSessionContextDoc(file, opts));\n } catch {\n // best-effort\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","join","existsSync","readFileSync","writeFileSync","rmSync","join","writeFileSync","doc","join","existsSync","rmSync","readFileSync","writeFileSync","writeFileSync","join","readFileSync","existsSync"]}
|