@titan-design/session-graph 0.4.1 → 0.5.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/README.md CHANGED
@@ -17,6 +17,12 @@ const summary = await refreshCorpus(graph, await discoverTranscripts());
17
17
  // summary.indexed, summary.unchanged, summary.rewound, summary.quarantined, summary.missing
18
18
  ```
19
19
 
20
+ `openSessionGraph` takes an optional `schemaVersion`: the highest migration version the
21
+ *caller* owns, checked before any migration runs. A product layering its own tables on this
22
+ graph passes its own top version, since this package's `MIGRATIONS` are one band of a shared
23
+ database rather than the top of it. `products/session-miner` numbers its own from 1000 and
24
+ passes 1002.
25
+
20
26
  ## What a refresh does
21
27
 
22
28
  1. For every transcript, `indexTranscript` asks the watermark table where it stopped,
package/dist/index.d.ts CHANGED
@@ -8,7 +8,15 @@ interface SessionGraph {
8
8
  edges: EdgeTable;
9
9
  spans: SpanFtsTables;
10
10
  }
11
- declare function openSessionGraph(dbPath: string): SessionGraph;
11
+ interface OpenSessionGraphOptions {
12
+ /**
13
+ * The highest migration version the caller owns, checked before any migration runs.
14
+ * A product layering its own tables on this graph passes its own top version, not this
15
+ * package's: `MIGRATIONS` here is one band of a shared database, never the top of it.
16
+ */
17
+ schemaVersion?: number;
18
+ }
19
+ declare function openSessionGraph(dbPath: string, options?: OpenSessionGraphOptions): SessionGraph;
12
20
  /**
13
21
  * Clear every derived row and rewind all watermarks so the next refresh
14
22
  * rebuilds from byte 0. Rewinding alone is not enough: the accumulating
@@ -214,4 +222,4 @@ declare function normalizedUsage(graph: SessionGraph, ref: string): NormalizedUs
214
222
  /** Ambiguity is explicit; workspace session bodies remain in their original namespace. */
215
223
  declare function resolveConversationAlias(db: Db, legacyRef: string): string | null;
216
224
 
217
- export { type ConversationSummary, DERIVED_TABLES, DOMAIN_DDL, type IndexOptions, type IndexedSpan, KIT, MIGRATIONS, NO_ENRICHMENT, type NormalizedIndexResult, type NormalizedUsageSummary, type ReconcileCounts, type RefreshSummary, type ResolvedTask, type SessionGraph, type TaskEnrichment, type TaskResolution, type TaskResolver, type TranscriptOutcome, allSessionIds, allTaskIds, applyDelta, enrichTasks, indexCodexSource, indexTranscript, normalizedSessions, normalizedUsage, openSessionGraph, purgeTranscript, readIndexedText, reconcile, refreshCorpus, resetIndex, resolveConversationAlias, rollupSessions };
225
+ export { type ConversationSummary, DERIVED_TABLES, DOMAIN_DDL, type IndexOptions, type IndexedSpan, KIT, MIGRATIONS, NO_ENRICHMENT, type NormalizedIndexResult, type NormalizedUsageSummary, type OpenSessionGraphOptions, type ReconcileCounts, type RefreshSummary, type ResolvedTask, type SessionGraph, type TaskEnrichment, type TaskResolution, type TaskResolver, type TranscriptOutcome, allSessionIds, allTaskIds, applyDelta, enrichTasks, indexCodexSource, indexTranscript, normalizedSessions, normalizedUsage, openSessionGraph, purgeTranscript, readIndexedText, reconcile, refreshCorpus, resetIndex, resolveConversationAlias, rollupSessions };
package/dist/index.js CHANGED
@@ -209,8 +209,8 @@ var MIGRATIONS = [
209
209
  ];
210
210
 
211
211
  // src/graph.ts
212
- function openSessionGraph(dbPath) {
213
- const db = openDatabase(dbPath);
212
+ function openSessionGraph(dbPath, options = {}) {
213
+ const db = openDatabase(dbPath, { schemaVersion: options.schemaVersion });
214
214
  runMigrations(db, MIGRATIONS);
215
215
  return {
216
216
  db,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/graph.ts","../src/normalized-schema.ts","../src/schema.ts","../src/apply.ts","../src/purge.ts","../src/rollup.ts","../src/refresh.ts","../src/tasks.ts","../src/normalized-index.ts","../src/normalized-project.ts","../src/normalized-query.ts"],"sourcesContent":["import { EdgeTable, SpanFtsTables, WatermarkTable, openDatabase, runMigrations, type Db } from \"@titan-design/store-sqlite\";\nimport { DERIVED_TABLES, KIT, MIGRATIONS } from \"./schema.js\";\n\n/** One open session graph: the connection plus the kit helpers bound to its tables. */\nexport interface SessionGraph {\n db: Db;\n transcripts: WatermarkTable;\n edges: EdgeTable;\n spans: SpanFtsTables;\n}\n\nexport function openSessionGraph(dbPath: string): SessionGraph {\n const db = openDatabase(dbPath);\n runMigrations(db, MIGRATIONS);\n return {\n db,\n transcripts: new WatermarkTable(db, { name: KIT.watermark }),\n edges: new EdgeTable(db, { name: KIT.edge }),\n spans: new SpanFtsTables(db, { name: KIT.spanFts }),\n };\n}\n\n/**\n * Clear every derived row and rewind all watermarks so the next refresh\n * rebuilds from byte 0. Rewinding alone is not enough: the accumulating\n * upserts would add a second copy of every count.\n */\nexport function resetIndex(graph: SessionGraph): void {\n graph.db.transaction(() => {\n for (const table of DERIVED_TABLES) graph.db.exec(`DELETE FROM \"${table}\"`);\n graph.spans.clearIndex();\n for (const row of graph.transcripts.list()) graph.transcripts.rewind(row.sourceKey);\n })();\n}\n\nexport function allSessionIds(graph: SessionGraph): string[] {\n return (graph.db.prepare(\"SELECT session_id FROM session\").all() as { session_id: string }[]).map((r) => r.session_id);\n}\n","import { conversationRef } from \"@titan-design/agent-protocol\";\nimport type { Db } from \"@titan-design/store-sqlite\";\n\n/** Additive: legacy rows, aliases and original locators are never rewritten. */\nexport const NORMALIZED_DDL = `\nCREATE TABLE IF NOT EXISTS conversation (\n ref TEXT PRIMARY KEY, harness TEXT NOT NULL, namespace TEXT NOT NULL,\n native_id TEXT NOT NULL, legacy_session_id TEXT,\n UNIQUE(harness, namespace, native_id)\n);\nCREATE TABLE IF NOT EXISTS conversation_alias (\n legacy_ref TEXT NOT NULL, conversation_ref TEXT NOT NULL,\n PRIMARY KEY(legacy_ref, conversation_ref)\n);\nCREATE TABLE IF NOT EXISTS normalized_source (\n transcript_id INTEGER PRIMARY KEY, source_id TEXT NOT NULL UNIQUE,\n conversation_ref TEXT NOT NULL, descriptor TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS normalized_event (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n subrecord_index INTEGER NOT NULL, byte_length INTEGER NOT NULL,\n conversation_ref TEXT NOT NULL, kind TEXT NOT NULL, ts TEXT,\n turn_ref TEXT, call_ref TEXT, item_ref TEXT, phase TEXT, is_error INTEGER,\n usage TEXT, metadata TEXT, history_origin TEXT, related_ref TEXT, relationship TEXT,\n PRIMARY KEY(transcript_id, byte_offset, subrecord_index)\n);\nCREATE INDEX IF NOT EXISTS idx_normalized_event_conversation ON normalized_event(conversation_ref, ts);\nCREATE TABLE IF NOT EXISTS normalized_span (\n span_id INTEGER PRIMARY KEY, locators TEXT NOT NULL\n);\n`;\n\n/** Only the old transcript session table proves Claude provenance, never a ref prefix. */\nexport function backfillClaudeAliases(db: Db, sessionIds?: readonly string[]): void {\n const insert = db.prepare(\"INSERT OR IGNORE INTO conversation(ref,harness,namespace,native_id,legacy_session_id) VALUES (?,?,?,?,?)\");\n const alias = db.prepare(\"INSERT OR IGNORE INTO conversation_alias VALUES (?,?)\");\n const rows = sessionIds?.map(session_id => ({ session_id })) ?? db.prepare(\"SELECT session_id FROM session\").all() as Iterable<{ session_id: string }>;\n for (const row of rows) {\n const ref = conversationRef({ harness: \"claude-code\", namespace: \"legacy\", nativeId: row.session_id });\n insert.run(ref, \"claude-code\", \"legacy\", row.session_id, row.session_id);\n alias.run(`session:${row.session_id}`, ref);\n }\n}\n\n/** Ambiguity is explicit; workspace session bodies remain in their original namespace. */\nexport function resolveConversationAlias(db: Db, legacyRef: string): string | null {\n const rows = db.prepare(\"SELECT conversation_ref FROM conversation_alias WHERE legacy_ref = ?\").all(legacyRef) as { conversation_ref: string }[];\n if (rows.length > 1) throw new Error(`ambiguous conversation alias: ${legacyRef}`);\n return rows[0]?.conversation_ref ?? null;\n}\n","import { NORMALIZED_DDL, backfillClaudeAliases } from \"./normalized-schema.js\";\nimport { SQL_NOW, kitMigration, type Migration } from \"@titan-design/store-sqlite\";\n\n/** Kit table names this graph uses. `watermark` rows are transcripts; `search_*` are the FTS spans. */\nexport const KIT = { watermark: \"transcript\", edge: \"edge\", spanFts: \"search\" } as const;\n\n/**\n * Legacy domain tables. Original sources can be pruned; schema migrations must\n * preserve these rows rather than assuming a replay is possible.\n * Locators are `(transcript_id, byte_offset, byte_length)`; `fact_id` rows\n * resolve through the facts table's unique `(transcript_id, byte_offset)`.\n */\nexport const DOMAIN_DDL = `\n CREATE TABLE IF NOT EXISTS fact (\n fact_id INTEGER PRIMARY KEY,\n transcript_id INTEGER NOT NULL,\n byte_offset INTEGER NOT NULL,\n byte_length INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n ts TEXT NOT NULL,\n seq INTEGER NOT NULL,\n session_id TEXT NOT NULL,\n prompt_id TEXT,\n tool_use_id TEXT,\n t_indexed TEXT NOT NULL DEFAULT (${SQL_NOW}),\n UNIQUE (transcript_id, byte_offset)\n );\n CREATE INDEX IF NOT EXISTS idx_fact_session_ts ON fact(session_id, ts);\n CREATE INDEX IF NOT EXISTS idx_fact_prompt ON fact(prompt_id);\n CREATE INDEX IF NOT EXISTS idx_fact_tool_use ON fact(tool_use_id);\n\n CREATE TABLE IF NOT EXISTS session (\n session_id TEXT PRIMARY KEY,\n transcript_id INTEGER,\n started_at TEXT,\n ended_at TEXT,\n start_type TEXT,\n cwd TEXT,\n git_branch TEXT,\n ai_title TEXT,\n seed_prompt TEXT,\n cli_version TEXT,\n turn_count INTEGER NOT NULL DEFAULT 0,\n commit_count INTEGER NOT NULL DEFAULT 0,\n push_count INTEGER NOT NULL DEFAULT 0\n );\n CREATE INDEX IF NOT EXISTS idx_session_started ON session(started_at);\n\n CREATE TABLE IF NOT EXISTS session_model_usage (\n session_id TEXT NOT NULL,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n cache_read_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n thinking_tokens INTEGER NOT NULL DEFAULT 0,\n request_count INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (session_id, model)\n );\n\n CREATE TABLE IF NOT EXISTS turn (\n prompt_id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n turn_index INTEGER NOT NULL,\n started_at TEXT NOT NULL,\n ended_at TEXT,\n duration_ms INTEGER,\n tool_call_count INTEGER NOT NULL DEFAULT 0,\n thinking_ms INTEGER NOT NULL DEFAULT 0,\n fact_id_start INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_turn_session ON turn(session_id, turn_index);\n\n CREATE TABLE IF NOT EXISTS permission_phase (\n phase_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n from_mode TEXT,\n to_mode TEXT NOT NULL,\n trigger TEXT NOT NULL,\n t_valid TEXT NOT NULL,\n t_invalid TEXT,\n fact_id INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_phase_session ON permission_phase(session_id, t_valid);\n\n CREATE TABLE IF NOT EXISTS human_edit (\n edit_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n file_path TEXT NOT NULL,\n ts TEXT NOT NULL,\n fact_id INTEGER,\n UNIQUE (session_id, file_path, ts)\n );\n\n CREATE TABLE IF NOT EXISTS file_checkpoint (\n checkpoint_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n file_path TEXT NOT NULL,\n backup_file_name TEXT NOT NULL,\n version INTEGER NOT NULL,\n backup_time TEXT NOT NULL,\n fact_id INTEGER,\n UNIQUE (session_id, file_path, backup_file_name)\n );\n\n CREATE TABLE IF NOT EXISTS pr (\n pr_ref TEXT PRIMARY KEY, number INTEGER, repo TEXT, title TEXT, state TEXT, url TEXT, merged_at TEXT\n );\n CREATE TABLE IF NOT EXISTS branch (\n branch_ref TEXT PRIMARY KEY, repo TEXT, name TEXT NOT NULL, base TEXT, created_at TEXT, deleted_at TEXT\n );\n CREATE TABLE IF NOT EXISTS file (\n file_ref TEXT PRIMARY KEY, repo TEXT, path TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS task (\n task_ref TEXT PRIMARY KEY, task_id TEXT NOT NULL, initiative TEXT, title TEXT, status TEXT\n );\n CREATE TABLE IF NOT EXISTS subagent (\n agent_ref TEXT PRIMARY KEY,\n session_id TEXT,\n child_session_id TEXT,\n parent_agent_ref TEXT,\n agent_type TEXT,\n label TEXT,\n started_at TEXT,\n ended_at TEXT,\n fact_id INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_subagent_child ON subagent(child_session_id);\n CREATE TABLE IF NOT EXISTS artifact (\n artifact_ref TEXT PRIMARY KEY, kind TEXT, title TEXT, url TEXT, path TEXT, created_at TEXT\n );\n CREATE TABLE IF NOT EXISTS pr_merge_observation (\n number INTEGER NOT NULL, repo_hint TEXT, merged_at TEXT NOT NULL,\n PRIMARY KEY (number, repo_hint, merged_at)\n );\n CREATE TABLE IF NOT EXISTS pr_create_observation (\n tool_use_id TEXT PRIMARY KEY, title TEXT, number INTEGER, repo TEXT, url TEXT\n );\n`;\n\n/** Every derived table, in an order safe to clear. The watermark table is not derived. */\nexport const DERIVED_TABLES = [\n \"normalized_span\",\n \"normalized_event\",\n \"normalized_source\",\n `${KIT.spanFts}_span`,\n KIT.edge,\n \"turn\",\n \"permission_phase\",\n \"human_edit\",\n \"file_checkpoint\",\n \"subagent\",\n \"session_model_usage\",\n \"session\",\n \"fact\",\n \"pr\",\n \"pr_merge_observation\",\n \"pr_create_observation\",\n \"branch\",\n \"file\",\n \"task\",\n \"artifact\",\n] as const;\n\nexport const MIGRATIONS: Migration[] = [\n kitMigration(1, { watermark: KIT.watermark, edge: KIT.edge, spanFts: KIT.spanFts }, \"kit tables\"),\n { version: 2, name: \"session graph tables\", up: (db) => db.exec(DOMAIN_DDL) },\n { version: 3, name: \"normalized conversations and source evidence\", up: (db) => { db.exec(NORMALIZED_DDL); backfillClaudeAliases(db); } },\n];\n","import { backfillClaudeAliases } from \"./normalized-schema.js\";\nimport { sessionRef, type TranscriptDelta } from \"@titan-design/session-read\";\nimport type { Db } from \"@titan-design/store-sqlite\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * Apply one transcript chunk's delta in a single transaction, so a crash can\n * never leave rows committed behind a stale watermark or ahead of their rows.\n * Append-only tables are idempotent through unique indexes; session and usage\n * rows accumulate; ref-keyed assets insert-if-absent with COALESCE merges that\n * mirror `EventFolder`'s rules.\n */\nexport function applyDelta(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta): void {\n graph.db.transaction(() => {\n applyFacts(graph.db, transcriptId, delta);\n applySessions(graph.db, transcriptId, delta);\n backfillClaudeAliases(graph.db, delta.sessions.map(s => s.sessionId));\n applyAssets(graph.db, delta);\n applyPhases(graph.db, transcriptId, delta);\n applyLinkedRows(graph, transcriptId, delta);\n })();\n}\n\nconst INSERT_FACT = `\n INSERT INTO fact (transcript_id, byte_offset, byte_length, event_type, ts, seq, session_id, prompt_id, tool_use_id)\n VALUES (@transcriptId, @byteOffset, @byteLength, @eventType, @ts, @seq, @sessionId, @promptId, @toolUseId)\n ON CONFLICT (transcript_id, byte_offset) DO NOTHING`;\n\n/** Facts carry a per-transcript `seq` continuing from whatever is stored. */\nfunction applyFacts(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const next = db.prepare(\"SELECT COALESCE(MAX(seq), -1) + 1 AS next FROM fact WHERE transcript_id = ?\").get(transcriptId) as { next: number };\n const insert = db.prepare(INSERT_FACT);\n let seq = next.next;\n for (const f of delta.facts) {\n insert.run({ transcriptId, byteOffset: f.byteOffset, byteLength: f.byteLength, eventType: f.eventType, ts: f.ts, seq: seq++, sessionId: f.sessionId, promptId: f.promptId, toolUseId: f.toolUseId });\n }\n}\n\nconst UPSERT_SESSION = `\n INSERT INTO session (session_id, transcript_id, started_at, ended_at, start_type, cwd, git_branch, ai_title, seed_prompt, cli_version, turn_count, commit_count, push_count)\n VALUES (@sessionId, @transcriptId, @startedAt, @endedAt, @startType, @cwd, @gitBranch, @aiTitle, @seedPrompt, @cliVersion, @turnDelta, @commitDelta, @pushDelta)\n ON CONFLICT (session_id) DO UPDATE SET\n started_at = MIN(COALESCE(started_at, excluded.started_at), COALESCE(excluded.started_at, started_at)),\n ended_at = MAX(COALESCE(ended_at, excluded.ended_at), COALESCE(excluded.ended_at, ended_at)),\n start_type = CASE\n WHEN excluded.start_type IS NULL THEN start_type\n WHEN start_type IS NULL THEN excluded.start_type\n WHEN COALESCE(excluded.started_at, started_at) < COALESCE(started_at, excluded.started_at) THEN excluded.start_type\n ELSE start_type END,\n cwd = COALESCE(excluded.cwd, cwd),\n git_branch = COALESCE(git_branch, excluded.git_branch),\n ai_title = COALESCE(excluded.ai_title, ai_title),\n seed_prompt = COALESCE(seed_prompt, excluded.seed_prompt),\n cli_version = COALESCE(excluded.cli_version, cli_version),\n turn_count = turn_count + excluded.turn_count,\n commit_count = commit_count + excluded.commit_count,\n push_count = push_count + excluded.push_count`;\n\nconst UPSERT_USAGE = `\n INSERT INTO session_model_usage (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, thinking_tokens, request_count)\n VALUES (@sessionId, @model, @inputTokens, @outputTokens, @cacheReadTokens, @cacheCreationTokens, @thinkingTokens, @requestCount)\n ON CONFLICT (session_id, model) DO UPDATE SET\n input_tokens = input_tokens + excluded.input_tokens, output_tokens = output_tokens + excluded.output_tokens,\n cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,\n cache_creation_tokens = cache_creation_tokens + excluded.cache_creation_tokens,\n thinking_tokens = thinking_tokens + excluded.thinking_tokens, request_count = request_count + excluded.request_count`;\n\nfunction applySessions(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const upsertSession = db.prepare(UPSERT_SESSION);\n for (const s of delta.sessions) upsertSession.run({ ...s, transcriptId });\n const upsertUsage = db.prepare(UPSERT_USAGE);\n for (const u of delta.usage) upsertUsage.run(u);\n}\n\nconst ASSET_UPSERTS = {\n prs: `INSERT INTO pr (pr_ref, number, repo, title, url) VALUES (@prRef, @number, @repo, @title, @url)\n ON CONFLICT (pr_ref) DO UPDATE SET title = COALESCE(title, excluded.title), url = COALESCE(url, excluded.url)`,\n branches: `INSERT INTO branch (branch_ref, repo, name, base, created_at, deleted_at) VALUES (@branchRef, @repo, @name, @base, @createdAt, @deletedAt)\n ON CONFLICT (branch_ref) DO UPDATE SET base = COALESCE(base, excluded.base),\n created_at = MIN(COALESCE(created_at, excluded.created_at), COALESCE(excluded.created_at, created_at)),\n deleted_at = MAX(COALESCE(deleted_at, excluded.deleted_at), COALESCE(excluded.deleted_at, deleted_at))`,\n files: `INSERT INTO file (file_ref, repo, path) VALUES (@fileRef, @repo, @path) ON CONFLICT (file_ref) DO NOTHING`,\n tasks: `INSERT INTO task (task_ref, task_id, status) VALUES (@taskRef, @taskId, @status)\n ON CONFLICT (task_ref) DO UPDATE SET status = COALESCE(excluded.status, status)`,\n artifacts: `INSERT INTO artifact (artifact_ref, kind, title, url, path, created_at) VALUES (@artifactRef, @artifactKind, @title, @url, @path, @ts)\n ON CONFLICT (artifact_ref) DO NOTHING`,\n prMerges: `INSERT INTO pr_merge_observation (number, repo_hint, merged_at) VALUES (@number, @repoHint, @ts)\n ON CONFLICT (number, repo_hint, merged_at) DO NOTHING`,\n prCreates: `INSERT INTO pr_create_observation (tool_use_id, title, number, repo, url) VALUES (@toolUseId, @title, @number, @repo, @url)\n ON CONFLICT (tool_use_id) DO UPDATE SET title = COALESCE(title, excluded.title), number = COALESCE(number, excluded.number),\n repo = COALESCE(repo, excluded.repo), url = COALESCE(url, excluded.url)`,\n} as const;\n\nfunction applyAssets(db: Db, delta: TranscriptDelta): void {\n for (const [kind, sql] of Object.entries(ASSET_UPSERTS) as [keyof typeof ASSET_UPSERTS, string][]) {\n const statement = db.prepare(sql);\n for (const row of delta[kind] as unknown as Record<string, unknown>[]) statement.run(pick(row, sql));\n }\n}\n\n/** better-sqlite3 rejects unused named parameters, so pass only the ones the statement binds. */\nfunction pick(row: Record<string, unknown>, sql: string): Record<string, unknown> {\n const names = new Set([...sql.matchAll(/@(\\w+)/g)].map((m) => m[1]!));\n return Object.fromEntries([...names].map((name) => [name, row[name] ?? null]));\n}\n\n/**\n * Bi-temporal phase collapse: a candidate matching the session's open phase is a\n * no-op; otherwise the open phase closes at the new `t_valid` and a replacement\n * row is inserted. Done here so a chunk boundary mid-run cannot add a phase.\n */\nfunction applyPhases(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const current = db.prepare(\"SELECT phase_id, to_mode FROM permission_phase WHERE session_id = ? AND trigger = ? AND t_invalid IS NULL ORDER BY phase_id DESC LIMIT 1\");\n const close = db.prepare(\"UPDATE permission_phase SET t_invalid = ? WHERE phase_id = ?\");\n const insert = db.prepare(\"INSERT INTO permission_phase (session_id, from_mode, to_mode, trigger, t_valid, fact_id) VALUES (?, ?, ?, ?, ?, ?)\");\n for (const phase of delta.phases) {\n const open = current.get(phase.sessionId, phase.trigger) as { phase_id: number; to_mode: string } | undefined;\n if (open?.to_mode === phase.toMode) continue;\n if (open) close.run(phase.ts, open.phase_id);\n insert.run(phase.sessionId, open?.to_mode ?? null, phase.toMode, phase.trigger, phase.ts, factIdFor(db, transcriptId, phase.byteOffset));\n }\n}\n\nfunction applyLinkedRows(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta): void {\n const { db } = graph;\n const factId = (byteOffset: number) => factIdFor(db, transcriptId, byteOffset);\n\n const upsertTurn = db.prepare(`\n INSERT INTO turn (prompt_id, session_id, turn_index, started_at, fact_id_start)\n VALUES (?, ?, (SELECT COALESCE(MAX(turn_index), -1) + 1 FROM turn WHERE session_id = ?), ?, ?)\n ON CONFLICT (prompt_id) DO UPDATE SET session_id = MIN(session_id, excluded.session_id)`);\n for (const t of delta.turns) upsertTurn.run(t.promptId, t.sessionId, t.sessionId, t.ts, factId(t.byteOffset));\n\n const insertEdit = db.prepare(\"INSERT INTO human_edit (session_id, file_path, ts, fact_id) VALUES (?, ?, ?, ?) ON CONFLICT (session_id, file_path, ts) DO NOTHING\");\n for (const e of delta.humanEdits) insertEdit.run(e.sessionId, e.filePath, e.ts, factId(e.byteOffset));\n\n const insertCheckpoint = db.prepare(`\n INSERT INTO file_checkpoint (session_id, file_path, backup_file_name, version, backup_time, fact_id) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (session_id, file_path, backup_file_name) DO NOTHING`);\n for (const c of delta.fileCheckpoints) insertCheckpoint.run(c.sessionId, c.filePath, c.backupFileName, c.version, c.backupTime, factId(c.byteOffset));\n\n const upsertSubagent = db.prepare(`\n INSERT INTO subagent (agent_ref, session_id, agent_type, label, started_at, fact_id) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (agent_ref) DO UPDATE SET session_id = COALESCE(session_id, excluded.session_id),\n agent_type = COALESCE(agent_type, excluded.agent_type), label = COALESCE(label, excluded.label), started_at = COALESCE(started_at, excluded.started_at)`);\n for (const s of delta.subagents) upsertSubagent.run(s.agentRef, s.sessionId, s.agentType, s.label, s.ts || null, factId(s.byteOffset));\n\n // The dispatch row and this bridge come from different lines; an upsert survives a chunk boundary between them.\n const linkTranscript = db.prepare(\"INSERT INTO subagent (agent_ref, child_session_id) VALUES (?, ?) ON CONFLICT (agent_ref) DO UPDATE SET child_session_id = excluded.child_session_id\");\n for (const l of delta.subagentTranscripts) linkTranscript.run(l.agentRef, l.childSessionId);\n\n for (const e of delta.edges) graph.edges.assert({ sourceRef: e.sourceRef, relation: e.relation, targetRef: e.targetRef, tValid: e.ts, factId: factId(e.byteOffset) });\n\n for (const span of delta.spans) {\n graph.spans.index({ ownerRef: sessionRef(span.sessionId), field: span.field, sourceId: transcriptId, byteOffset: span.byteOffset, byteLength: span.byteLength }, span.text);\n }\n}\n\nfunction factIdFor(db: Db, transcriptId: number, byteOffset: number): number | null {\n const row = db.prepare(\"SELECT fact_id FROM fact WHERE transcript_id = ? AND byte_offset = ?\").get(transcriptId, byteOffset) as { fact_id: number } | undefined;\n return row?.fact_id ?? null;\n}\n","import { sessionRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { KIT } from \"./schema.js\";\n\n/** Derived tables keyed by `session_id`, reachable from a transcript through `session`. */\nconst SESSION_SCOPED = [\"turn\", \"permission_phase\", \"human_edit\", \"file_checkpoint\", \"subagent\", \"session_model_usage\"] as const;\n\n/**\n * Drop every derived row one transcript produced, so re-reading it from byte 0\n * rebuilds those rows instead of doubling them.\n *\n * `resetIndex` already states the reason at corpus scale: rewinding alone is not\n * enough, because the accumulating upserts add a second copy of every count. A\n * rewritten single transcript has the same problem one file at a time.\n * `session.turn_count`, the `session_model_usage` token buckets and the\n * commit/push counts all sum into what is already there, and rows the rewrite\n * removed are never deleted — `fact` survives on its unique\n * `(transcript_id, byte_offset)` index, so stale facts simply persist.\n *\n * Shared identity rows survive on purpose. `pr`, `branch`, `file`, `task` and\n * `artifact` are keyed by ref, many transcripts assert the same one, and the\n * inserts are insert-if-absent; a single transcript cannot know whether it was\n * the only source. The two observation tables are left for the same reason —\n * `reconcile` folds them, and re-reading re-asserts the same rows.\n */\nexport function purgeTranscript(graph: SessionGraph, transcriptId: number): void {\n graph.db.transaction(() => {\n const owned = graph.db.prepare(\"SELECT session_id FROM session WHERE transcript_id = ?\").all(transcriptId) as { session_id: string }[];\n for (const { session_id } of owned) graph.spans.purgeOwner(sessionRef(session_id));\n\n // Children first: each subquery reads the `session` rows deleted last.\n for (const table of SESSION_SCOPED) {\n graph.db.prepare(`DELETE FROM \"${table}\" WHERE session_id IN (SELECT session_id FROM session WHERE transcript_id = ?)`).run(transcriptId);\n }\n graph.db.prepare(`DELETE FROM \"${KIT.edge}\" WHERE fact_id IN (SELECT fact_id FROM fact WHERE transcript_id = ?)`).run(transcriptId);\n graph.db.prepare(\"DELETE FROM fact WHERE transcript_id = ?\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM session WHERE transcript_id = ?\").run(transcriptId);\n })();\n}\n","import type { SessionGraph } from \"./graph.js\";\n\nconst BATCH = 400;\n\n/**\n * Turn aggregates that no single line can supply. Contract: recompute, never\n * accumulate. Every value is a pure function of the fact rows, so incremental\n * refreshes and full rebuilds converge regardless of chunk boundaries.\n */\nconst ROLLUP = `\n WITH ordered AS (\n SELECT fact_id, session_id, event_type, ts, prompt_id,\n COUNT(CASE WHEN event_type = 'user_prompt' THEN 1 END) OVER (\n PARTITION BY session_id ORDER BY ts, transcript_id, seq ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS turn_no,\n LAG(event_type) OVER (PARTITION BY session_id ORDER BY ts, transcript_id, seq) AS prev_type,\n LAG(ts) OVER (PARTITION BY session_id ORDER BY ts, transcript_id, seq) AS prev_ts\n FROM fact WHERE session_id IN (SELECT value FROM json_each(@sessionIds))\n ),\n owners AS (SELECT session_id, turn_no, prompt_id FROM ordered WHERE event_type = 'user_prompt'),\n agg AS (\n SELECT owners.prompt_id AS prompt_id, MAX(ordered.ts) AS ended_at,\n CAST(ROUND((julianday(MAX(ordered.ts)) - julianday(MIN(ordered.ts))) * 86400000) AS INTEGER) AS duration_ms,\n SUM(CASE WHEN ordered.event_type = 'tool_decision' THEN 1 ELSE 0 END) AS tool_call_count,\n CAST(COALESCE(SUM(CASE\n WHEN ordered.event_type IN ('assistant_response', 'tool_decision') AND ordered.prev_type IN ('user_prompt', 'tool_result', 'tool_result_error')\n THEN ROUND((julianday(ordered.ts) - julianday(ordered.prev_ts)) * 86400000) END), 0) AS INTEGER) AS thinking_ms\n FROM ordered JOIN owners ON owners.session_id = ordered.session_id AND owners.turn_no = ordered.turn_no\n GROUP BY owners.prompt_id\n )\n UPDATE turn SET ended_at = agg.ended_at, duration_ms = COALESCE(agg.duration_ms, 0),\n tool_call_count = agg.tool_call_count, thinking_ms = COALESCE(agg.thinking_ms, 0)\n FROM agg WHERE turn.prompt_id = agg.prompt_id`;\n\n/** `turn_index` is insert-order at write time; recomputing it from `started_at` makes it a property of the corpus. */\nconst RENUMBER_TURNS = `\n WITH ordered AS (\n SELECT prompt_id, ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY started_at, prompt_id) - 1 AS idx\n FROM turn WHERE session_id IN (SELECT value FROM json_each(@sessionIds))\n )\n UPDATE turn SET turn_index = ordered.idx FROM ordered WHERE turn.prompt_id = ordered.prompt_id`;\n\n/** Recompute turn aggregates for the given sessions in one transaction. */\nexport function rollupSessions(graph: SessionGraph, sessionIds: readonly string[]): number {\n const unique = [...new Set(sessionIds)];\n if (unique.length === 0) return 0;\n const aggregate = graph.db.prepare(ROLLUP);\n const renumber = graph.db.prepare(RENUMBER_TURNS);\n return graph.db.transaction(() => {\n let updated = 0;\n for (let i = 0; i < unique.length; i += BATCH) {\n const batch = JSON.stringify(unique.slice(i, i + BATCH));\n renumber.run({ sessionIds: batch });\n updated += aggregate.run({ sessionIds: batch }).changes;\n }\n return updated;\n })();\n}\n\n/** A merge sighting and the `pr-link` naming the same PR routinely live in different transcripts; fold at pass end. */\nconst RECONCILE_PR_MERGES = `\n UPDATE pr SET state = 'merged',\n merged_at = (SELECT MIN(o.merged_at) FROM pr_merge_observation o WHERE o.number = pr.number AND (o.repo_hint IS NULL OR pr.repo LIKE '%/' || o.repo_hint))\n WHERE EXISTS (SELECT 1 FROM pr_merge_observation o WHERE o.number = pr.number AND (o.repo_hint IS NULL OR pr.repo LIKE '%/' || o.repo_hint))`;\n\n/** Both halves of a `gh pr create` make a real PR whether or not a `pr-link` ever mentioned it. */\nconst RECONCILE_PR_CREATES = `\n INSERT INTO pr (pr_ref, number, repo, title, url)\n SELECT 'pr:' || repo || '#' || number, number, repo, title, url FROM pr_create_observation\n WHERE number IS NOT NULL AND repo IS NOT NULL AND title IS NOT NULL\n ON CONFLICT (pr_ref) DO UPDATE SET title = COALESCE(pr.title, excluded.title), url = COALESCE(pr.url, excluded.url)`;\n\n/**\n * A subagent ends when its child session's last line does, not when the\n * dispatch's tool_result returns (most dispatches are async and return at\n * launch). Parentage comes from the dispatch that ran inside a child transcript.\n */\nconst RECONCILE_SUBAGENTS = `\n UPDATE subagent SET\n ended_at = COALESCE((SELECT s.ended_at FROM session s WHERE s.session_id = subagent.child_session_id), ended_at),\n parent_agent_ref = COALESCE((SELECT p.agent_ref FROM subagent p WHERE p.child_session_id = subagent.session_id), parent_agent_ref)`;\n\nexport interface ReconcileCounts {\n prMerges: number;\n prCreates: number;\n subagents: number;\n}\n\n/** Whole-table reconciliations, run once at the end of a refresh pass after the rollup. */\nexport function reconcile(graph: SessionGraph): ReconcileCounts {\n return {\n prCreates: graph.db.prepare(RECONCILE_PR_CREATES).run().changes,\n prMerges: graph.db.prepare(RECONCILE_PR_MERGES).run().changes,\n subagents: graph.db.prepare(RECONCILE_SUBAGENTS).run().changes,\n };\n}\n","import { promises as fs } from \"node:fs\";\nimport { contentHash, resumePoint } from \"@titan-design/locator\";\nimport { TranscriptParseError, extractTranscript, type DiscoveredTranscript } from \"@titan-design/session-read\";\nimport { applyDelta } from \"./apply.js\";\nimport { allSessionIds, type SessionGraph } from \"./graph.js\";\nimport { purgeTranscript } from \"./purge.js\";\nimport { reconcile, rollupSessions, type ReconcileCounts } from \"./rollup.js\";\nimport { allTaskIds, enrichTasks, NO_ENRICHMENT, type TaskEnrichment, type TaskResolver } from \"./tasks.js\";\n\nexport interface IndexOptions {\n /** Re-hash the already-read prefix to catch same-length rewrites. O(file), so opt in. */\n verifyHash?: boolean;\n /** Store a whole-file content hash for durability reporting. O(file), so opt in. */\n withContentHash?: boolean;\n /** Fill task title, initiative and present status from the caller's store. Absent means transcripts alone. */\n resolveTasks?: TaskResolver;\n}\n\nexport type TranscriptOutcome =\n | { status: \"unchanged\" | \"indexed\" | \"rewound\"; transcriptId: number; sessionIds: string[]; facts: number; tasks: TaskEnrichment }\n | { status: \"missing\" | \"quarantined\"; transcriptId: number; sessionIds: string[]; facts: 0; tasks: TaskEnrichment; reason: string };\n\n/**\n * Bring one transcript up to date: read from its watermark (or byte 0 after a\n * rewrite), apply the delta, advance the watermark. A malformed line\n * quarantines the transcript instead of poisoning the pass.\n */\nexport async function indexTranscript(graph: SessionGraph, transcript: DiscoveredTranscript, options: IndexOptions = {}): Promise<TranscriptOutcome> {\n const row = graph.transcripts.ensure(transcript.displayPath);\n const entry = { path: transcript.displayPath, lastByteOffset: row.lastOffset, prefixHash: row.prefixHash, mtime: row.fileMtime };\n const point = await resumePoint(entry, transcript.absolutePath, { verifyHash: options.verifyHash });\n const base = { transcriptId: row.sourceId, sessionIds: [] as string[], tasks: NO_ENRICHMENT };\n if (point.state === \"missing\") {\n graph.transcripts.markStatus(transcript.displayPath, \"missing\", \"source file no longer exists\");\n return { ...base, status: \"missing\", facts: 0, reason: \"source file no longer exists\" };\n }\n if (point.state === \"unchanged\") {\n // A file that vanished and came back unchanged resumes at EOF, so there are no\n // bytes to read and `advance()` — the only writer of status 'ok' — never runs,\n // stranding the row on 'missing' forever. Clear it here rather than forcing a\n // re-read, which would rewrite the prefix hash from a zero-byte delta.\n // Quarantine is deliberately not cleared: the same bytes still will not parse.\n if (row.status === \"missing\") graph.transcripts.markStatus(transcript.displayPath, \"ok\", null);\n return { ...base, status: \"unchanged\", facts: 0 };\n }\n\n try {\n const delta = await extractTranscript(transcript.absolutePath, {\n fromByteOffset: point.start,\n priorPrefixHash: point.state === \"rewritten\" ? null : row.prefixHash,\n subagentId: transcript.subagentId,\n });\n // Two ways to learn a transcript was rewritten, and both must purge. `resumePoint`\n // catches a file that shrank; `extractTranscript` re-hashes the prefix it was asked\n // to skip and restarts from zero on its own when the bytes moved — a rotation that\n // left the file the same length or longer. Ignoring its answer replays the whole\n // file onto rows that were never removed, which is the accumulation purge exists\n // to prevent, reached by a different road.\n const rewound = point.state === \"rewritten\" || delta.restartedFromZero;\n // After extraction, never before: a parse failure quarantines the transcript,\n // and purging first would destroy rows we could then no longer rebuild.\n if (rewound) purgeTranscript(graph, row.sourceId);\n applyDelta(graph, row.sourceId, delta);\n const stat = await fs.stat(transcript.absolutePath);\n graph.transcripts.advance(transcript.displayPath, {\n lastOffset: delta.lastByteOffset,\n prefixHash: delta.prefixHash,\n fileSize: stat.size,\n fileMtime: stat.mtime.toISOString(),\n contentHash: options.withContentHash ? await contentHash(transcript.absolutePath) : null,\n });\n const sessionIds = delta.sessions.map((s) => s.sessionId);\n const taskIds = delta.tasks.map((t) => t.taskId);\n const tasks = taskIds.length > 0 ? await enrichTasks(graph, options.resolveTasks, taskIds) : NO_ENRICHMENT;\n return { ...base, sessionIds, tasks, status: rewound ? \"rewound\" : \"indexed\", facts: delta.facts.length };\n } catch (err) {\n if (!(err instanceof TranscriptParseError)) throw err;\n graph.transcripts.markStatus(transcript.displayPath, \"quarantined\", err.message);\n return { ...base, status: \"quarantined\", facts: 0, reason: err.message };\n }\n}\n\nexport interface RefreshSummary {\n transcripts: number;\n indexed: number;\n unchanged: number;\n rewound: number;\n missing: number;\n quarantined: number;\n facts: number;\n turnsRolledUp: number;\n reconciled: ReconcileCounts;\n tasks: TaskEnrichment;\n markedMissing: number;\n}\n\n/**\n * One pass over a corpus: index every transcript, roll up the sessions that\n * changed, reconcile cross-transcript observations, enrich tasks, and mark rows\n * whose source file is gone. Idempotent: a second pass over unchanged files\n * changes nothing.\n *\n * The resolver is hoisted out of the per-transcript loop and run once over the\n * whole task table, so a corpus of N transcripts costs one resolver call rather\n * than N, and a task whose store row changed refreshes even when no transcript\n * did. That bounds staleness to one pass.\n */\nexport async function refreshCorpus(graph: SessionGraph, transcripts: readonly DiscoveredTranscript[], options: IndexOptions & { full?: boolean } = {}): Promise<RefreshSummary> {\n const { resolveTasks, ...perTranscript } = options;\n const counts = { indexed: 0, unchanged: 0, rewound: 0, missing: 0, quarantined: 0 };\n const touched: string[] = [];\n let facts = 0;\n for (const transcript of transcripts) {\n const outcome = await indexTranscript(graph, transcript, perTranscript);\n counts[outcome.status] += 1;\n facts += outcome.facts;\n touched.push(...outcome.sessionIds);\n }\n const turnsRolledUp = rollupSessions(graph, options.full ? allSessionIds(graph) : touched);\n const reconciled = reconcile(graph);\n const tasks = await enrichTasks(graph, resolveTasks, allTaskIds(graph));\n const markedMissing = await markMissing(graph, transcripts);\n return { transcripts: transcripts.length, ...counts, facts, turnsRolledUp, reconciled, tasks, markedMissing };\n}\n\n/** Rows absent from discovery are only nominated; an `fs.stat` decides, so an empty scan cannot condemn the corpus. */\nasync function markMissing(graph: SessionGraph, discovered: readonly DiscoveredTranscript[]): Promise<number> {\n const present = new Set(discovered.map((t) => t.displayPath));\n const byPath = new Map(discovered.map((t) => [t.displayPath, t.absolutePath]));\n let marked = 0;\n for (const row of graph.transcripts.list()) {\n if (row.status === \"missing\" || present.has(row.sourceKey)) continue;\n const normalized = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(row.sourceId) as { descriptor: string } | undefined;\n const absolute = normalized ? (JSON.parse(normalized.descriptor) as { path: string }).path : byPath.get(row.sourceKey) ?? row.sourceKey;\n if (await exists(absolute)) continue;\n graph.transcripts.markStatus(row.sourceKey, \"missing\", \"source file no longer exists\");\n marked += 1;\n }\n return marked;\n}\n\nasync function exists(absolutePath: string): Promise<boolean> {\n try {\n await fs.stat(absolutePath);\n return true;\n } catch {\n return false;\n }\n}\n","import { taskRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * What a product's task store knows and a transcript cannot state: the task's\n * present title, its initiative, and the status it holds right now rather than\n * the one some command was observed to set. Every field is optional; an omitted\n * or null field leaves whatever the transcripts derived in place.\n */\nexport interface ResolvedTask {\n initiative?: string | null;\n title?: string | null;\n status?: string | null;\n}\n\n/** Resolutions keyed by bare task id (`AW-23`, not `task:AW-23`). */\nexport type TaskResolution = ReadonlyMap<string, ResolvedTask | null | undefined>;\n\n/**\n * Supplied by the caller, never by this package: session-graph is tier 2 and\n * must not learn where any product keeps its tasks. Called once per pass with\n * every task id at once, because a real resolver reads a store. Returning an id\n * that no transcript mentioned inserts that task; that is how a task nobody\n * worked on gets into the graph.\n */\nexport type TaskResolver = (taskIds: readonly string[]) => PromiseLike<TaskResolution> | TaskResolution;\n\nexport interface TaskEnrichment {\n /** Task ids handed to the resolver. */\n requested: number;\n /** Task rows the resolver wrote. */\n applied: number;\n /** The resolver threw; rows stand as the transcripts left them. */\n failed: boolean;\n /** Why it failed, for the caller to log. Absent unless `failed`. */\n error?: string;\n}\n\nexport const NO_ENRICHMENT: TaskEnrichment = Object.freeze({ requested: 0, applied: 0, failed: false });\n\n/**\n * COALESCE gives the resolver precedence over the transcripts on every field it\n * states, and none on the fields it omits: the store is the system of record for\n * a task's present status, while a transcript only witnesses a command that ran.\n */\nconst UPSERT_TASK = `\n INSERT INTO task (task_ref, task_id, initiative, title, status) VALUES (@taskRef, @taskId, @initiative, @title, @status)\n ON CONFLICT (task_ref) DO UPDATE SET\n initiative = COALESCE(excluded.initiative, initiative),\n title = COALESCE(excluded.title, title),\n status = COALESCE(excluded.status, status)`;\n\nexport function allTaskIds(graph: SessionGraph): string[] {\n return (graph.db.prepare(\"SELECT task_id FROM task\").all() as { task_id: string }[]).map((r) => r.task_id);\n}\n\n/**\n * Enrich task rows from a caller-supplied resolver. Runs outside the delta's\n * transaction because a resolver is async; a crash between the two leaves rows\n * un-enriched, which the next pass corrects. A resolver that throws costs this\n * pass its enrichment and nothing else.\n */\nexport async function enrichTasks(graph: SessionGraph, resolver: TaskResolver | undefined, taskIds: readonly string[]): Promise<TaskEnrichment> {\n const unique = [...new Set(taskIds)];\n if (!resolver) return NO_ENRICHMENT;\n try {\n const resolved = await resolver(unique);\n return { requested: unique.length, applied: write(graph, resolved), failed: false };\n } catch (err) {\n return { requested: unique.length, applied: 0, failed: true, error: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction write(graph: SessionGraph, resolved: TaskResolution): number {\n const upsert = graph.db.prepare(UPSERT_TASK);\n return graph.db.transaction(() => {\n let applied = 0;\n for (const [taskId, fields] of resolved) {\n if (!fields) continue;\n applied += upsert.run({\n taskRef: taskRef(taskId),\n taskId,\n initiative: fields.initiative ?? null,\n title: fields.title ?? null,\n status: fields.status ?? null,\n }).changes;\n }\n return applied;\n })();\n}\n","import { randomUUID } from \"node:crypto\";\nimport { stat } from \"node:fs/promises\";\nimport { contentHash } from \"@titan-design/locator\";\nimport { conversationRef } from \"@titan-design/agent-protocol\";\nimport { SPAN_TEXT_CAP, readCodexObservations, type CodexReadResult, type NormalizedSessionObservation, type SessionSourceDescriptor, type SourceTextLocator } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { insertObservation, observationText } from \"./normalized-project.js\";\n\nexport interface NormalizedIndexResult { status: \"indexed\" | \"unchanged\" | \"missing\" | \"quarantined\"; conversationRef: string; observations: number; reason?: string }\n\n/** Replay changed sources into temporary staging; swap rows and watermark atomically. */\nexport async function indexCodexSource(graph: SessionGraph, source: SessionSourceDescriptor): Promise<NormalizedIndexResult> {\n const ref = conversationRef(source.conversation);\n const base = { conversationRef: ref, observations: 0 };\n const row = graph.transcripts.ensure(source.sourceId);\n let info;\n try { info = await stat(source.path); } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n graph.transcripts.markStatus(source.sourceId, \"missing\", \"source unavailable\");\n return { ...base, status: \"missing\" };\n }\n // Always replay: the decoder verifies bytes, detects projections that arrived later,\n // and avoids trusting mtime alone. Equality of the resulting prefix is the fast no-op.\n const stage = `stage_${randomUUID().replaceAll(\"-\", \"\")}`;\n graph.db.exec(`CREATE TEMP TABLE ${stage}(seq INTEGER PRIMARY KEY, observation TEXT NOT NULL)`);\n let result: CodexReadResult | undefined;\n try {\n const fingerprint = await contentHash(source.path);\n const insert = graph.db.prepare(`INSERT INTO ${stage}(observation) VALUES (?)`);\n for await (const observation of readCodexObservations(source, {}, done => { result = done; })) insert.run(JSON.stringify(observation));\n if (!result) throw new Error(\"decoder completed without a resume boundary\");\n const boundary = (result as CodexReadResult).resumeBoundary;\n const prior = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(row.sourceId) as { descriptor: string } | undefined;\n if (prior && row.contentHash === fingerprint && row.prefixHash === boundary.prefixHash && row.lastOffset === boundary.byteOffset) {\n graph.db.prepare(\"UPDATE normalized_source SET descriptor = ? WHERE transcript_id = ?\").run(JSON.stringify(source), row.sourceId);\n graph.transcripts.markStatus(source.sourceId, \"ok\", null);\n return { ...base, status: \"unchanged\" };\n }\n const count = graph.db.transaction(() => {\n purgeNormalizedSource(graph, row.sourceId);\n graph.db.prepare(\"INSERT OR IGNORE INTO conversation(ref,harness,namespace,native_id) VALUES (?,?,?,?)\").run(ref, source.harness, source.namespace, source.conversation.nativeId);\n graph.db.prepare(\"INSERT OR REPLACE INTO normalized_source VALUES (?,?,?,?)\").run(row.sourceId, source.sourceId, ref, JSON.stringify(source));\n const n = applyStaged(graph, stage, row.sourceId, ref);\n graph.transcripts.advance(source.sourceId, { lastOffset: boundary.byteOffset, prefixHash: boundary.prefixHash, fileSize: info.size, fileMtime: info.mtime.toISOString(), contentHash: fingerprint });\n return n;\n })();\n return { ...base, status: \"indexed\", observations: count };\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n graph.transcripts.markStatus(source.sourceId, \"quarantined\", reason);\n return { ...base, status: \"quarantined\", reason };\n } finally { graph.db.exec(`DROP TABLE ${stage}`); }\n}\n\nfunction applyStaged(graph: SessionGraph, stage: string, transcriptId: number, ref: string): number {\n const spans = `${stage}_spans`;\n graph.db.exec(`CREATE TEMP TABLE ${spans}(field TEXT, offset INTEGER, length INTEGER, text TEXT, locators TEXT, PRIMARY KEY(field,offset))`);\n let count = 0;\n try {\n for (const row of stagedRows<{ observation: string }>(graph, stage)) {\n const o = JSON.parse(row.observation) as NormalizedSessionObservation;\n insertObservation(graph, transcriptId, o);\n for (const p of observationText(o)) stageText(graph, spans, p.field, p.text, p.locator);\n count++;\n }\n for (const s of stagedRows<{ field: string; offset: number; length: number; text: string; locators: string }>(graph, spans)) {\n const id = graph.spans.index({ ownerRef: ref, sourceId: transcriptId, field: s.field, byteOffset: s.offset, byteLength: s.length }, s.text);\n graph.db.prepare(\"INSERT INTO normalized_span VALUES (?,?)\").run(id, s.locators);\n }\n } finally { graph.db.exec(`DROP TABLE ${spans}`); }\n return count;\n}\n\nfunction stageText(graph: SessionGraph, table: string, field: string, text: string, locator: SourceTextLocator): void {\n const line = locator.evidence.line;\n const prior = graph.db.prepare(`SELECT text,locators FROM ${table} WHERE field = ? AND offset = ?`).get(field, line.byteOffset) as { text: string; locators: string } | undefined;\n const locators = prior ? JSON.parse(prior.locators) as unknown[] : [];\n locators.push(locator);\n graph.db.prepare(`INSERT OR REPLACE INTO ${table} VALUES (?,?,?,?,?)`).run(field, line.byteOffset, line.byteLength, (prior ? `${prior.text}\\n${text}` : text).slice(0, SPAN_TEXT_CAP), JSON.stringify(locators));\n}\n\nfunction purgeNormalizedSource(graph: SessionGraph, transcriptId: number): void {\n graph.db.prepare(\"DELETE FROM normalized_span WHERE span_id IN (SELECT span_id FROM search_span WHERE source_id = ?)\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM search_span WHERE source_id = ?\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM normalized_event WHERE transcript_id = ?\").run(transcriptId);\n}\n\n\n/** Finish each SELECT before writing; SQLite forbids writes during an active iterator. */\nfunction* stagedRows<T>(graph: SessionGraph, table: string): Generator<T> {\n const select = graph.db.prepare(`SELECT rowid AS cursor, * FROM ${table} WHERE rowid > ? ORDER BY rowid LIMIT 128`);\n let cursor = 0;\n for (;;) {\n const rows = select.all(cursor) as (T & { cursor: number })[];\n if (!rows.length) return;\n for (const row of rows) { cursor = row.cursor; yield row; }\n }\n}\n","import { conversationRef } from \"@titan-design/agent-protocol\";\nimport { normalizedSearchText, SPAN_TEXT_CAP, scopedConversationItemRef, type NormalizedSessionObservation as Observation, type SourceTextLocator } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/** Persist structural evidence only; prose remains in the source and transient FTS input. */\nexport function insertObservation(graph: SessionGraph, transcriptId: number, o: Observation): void {\n graph.db.prepare(`INSERT INTO normalized_event VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(\n transcriptId, o.evidence.line.byteOffset, o.evidence.subrecord.index, o.evidence.line.byteLength,\n conversationRef(o.conversation), o.kind, o.timestamp,\n \"turn\" in o && o.turn ? scopedConversationItemRef(o.turn) : null,\n \"call\" in o ? scopedConversationItemRef(o.call) : null,\n \"item\" in o && o.item ? scopedConversationItemRef(o.item) : null,\n o.kind === \"native_turn\" ? o.phase : null,\n o.kind === \"tool_result\" && o.isError !== null ? Number(o.isError) : null,\n o.kind === \"usage\" ? JSON.stringify(o.measurement) : null,\n o.kind === \"metadata\" ? JSON.stringify(o.entries.filter(e => [\"cwd\", \"model\", \"cli_version\", \"git_branch\", \"title\"].includes(e.name))) : null,\n o.historyOrigin ? conversationRef(o.historyOrigin) : null,\n o.kind === \"lineage\" ? conversationRef(o.relatedConversation) : null,\n o.kind === \"lineage\" ? o.relationship : null,\n );\n}\n\nexport interface ObservationText { field: string; text: string; locator: SourceTextLocator }\nexport function observationText(o: Observation): ObservationText[] {\n if (o.kind === \"message\") return o.content.map(p => ({ field: o.role === \"user\" ? \"prompt\" : \"assistant_response\", text: p.text.slice(0, SPAN_TEXT_CAP), locator: p.locator }));\n if (o.kind === \"tool_call\" && o.inputLocator) return [{ field: \"tool_input\", text: normalizedSearchText(o.input), locator: o.inputLocator }];\n if (o.kind === \"tool_result\" && o.outputLocator) return [{ field: \"tool_result\", text: normalizedSearchText(o.output), locator: o.outputLocator }];\n return [];\n}\n","import { contentHash, prefixHash } from \"@titan-design/locator\";\nimport type { UsageMeasurement } from \"@titan-design/agent-protocol\";\nimport { readSessionSourceText, readSessionText, SessionUsageAccumulator, type SessionUsageSummary, type SourceTextLocator, type SessionSourceDescriptor, type SpanField } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\nexport interface IndexedSpan { sourceId: number; byteOffset: number; byteLength: number; field: string; spanId?: number }\n/** One resolver for search and error clustering; never display a raw JSON line. */\nexport async function readIndexedText(graph: SessionGraph, span: IndexedSpan): Promise<string | null> {\n const row = graph.db.prepare(`SELECT n.locators FROM normalized_span n JOIN search_span s USING(span_id)\n WHERE s.source_id = ? AND s.byte_offset = ? AND s.field = ?`).get(span.sourceId, span.byteOffset, span.field) as { locators: string } | undefined;\n if (row) {\n const source = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(span.sourceId) as { descriptor: string } | undefined;\n if (!source) return null;\n const descriptor = JSON.parse(source.descriptor) as SessionSourceDescriptor;\n const watermark = graph.transcripts.list().find(t => t.sourceId === span.sourceId);\n try { if (!watermark?.prefixHash || await prefixHash(descriptor.path, watermark.lastOffset) !== watermark.prefixHash) return null; }\n catch { return null; }\n if (span.byteOffset + span.byteLength + 1 > watermark.lastOffset) {\n try { if (!watermark.contentHash || await contentHash(descriptor.path) !== watermark.contentHash) return null; } catch { return null; }\n }\n const texts = await Promise.all((JSON.parse(row.locators) as SourceTextLocator[]).map(locator => readSessionSourceText(locator, { sources: [JSON.parse(source.descriptor) as SessionSourceDescriptor] })));\n return texts.some(text => text === null) ? null : texts.join(\"\\n\");\n }\n const normalized = graph.db.prepare(\"SELECT 1 FROM normalized_source WHERE transcript_id = ?\").get(span.sourceId);\n if (normalized) return null;\n const legacy = graph.transcripts.list().find(t => t.sourceId === span.sourceId);\n return legacy ? readSessionText({ path: legacy.sourceKey, byteOffset: span.byteOffset, byteLength: span.byteLength, field: span.field as SpanField }) : null;\n}\n\nexport interface ConversationSummary {\n sessionId: string; harness: string; nativeId: string; namespace: string;\n title: string | null; startedAt: string | null; endedAt: string | null;\n cwd: string | null; gitBranch: string | null; turnCount: number; commitCount: number | null; pushCount: number | null;\n}\nexport function normalizedSessions(graph: SessionGraph, options: { ref?: string; limit?: number; since?: string } = {}): ConversationSummary[] {\n const rows = graph.db.prepare(`SELECT c.ref,c.harness,c.native_id,c.namespace,MIN(e.ts) AS started,\n COUNT(DISTINCT CASE WHEN e.kind = 'native_turn' THEN e.turn_ref END) AS turns\n FROM conversation c JOIN (SELECT DISTINCT conversation_ref FROM normalized_source) s ON s.conversation_ref = c.ref\n LEFT JOIN normalized_event e ON e.conversation_ref = c.ref AND e.history_origin IS NULL\n WHERE c.legacy_session_id IS NULL AND (? IS NULL OR c.ref = ?)\n GROUP BY c.ref HAVING (? IS NULL OR MIN(e.ts) >= ?)\n ORDER BY started DESC LIMIT ?`).all(options.ref ?? null,options.ref ?? null,options.since ?? null,options.since ?? null,options.limit ?? -1) as\n { ref: string; harness: string; native_id: string; namespace: string; started: string | null; turns: number }[];\n const meta = graph.db.prepare(`SELECT json_extract(j.value,'$.value') AS value\n FROM normalized_event e,json_each(e.metadata) j WHERE e.conversation_ref = ?\n AND e.history_origin IS NULL AND json_extract(j.value,'$.name') = ? ORDER BY e.ts DESC,e.byte_offset DESC,e.subrecord_index DESC LIMIT 1`);\n return rows.map(c => {\n const text = (key: string) => { const value = (meta.get(c.ref,key) as { value: unknown } | undefined)?.value; return typeof value === \"string\" ? value : null; };\n return { sessionId:c.ref,harness:c.harness,nativeId:c.native_id,namespace:c.namespace,title:text(\"title\"),startedAt:c.started,\n endedAt:null,cwd:text(\"cwd\"),gitBranch:text(\"git_branch\"),turnCount:c.turns,commitCount:null,pushCount:null };\n });\n}\n\nexport type NormalizedUsageSummary = SessionUsageSummary;\n/** Shared storage-free usage policy keeps graph and direct readers consistent. */\nexport function normalizedUsage(graph: SessionGraph, ref: string): NormalizedUsageSummary[] {\n // Snapshot epochs are local to a physical source; never add copies from different files.\n // Pick the source with the latest native timestamp (then fullest coverage, then stable ID).\n const preferred = graph.db.prepare(`SELECT transcript_id FROM normalized_event\n WHERE conversation_ref = ? AND usage IS NOT NULL AND history_origin IS NULL\n GROUP BY transcript_id ORDER BY MAX(ts) DESC, COUNT(*) DESC, transcript_id ASC LIMIT 1`).get(ref) as { transcript_id: number } | undefined;\n const rows = graph.db.prepare(`SELECT usage FROM normalized_event WHERE conversation_ref = ?\n AND usage IS NOT NULL AND history_origin IS NULL\n AND (json_extract(usage,'$.kind') = 'delta' OR transcript_id = ?)\n ORDER BY byte_offset,subrecord_index`);\n const accumulator = new SessionUsageAccumulator();\n for (const row of rows.iterate(ref, preferred?.transcript_id ?? -1) as Iterable<{ usage: string }>) {\n accumulator.add(JSON.parse(row.usage) as UsageMeasurement);\n }\n return accumulator.summaries();\n}\n"],"mappings":"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","names":["sessionRef","sessionRef","stat","contentHash","conversationRef","SPAN_TEXT_CAP","conversationRef","conversationRef","contentHash","SPAN_TEXT_CAP","contentHash"]}
1
+ {"version":3,"sources":["../src/graph.ts","../src/normalized-schema.ts","../src/schema.ts","../src/apply.ts","../src/purge.ts","../src/rollup.ts","../src/refresh.ts","../src/tasks.ts","../src/normalized-index.ts","../src/normalized-project.ts","../src/normalized-query.ts"],"sourcesContent":["import { EdgeTable, SpanFtsTables, WatermarkTable, openDatabase, runMigrations, type Db } from \"@titan-design/store-sqlite\";\nimport { DERIVED_TABLES, KIT, MIGRATIONS } from \"./schema.js\";\n\n/** One open session graph: the connection plus the kit helpers bound to its tables. */\nexport interface SessionGraph {\n db: Db;\n transcripts: WatermarkTable;\n edges: EdgeTable;\n spans: SpanFtsTables;\n}\n\nexport interface OpenSessionGraphOptions {\n /**\n * The highest migration version the caller owns, checked before any migration runs.\n * A product layering its own tables on this graph passes its own top version, not this\n * package's: `MIGRATIONS` here is one band of a shared database, never the top of it.\n */\n schemaVersion?: number;\n}\n\nexport function openSessionGraph(dbPath: string, options: OpenSessionGraphOptions = {}): SessionGraph {\n const db = openDatabase(dbPath, { schemaVersion: options.schemaVersion });\n runMigrations(db, MIGRATIONS);\n return {\n db,\n transcripts: new WatermarkTable(db, { name: KIT.watermark }),\n edges: new EdgeTable(db, { name: KIT.edge }),\n spans: new SpanFtsTables(db, { name: KIT.spanFts }),\n };\n}\n\n/**\n * Clear every derived row and rewind all watermarks so the next refresh\n * rebuilds from byte 0. Rewinding alone is not enough: the accumulating\n * upserts would add a second copy of every count.\n */\nexport function resetIndex(graph: SessionGraph): void {\n graph.db.transaction(() => {\n for (const table of DERIVED_TABLES) graph.db.exec(`DELETE FROM \"${table}\"`);\n graph.spans.clearIndex();\n for (const row of graph.transcripts.list()) graph.transcripts.rewind(row.sourceKey);\n })();\n}\n\nexport function allSessionIds(graph: SessionGraph): string[] {\n return (graph.db.prepare(\"SELECT session_id FROM session\").all() as { session_id: string }[]).map((r) => r.session_id);\n}\n","import { conversationRef } from \"@titan-design/agent-protocol\";\nimport type { Db } from \"@titan-design/store-sqlite\";\n\n/** Additive: legacy rows, aliases and original locators are never rewritten. */\nexport const NORMALIZED_DDL = `\nCREATE TABLE IF NOT EXISTS conversation (\n ref TEXT PRIMARY KEY, harness TEXT NOT NULL, namespace TEXT NOT NULL,\n native_id TEXT NOT NULL, legacy_session_id TEXT,\n UNIQUE(harness, namespace, native_id)\n);\nCREATE TABLE IF NOT EXISTS conversation_alias (\n legacy_ref TEXT NOT NULL, conversation_ref TEXT NOT NULL,\n PRIMARY KEY(legacy_ref, conversation_ref)\n);\nCREATE TABLE IF NOT EXISTS normalized_source (\n transcript_id INTEGER PRIMARY KEY, source_id TEXT NOT NULL UNIQUE,\n conversation_ref TEXT NOT NULL, descriptor TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS normalized_event (\n transcript_id INTEGER NOT NULL, byte_offset INTEGER NOT NULL,\n subrecord_index INTEGER NOT NULL, byte_length INTEGER NOT NULL,\n conversation_ref TEXT NOT NULL, kind TEXT NOT NULL, ts TEXT,\n turn_ref TEXT, call_ref TEXT, item_ref TEXT, phase TEXT, is_error INTEGER,\n usage TEXT, metadata TEXT, history_origin TEXT, related_ref TEXT, relationship TEXT,\n PRIMARY KEY(transcript_id, byte_offset, subrecord_index)\n);\nCREATE INDEX IF NOT EXISTS idx_normalized_event_conversation ON normalized_event(conversation_ref, ts);\nCREATE TABLE IF NOT EXISTS normalized_span (\n span_id INTEGER PRIMARY KEY, locators TEXT NOT NULL\n);\n`;\n\n/** Only the old transcript session table proves Claude provenance, never a ref prefix. */\nexport function backfillClaudeAliases(db: Db, sessionIds?: readonly string[]): void {\n const insert = db.prepare(\"INSERT OR IGNORE INTO conversation(ref,harness,namespace,native_id,legacy_session_id) VALUES (?,?,?,?,?)\");\n const alias = db.prepare(\"INSERT OR IGNORE INTO conversation_alias VALUES (?,?)\");\n const rows = sessionIds?.map(session_id => ({ session_id })) ?? db.prepare(\"SELECT session_id FROM session\").all() as Iterable<{ session_id: string }>;\n for (const row of rows) {\n const ref = conversationRef({ harness: \"claude-code\", namespace: \"legacy\", nativeId: row.session_id });\n insert.run(ref, \"claude-code\", \"legacy\", row.session_id, row.session_id);\n alias.run(`session:${row.session_id}`, ref);\n }\n}\n\n/** Ambiguity is explicit; workspace session bodies remain in their original namespace. */\nexport function resolveConversationAlias(db: Db, legacyRef: string): string | null {\n const rows = db.prepare(\"SELECT conversation_ref FROM conversation_alias WHERE legacy_ref = ?\").all(legacyRef) as { conversation_ref: string }[];\n if (rows.length > 1) throw new Error(`ambiguous conversation alias: ${legacyRef}`);\n return rows[0]?.conversation_ref ?? null;\n}\n","import { NORMALIZED_DDL, backfillClaudeAliases } from \"./normalized-schema.js\";\nimport { SQL_NOW, kitMigration, type Migration } from \"@titan-design/store-sqlite\";\n\n/** Kit table names this graph uses. `watermark` rows are transcripts; `search_*` are the FTS spans. */\nexport const KIT = { watermark: \"transcript\", edge: \"edge\", spanFts: \"search\" } as const;\n\n/**\n * Legacy domain tables. Original sources can be pruned; schema migrations must\n * preserve these rows rather than assuming a replay is possible.\n * Locators are `(transcript_id, byte_offset, byte_length)`; `fact_id` rows\n * resolve through the facts table's unique `(transcript_id, byte_offset)`.\n */\nexport const DOMAIN_DDL = `\n CREATE TABLE IF NOT EXISTS fact (\n fact_id INTEGER PRIMARY KEY,\n transcript_id INTEGER NOT NULL,\n byte_offset INTEGER NOT NULL,\n byte_length INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n ts TEXT NOT NULL,\n seq INTEGER NOT NULL,\n session_id TEXT NOT NULL,\n prompt_id TEXT,\n tool_use_id TEXT,\n t_indexed TEXT NOT NULL DEFAULT (${SQL_NOW}),\n UNIQUE (transcript_id, byte_offset)\n );\n CREATE INDEX IF NOT EXISTS idx_fact_session_ts ON fact(session_id, ts);\n CREATE INDEX IF NOT EXISTS idx_fact_prompt ON fact(prompt_id);\n CREATE INDEX IF NOT EXISTS idx_fact_tool_use ON fact(tool_use_id);\n\n CREATE TABLE IF NOT EXISTS session (\n session_id TEXT PRIMARY KEY,\n transcript_id INTEGER,\n started_at TEXT,\n ended_at TEXT,\n start_type TEXT,\n cwd TEXT,\n git_branch TEXT,\n ai_title TEXT,\n seed_prompt TEXT,\n cli_version TEXT,\n turn_count INTEGER NOT NULL DEFAULT 0,\n commit_count INTEGER NOT NULL DEFAULT 0,\n push_count INTEGER NOT NULL DEFAULT 0\n );\n CREATE INDEX IF NOT EXISTS idx_session_started ON session(started_at);\n\n CREATE TABLE IF NOT EXISTS session_model_usage (\n session_id TEXT NOT NULL,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n cache_read_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n thinking_tokens INTEGER NOT NULL DEFAULT 0,\n request_count INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (session_id, model)\n );\n\n CREATE TABLE IF NOT EXISTS turn (\n prompt_id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n turn_index INTEGER NOT NULL,\n started_at TEXT NOT NULL,\n ended_at TEXT,\n duration_ms INTEGER,\n tool_call_count INTEGER NOT NULL DEFAULT 0,\n thinking_ms INTEGER NOT NULL DEFAULT 0,\n fact_id_start INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_turn_session ON turn(session_id, turn_index);\n\n CREATE TABLE IF NOT EXISTS permission_phase (\n phase_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n from_mode TEXT,\n to_mode TEXT NOT NULL,\n trigger TEXT NOT NULL,\n t_valid TEXT NOT NULL,\n t_invalid TEXT,\n fact_id INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_phase_session ON permission_phase(session_id, t_valid);\n\n CREATE TABLE IF NOT EXISTS human_edit (\n edit_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n file_path TEXT NOT NULL,\n ts TEXT NOT NULL,\n fact_id INTEGER,\n UNIQUE (session_id, file_path, ts)\n );\n\n CREATE TABLE IF NOT EXISTS file_checkpoint (\n checkpoint_id INTEGER PRIMARY KEY,\n session_id TEXT NOT NULL,\n file_path TEXT NOT NULL,\n backup_file_name TEXT NOT NULL,\n version INTEGER NOT NULL,\n backup_time TEXT NOT NULL,\n fact_id INTEGER,\n UNIQUE (session_id, file_path, backup_file_name)\n );\n\n CREATE TABLE IF NOT EXISTS pr (\n pr_ref TEXT PRIMARY KEY, number INTEGER, repo TEXT, title TEXT, state TEXT, url TEXT, merged_at TEXT\n );\n CREATE TABLE IF NOT EXISTS branch (\n branch_ref TEXT PRIMARY KEY, repo TEXT, name TEXT NOT NULL, base TEXT, created_at TEXT, deleted_at TEXT\n );\n CREATE TABLE IF NOT EXISTS file (\n file_ref TEXT PRIMARY KEY, repo TEXT, path TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS task (\n task_ref TEXT PRIMARY KEY, task_id TEXT NOT NULL, initiative TEXT, title TEXT, status TEXT\n );\n CREATE TABLE IF NOT EXISTS subagent (\n agent_ref TEXT PRIMARY KEY,\n session_id TEXT,\n child_session_id TEXT,\n parent_agent_ref TEXT,\n agent_type TEXT,\n label TEXT,\n started_at TEXT,\n ended_at TEXT,\n fact_id INTEGER\n );\n CREATE INDEX IF NOT EXISTS idx_subagent_child ON subagent(child_session_id);\n CREATE TABLE IF NOT EXISTS artifact (\n artifact_ref TEXT PRIMARY KEY, kind TEXT, title TEXT, url TEXT, path TEXT, created_at TEXT\n );\n CREATE TABLE IF NOT EXISTS pr_merge_observation (\n number INTEGER NOT NULL, repo_hint TEXT, merged_at TEXT NOT NULL,\n PRIMARY KEY (number, repo_hint, merged_at)\n );\n CREATE TABLE IF NOT EXISTS pr_create_observation (\n tool_use_id TEXT PRIMARY KEY, title TEXT, number INTEGER, repo TEXT, url TEXT\n );\n`;\n\n/** Every derived table, in an order safe to clear. The watermark table is not derived. */\nexport const DERIVED_TABLES = [\n \"normalized_span\",\n \"normalized_event\",\n \"normalized_source\",\n `${KIT.spanFts}_span`,\n KIT.edge,\n \"turn\",\n \"permission_phase\",\n \"human_edit\",\n \"file_checkpoint\",\n \"subagent\",\n \"session_model_usage\",\n \"session\",\n \"fact\",\n \"pr\",\n \"pr_merge_observation\",\n \"pr_create_observation\",\n \"branch\",\n \"file\",\n \"task\",\n \"artifact\",\n] as const;\n\nexport const MIGRATIONS: Migration[] = [\n kitMigration(1, { watermark: KIT.watermark, edge: KIT.edge, spanFts: KIT.spanFts }, \"kit tables\"),\n { version: 2, name: \"session graph tables\", up: (db) => db.exec(DOMAIN_DDL) },\n { version: 3, name: \"normalized conversations and source evidence\", up: (db) => { db.exec(NORMALIZED_DDL); backfillClaudeAliases(db); } },\n];\n","import { backfillClaudeAliases } from \"./normalized-schema.js\";\nimport { sessionRef, type TranscriptDelta } from \"@titan-design/session-read\";\nimport type { Db } from \"@titan-design/store-sqlite\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * Apply one transcript chunk's delta in a single transaction, so a crash can\n * never leave rows committed behind a stale watermark or ahead of their rows.\n * Append-only tables are idempotent through unique indexes; session and usage\n * rows accumulate; ref-keyed assets insert-if-absent with COALESCE merges that\n * mirror `EventFolder`'s rules.\n */\nexport function applyDelta(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta): void {\n graph.db.transaction(() => {\n applyFacts(graph.db, transcriptId, delta);\n applySessions(graph.db, transcriptId, delta);\n backfillClaudeAliases(graph.db, delta.sessions.map(s => s.sessionId));\n applyAssets(graph.db, delta);\n applyPhases(graph.db, transcriptId, delta);\n applyLinkedRows(graph, transcriptId, delta);\n })();\n}\n\nconst INSERT_FACT = `\n INSERT INTO fact (transcript_id, byte_offset, byte_length, event_type, ts, seq, session_id, prompt_id, tool_use_id)\n VALUES (@transcriptId, @byteOffset, @byteLength, @eventType, @ts, @seq, @sessionId, @promptId, @toolUseId)\n ON CONFLICT (transcript_id, byte_offset) DO NOTHING`;\n\n/** Facts carry a per-transcript `seq` continuing from whatever is stored. */\nfunction applyFacts(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const next = db.prepare(\"SELECT COALESCE(MAX(seq), -1) + 1 AS next FROM fact WHERE transcript_id = ?\").get(transcriptId) as { next: number };\n const insert = db.prepare(INSERT_FACT);\n let seq = next.next;\n for (const f of delta.facts) {\n insert.run({ transcriptId, byteOffset: f.byteOffset, byteLength: f.byteLength, eventType: f.eventType, ts: f.ts, seq: seq++, sessionId: f.sessionId, promptId: f.promptId, toolUseId: f.toolUseId });\n }\n}\n\nconst UPSERT_SESSION = `\n INSERT INTO session (session_id, transcript_id, started_at, ended_at, start_type, cwd, git_branch, ai_title, seed_prompt, cli_version, turn_count, commit_count, push_count)\n VALUES (@sessionId, @transcriptId, @startedAt, @endedAt, @startType, @cwd, @gitBranch, @aiTitle, @seedPrompt, @cliVersion, @turnDelta, @commitDelta, @pushDelta)\n ON CONFLICT (session_id) DO UPDATE SET\n started_at = MIN(COALESCE(started_at, excluded.started_at), COALESCE(excluded.started_at, started_at)),\n ended_at = MAX(COALESCE(ended_at, excluded.ended_at), COALESCE(excluded.ended_at, ended_at)),\n start_type = CASE\n WHEN excluded.start_type IS NULL THEN start_type\n WHEN start_type IS NULL THEN excluded.start_type\n WHEN COALESCE(excluded.started_at, started_at) < COALESCE(started_at, excluded.started_at) THEN excluded.start_type\n ELSE start_type END,\n cwd = COALESCE(excluded.cwd, cwd),\n git_branch = COALESCE(git_branch, excluded.git_branch),\n ai_title = COALESCE(excluded.ai_title, ai_title),\n seed_prompt = COALESCE(seed_prompt, excluded.seed_prompt),\n cli_version = COALESCE(excluded.cli_version, cli_version),\n turn_count = turn_count + excluded.turn_count,\n commit_count = commit_count + excluded.commit_count,\n push_count = push_count + excluded.push_count`;\n\nconst UPSERT_USAGE = `\n INSERT INTO session_model_usage (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, thinking_tokens, request_count)\n VALUES (@sessionId, @model, @inputTokens, @outputTokens, @cacheReadTokens, @cacheCreationTokens, @thinkingTokens, @requestCount)\n ON CONFLICT (session_id, model) DO UPDATE SET\n input_tokens = input_tokens + excluded.input_tokens, output_tokens = output_tokens + excluded.output_tokens,\n cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,\n cache_creation_tokens = cache_creation_tokens + excluded.cache_creation_tokens,\n thinking_tokens = thinking_tokens + excluded.thinking_tokens, request_count = request_count + excluded.request_count`;\n\nfunction applySessions(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const upsertSession = db.prepare(UPSERT_SESSION);\n for (const s of delta.sessions) upsertSession.run({ ...s, transcriptId });\n const upsertUsage = db.prepare(UPSERT_USAGE);\n for (const u of delta.usage) upsertUsage.run(u);\n}\n\nconst ASSET_UPSERTS = {\n prs: `INSERT INTO pr (pr_ref, number, repo, title, url) VALUES (@prRef, @number, @repo, @title, @url)\n ON CONFLICT (pr_ref) DO UPDATE SET title = COALESCE(title, excluded.title), url = COALESCE(url, excluded.url)`,\n branches: `INSERT INTO branch (branch_ref, repo, name, base, created_at, deleted_at) VALUES (@branchRef, @repo, @name, @base, @createdAt, @deletedAt)\n ON CONFLICT (branch_ref) DO UPDATE SET base = COALESCE(base, excluded.base),\n created_at = MIN(COALESCE(created_at, excluded.created_at), COALESCE(excluded.created_at, created_at)),\n deleted_at = MAX(COALESCE(deleted_at, excluded.deleted_at), COALESCE(excluded.deleted_at, deleted_at))`,\n files: `INSERT INTO file (file_ref, repo, path) VALUES (@fileRef, @repo, @path) ON CONFLICT (file_ref) DO NOTHING`,\n tasks: `INSERT INTO task (task_ref, task_id, status) VALUES (@taskRef, @taskId, @status)\n ON CONFLICT (task_ref) DO UPDATE SET status = COALESCE(excluded.status, status)`,\n artifacts: `INSERT INTO artifact (artifact_ref, kind, title, url, path, created_at) VALUES (@artifactRef, @artifactKind, @title, @url, @path, @ts)\n ON CONFLICT (artifact_ref) DO NOTHING`,\n prMerges: `INSERT INTO pr_merge_observation (number, repo_hint, merged_at) VALUES (@number, @repoHint, @ts)\n ON CONFLICT (number, repo_hint, merged_at) DO NOTHING`,\n prCreates: `INSERT INTO pr_create_observation (tool_use_id, title, number, repo, url) VALUES (@toolUseId, @title, @number, @repo, @url)\n ON CONFLICT (tool_use_id) DO UPDATE SET title = COALESCE(title, excluded.title), number = COALESCE(number, excluded.number),\n repo = COALESCE(repo, excluded.repo), url = COALESCE(url, excluded.url)`,\n} as const;\n\nfunction applyAssets(db: Db, delta: TranscriptDelta): void {\n for (const [kind, sql] of Object.entries(ASSET_UPSERTS) as [keyof typeof ASSET_UPSERTS, string][]) {\n const statement = db.prepare(sql);\n for (const row of delta[kind] as unknown as Record<string, unknown>[]) statement.run(pick(row, sql));\n }\n}\n\n/** better-sqlite3 rejects unused named parameters, so pass only the ones the statement binds. */\nfunction pick(row: Record<string, unknown>, sql: string): Record<string, unknown> {\n const names = new Set([...sql.matchAll(/@(\\w+)/g)].map((m) => m[1]!));\n return Object.fromEntries([...names].map((name) => [name, row[name] ?? null]));\n}\n\n/**\n * Bi-temporal phase collapse: a candidate matching the session's open phase is a\n * no-op; otherwise the open phase closes at the new `t_valid` and a replacement\n * row is inserted. Done here so a chunk boundary mid-run cannot add a phase.\n */\nfunction applyPhases(db: Db, transcriptId: number, delta: TranscriptDelta): void {\n const current = db.prepare(\"SELECT phase_id, to_mode FROM permission_phase WHERE session_id = ? AND trigger = ? AND t_invalid IS NULL ORDER BY phase_id DESC LIMIT 1\");\n const close = db.prepare(\"UPDATE permission_phase SET t_invalid = ? WHERE phase_id = ?\");\n const insert = db.prepare(\"INSERT INTO permission_phase (session_id, from_mode, to_mode, trigger, t_valid, fact_id) VALUES (?, ?, ?, ?, ?, ?)\");\n for (const phase of delta.phases) {\n const open = current.get(phase.sessionId, phase.trigger) as { phase_id: number; to_mode: string } | undefined;\n if (open?.to_mode === phase.toMode) continue;\n if (open) close.run(phase.ts, open.phase_id);\n insert.run(phase.sessionId, open?.to_mode ?? null, phase.toMode, phase.trigger, phase.ts, factIdFor(db, transcriptId, phase.byteOffset));\n }\n}\n\nfunction applyLinkedRows(graph: SessionGraph, transcriptId: number, delta: TranscriptDelta): void {\n const { db } = graph;\n const factId = (byteOffset: number) => factIdFor(db, transcriptId, byteOffset);\n\n const upsertTurn = db.prepare(`\n INSERT INTO turn (prompt_id, session_id, turn_index, started_at, fact_id_start)\n VALUES (?, ?, (SELECT COALESCE(MAX(turn_index), -1) + 1 FROM turn WHERE session_id = ?), ?, ?)\n ON CONFLICT (prompt_id) DO UPDATE SET session_id = MIN(session_id, excluded.session_id)`);\n for (const t of delta.turns) upsertTurn.run(t.promptId, t.sessionId, t.sessionId, t.ts, factId(t.byteOffset));\n\n const insertEdit = db.prepare(\"INSERT INTO human_edit (session_id, file_path, ts, fact_id) VALUES (?, ?, ?, ?) ON CONFLICT (session_id, file_path, ts) DO NOTHING\");\n for (const e of delta.humanEdits) insertEdit.run(e.sessionId, e.filePath, e.ts, factId(e.byteOffset));\n\n const insertCheckpoint = db.prepare(`\n INSERT INTO file_checkpoint (session_id, file_path, backup_file_name, version, backup_time, fact_id) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (session_id, file_path, backup_file_name) DO NOTHING`);\n for (const c of delta.fileCheckpoints) insertCheckpoint.run(c.sessionId, c.filePath, c.backupFileName, c.version, c.backupTime, factId(c.byteOffset));\n\n const upsertSubagent = db.prepare(`\n INSERT INTO subagent (agent_ref, session_id, agent_type, label, started_at, fact_id) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (agent_ref) DO UPDATE SET session_id = COALESCE(session_id, excluded.session_id),\n agent_type = COALESCE(agent_type, excluded.agent_type), label = COALESCE(label, excluded.label), started_at = COALESCE(started_at, excluded.started_at)`);\n for (const s of delta.subagents) upsertSubagent.run(s.agentRef, s.sessionId, s.agentType, s.label, s.ts || null, factId(s.byteOffset));\n\n // The dispatch row and this bridge come from different lines; an upsert survives a chunk boundary between them.\n const linkTranscript = db.prepare(\"INSERT INTO subagent (agent_ref, child_session_id) VALUES (?, ?) ON CONFLICT (agent_ref) DO UPDATE SET child_session_id = excluded.child_session_id\");\n for (const l of delta.subagentTranscripts) linkTranscript.run(l.agentRef, l.childSessionId);\n\n for (const e of delta.edges) graph.edges.assert({ sourceRef: e.sourceRef, relation: e.relation, targetRef: e.targetRef, tValid: e.ts, factId: factId(e.byteOffset) });\n\n for (const span of delta.spans) {\n graph.spans.index({ ownerRef: sessionRef(span.sessionId), field: span.field, sourceId: transcriptId, byteOffset: span.byteOffset, byteLength: span.byteLength }, span.text);\n }\n}\n\nfunction factIdFor(db: Db, transcriptId: number, byteOffset: number): number | null {\n const row = db.prepare(\"SELECT fact_id FROM fact WHERE transcript_id = ? AND byte_offset = ?\").get(transcriptId, byteOffset) as { fact_id: number } | undefined;\n return row?.fact_id ?? null;\n}\n","import { sessionRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { KIT } from \"./schema.js\";\n\n/** Derived tables keyed by `session_id`, reachable from a transcript through `session`. */\nconst SESSION_SCOPED = [\"turn\", \"permission_phase\", \"human_edit\", \"file_checkpoint\", \"subagent\", \"session_model_usage\"] as const;\n\n/**\n * Drop every derived row one transcript produced, so re-reading it from byte 0\n * rebuilds those rows instead of doubling them.\n *\n * `resetIndex` already states the reason at corpus scale: rewinding alone is not\n * enough, because the accumulating upserts add a second copy of every count. A\n * rewritten single transcript has the same problem one file at a time.\n * `session.turn_count`, the `session_model_usage` token buckets and the\n * commit/push counts all sum into what is already there, and rows the rewrite\n * removed are never deleted — `fact` survives on its unique\n * `(transcript_id, byte_offset)` index, so stale facts simply persist.\n *\n * Shared identity rows survive on purpose. `pr`, `branch`, `file`, `task` and\n * `artifact` are keyed by ref, many transcripts assert the same one, and the\n * inserts are insert-if-absent; a single transcript cannot know whether it was\n * the only source. The two observation tables are left for the same reason —\n * `reconcile` folds them, and re-reading re-asserts the same rows.\n */\nexport function purgeTranscript(graph: SessionGraph, transcriptId: number): void {\n graph.db.transaction(() => {\n const owned = graph.db.prepare(\"SELECT session_id FROM session WHERE transcript_id = ?\").all(transcriptId) as { session_id: string }[];\n for (const { session_id } of owned) graph.spans.purgeOwner(sessionRef(session_id));\n\n // Children first: each subquery reads the `session` rows deleted last.\n for (const table of SESSION_SCOPED) {\n graph.db.prepare(`DELETE FROM \"${table}\" WHERE session_id IN (SELECT session_id FROM session WHERE transcript_id = ?)`).run(transcriptId);\n }\n graph.db.prepare(`DELETE FROM \"${KIT.edge}\" WHERE fact_id IN (SELECT fact_id FROM fact WHERE transcript_id = ?)`).run(transcriptId);\n graph.db.prepare(\"DELETE FROM fact WHERE transcript_id = ?\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM session WHERE transcript_id = ?\").run(transcriptId);\n })();\n}\n","import type { SessionGraph } from \"./graph.js\";\n\nconst BATCH = 400;\n\n/**\n * Turn aggregates that no single line can supply. Contract: recompute, never\n * accumulate. Every value is a pure function of the fact rows, so incremental\n * refreshes and full rebuilds converge regardless of chunk boundaries.\n */\nconst ROLLUP = `\n WITH ordered AS (\n SELECT fact_id, session_id, event_type, ts, prompt_id,\n COUNT(CASE WHEN event_type = 'user_prompt' THEN 1 END) OVER (\n PARTITION BY session_id ORDER BY ts, transcript_id, seq ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS turn_no,\n LAG(event_type) OVER (PARTITION BY session_id ORDER BY ts, transcript_id, seq) AS prev_type,\n LAG(ts) OVER (PARTITION BY session_id ORDER BY ts, transcript_id, seq) AS prev_ts\n FROM fact WHERE session_id IN (SELECT value FROM json_each(@sessionIds))\n ),\n owners AS (SELECT session_id, turn_no, prompt_id FROM ordered WHERE event_type = 'user_prompt'),\n agg AS (\n SELECT owners.prompt_id AS prompt_id, MAX(ordered.ts) AS ended_at,\n CAST(ROUND((julianday(MAX(ordered.ts)) - julianday(MIN(ordered.ts))) * 86400000) AS INTEGER) AS duration_ms,\n SUM(CASE WHEN ordered.event_type = 'tool_decision' THEN 1 ELSE 0 END) AS tool_call_count,\n CAST(COALESCE(SUM(CASE\n WHEN ordered.event_type IN ('assistant_response', 'tool_decision') AND ordered.prev_type IN ('user_prompt', 'tool_result', 'tool_result_error')\n THEN ROUND((julianday(ordered.ts) - julianday(ordered.prev_ts)) * 86400000) END), 0) AS INTEGER) AS thinking_ms\n FROM ordered JOIN owners ON owners.session_id = ordered.session_id AND owners.turn_no = ordered.turn_no\n GROUP BY owners.prompt_id\n )\n UPDATE turn SET ended_at = agg.ended_at, duration_ms = COALESCE(agg.duration_ms, 0),\n tool_call_count = agg.tool_call_count, thinking_ms = COALESCE(agg.thinking_ms, 0)\n FROM agg WHERE turn.prompt_id = agg.prompt_id`;\n\n/** `turn_index` is insert-order at write time; recomputing it from `started_at` makes it a property of the corpus. */\nconst RENUMBER_TURNS = `\n WITH ordered AS (\n SELECT prompt_id, ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY started_at, prompt_id) - 1 AS idx\n FROM turn WHERE session_id IN (SELECT value FROM json_each(@sessionIds))\n )\n UPDATE turn SET turn_index = ordered.idx FROM ordered WHERE turn.prompt_id = ordered.prompt_id`;\n\n/** Recompute turn aggregates for the given sessions in one transaction. */\nexport function rollupSessions(graph: SessionGraph, sessionIds: readonly string[]): number {\n const unique = [...new Set(sessionIds)];\n if (unique.length === 0) return 0;\n const aggregate = graph.db.prepare(ROLLUP);\n const renumber = graph.db.prepare(RENUMBER_TURNS);\n return graph.db.transaction(() => {\n let updated = 0;\n for (let i = 0; i < unique.length; i += BATCH) {\n const batch = JSON.stringify(unique.slice(i, i + BATCH));\n renumber.run({ sessionIds: batch });\n updated += aggregate.run({ sessionIds: batch }).changes;\n }\n return updated;\n })();\n}\n\n/** A merge sighting and the `pr-link` naming the same PR routinely live in different transcripts; fold at pass end. */\nconst RECONCILE_PR_MERGES = `\n UPDATE pr SET state = 'merged',\n merged_at = (SELECT MIN(o.merged_at) FROM pr_merge_observation o WHERE o.number = pr.number AND (o.repo_hint IS NULL OR pr.repo LIKE '%/' || o.repo_hint))\n WHERE EXISTS (SELECT 1 FROM pr_merge_observation o WHERE o.number = pr.number AND (o.repo_hint IS NULL OR pr.repo LIKE '%/' || o.repo_hint))`;\n\n/** Both halves of a `gh pr create` make a real PR whether or not a `pr-link` ever mentioned it. */\nconst RECONCILE_PR_CREATES = `\n INSERT INTO pr (pr_ref, number, repo, title, url)\n SELECT 'pr:' || repo || '#' || number, number, repo, title, url FROM pr_create_observation\n WHERE number IS NOT NULL AND repo IS NOT NULL AND title IS NOT NULL\n ON CONFLICT (pr_ref) DO UPDATE SET title = COALESCE(pr.title, excluded.title), url = COALESCE(pr.url, excluded.url)`;\n\n/**\n * A subagent ends when its child session's last line does, not when the\n * dispatch's tool_result returns (most dispatches are async and return at\n * launch). Parentage comes from the dispatch that ran inside a child transcript.\n */\nconst RECONCILE_SUBAGENTS = `\n UPDATE subagent SET\n ended_at = COALESCE((SELECT s.ended_at FROM session s WHERE s.session_id = subagent.child_session_id), ended_at),\n parent_agent_ref = COALESCE((SELECT p.agent_ref FROM subagent p WHERE p.child_session_id = subagent.session_id), parent_agent_ref)`;\n\nexport interface ReconcileCounts {\n prMerges: number;\n prCreates: number;\n subagents: number;\n}\n\n/** Whole-table reconciliations, run once at the end of a refresh pass after the rollup. */\nexport function reconcile(graph: SessionGraph): ReconcileCounts {\n return {\n prCreates: graph.db.prepare(RECONCILE_PR_CREATES).run().changes,\n prMerges: graph.db.prepare(RECONCILE_PR_MERGES).run().changes,\n subagents: graph.db.prepare(RECONCILE_SUBAGENTS).run().changes,\n };\n}\n","import { promises as fs } from \"node:fs\";\nimport { contentHash, resumePoint } from \"@titan-design/locator\";\nimport { TranscriptParseError, extractTranscript, type DiscoveredTranscript } from \"@titan-design/session-read\";\nimport { applyDelta } from \"./apply.js\";\nimport { allSessionIds, type SessionGraph } from \"./graph.js\";\nimport { purgeTranscript } from \"./purge.js\";\nimport { reconcile, rollupSessions, type ReconcileCounts } from \"./rollup.js\";\nimport { allTaskIds, enrichTasks, NO_ENRICHMENT, type TaskEnrichment, type TaskResolver } from \"./tasks.js\";\n\nexport interface IndexOptions {\n /** Re-hash the already-read prefix to catch same-length rewrites. O(file), so opt in. */\n verifyHash?: boolean;\n /** Store a whole-file content hash for durability reporting. O(file), so opt in. */\n withContentHash?: boolean;\n /** Fill task title, initiative and present status from the caller's store. Absent means transcripts alone. */\n resolveTasks?: TaskResolver;\n}\n\nexport type TranscriptOutcome =\n | { status: \"unchanged\" | \"indexed\" | \"rewound\"; transcriptId: number; sessionIds: string[]; facts: number; tasks: TaskEnrichment }\n | { status: \"missing\" | \"quarantined\"; transcriptId: number; sessionIds: string[]; facts: 0; tasks: TaskEnrichment; reason: string };\n\n/**\n * Bring one transcript up to date: read from its watermark (or byte 0 after a\n * rewrite), apply the delta, advance the watermark. A malformed line\n * quarantines the transcript instead of poisoning the pass.\n */\nexport async function indexTranscript(graph: SessionGraph, transcript: DiscoveredTranscript, options: IndexOptions = {}): Promise<TranscriptOutcome> {\n const row = graph.transcripts.ensure(transcript.displayPath);\n const entry = { path: transcript.displayPath, lastByteOffset: row.lastOffset, prefixHash: row.prefixHash, mtime: row.fileMtime };\n const point = await resumePoint(entry, transcript.absolutePath, { verifyHash: options.verifyHash });\n const base = { transcriptId: row.sourceId, sessionIds: [] as string[], tasks: NO_ENRICHMENT };\n if (point.state === \"missing\") {\n graph.transcripts.markStatus(transcript.displayPath, \"missing\", \"source file no longer exists\");\n return { ...base, status: \"missing\", facts: 0, reason: \"source file no longer exists\" };\n }\n if (point.state === \"unchanged\") {\n // A file that vanished and came back unchanged resumes at EOF, so there are no\n // bytes to read and `advance()` — the only writer of status 'ok' — never runs,\n // stranding the row on 'missing' forever. Clear it here rather than forcing a\n // re-read, which would rewrite the prefix hash from a zero-byte delta.\n // Quarantine is deliberately not cleared: the same bytes still will not parse.\n if (row.status === \"missing\") graph.transcripts.markStatus(transcript.displayPath, \"ok\", null);\n return { ...base, status: \"unchanged\", facts: 0 };\n }\n\n try {\n const delta = await extractTranscript(transcript.absolutePath, {\n fromByteOffset: point.start,\n priorPrefixHash: point.state === \"rewritten\" ? null : row.prefixHash,\n subagentId: transcript.subagentId,\n });\n // Two ways to learn a transcript was rewritten, and both must purge. `resumePoint`\n // catches a file that shrank; `extractTranscript` re-hashes the prefix it was asked\n // to skip and restarts from zero on its own when the bytes moved — a rotation that\n // left the file the same length or longer. Ignoring its answer replays the whole\n // file onto rows that were never removed, which is the accumulation purge exists\n // to prevent, reached by a different road.\n const rewound = point.state === \"rewritten\" || delta.restartedFromZero;\n // After extraction, never before: a parse failure quarantines the transcript,\n // and purging first would destroy rows we could then no longer rebuild.\n if (rewound) purgeTranscript(graph, row.sourceId);\n applyDelta(graph, row.sourceId, delta);\n const stat = await fs.stat(transcript.absolutePath);\n graph.transcripts.advance(transcript.displayPath, {\n lastOffset: delta.lastByteOffset,\n prefixHash: delta.prefixHash,\n fileSize: stat.size,\n fileMtime: stat.mtime.toISOString(),\n contentHash: options.withContentHash ? await contentHash(transcript.absolutePath) : null,\n });\n const sessionIds = delta.sessions.map((s) => s.sessionId);\n const taskIds = delta.tasks.map((t) => t.taskId);\n const tasks = taskIds.length > 0 ? await enrichTasks(graph, options.resolveTasks, taskIds) : NO_ENRICHMENT;\n return { ...base, sessionIds, tasks, status: rewound ? \"rewound\" : \"indexed\", facts: delta.facts.length };\n } catch (err) {\n if (!(err instanceof TranscriptParseError)) throw err;\n graph.transcripts.markStatus(transcript.displayPath, \"quarantined\", err.message);\n return { ...base, status: \"quarantined\", facts: 0, reason: err.message };\n }\n}\n\nexport interface RefreshSummary {\n transcripts: number;\n indexed: number;\n unchanged: number;\n rewound: number;\n missing: number;\n quarantined: number;\n facts: number;\n turnsRolledUp: number;\n reconciled: ReconcileCounts;\n tasks: TaskEnrichment;\n markedMissing: number;\n}\n\n/**\n * One pass over a corpus: index every transcript, roll up the sessions that\n * changed, reconcile cross-transcript observations, enrich tasks, and mark rows\n * whose source file is gone. Idempotent: a second pass over unchanged files\n * changes nothing.\n *\n * The resolver is hoisted out of the per-transcript loop and run once over the\n * whole task table, so a corpus of N transcripts costs one resolver call rather\n * than N, and a task whose store row changed refreshes even when no transcript\n * did. That bounds staleness to one pass.\n */\nexport async function refreshCorpus(graph: SessionGraph, transcripts: readonly DiscoveredTranscript[], options: IndexOptions & { full?: boolean } = {}): Promise<RefreshSummary> {\n const { resolveTasks, ...perTranscript } = options;\n const counts = { indexed: 0, unchanged: 0, rewound: 0, missing: 0, quarantined: 0 };\n const touched: string[] = [];\n let facts = 0;\n for (const transcript of transcripts) {\n const outcome = await indexTranscript(graph, transcript, perTranscript);\n counts[outcome.status] += 1;\n facts += outcome.facts;\n touched.push(...outcome.sessionIds);\n }\n const turnsRolledUp = rollupSessions(graph, options.full ? allSessionIds(graph) : touched);\n const reconciled = reconcile(graph);\n const tasks = await enrichTasks(graph, resolveTasks, allTaskIds(graph));\n const markedMissing = await markMissing(graph, transcripts);\n return { transcripts: transcripts.length, ...counts, facts, turnsRolledUp, reconciled, tasks, markedMissing };\n}\n\n/** Rows absent from discovery are only nominated; an `fs.stat` decides, so an empty scan cannot condemn the corpus. */\nasync function markMissing(graph: SessionGraph, discovered: readonly DiscoveredTranscript[]): Promise<number> {\n const present = new Set(discovered.map((t) => t.displayPath));\n const byPath = new Map(discovered.map((t) => [t.displayPath, t.absolutePath]));\n let marked = 0;\n for (const row of graph.transcripts.list()) {\n if (row.status === \"missing\" || present.has(row.sourceKey)) continue;\n const normalized = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(row.sourceId) as { descriptor: string } | undefined;\n const absolute = normalized ? (JSON.parse(normalized.descriptor) as { path: string }).path : byPath.get(row.sourceKey) ?? row.sourceKey;\n if (await exists(absolute)) continue;\n graph.transcripts.markStatus(row.sourceKey, \"missing\", \"source file no longer exists\");\n marked += 1;\n }\n return marked;\n}\n\nasync function exists(absolutePath: string): Promise<boolean> {\n try {\n await fs.stat(absolutePath);\n return true;\n } catch {\n return false;\n }\n}\n","import { taskRef } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/**\n * What a product's task store knows and a transcript cannot state: the task's\n * present title, its initiative, and the status it holds right now rather than\n * the one some command was observed to set. Every field is optional; an omitted\n * or null field leaves whatever the transcripts derived in place.\n */\nexport interface ResolvedTask {\n initiative?: string | null;\n title?: string | null;\n status?: string | null;\n}\n\n/** Resolutions keyed by bare task id (`AW-23`, not `task:AW-23`). */\nexport type TaskResolution = ReadonlyMap<string, ResolvedTask | null | undefined>;\n\n/**\n * Supplied by the caller, never by this package: session-graph is tier 2 and\n * must not learn where any product keeps its tasks. Called once per pass with\n * every task id at once, because a real resolver reads a store. Returning an id\n * that no transcript mentioned inserts that task; that is how a task nobody\n * worked on gets into the graph.\n */\nexport type TaskResolver = (taskIds: readonly string[]) => PromiseLike<TaskResolution> | TaskResolution;\n\nexport interface TaskEnrichment {\n /** Task ids handed to the resolver. */\n requested: number;\n /** Task rows the resolver wrote. */\n applied: number;\n /** The resolver threw; rows stand as the transcripts left them. */\n failed: boolean;\n /** Why it failed, for the caller to log. Absent unless `failed`. */\n error?: string;\n}\n\nexport const NO_ENRICHMENT: TaskEnrichment = Object.freeze({ requested: 0, applied: 0, failed: false });\n\n/**\n * COALESCE gives the resolver precedence over the transcripts on every field it\n * states, and none on the fields it omits: the store is the system of record for\n * a task's present status, while a transcript only witnesses a command that ran.\n */\nconst UPSERT_TASK = `\n INSERT INTO task (task_ref, task_id, initiative, title, status) VALUES (@taskRef, @taskId, @initiative, @title, @status)\n ON CONFLICT (task_ref) DO UPDATE SET\n initiative = COALESCE(excluded.initiative, initiative),\n title = COALESCE(excluded.title, title),\n status = COALESCE(excluded.status, status)`;\n\nexport function allTaskIds(graph: SessionGraph): string[] {\n return (graph.db.prepare(\"SELECT task_id FROM task\").all() as { task_id: string }[]).map((r) => r.task_id);\n}\n\n/**\n * Enrich task rows from a caller-supplied resolver. Runs outside the delta's\n * transaction because a resolver is async; a crash between the two leaves rows\n * un-enriched, which the next pass corrects. A resolver that throws costs this\n * pass its enrichment and nothing else.\n */\nexport async function enrichTasks(graph: SessionGraph, resolver: TaskResolver | undefined, taskIds: readonly string[]): Promise<TaskEnrichment> {\n const unique = [...new Set(taskIds)];\n if (!resolver) return NO_ENRICHMENT;\n try {\n const resolved = await resolver(unique);\n return { requested: unique.length, applied: write(graph, resolved), failed: false };\n } catch (err) {\n return { requested: unique.length, applied: 0, failed: true, error: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction write(graph: SessionGraph, resolved: TaskResolution): number {\n const upsert = graph.db.prepare(UPSERT_TASK);\n return graph.db.transaction(() => {\n let applied = 0;\n for (const [taskId, fields] of resolved) {\n if (!fields) continue;\n applied += upsert.run({\n taskRef: taskRef(taskId),\n taskId,\n initiative: fields.initiative ?? null,\n title: fields.title ?? null,\n status: fields.status ?? null,\n }).changes;\n }\n return applied;\n })();\n}\n","import { randomUUID } from \"node:crypto\";\nimport { stat } from \"node:fs/promises\";\nimport { contentHash } from \"@titan-design/locator\";\nimport { conversationRef } from \"@titan-design/agent-protocol\";\nimport { SPAN_TEXT_CAP, readCodexObservations, type CodexReadResult, type NormalizedSessionObservation, type SessionSourceDescriptor, type SourceTextLocator } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\nimport { insertObservation, observationText } from \"./normalized-project.js\";\n\nexport interface NormalizedIndexResult { status: \"indexed\" | \"unchanged\" | \"missing\" | \"quarantined\"; conversationRef: string; observations: number; reason?: string }\n\n/** Replay changed sources into temporary staging; swap rows and watermark atomically. */\nexport async function indexCodexSource(graph: SessionGraph, source: SessionSourceDescriptor): Promise<NormalizedIndexResult> {\n const ref = conversationRef(source.conversation);\n const base = { conversationRef: ref, observations: 0 };\n const row = graph.transcripts.ensure(source.sourceId);\n let info;\n try { info = await stat(source.path); } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n graph.transcripts.markStatus(source.sourceId, \"missing\", \"source unavailable\");\n return { ...base, status: \"missing\" };\n }\n // Always replay: the decoder verifies bytes, detects projections that arrived later,\n // and avoids trusting mtime alone. Equality of the resulting prefix is the fast no-op.\n const stage = `stage_${randomUUID().replaceAll(\"-\", \"\")}`;\n graph.db.exec(`CREATE TEMP TABLE ${stage}(seq INTEGER PRIMARY KEY, observation TEXT NOT NULL)`);\n let result: CodexReadResult | undefined;\n try {\n const fingerprint = await contentHash(source.path);\n const insert = graph.db.prepare(`INSERT INTO ${stage}(observation) VALUES (?)`);\n for await (const observation of readCodexObservations(source, {}, done => { result = done; })) insert.run(JSON.stringify(observation));\n if (!result) throw new Error(\"decoder completed without a resume boundary\");\n const boundary = (result as CodexReadResult).resumeBoundary;\n const prior = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(row.sourceId) as { descriptor: string } | undefined;\n if (prior && row.contentHash === fingerprint && row.prefixHash === boundary.prefixHash && row.lastOffset === boundary.byteOffset) {\n graph.db.prepare(\"UPDATE normalized_source SET descriptor = ? WHERE transcript_id = ?\").run(JSON.stringify(source), row.sourceId);\n graph.transcripts.markStatus(source.sourceId, \"ok\", null);\n return { ...base, status: \"unchanged\" };\n }\n const count = graph.db.transaction(() => {\n purgeNormalizedSource(graph, row.sourceId);\n graph.db.prepare(\"INSERT OR IGNORE INTO conversation(ref,harness,namespace,native_id) VALUES (?,?,?,?)\").run(ref, source.harness, source.namespace, source.conversation.nativeId);\n graph.db.prepare(\"INSERT OR REPLACE INTO normalized_source VALUES (?,?,?,?)\").run(row.sourceId, source.sourceId, ref, JSON.stringify(source));\n const n = applyStaged(graph, stage, row.sourceId, ref);\n graph.transcripts.advance(source.sourceId, { lastOffset: boundary.byteOffset, prefixHash: boundary.prefixHash, fileSize: info.size, fileMtime: info.mtime.toISOString(), contentHash: fingerprint });\n return n;\n })();\n return { ...base, status: \"indexed\", observations: count };\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n graph.transcripts.markStatus(source.sourceId, \"quarantined\", reason);\n return { ...base, status: \"quarantined\", reason };\n } finally { graph.db.exec(`DROP TABLE ${stage}`); }\n}\n\nfunction applyStaged(graph: SessionGraph, stage: string, transcriptId: number, ref: string): number {\n const spans = `${stage}_spans`;\n graph.db.exec(`CREATE TEMP TABLE ${spans}(field TEXT, offset INTEGER, length INTEGER, text TEXT, locators TEXT, PRIMARY KEY(field,offset))`);\n let count = 0;\n try {\n for (const row of stagedRows<{ observation: string }>(graph, stage)) {\n const o = JSON.parse(row.observation) as NormalizedSessionObservation;\n insertObservation(graph, transcriptId, o);\n for (const p of observationText(o)) stageText(graph, spans, p.field, p.text, p.locator);\n count++;\n }\n for (const s of stagedRows<{ field: string; offset: number; length: number; text: string; locators: string }>(graph, spans)) {\n const id = graph.spans.index({ ownerRef: ref, sourceId: transcriptId, field: s.field, byteOffset: s.offset, byteLength: s.length }, s.text);\n graph.db.prepare(\"INSERT INTO normalized_span VALUES (?,?)\").run(id, s.locators);\n }\n } finally { graph.db.exec(`DROP TABLE ${spans}`); }\n return count;\n}\n\nfunction stageText(graph: SessionGraph, table: string, field: string, text: string, locator: SourceTextLocator): void {\n const line = locator.evidence.line;\n const prior = graph.db.prepare(`SELECT text,locators FROM ${table} WHERE field = ? AND offset = ?`).get(field, line.byteOffset) as { text: string; locators: string } | undefined;\n const locators = prior ? JSON.parse(prior.locators) as unknown[] : [];\n locators.push(locator);\n graph.db.prepare(`INSERT OR REPLACE INTO ${table} VALUES (?,?,?,?,?)`).run(field, line.byteOffset, line.byteLength, (prior ? `${prior.text}\\n${text}` : text).slice(0, SPAN_TEXT_CAP), JSON.stringify(locators));\n}\n\nfunction purgeNormalizedSource(graph: SessionGraph, transcriptId: number): void {\n graph.db.prepare(\"DELETE FROM normalized_span WHERE span_id IN (SELECT span_id FROM search_span WHERE source_id = ?)\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM search_span WHERE source_id = ?\").run(transcriptId);\n graph.db.prepare(\"DELETE FROM normalized_event WHERE transcript_id = ?\").run(transcriptId);\n}\n\n\n/** Finish each SELECT before writing; SQLite forbids writes during an active iterator. */\nfunction* stagedRows<T>(graph: SessionGraph, table: string): Generator<T> {\n const select = graph.db.prepare(`SELECT rowid AS cursor, * FROM ${table} WHERE rowid > ? ORDER BY rowid LIMIT 128`);\n let cursor = 0;\n for (;;) {\n const rows = select.all(cursor) as (T & { cursor: number })[];\n if (!rows.length) return;\n for (const row of rows) { cursor = row.cursor; yield row; }\n }\n}\n","import { conversationRef } from \"@titan-design/agent-protocol\";\nimport { normalizedSearchText, SPAN_TEXT_CAP, scopedConversationItemRef, type NormalizedSessionObservation as Observation, type SourceTextLocator } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\n/** Persist structural evidence only; prose remains in the source and transient FTS input. */\nexport function insertObservation(graph: SessionGraph, transcriptId: number, o: Observation): void {\n graph.db.prepare(`INSERT INTO normalized_event VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(\n transcriptId, o.evidence.line.byteOffset, o.evidence.subrecord.index, o.evidence.line.byteLength,\n conversationRef(o.conversation), o.kind, o.timestamp,\n \"turn\" in o && o.turn ? scopedConversationItemRef(o.turn) : null,\n \"call\" in o ? scopedConversationItemRef(o.call) : null,\n \"item\" in o && o.item ? scopedConversationItemRef(o.item) : null,\n o.kind === \"native_turn\" ? o.phase : null,\n o.kind === \"tool_result\" && o.isError !== null ? Number(o.isError) : null,\n o.kind === \"usage\" ? JSON.stringify(o.measurement) : null,\n o.kind === \"metadata\" ? JSON.stringify(o.entries.filter(e => [\"cwd\", \"model\", \"cli_version\", \"git_branch\", \"title\"].includes(e.name))) : null,\n o.historyOrigin ? conversationRef(o.historyOrigin) : null,\n o.kind === \"lineage\" ? conversationRef(o.relatedConversation) : null,\n o.kind === \"lineage\" ? o.relationship : null,\n );\n}\n\nexport interface ObservationText { field: string; text: string; locator: SourceTextLocator }\nexport function observationText(o: Observation): ObservationText[] {\n if (o.kind === \"message\") return o.content.map(p => ({ field: o.role === \"user\" ? \"prompt\" : \"assistant_response\", text: p.text.slice(0, SPAN_TEXT_CAP), locator: p.locator }));\n if (o.kind === \"tool_call\" && o.inputLocator) return [{ field: \"tool_input\", text: normalizedSearchText(o.input), locator: o.inputLocator }];\n if (o.kind === \"tool_result\" && o.outputLocator) return [{ field: \"tool_result\", text: normalizedSearchText(o.output), locator: o.outputLocator }];\n return [];\n}\n","import { contentHash, prefixHash } from \"@titan-design/locator\";\nimport type { UsageMeasurement } from \"@titan-design/agent-protocol\";\nimport { readSessionSourceText, readSessionText, SessionUsageAccumulator, type SessionUsageSummary, type SourceTextLocator, type SessionSourceDescriptor, type SpanField } from \"@titan-design/session-read\";\nimport type { SessionGraph } from \"./graph.js\";\n\nexport interface IndexedSpan { sourceId: number; byteOffset: number; byteLength: number; field: string; spanId?: number }\n/** One resolver for search and error clustering; never display a raw JSON line. */\nexport async function readIndexedText(graph: SessionGraph, span: IndexedSpan): Promise<string | null> {\n const row = graph.db.prepare(`SELECT n.locators FROM normalized_span n JOIN search_span s USING(span_id)\n WHERE s.source_id = ? AND s.byte_offset = ? AND s.field = ?`).get(span.sourceId, span.byteOffset, span.field) as { locators: string } | undefined;\n if (row) {\n const source = graph.db.prepare(\"SELECT descriptor FROM normalized_source WHERE transcript_id = ?\").get(span.sourceId) as { descriptor: string } | undefined;\n if (!source) return null;\n const descriptor = JSON.parse(source.descriptor) as SessionSourceDescriptor;\n const watermark = graph.transcripts.list().find(t => t.sourceId === span.sourceId);\n try { if (!watermark?.prefixHash || await prefixHash(descriptor.path, watermark.lastOffset) !== watermark.prefixHash) return null; }\n catch { return null; }\n if (span.byteOffset + span.byteLength + 1 > watermark.lastOffset) {\n try { if (!watermark.contentHash || await contentHash(descriptor.path) !== watermark.contentHash) return null; } catch { return null; }\n }\n const texts = await Promise.all((JSON.parse(row.locators) as SourceTextLocator[]).map(locator => readSessionSourceText(locator, { sources: [JSON.parse(source.descriptor) as SessionSourceDescriptor] })));\n return texts.some(text => text === null) ? null : texts.join(\"\\n\");\n }\n const normalized = graph.db.prepare(\"SELECT 1 FROM normalized_source WHERE transcript_id = ?\").get(span.sourceId);\n if (normalized) return null;\n const legacy = graph.transcripts.list().find(t => t.sourceId === span.sourceId);\n return legacy ? readSessionText({ path: legacy.sourceKey, byteOffset: span.byteOffset, byteLength: span.byteLength, field: span.field as SpanField }) : null;\n}\n\nexport interface ConversationSummary {\n sessionId: string; harness: string; nativeId: string; namespace: string;\n title: string | null; startedAt: string | null; endedAt: string | null;\n cwd: string | null; gitBranch: string | null; turnCount: number; commitCount: number | null; pushCount: number | null;\n}\nexport function normalizedSessions(graph: SessionGraph, options: { ref?: string; limit?: number; since?: string } = {}): ConversationSummary[] {\n const rows = graph.db.prepare(`SELECT c.ref,c.harness,c.native_id,c.namespace,MIN(e.ts) AS started,\n COUNT(DISTINCT CASE WHEN e.kind = 'native_turn' THEN e.turn_ref END) AS turns\n FROM conversation c JOIN (SELECT DISTINCT conversation_ref FROM normalized_source) s ON s.conversation_ref = c.ref\n LEFT JOIN normalized_event e ON e.conversation_ref = c.ref AND e.history_origin IS NULL\n WHERE c.legacy_session_id IS NULL AND (? IS NULL OR c.ref = ?)\n GROUP BY c.ref HAVING (? IS NULL OR MIN(e.ts) >= ?)\n ORDER BY started DESC LIMIT ?`).all(options.ref ?? null,options.ref ?? null,options.since ?? null,options.since ?? null,options.limit ?? -1) as\n { ref: string; harness: string; native_id: string; namespace: string; started: string | null; turns: number }[];\n const meta = graph.db.prepare(`SELECT json_extract(j.value,'$.value') AS value\n FROM normalized_event e,json_each(e.metadata) j WHERE e.conversation_ref = ?\n AND e.history_origin IS NULL AND json_extract(j.value,'$.name') = ? ORDER BY e.ts DESC,e.byte_offset DESC,e.subrecord_index DESC LIMIT 1`);\n return rows.map(c => {\n const text = (key: string) => { const value = (meta.get(c.ref,key) as { value: unknown } | undefined)?.value; return typeof value === \"string\" ? value : null; };\n return { sessionId:c.ref,harness:c.harness,nativeId:c.native_id,namespace:c.namespace,title:text(\"title\"),startedAt:c.started,\n endedAt:null,cwd:text(\"cwd\"),gitBranch:text(\"git_branch\"),turnCount:c.turns,commitCount:null,pushCount:null };\n });\n}\n\nexport type NormalizedUsageSummary = SessionUsageSummary;\n/** Shared storage-free usage policy keeps graph and direct readers consistent. */\nexport function normalizedUsage(graph: SessionGraph, ref: string): NormalizedUsageSummary[] {\n // Snapshot epochs are local to a physical source; never add copies from different files.\n // Pick the source with the latest native timestamp (then fullest coverage, then stable ID).\n const preferred = graph.db.prepare(`SELECT transcript_id FROM normalized_event\n WHERE conversation_ref = ? AND usage IS NOT NULL AND history_origin IS NULL\n GROUP BY transcript_id ORDER BY MAX(ts) DESC, COUNT(*) DESC, transcript_id ASC LIMIT 1`).get(ref) as { transcript_id: number } | undefined;\n const rows = graph.db.prepare(`SELECT usage FROM normalized_event WHERE conversation_ref = ?\n AND usage IS NOT NULL AND history_origin IS NULL\n AND (json_extract(usage,'$.kind') = 'delta' OR transcript_id = ?)\n ORDER BY byte_offset,subrecord_index`);\n const accumulator = new SessionUsageAccumulator();\n for (const row of rows.iterate(ref, preferred?.transcript_id ?? -1) as Iterable<{ usage: string }>) {\n accumulator.add(JSON.parse(row.usage) as UsageMeasurement);\n }\n return accumulator.summaries();\n}\n"],"mappings":"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","names":["sessionRef","sessionRef","stat","contentHash","conversationRef","SPAN_TEXT_CAP","conversationRef","conversationRef","contentHash","SPAN_TEXT_CAP","contentHash"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@titan-design/session-graph",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Fold session events into the activity graph on store-sqlite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "@titan-design/cluster": "^0.1.2",
35
35
  "@titan-design/locator": "^0.2.1",
36
36
  "@titan-design/session-read": "^0.4.0",
37
- "@titan-design/store-sqlite": "^0.2.1",
37
+ "@titan-design/store-sqlite": "^0.3.0",
38
38
  "@titan-design/agent-protocol": "^0.1.0"
39
39
  },
40
40
  "scripts": {