@jigyasudham/veto 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +630 -627
  3. package/dist/agents/llm-runner.js +57 -57
  4. package/dist/cli/statusline.js +2 -2
  5. package/dist/cli.js +26 -26
  6. package/dist/memory/decisions.js +1 -1
  7. package/dist/memory/local.js +144 -144
  8. package/dist/memory/schema.js +228 -228
  9. package/dist/memory/sync.js +27 -27
  10. package/dist/memory/sync.js.map +1 -1
  11. package/dist/router/implicit-outcomes.js +4 -4
  12. package/dist/router/learning-updater.js +45 -45
  13. package/dist/server.js +1 -1
  14. package/dist/tools/compact.js +3 -3
  15. package/dist/transcripts/archive.js +5 -5
  16. package/dist/transcripts/ingest.js +6 -6
  17. package/dist/transcripts/mapping.js +5 -5
  18. package/dist/transcripts/pyramid.js +2 -2
  19. package/dist/transcripts/schema.js +104 -104
  20. package/dist/transcripts/search.js +3 -3
  21. package/dist/transcripts/vectors.js +2 -2
  22. package/package.json +70 -62
  23. package/server.json +23 -23
  24. package/dist/skills/development/skill-api-design.d.ts +0 -15
  25. package/dist/skills/development/skill-api-design.d.ts.map +0 -1
  26. package/dist/skills/development/skill-api-design.js +0 -313
  27. package/dist/skills/development/skill-api-design.js.map +0 -1
  28. package/dist/skills/development/skill-auth.d.ts +0 -15
  29. package/dist/skills/development/skill-auth.d.ts.map +0 -1
  30. package/dist/skills/development/skill-auth.js +0 -255
  31. package/dist/skills/development/skill-auth.js.map +0 -1
  32. package/dist/skills/development/skill-ci-cd.d.ts +0 -2
  33. package/dist/skills/development/skill-ci-cd.d.ts.map +0 -1
  34. package/dist/skills/development/skill-ci-cd.js +0 -2
  35. package/dist/skills/development/skill-ci-cd.js.map +0 -1
  36. package/dist/skills/development/skill-crud.d.ts +0 -15
  37. package/dist/skills/development/skill-crud.d.ts.map +0 -1
  38. package/dist/skills/development/skill-crud.js +0 -193
  39. package/dist/skills/development/skill-crud.js.map +0 -1
  40. package/dist/skills/development/skill-db-schema.d.ts +0 -2
  41. package/dist/skills/development/skill-db-schema.d.ts.map +0 -1
  42. package/dist/skills/development/skill-db-schema.js +0 -2
  43. package/dist/skills/development/skill-db-schema.js.map +0 -1
  44. package/dist/skills/development/skill-docker.d.ts +0 -2
  45. package/dist/skills/development/skill-docker.d.ts.map +0 -1
  46. package/dist/skills/development/skill-docker.js +0 -2
  47. package/dist/skills/development/skill-docker.js.map +0 -1
  48. package/dist/skills/development/skill-env-setup.d.ts +0 -2
  49. package/dist/skills/development/skill-env-setup.d.ts.map +0 -1
  50. package/dist/skills/development/skill-env-setup.js +0 -2
  51. package/dist/skills/development/skill-env-setup.js.map +0 -1
  52. package/dist/skills/development/skill-scaffold.d.ts +0 -15
  53. package/dist/skills/development/skill-scaffold.d.ts.map +0 -1
  54. package/dist/skills/development/skill-scaffold.js +0 -299
  55. package/dist/skills/development/skill-scaffold.js.map +0 -1
  56. package/dist/skills/intelligence/skill-complexity-score.d.ts +0 -15
  57. package/dist/skills/intelligence/skill-complexity-score.d.ts.map +0 -1
  58. package/dist/skills/intelligence/skill-complexity-score.js +0 -66
  59. package/dist/skills/intelligence/skill-complexity-score.js.map +0 -1
  60. package/dist/skills/intelligence/skill-cost-track.d.ts +0 -14
  61. package/dist/skills/intelligence/skill-cost-track.d.ts.map +0 -1
  62. package/dist/skills/intelligence/skill-cost-track.js +0 -36
  63. package/dist/skills/intelligence/skill-cost-track.js.map +0 -1
  64. package/dist/skills/intelligence/skill-learning-loop.d.ts +0 -15
  65. package/dist/skills/intelligence/skill-learning-loop.d.ts.map +0 -1
  66. package/dist/skills/intelligence/skill-learning-loop.js +0 -66
  67. package/dist/skills/intelligence/skill-learning-loop.js.map +0 -1
  68. package/dist/skills/intelligence/skill-pattern-detect.d.ts +0 -14
  69. package/dist/skills/intelligence/skill-pattern-detect.d.ts.map +0 -1
  70. package/dist/skills/intelligence/skill-pattern-detect.js +0 -35
  71. package/dist/skills/intelligence/skill-pattern-detect.js.map +0 -1
  72. package/dist/skills/intelligence/skill-rate-watch.d.ts +0 -15
  73. package/dist/skills/intelligence/skill-rate-watch.d.ts.map +0 -1
  74. package/dist/skills/intelligence/skill-rate-watch.js +0 -58
  75. package/dist/skills/intelligence/skill-rate-watch.js.map +0 -1
  76. package/dist/skills/memory/skill-context-compress.d.ts +0 -15
  77. package/dist/skills/memory/skill-context-compress.d.ts.map +0 -1
  78. package/dist/skills/memory/skill-context-compress.js +0 -82
  79. package/dist/skills/memory/skill-context-compress.js.map +0 -1
  80. package/dist/skills/memory/skill-cross-sync.d.ts +0 -15
  81. package/dist/skills/memory/skill-cross-sync.d.ts.map +0 -1
  82. package/dist/skills/memory/skill-cross-sync.js +0 -88
  83. package/dist/skills/memory/skill-cross-sync.js.map +0 -1
  84. package/dist/skills/memory/skill-decision-log.d.ts +0 -15
  85. package/dist/skills/memory/skill-decision-log.d.ts.map +0 -1
  86. package/dist/skills/memory/skill-decision-log.js +0 -103
  87. package/dist/skills/memory/skill-decision-log.js.map +0 -1
  88. package/dist/skills/memory/skill-session-restore.d.ts +0 -15
  89. package/dist/skills/memory/skill-session-restore.d.ts.map +0 -1
  90. package/dist/skills/memory/skill-session-restore.js +0 -39
  91. package/dist/skills/memory/skill-session-restore.js.map +0 -1
  92. package/dist/skills/memory/skill-session-save.d.ts +0 -15
  93. package/dist/skills/memory/skill-session-save.d.ts.map +0 -1
  94. package/dist/skills/memory/skill-session-save.js +0 -74
  95. package/dist/skills/memory/skill-session-save.js.map +0 -1
  96. package/dist/skills/quality/skill-accessibility.d.ts +0 -2
  97. package/dist/skills/quality/skill-accessibility.d.ts.map +0 -1
  98. package/dist/skills/quality/skill-accessibility.js +0 -2
  99. package/dist/skills/quality/skill-accessibility.js.map +0 -1
  100. package/dist/skills/quality/skill-code-review.d.ts +0 -15
  101. package/dist/skills/quality/skill-code-review.d.ts.map +0 -1
  102. package/dist/skills/quality/skill-code-review.js +0 -60
  103. package/dist/skills/quality/skill-code-review.js.map +0 -1
  104. package/dist/skills/quality/skill-docs-gen.d.ts +0 -2
  105. package/dist/skills/quality/skill-docs-gen.d.ts.map +0 -1
  106. package/dist/skills/quality/skill-docs-gen.js +0 -2
  107. package/dist/skills/quality/skill-docs-gen.js.map +0 -1
  108. package/dist/skills/quality/skill-perf-audit.d.ts +0 -2
  109. package/dist/skills/quality/skill-perf-audit.d.ts.map +0 -1
  110. package/dist/skills/quality/skill-perf-audit.js +0 -2
  111. package/dist/skills/quality/skill-perf-audit.js.map +0 -1
  112. package/dist/skills/quality/skill-security-scan.d.ts +0 -15
  113. package/dist/skills/quality/skill-security-scan.d.ts.map +0 -1
  114. package/dist/skills/quality/skill-security-scan.js +0 -67
  115. package/dist/skills/quality/skill-security-scan.js.map +0 -1
  116. package/dist/skills/quality/skill-test-suite.d.ts +0 -15
  117. package/dist/skills/quality/skill-test-suite.d.ts.map +0 -1
  118. package/dist/skills/quality/skill-test-suite.js +0 -274
  119. package/dist/skills/quality/skill-test-suite.js.map +0 -1
  120. package/dist/skills/workflow/skill-deploy.d.ts +0 -2
  121. package/dist/skills/workflow/skill-deploy.d.ts.map +0 -1
  122. package/dist/skills/workflow/skill-deploy.js +0 -2
  123. package/dist/skills/workflow/skill-deploy.js.map +0 -1
  124. package/dist/skills/workflow/skill-git-workflow.d.ts +0 -2
  125. package/dist/skills/workflow/skill-git-workflow.d.ts.map +0 -1
  126. package/dist/skills/workflow/skill-git-workflow.js +0 -2
  127. package/dist/skills/workflow/skill-git-workflow.js.map +0 -1
  128. package/dist/skills/workflow/skill-rollback.d.ts +0 -2
  129. package/dist/skills/workflow/skill-rollback.d.ts.map +0 -1
  130. package/dist/skills/workflow/skill-rollback.js +0 -2
  131. package/dist/skills/workflow/skill-rollback.js.map +0 -1
  132. package/dist/skills/workflow/skill-task-breakdown.d.ts +0 -2
  133. package/dist/skills/workflow/skill-task-breakdown.d.ts.map +0 -1
  134. package/dist/skills/workflow/skill-task-breakdown.js +0 -2
  135. package/dist/skills/workflow/skill-task-breakdown.js.map +0 -1
@@ -36,10 +36,10 @@ export function mineImplicitOutcomes() {
36
36
  const wmRow = db.prepare('SELECT pattern_val FROM patterns WHERE pattern_key = ?').get(WATERMARK_KEY);
37
37
  const since = wmRow?.pattern_val ?? '1970-01-01 00:00:00';
38
38
  const placeholders = toolNames.map(() => '?').join(',');
39
- const rows = db.prepare(`SELECT id, session_id, tool_name, result_status, recorded_at
40
- FROM tool_call_trace_log
41
- WHERE recorded_at > ? AND tool_name IN (${placeholders})
42
- ORDER BY recorded_at ASC
39
+ const rows = db.prepare(`SELECT id, session_id, tool_name, result_status, recorded_at
40
+ FROM tool_call_trace_log
41
+ WHERE recorded_at > ? AND tool_name IN (${placeholders})
42
+ ORDER BY recorded_at ASC
43
43
  LIMIT ${BATCH_LIMIT}`).all(since, ...toolNames);
44
44
  if (rows.length === 0)
45
45
  return { mined: 0, errors: 0, retries: 0 };
@@ -5,7 +5,7 @@ import { getConfig } from '../memory/config.js';
5
5
  // ─── Record ───────────────────────────────────────────────────────────────────
6
6
  export function recordOutcome(taskType, complexity, modelTier, agent, outputQuality, tokensUsed = 0, fileExt) {
7
7
  const db = getDb();
8
- db.prepare(`INSERT INTO learning_data (id, task_type, complexity, model_tier, output_quality, tokens_used, agent)
8
+ db.prepare(`INSERT INTO learning_data (id, task_type, complexity, model_tier, output_quality, tokens_used, agent)
9
9
  VALUES (?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), taskType, complexity, modelTier, outputQuality, tokensUsed, agent);
10
10
  // Predictive routing: track agent quality per file extension and task type keyword
11
11
  if (agent && agent !== 'dynamic' && outputQuality > 0) {
@@ -50,8 +50,8 @@ export function getRecommendedAgent(taskType, fileExt) {
50
50
  const db = getDb();
51
51
  // Try file extension match first (more specific)
52
52
  if (fileExt) {
53
- const rows = db.prepare(`SELECT pattern_key, pattern_val, confidence, seen_count FROM patterns
54
- WHERE pattern_key LIKE ? AND seen_count >= 3
53
+ const rows = db.prepare(`SELECT pattern_key, pattern_val, confidence, seen_count FROM patterns
54
+ WHERE pattern_key LIKE ? AND seen_count >= 3
55
55
  ORDER BY confidence DESC LIMIT 1`).all(`file_agent:${fileExt.toLowerCase()}:%`);
56
56
  if (rows.length > 0 && rows[0].confidence >= 0.7)
57
57
  return rows[0].pattern_val;
@@ -59,8 +59,8 @@ export function getRecommendedAgent(taskType, fileExt) {
59
59
  // Fall back to task keyword match
60
60
  const keyword = taskType.toLowerCase().split(/[\s_-]/)[0];
61
61
  if (keyword) {
62
- const rows = db.prepare(`SELECT pattern_key, pattern_val, confidence, seen_count FROM patterns
63
- WHERE pattern_key LIKE ? AND seen_count >= 3
62
+ const rows = db.prepare(`SELECT pattern_key, pattern_val, confidence, seen_count FROM patterns
63
+ WHERE pattern_key LIKE ? AND seen_count >= 3
64
64
  ORDER BY confidence DESC LIMIT 1`).all(`task_agent:${keyword}:%`);
65
65
  if (rows.length > 0 && rows[0].confidence >= 0.7)
66
66
  return rows[0].pattern_val;
@@ -70,7 +70,7 @@ export function getRecommendedAgent(taskType, fileExt) {
70
70
  // ─── Stats ────────────────────────────────────────────────────────────────────
71
71
  export function getLearningStats() {
72
72
  const db = getDb();
73
- const rows = db.prepare(`SELECT model_tier, COUNT(*) as count, AVG(output_quality) as avg_quality
73
+ const rows = db.prepare(`SELECT model_tier, COUNT(*) as count, AVG(output_quality) as avg_quality
74
74
  FROM learning_data GROUP BY model_tier`).all();
75
75
  const total_tasks = rows.reduce((sum, r) => sum + r.count, 0);
76
76
  const tier_breakdown = {};
@@ -85,16 +85,16 @@ export function getLearningStats() {
85
85
  }
86
86
  export function getAgentPerformanceStats() {
87
87
  const db = getDb();
88
- const rows = db.prepare(`
89
- SELECT agent,
90
- COUNT(*) as task_count,
91
- AVG(output_quality) as avg_quality,
92
- AVG(tokens_used) as avg_tokens,
93
- SUM(CASE WHEN output_quality < 60 THEN 1 ELSE 0 END) as low_quality_count
94
- FROM learning_data
95
- WHERE agent IS NOT NULL
96
- GROUP BY agent
97
- ORDER BY avg_quality ASC
88
+ const rows = db.prepare(`
89
+ SELECT agent,
90
+ COUNT(*) as task_count,
91
+ AVG(output_quality) as avg_quality,
92
+ AVG(tokens_used) as avg_tokens,
93
+ SUM(CASE WHEN output_quality < 60 THEN 1 ELSE 0 END) as low_quality_count
94
+ FROM learning_data
95
+ WHERE agent IS NOT NULL
96
+ GROUP BY agent
97
+ ORDER BY avg_quality ASC
98
98
  `).all();
99
99
  return rows.map(r => ({
100
100
  agent: r.agent,
@@ -107,23 +107,23 @@ export function getAgentPerformanceStats() {
107
107
  }
108
108
  export function getTaskTypeBreakdown() {
109
109
  const db = getDb();
110
- const rows = db.prepare(`
111
- SELECT task_type,
112
- COUNT(*) as count,
113
- AVG(complexity) as avg_complexity,
114
- AVG(output_quality) as avg_quality,
115
- model_tier as dominant_tier
116
- FROM learning_data
117
- GROUP BY task_type, model_tier
118
- HAVING COUNT(*) = (
119
- SELECT MAX(c) FROM (
120
- SELECT COUNT(*) as c FROM learning_data l2
121
- WHERE l2.task_type = learning_data.task_type
122
- GROUP BY l2.model_tier
123
- )
124
- )
125
- ORDER BY count DESC
126
- LIMIT 50
110
+ const rows = db.prepare(`
111
+ SELECT task_type,
112
+ COUNT(*) as count,
113
+ AVG(complexity) as avg_complexity,
114
+ AVG(output_quality) as avg_quality,
115
+ model_tier as dominant_tier
116
+ FROM learning_data
117
+ GROUP BY task_type, model_tier
118
+ HAVING COUNT(*) = (
119
+ SELECT MAX(c) FROM (
120
+ SELECT COUNT(*) as c FROM learning_data l2
121
+ WHERE l2.task_type = learning_data.task_type
122
+ GROUP BY l2.model_tier
123
+ )
124
+ )
125
+ ORDER BY count DESC
126
+ LIMIT 50
127
127
  `).all();
128
128
  return rows.map(r => {
129
129
  // Under-tiered: task scored high quality but at a tier below what complexity suggests
@@ -142,15 +142,15 @@ export function getTaskTypeBreakdown() {
142
142
  export function getCouncilInsights() {
143
143
  const db = getDb();
144
144
  // Correlate council approvals (GREEN/YELLOW) with debugging sessions that came after
145
- const rows = db.prepare(`
146
- SELECT co.verdict, co.task, co.debated_at,
147
- d.decision as related_decision, d.made_at as decision_at
148
- FROM council_outcomes co
149
- LEFT JOIN decisions d ON d.session_id = co.session_id
150
- AND d.decision LIKE '%debug%' OR d.decision LIKE '%fix%' OR d.decision LIKE '%bug%'
151
- WHERE co.verdict IN ('GREEN', 'YELLOW')
152
- ORDER BY co.debated_at DESC
153
- LIMIT 30
145
+ const rows = db.prepare(`
146
+ SELECT co.verdict, co.task, co.debated_at,
147
+ d.decision as related_decision, d.made_at as decision_at
148
+ FROM council_outcomes co
149
+ LEFT JOIN decisions d ON d.session_id = co.session_id
150
+ AND d.decision LIKE '%debug%' OR d.decision LIKE '%fix%' OR d.decision LIKE '%bug%'
151
+ WHERE co.verdict IN ('GREEN', 'YELLOW')
152
+ ORDER BY co.debated_at DESC
153
+ LIMIT 30
154
154
  `).all();
155
155
  return rows.map(r => {
156
156
  let days_to_debug = null;
@@ -273,9 +273,9 @@ export function recordRoutingFeedback(opts) {
273
273
  const now = new Date().toISOString();
274
274
  const expiresAt = new Date(Date.now() + FEEDBACK_TTL_DAYS * 86_400_000).toISOString();
275
275
  const task_hash = opts.task.toLowerCase().replace(/\s+/g, '_').slice(0, 40);
276
- db.prepare(`
277
- INSERT INTO routing_feedback (id, task_hash, task_snippet, complexity, model_tier, agent, outcome, session_id, recorded_at, expires_at)
278
- VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)
276
+ db.prepare(`
277
+ INSERT INTO routing_feedback (id, task_hash, task_snippet, complexity, model_tier, agent, outcome, session_id, recorded_at, expires_at)
278
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)
279
279
  `).run(id, task_hash, opts.task.slice(0, 100), opts.complexity, opts.model_tier, opts.agent ?? null, opts.session_id ?? null, now, expiresAt);
280
280
  return id;
281
281
  }
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Veto MCP Server — 92 tools, LLM council + auto-learning router
2
+ // Veto MCP Server — 93 tools, LLM council + auto-learning router
3
3
  // Suppress node:sqlite experimental warning — it would corrupt the MCP stdio protocol
4
4
  process.removeAllListeners('warning');
5
5
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -1,5 +1,5 @@
1
- // Compact tool surface — progressive disclosure for clients where 92 tool
2
- // schemas would burn ~25K context tokens before the user types a word.
1
+ // Compact tool surface — progressive disclosure for clients where 93 tool
2
+ // schemas would burn ~20K context tokens before the user types a word.
3
3
  //
4
4
  // In compact mode (VETO_COMPACT=1 or config compact_tools=true) ListTools
5
5
  // exposes only a small first-class set plus two meta-tools: veto_find_tools
@@ -22,7 +22,7 @@ const FIRST_CLASS = [
22
22
  export const META_TOOL_DEFINITIONS = [
23
23
  {
24
24
  name: 'veto_find_tools',
25
- description: 'Searches the full Veto catalog (92 tools: agents, reviews, advisors, generators, memory, learning, git, workflow) by keyword and returns matching tool schemas. Compact mode exposes only core tools up front — call this first to discover the right tool, then invoke it via veto_call.',
25
+ description: 'Searches the full Veto catalog (93 tools: agents, reviews, advisors, generators, memory, learning, git, workflow) by keyword and returns matching tool schemas. Compact mode exposes only core tools up front — call this first to discover the right tool, then invoke it via veto_call.',
26
26
  inputSchema: {
27
27
  type: 'object',
28
28
  properties: {
@@ -79,14 +79,14 @@ export async function captureSession(opts = {}) {
79
79
  if (existing) {
80
80
  // Content changed (grew): overwrite the row, reset the index watermark AND
81
81
  // parser_version so Step 6 re-derives events from the new bytes.
82
- db.prepare(`UPDATE archives SET archive_path=?, content_sha256=?, source_bytes=?, archive_bytes=?,
83
- project_dir=COALESCE(?, project_dir), veto_session_id=COALESCE(?, veto_session_id),
82
+ db.prepare(`UPDATE archives SET archive_path=?, content_sha256=?, source_bytes=?, archive_bytes=?,
83
+ project_dir=COALESCE(?, project_dir), veto_session_id=COALESCE(?, veto_session_id),
84
84
  updated_at=?, indexed_through_seq=0, parser_version=0 WHERE id=?`).run(archivePath, sha256, bytes, archiveBytes, proj, opts.vetoSessionId ?? null, now, id);
85
85
  }
86
86
  else {
87
- db.prepare(`INSERT INTO archives (id, source, source_session_id, project_dir, veto_session_id, archive_path,
88
- content_sha256, source_bytes, archive_bytes, source_format_hint, parser_version, indexed_through_seq,
89
- captured_at, updated_at)
87
+ db.prepare(`INSERT INTO archives (id, source, source_session_id, project_dir, veto_session_id, archive_path,
88
+ content_sha256, source_bytes, archive_bytes, source_format_hint, parser_version, indexed_through_seq,
89
+ captured_at, updated_at)
90
90
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?)`).run(id, source, sessionId, proj, opts.vetoSessionId ?? null, archivePath, sha256, bytes, archiveBytes, formatHint, now, now);
91
91
  }
92
92
  return {
@@ -42,13 +42,13 @@ export function ingestArchive(archiveId) {
42
42
  if (sessionIds.size > 0 && !sessionIds.has(row.source_session_id)) {
43
43
  return { status: 'session_mismatch', reason: 'archive session id not present in transcript' };
44
44
  }
45
- const ins = db.prepare(`INSERT INTO events (id, archive_id, source_session_id, seq, line_index, block_index, kind,
46
- source_type, role, tool_name, text, secret_count, event_uuid, parent_uuid, is_sidechain,
47
- ts_source, ts_utc, raw_offset, raw_length)
45
+ const ins = db.prepare(`INSERT INTO events (id, archive_id, source_session_id, seq, line_index, block_index, kind,
46
+ source_type, role, tool_name, text, secret_count, event_uuid, parent_uuid, is_sidechain,
47
+ ts_source, ts_utc, raw_offset, raw_length)
48
48
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
49
- const insDoc = db.prepare(`INSERT INTO search_docs (event_id, archive_id, source_session_id, project_dir, seq, kind, len)
49
+ const insDoc = db.prepare(`INSERT INTO search_docs (event_id, archive_id, source_session_id, project_dir, seq, kind, len)
50
50
  VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id`);
51
- const insTerm = db.prepare(`INSERT INTO search_terms (term) VALUES (?)
51
+ const insTerm = db.prepare(`INSERT INTO search_terms (term) VALUES (?)
52
52
  ON CONFLICT(term) DO UPDATE SET term = term RETURNING id`);
53
53
  const insPosting = db.prepare(`INSERT INTO search_postings (term_id, doc_id, tf) VALUES (?, ?, ?)`);
54
54
  // Term ids are cached per ingest run — a session reuses its vocabulary
@@ -86,7 +86,7 @@ export function ingestArchive(archiveId) {
86
86
  }
87
87
  // Vectors were just dropped with their docs; clearing the marker lets
88
88
  // the lazy embed pass rebuild them without re-parsing the transcript.
89
- db.prepare(`UPDATE archives SET indexed_through_seq = ?, parser_version = ?, chunker_version = 0, embed_model = NULL
89
+ db.prepare(`UPDATE archives SET indexed_through_seq = ?, parser_version = ?, chunker_version = 0, embed_model = NULL
90
90
  WHERE id = ?`).run(events.length, PARSER_VERSION, archiveId);
91
91
  db.exec('COMMIT');
92
92
  resetCaches();
@@ -17,11 +17,11 @@ export function recordSessionMapping(m) {
17
17
  const db = getTranscriptsDb();
18
18
  const now = new Date().toISOString();
19
19
  const proj = m.projectDir ? normalizeProjectDir(m.projectDir) : null;
20
- db.prepare(`INSERT INTO session_map (source, source_session_id, transcript_path, project_dir, last_seen_at)
21
- VALUES (?, ?, ?, ?, ?)
22
- ON CONFLICT(source, source_session_id) DO UPDATE SET
23
- transcript_path = excluded.transcript_path,
24
- project_dir = excluded.project_dir,
20
+ db.prepare(`INSERT INTO session_map (source, source_session_id, transcript_path, project_dir, last_seen_at)
21
+ VALUES (?, ?, ?, ?, ?)
22
+ ON CONFLICT(source, source_session_id) DO UPDATE SET
23
+ transcript_path = excluded.transcript_path,
24
+ project_dir = excluded.project_dir,
25
25
  last_seen_at = excluded.last_seen_at`).run(m.source ?? 'claude', m.sourceSessionId, m.transcriptPath, proj, now);
26
26
  }
27
27
  /** Look up the mapping for an exact session id. */
@@ -15,8 +15,8 @@ export const ERROR_RE = /\b(error|errno|failed|failure|exception|traceback|not f
15
15
  /** L2 — the conversation spine (user + assistant text, in order). */
16
16
  export function buildSpine(archiveId) {
17
17
  const db = getTranscriptsDb();
18
- const rows = db.prepare(`SELECT seq, role, kind, ts_utc, text FROM events
19
- WHERE archive_id = ? AND kind IN ('user_message','assistant_message')
18
+ const rows = db.prepare(`SELECT seq, role, kind, ts_utc, text FROM events
19
+ WHERE archive_id = ? AND kind IN ('user_message','assistant_message')
20
20
  ORDER BY seq`).all(archiveId);
21
21
  return rows.map(r => ({
22
22
  seq: r.seq,
@@ -17,39 +17,39 @@ export const MIGRATIONS = [
17
17
  // v1 — the two foundational anchor tables. Events / segments / FTS get their
18
18
  // own later migrations (Steps 6/9/10) so we don't big-bang the schema.
19
19
  version: 1,
20
- up: `
21
- -- L0 archive index: one row per captured source-CLI session.
22
- CREATE TABLE IF NOT EXISTS archives (
23
- id TEXT PRIMARY KEY,
24
- source TEXT NOT NULL DEFAULT 'claude', -- claude|codex|gemini
25
- source_session_id TEXT NOT NULL,
26
- project_dir TEXT, -- normalized (lowercase drive)
27
- veto_session_id TEXT, -- link to veto.db sessions.id when known
28
- archive_path TEXT NOT NULL, -- absolute path to the .gz on disk
29
- content_sha256 TEXT NOT NULL, -- of raw source bytes (dedup key)
30
- source_bytes INTEGER NOT NULL DEFAULT 0, -- uncompressed size
31
- archive_bytes INTEGER NOT NULL DEFAULT 0, -- gz size on disk
32
- source_format_hint TEXT, -- e.g. 'claude-jsonl'
33
- parser_version INTEGER NOT NULL DEFAULT 0,
34
- indexed_through_seq INTEGER NOT NULL DEFAULT 0, -- lazy-index watermark
35
- captured_at TEXT NOT NULL,
36
- updated_at TEXT NOT NULL,
37
- UNIQUE(source, source_session_id)
38
- );
39
- CREATE INDEX IF NOT EXISTS idx_archives_project ON archives(project_dir);
40
- CREATE INDEX IF NOT EXISTS idx_archives_sha ON archives(content_sha256);
41
-
42
- -- Live host-session -> transcript-file mapping, UPSERTed fire-and-forget by
43
- -- the statusline (Step 4) so save-time capture knows which file to read.
44
- CREATE TABLE IF NOT EXISTS session_map (
45
- source TEXT NOT NULL DEFAULT 'claude',
46
- source_session_id TEXT NOT NULL,
47
- transcript_path TEXT NOT NULL,
48
- project_dir TEXT,
49
- last_seen_at TEXT NOT NULL,
50
- PRIMARY KEY (source, source_session_id)
51
- );
52
- CREATE INDEX IF NOT EXISTS idx_session_map_project ON session_map(project_dir);
20
+ up: `
21
+ -- L0 archive index: one row per captured source-CLI session.
22
+ CREATE TABLE IF NOT EXISTS archives (
23
+ id TEXT PRIMARY KEY,
24
+ source TEXT NOT NULL DEFAULT 'claude', -- claude|codex|gemini
25
+ source_session_id TEXT NOT NULL,
26
+ project_dir TEXT, -- normalized (lowercase drive)
27
+ veto_session_id TEXT, -- link to veto.db sessions.id when known
28
+ archive_path TEXT NOT NULL, -- absolute path to the .gz on disk
29
+ content_sha256 TEXT NOT NULL, -- of raw source bytes (dedup key)
30
+ source_bytes INTEGER NOT NULL DEFAULT 0, -- uncompressed size
31
+ archive_bytes INTEGER NOT NULL DEFAULT 0, -- gz size on disk
32
+ source_format_hint TEXT, -- e.g. 'claude-jsonl'
33
+ parser_version INTEGER NOT NULL DEFAULT 0,
34
+ indexed_through_seq INTEGER NOT NULL DEFAULT 0, -- lazy-index watermark
35
+ captured_at TEXT NOT NULL,
36
+ updated_at TEXT NOT NULL,
37
+ UNIQUE(source, source_session_id)
38
+ );
39
+ CREATE INDEX IF NOT EXISTS idx_archives_project ON archives(project_dir);
40
+ CREATE INDEX IF NOT EXISTS idx_archives_sha ON archives(content_sha256);
41
+
42
+ -- Live host-session -> transcript-file mapping, UPSERTed fire-and-forget by
43
+ -- the statusline (Step 4) so save-time capture knows which file to read.
44
+ CREATE TABLE IF NOT EXISTS session_map (
45
+ source TEXT NOT NULL DEFAULT 'claude',
46
+ source_session_id TEXT NOT NULL,
47
+ transcript_path TEXT NOT NULL,
48
+ project_dir TEXT,
49
+ last_seen_at TEXT NOT NULL,
50
+ PRIMARY KEY (source, source_session_id)
51
+ );
52
+ CREATE INDEX IF NOT EXISTS idx_session_map_project ON session_map(project_dir);
53
53
  `,
54
54
  },
55
55
  {
@@ -58,33 +58,33 @@ export const MIGRATIONS = [
58
58
  // UNCOMPRESSED L0 bytes for exact expansion (Step 11). Unknown/unparsed lines
59
59
  // are stored+ordered (kind='unknown') so format drift never loses data.
60
60
  version: 2,
61
- up: `
62
- CREATE TABLE IF NOT EXISTS events (
63
- id TEXT PRIMARY KEY,
64
- archive_id TEXT NOT NULL,
65
- source_session_id TEXT NOT NULL,
66
- seq INTEGER NOT NULL, -- monotonic per archive, per event
67
- line_index INTEGER NOT NULL, -- source JSONL line (0-based)
68
- block_index INTEGER NOT NULL DEFAULT 0,
69
- kind TEXT NOT NULL, -- EVENT_KINDS
70
- source_type TEXT, -- verbatim source 'type' (user/assistant/system/…)
71
- role TEXT,
72
- tool_name TEXT, -- for tool_call/tool_result
73
- text TEXT, -- normalized + MASKED content for FTS
74
- secret_count INTEGER NOT NULL DEFAULT 0,
75
- event_uuid TEXT, -- native uuid (Claude)
76
- parent_uuid TEXT, -- native parentUuid (tree)
77
- is_sidechain INTEGER NOT NULL DEFAULT 0,
78
- ts_source TEXT, -- verbatim timestamp string
79
- ts_utc TEXT, -- normalized ISO
80
- raw_offset INTEGER NOT NULL DEFAULT 0,
81
- raw_length INTEGER NOT NULL DEFAULT 0,
82
- UNIQUE(archive_id, seq),
83
- FOREIGN KEY (archive_id) REFERENCES archives(id)
84
- );
85
- CREATE INDEX IF NOT EXISTS idx_events_archive ON events(archive_id, seq);
86
- CREATE INDEX IF NOT EXISTS idx_events_session ON events(source_session_id);
87
- CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind);
61
+ up: `
62
+ CREATE TABLE IF NOT EXISTS events (
63
+ id TEXT PRIMARY KEY,
64
+ archive_id TEXT NOT NULL,
65
+ source_session_id TEXT NOT NULL,
66
+ seq INTEGER NOT NULL, -- monotonic per archive, per event
67
+ line_index INTEGER NOT NULL, -- source JSONL line (0-based)
68
+ block_index INTEGER NOT NULL DEFAULT 0,
69
+ kind TEXT NOT NULL, -- EVENT_KINDS
70
+ source_type TEXT, -- verbatim source 'type' (user/assistant/system/…)
71
+ role TEXT,
72
+ tool_name TEXT, -- for tool_call/tool_result
73
+ text TEXT, -- normalized + MASKED content for FTS
74
+ secret_count INTEGER NOT NULL DEFAULT 0,
75
+ event_uuid TEXT, -- native uuid (Claude)
76
+ parent_uuid TEXT, -- native parentUuid (tree)
77
+ is_sidechain INTEGER NOT NULL DEFAULT 0,
78
+ ts_source TEXT, -- verbatim timestamp string
79
+ ts_utc TEXT, -- normalized ISO
80
+ raw_offset INTEGER NOT NULL DEFAULT 0,
81
+ raw_length INTEGER NOT NULL DEFAULT 0,
82
+ UNIQUE(archive_id, seq),
83
+ FOREIGN KEY (archive_id) REFERENCES archives(id)
84
+ );
85
+ CREATE INDEX IF NOT EXISTS idx_events_archive ON events(archive_id, seq);
86
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(source_session_id);
87
+ CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind);
88
88
  `,
89
89
  },
90
90
  {
@@ -104,39 +104,39 @@ export const MIGRATIONS = [
104
104
  // loads its module, which throws on exactly the Nodes this redo supports.
105
105
  // Databases that ran the old dev-only v3 keep an inert orphan table.
106
106
  version: 3,
107
- up: `
108
- -- One row per indexed event. project_dir/session denormalized so the
109
- -- hot query is a single join (same shape events_fts had). The INTEGER id
110
- -- exists so postings never carry a 36-char uuid — measured, that uuid
111
- -- (stored twice per posting: PK + purge index) was ~3x the entire
112
- -- index's justified size.
113
- CREATE TABLE IF NOT EXISTS search_docs (
114
- id INTEGER PRIMARY KEY,
115
- event_id TEXT NOT NULL UNIQUE,
116
- archive_id TEXT NOT NULL,
117
- source_session_id TEXT NOT NULL,
118
- project_dir TEXT,
119
- seq INTEGER NOT NULL,
120
- kind TEXT NOT NULL,
121
- len INTEGER NOT NULL -- token count, for BM25 length norm
122
- );
123
- CREATE INDEX IF NOT EXISTS idx_search_docs_archive ON search_docs(archive_id);
124
- CREATE INDEX IF NOT EXISTS idx_search_docs_scope
125
- ON search_docs(project_dir, source_session_id);
126
-
127
- -- Term dictionary: term text stored once, compact integer join key.
128
- CREATE TABLE IF NOT EXISTS search_terms (
129
- id INTEGER PRIMARY KEY,
130
- term TEXT NOT NULL UNIQUE
131
- );
132
-
133
- CREATE TABLE IF NOT EXISTS search_postings (
134
- term_id INTEGER NOT NULL,
135
- doc_id INTEGER NOT NULL,
136
- tf INTEGER NOT NULL,
137
- PRIMARY KEY (term_id, doc_id)
138
- ) WITHOUT ROWID;
139
- CREATE INDEX IF NOT EXISTS idx_postings_doc ON search_postings(doc_id);
107
+ up: `
108
+ -- One row per indexed event. project_dir/session denormalized so the
109
+ -- hot query is a single join (same shape events_fts had). The INTEGER id
110
+ -- exists so postings never carry a 36-char uuid — measured, that uuid
111
+ -- (stored twice per posting: PK + purge index) was ~3x the entire
112
+ -- index's justified size.
113
+ CREATE TABLE IF NOT EXISTS search_docs (
114
+ id INTEGER PRIMARY KEY,
115
+ event_id TEXT NOT NULL UNIQUE,
116
+ archive_id TEXT NOT NULL,
117
+ source_session_id TEXT NOT NULL,
118
+ project_dir TEXT,
119
+ seq INTEGER NOT NULL,
120
+ kind TEXT NOT NULL,
121
+ len INTEGER NOT NULL -- token count, for BM25 length norm
122
+ );
123
+ CREATE INDEX IF NOT EXISTS idx_search_docs_archive ON search_docs(archive_id);
124
+ CREATE INDEX IF NOT EXISTS idx_search_docs_scope
125
+ ON search_docs(project_dir, source_session_id);
126
+
127
+ -- Term dictionary: term text stored once, compact integer join key.
128
+ CREATE TABLE IF NOT EXISTS search_terms (
129
+ id INTEGER PRIMARY KEY,
130
+ term TEXT NOT NULL UNIQUE
131
+ );
132
+
133
+ CREATE TABLE IF NOT EXISTS search_postings (
134
+ term_id INTEGER NOT NULL,
135
+ doc_id INTEGER NOT NULL,
136
+ tf INTEGER NOT NULL,
137
+ PRIMARY KEY (term_id, doc_id)
138
+ ) WITHOUT ROWID;
139
+ CREATE INDEX IF NOT EXISTS idx_postings_doc ON search_postings(doc_id);
140
140
  `,
141
141
  },
142
142
  {
@@ -158,17 +158,17 @@ export const MIGRATIONS = [
158
158
  // parser_version, so ingest never depends on the model being installed and
159
159
  // installing it later backfills without re-parsing anything.
160
160
  version: 4,
161
- up: `
162
- CREATE TABLE IF NOT EXISTS search_vectors (
163
- doc_id INTEGER NOT NULL,
164
- chunk_index INTEGER NOT NULL,
165
- vec BLOB NOT NULL, -- int8, one byte per dimension
166
- norm REAL NOT NULL, -- L2 norm of the int8 vector
167
- PRIMARY KEY (doc_id, chunk_index)
168
- );
169
-
170
- ALTER TABLE archives ADD COLUMN chunker_version INTEGER NOT NULL DEFAULT 0;
171
- ALTER TABLE archives ADD COLUMN embed_model TEXT;
161
+ up: `
162
+ CREATE TABLE IF NOT EXISTS search_vectors (
163
+ doc_id INTEGER NOT NULL,
164
+ chunk_index INTEGER NOT NULL,
165
+ vec BLOB NOT NULL, -- int8, one byte per dimension
166
+ norm REAL NOT NULL, -- L2 norm of the int8 vector
167
+ PRIMARY KEY (doc_id, chunk_index)
168
+ );
169
+
170
+ ALTER TABLE archives ADD COLUMN chunker_version INTEGER NOT NULL DEFAULT 0;
171
+ ALTER TABLE archives ADD COLUMN embed_model TEXT;
172
172
  `,
173
173
  },
174
174
  ];
@@ -119,8 +119,8 @@ export function searchEvents(query, opts = {}) {
119
119
  // Metadata and text for the returned rows only.
120
120
  const docIds = top.map(([id]) => id);
121
121
  const marks2 = docIds.map(() => '?').join(', ');
122
- const metaRows = db.prepare(`SELECT d.id, d.event_id, d.archive_id, d.source_session_id, d.seq, d.kind, e.text
123
- FROM search_docs d JOIN events e ON e.id = d.event_id
122
+ const metaRows = db.prepare(`SELECT d.id, d.event_id, d.archive_id, d.source_session_id, d.seq, d.kind, e.text
123
+ FROM search_docs d JOIN events e ON e.id = d.event_id
124
124
  WHERE d.id IN (${marks2})`).all(...docIds);
125
125
  const metaById = new Map(metaRows.map(r => [r.id, r]));
126
126
  const termSet = new Set(kept.map(t => t.term));
@@ -261,7 +261,7 @@ export function searchEventsHybrid(query, opts = {}) {
261
261
  const db = getTranscriptsDb();
262
262
  // Resolve the semantic hits' doc ids to event metadata in one round trip.
263
263
  const docIds = semantic.map(s => s.docId);
264
- const docRows = db.prepare(`SELECT id, event_id, archive_id, source_session_id, seq, kind
264
+ const docRows = db.prepare(`SELECT id, event_id, archive_id, source_session_id, seq, kind
265
265
  FROM search_docs WHERE id IN (${docIds.map(() => '?').join(', ')})`).all(...docIds);
266
266
  const docById = new Map(docRows.map(r => [r.id, r]));
267
267
  const fused = new Map();
@@ -68,8 +68,8 @@ export function embedArchive(archiveId) {
68
68
  return { status: 'already_embedded' };
69
69
  // Only indexed, searchable docs get vectors — the same set BM25 covers,
70
70
  // read with their masked text (C3: masked text is the only text there is).
71
- const docs = db.prepare(`SELECT d.id AS doc_id, e.text AS text
72
- FROM search_docs d JOIN events e ON e.id = d.event_id
71
+ const docs = db.prepare(`SELECT d.id AS doc_id, e.text AS text
72
+ FROM search_docs d JOIN events e ON e.id = d.event_id
73
73
  WHERE d.archive_id = ?`).all(archiveId);
74
74
  const insVec = db.prepare(`INSERT INTO search_vectors (doc_id, chunk_index, vec, norm) VALUES (?, ?, ?, ?)`);
75
75
  let chunks = 0;