@code-insights/cli 3.6.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +40 -4
  3. package/dashboard-dist/assets/index-D1JDyyu5.js +660 -0
  4. package/dashboard-dist/assets/index-kwbCW1n2.css +1 -0
  5. package/dashboard-dist/index.html +2 -2
  6. package/dist/commands/reflect.d.ts +3 -0
  7. package/dist/commands/reflect.d.ts.map +1 -0
  8. package/dist/commands/reflect.js +457 -0
  9. package/dist/commands/reflect.js.map +1 -0
  10. package/dist/commands/reset.d.ts.map +1 -1
  11. package/dist/commands/reset.js +3 -1
  12. package/dist/commands/reset.js.map +1 -1
  13. package/dist/commands/stats/actions/patterns.d.ts +3 -0
  14. package/dist/commands/stats/actions/patterns.d.ts.map +1 -0
  15. package/dist/commands/stats/actions/patterns.js +140 -0
  16. package/dist/commands/stats/actions/patterns.js.map +1 -0
  17. package/dist/commands/stats/data/aggregation-helpers.d.ts +23 -0
  18. package/dist/commands/stats/data/aggregation-helpers.d.ts.map +1 -0
  19. package/dist/commands/stats/data/aggregation-helpers.js +128 -0
  20. package/dist/commands/stats/data/aggregation-helpers.js.map +1 -0
  21. package/dist/commands/stats/data/aggregation.d.ts +3 -35
  22. package/dist/commands/stats/data/aggregation.d.ts.map +1 -1
  23. package/dist/commands/stats/data/aggregation.js +8 -290
  24. package/dist/commands/stats/data/aggregation.js.map +1 -1
  25. package/dist/commands/stats/data/time-series.d.ts +24 -0
  26. package/dist/commands/stats/data/time-series.d.ts.map +1 -0
  27. package/dist/commands/stats/data/time-series.js +162 -0
  28. package/dist/commands/stats/data/time-series.js.map +1 -0
  29. package/dist/commands/stats/index.d.ts.map +1 -1
  30. package/dist/commands/stats/index.js +7 -1
  31. package/dist/commands/stats/index.js.map +1 -1
  32. package/dist/commands/sync.d.ts +19 -0
  33. package/dist/commands/sync.d.ts.map +1 -1
  34. package/dist/commands/sync.js +67 -1
  35. package/dist/commands/sync.js.map +1 -1
  36. package/dist/constants/llm-providers.js +1 -1
  37. package/dist/constants/llm-providers.js.map +1 -1
  38. package/dist/db/client.d.ts +7 -0
  39. package/dist/db/client.d.ts.map +1 -1
  40. package/dist/db/client.js +11 -1
  41. package/dist/db/client.js.map +1 -1
  42. package/dist/db/migrate.d.ts +10 -1
  43. package/dist/db/migrate.d.ts.map +1 -1
  44. package/dist/db/migrate.js +96 -0
  45. package/dist/db/migrate.js.map +1 -1
  46. package/dist/db/read.d.ts +5 -0
  47. package/dist/db/read.d.ts.map +1 -1
  48. package/dist/db/read.js +20 -3
  49. package/dist/db/read.js.map +1 -1
  50. package/dist/db/schema.d.ts +1 -1
  51. package/dist/db/schema.js +2 -2
  52. package/dist/db/schema.js.map +1 -1
  53. package/dist/db/write.d.ts.map +1 -1
  54. package/dist/db/write.js +8 -2
  55. package/dist/db/write.js.map +1 -1
  56. package/dist/index.js +39 -3
  57. package/dist/index.js.map +1 -1
  58. package/dist/parser/jsonl.d.ts +19 -1
  59. package/dist/parser/jsonl.d.ts.map +1 -1
  60. package/dist/parser/jsonl.js +109 -3
  61. package/dist/parser/jsonl.js.map +1 -1
  62. package/dist/providers/codex.js +4 -1
  63. package/dist/providers/codex.js.map +1 -1
  64. package/dist/providers/copilot-cli.js +3 -0
  65. package/dist/providers/copilot-cli.js.map +1 -1
  66. package/dist/providers/copilot.js +3 -0
  67. package/dist/providers/copilot.js.map +1 -1
  68. package/dist/providers/cursor.js +3 -0
  69. package/dist/providers/cursor.js.map +1 -1
  70. package/dist/types.d.ts +109 -0
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/utils/date-utils.d.ts +6 -0
  73. package/dist/utils/date-utils.d.ts.map +1 -0
  74. package/dist/utils/date-utils.js +26 -0
  75. package/dist/utils/date-utils.js.map +1 -0
  76. package/dist/utils/telemetry.d.ts +1 -1
  77. package/dist/utils/telemetry.d.ts.map +1 -1
  78. package/dist/utils/telemetry.js +1 -1
  79. package/dist/utils/telemetry.js.map +1 -1
  80. package/package.json +2 -1
  81. package/server-dist/export/agent-rules.d.ts.map +1 -1
  82. package/server-dist/export/agent-rules.js +15 -4
  83. package/server-dist/export/agent-rules.js.map +1 -1
  84. package/server-dist/export/knowledge-base.d.ts.map +1 -1
  85. package/server-dist/export/knowledge-base.js +30 -4
  86. package/server-dist/export/knowledge-base.js.map +1 -1
  87. package/server-dist/index.d.ts.map +1 -1
  88. package/server-dist/index.js +4 -0
  89. package/server-dist/index.js.map +1 -1
  90. package/server-dist/llm/analysis-db.d.ts +51 -0
  91. package/server-dist/llm/analysis-db.d.ts.map +1 -0
  92. package/server-dist/llm/analysis-db.js +208 -0
  93. package/server-dist/llm/analysis-db.js.map +1 -0
  94. package/server-dist/llm/analysis-internal.d.ts +36 -0
  95. package/server-dist/llm/analysis-internal.d.ts.map +1 -0
  96. package/server-dist/llm/analysis-internal.js +23 -0
  97. package/server-dist/llm/analysis-internal.js.map +1 -0
  98. package/server-dist/llm/analysis-pricing.d.ts +25 -0
  99. package/server-dist/llm/analysis-pricing.d.ts.map +1 -0
  100. package/server-dist/llm/analysis-pricing.js +74 -0
  101. package/server-dist/llm/analysis-pricing.js.map +1 -0
  102. package/server-dist/llm/analysis-usage-db.d.ts +45 -0
  103. package/server-dist/llm/analysis-usage-db.d.ts.map +1 -0
  104. package/server-dist/llm/analysis-usage-db.js +35 -0
  105. package/server-dist/llm/analysis-usage-db.js.map +1 -0
  106. package/server-dist/llm/analysis.d.ts +9 -79
  107. package/server-dist/llm/analysis.d.ts.map +1 -1
  108. package/server-dist/llm/analysis.js +119 -375
  109. package/server-dist/llm/analysis.js.map +1 -1
  110. package/server-dist/llm/facet-extraction.d.ts +14 -0
  111. package/server-dist/llm/facet-extraction.d.ts.map +1 -0
  112. package/server-dist/llm/facet-extraction.js +91 -0
  113. package/server-dist/llm/facet-extraction.js.map +1 -0
  114. package/server-dist/llm/friction-normalize.d.ts +16 -0
  115. package/server-dist/llm/friction-normalize.d.ts.map +1 -0
  116. package/server-dist/llm/friction-normalize.js +54 -0
  117. package/server-dist/llm/friction-normalize.js.map +1 -0
  118. package/server-dist/llm/index.d.ts +3 -2
  119. package/server-dist/llm/index.d.ts.map +1 -1
  120. package/server-dist/llm/index.js +1 -1
  121. package/server-dist/llm/index.js.map +1 -1
  122. package/server-dist/llm/message-format.d.ts +32 -0
  123. package/server-dist/llm/message-format.d.ts.map +1 -0
  124. package/server-dist/llm/message-format.js +129 -0
  125. package/server-dist/llm/message-format.js.map +1 -0
  126. package/server-dist/llm/normalize-utils.d.ts +22 -0
  127. package/server-dist/llm/normalize-utils.d.ts.map +1 -0
  128. package/server-dist/llm/normalize-utils.js +71 -0
  129. package/server-dist/llm/normalize-utils.js.map +1 -0
  130. package/server-dist/llm/pattern-normalize.d.ts +19 -0
  131. package/server-dist/llm/pattern-normalize.d.ts.map +1 -0
  132. package/server-dist/llm/pattern-normalize.js +90 -0
  133. package/server-dist/llm/pattern-normalize.js.map +1 -0
  134. package/server-dist/llm/prompt-constants.d.ts +9 -0
  135. package/server-dist/llm/prompt-constants.d.ts.map +1 -0
  136. package/server-dist/llm/prompt-constants.js +169 -0
  137. package/server-dist/llm/prompt-constants.js.map +1 -0
  138. package/server-dist/llm/prompt-quality-analysis.d.ts +8 -0
  139. package/server-dist/llm/prompt-quality-analysis.d.ts.map +1 -0
  140. package/server-dist/llm/prompt-quality-analysis.js +133 -0
  141. package/server-dist/llm/prompt-quality-analysis.js.map +1 -0
  142. package/server-dist/llm/prompt-quality-normalize.d.ts +26 -0
  143. package/server-dist/llm/prompt-quality-normalize.d.ts.map +1 -0
  144. package/server-dist/llm/prompt-quality-normalize.js +116 -0
  145. package/server-dist/llm/prompt-quality-normalize.js.map +1 -0
  146. package/server-dist/llm/prompt-types.d.ts +124 -0
  147. package/server-dist/llm/prompt-types.d.ts.map +1 -0
  148. package/server-dist/llm/prompt-types.js +4 -0
  149. package/server-dist/llm/prompt-types.js.map +1 -0
  150. package/server-dist/llm/prompts.d.ts +57 -100
  151. package/server-dist/llm/prompts.d.ts.map +1 -1
  152. package/server-dist/llm/prompts.js +606 -232
  153. package/server-dist/llm/prompts.js.map +1 -1
  154. package/server-dist/llm/providers/anthropic.d.ts.map +1 -1
  155. package/server-dist/llm/providers/anthropic.js +12 -0
  156. package/server-dist/llm/providers/anthropic.js.map +1 -1
  157. package/server-dist/llm/providers/gemini.d.ts.map +1 -1
  158. package/server-dist/llm/providers/gemini.js +10 -2
  159. package/server-dist/llm/providers/gemini.js.map +1 -1
  160. package/server-dist/llm/providers/ollama.d.ts.map +1 -1
  161. package/server-dist/llm/providers/ollama.js +3 -1
  162. package/server-dist/llm/providers/ollama.js.map +1 -1
  163. package/server-dist/llm/providers/openai.d.ts.map +1 -1
  164. package/server-dist/llm/providers/openai.js +4 -1
  165. package/server-dist/llm/providers/openai.js.map +1 -1
  166. package/server-dist/llm/recurring-insights.d.ts +26 -0
  167. package/server-dist/llm/recurring-insights.d.ts.map +1 -0
  168. package/server-dist/llm/recurring-insights.js +119 -0
  169. package/server-dist/llm/recurring-insights.js.map +1 -0
  170. package/server-dist/llm/reflect-prompts.d.ts +55 -0
  171. package/server-dist/llm/reflect-prompts.d.ts.map +1 -0
  172. package/server-dist/llm/reflect-prompts.js +151 -0
  173. package/server-dist/llm/reflect-prompts.js.map +1 -0
  174. package/server-dist/llm/response-parsers.d.ts +8 -0
  175. package/server-dist/llm/response-parsers.d.ts.map +1 -0
  176. package/server-dist/llm/response-parsers.js +151 -0
  177. package/server-dist/llm/response-parsers.js.map +1 -0
  178. package/server-dist/llm/types.d.ts +23 -1
  179. package/server-dist/llm/types.d.ts.map +1 -1
  180. package/server-dist/llm/types.js +10 -1
  181. package/server-dist/llm/types.js.map +1 -1
  182. package/server-dist/routes/analysis.d.ts.map +1 -1
  183. package/server-dist/routes/analysis.js +107 -282
  184. package/server-dist/routes/analysis.js.map +1 -1
  185. package/server-dist/routes/analytics.d.ts.map +1 -1
  186. package/server-dist/routes/analytics.js +3 -1
  187. package/server-dist/routes/analytics.js.map +1 -1
  188. package/server-dist/routes/export.d.ts.map +1 -1
  189. package/server-dist/routes/export.js +19 -27
  190. package/server-dist/routes/export.js.map +1 -1
  191. package/server-dist/routes/facets.d.ts +4 -0
  192. package/server-dist/routes/facets.d.ts.map +1 -0
  193. package/server-dist/routes/facets.js +208 -0
  194. package/server-dist/routes/facets.js.map +1 -0
  195. package/server-dist/routes/insights.d.ts.map +1 -1
  196. package/server-dist/routes/insights.js +12 -11
  197. package/server-dist/routes/insights.js.map +1 -1
  198. package/server-dist/routes/reflect.d.ts +4 -0
  199. package/server-dist/routes/reflect.d.ts.map +1 -0
  200. package/server-dist/routes/reflect.js +332 -0
  201. package/server-dist/routes/reflect.js.map +1 -0
  202. package/server-dist/routes/route-helpers.d.ts +124 -0
  203. package/server-dist/routes/route-helpers.d.ts.map +1 -0
  204. package/server-dist/routes/route-helpers.js +242 -0
  205. package/server-dist/routes/route-helpers.js.map +1 -0
  206. package/server-dist/routes/sessions.d.ts.map +1 -1
  207. package/server-dist/routes/sessions.js +29 -5
  208. package/server-dist/routes/sessions.js.map +1 -1
  209. package/server-dist/routes/shared-aggregation.d.ts +82 -0
  210. package/server-dist/routes/shared-aggregation.d.ts.map +1 -0
  211. package/server-dist/routes/shared-aggregation.js +384 -0
  212. package/server-dist/routes/shared-aggregation.js.map +1 -0
  213. package/dashboard-dist/assets/index-BaKju1iW.js +0 -607
  214. package/dashboard-dist/assets/index-_SWpRg6C.css +0 -1
@@ -0,0 +1,332 @@
1
+ import { Hono } from 'hono';
2
+ import { streamSSE } from 'hono/streaming';
3
+ import { getDb } from '@code-insights/cli/db/client';
4
+ import { jsonrepair } from 'jsonrepair';
5
+ import { createLLMClient } from '../llm/client.js';
6
+ import { requireLLM } from './route-helpers.js';
7
+ import { extractJsonPayload } from '../llm/response-parsers.js';
8
+ import { FRICTION_WINS_SYSTEM_PROMPT, generateFrictionWinsPrompt, RULES_SKILLS_SYSTEM_PROMPT, generateRulesSkillsPrompt, WORKING_STYLE_SYSTEM_PROMPT, generateWorkingStylePrompt, } from '../llm/reflect-prompts.js';
9
+ import { buildWhereClause, buildPeriodFilter, parseIsoWeek, formatIsoWeek, getAggregatedData } from './shared-aggregation.js';
10
+ const app = new Hono();
11
+ const MIN_FACETS_FOR_REFLECT = 8;
12
+ const ALL_SECTIONS = ['friction-wins', 'rules-skills', 'working-style'];
13
+ // Parse LLM JSON response wrapped in <json>...</json> tags with jsonrepair fallback.
14
+ function parseLLMJson(response) {
15
+ const payload = extractJsonPayload(response);
16
+ if (!payload)
17
+ return null;
18
+ try {
19
+ return JSON.parse(payload);
20
+ }
21
+ catch {
22
+ try {
23
+ return JSON.parse(jsonrepair(payload));
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ }
30
+ // Detect the dominant source tool from the database to target artifact generation.
31
+ function detectTargetTool(db) {
32
+ const row = db.prepare(`SELECT source_tool, COUNT(*) as count FROM sessions WHERE deleted_at IS NULL GROUP BY source_tool ORDER BY count DESC LIMIT 1`).get();
33
+ return row?.source_tool || 'claude-code';
34
+ }
35
+ // POST /api/reflect/generate
36
+ // Body: { sections?: ReflectSection[], period?: string, project?: string, source?: string }
37
+ // SSE endpoint: aggregates facets in code, then calls synthesis prompts for each section.
38
+ // Streams progress events so the UI can show phase-by-phase progress.
39
+ app.post('/generate', requireLLM(), async (c) => {
40
+ const body = await c.req.json();
41
+ const sections = body.sections && body.sections.length > 0 ? body.sections : ALL_SECTIONS;
42
+ const period = body.period || '30d';
43
+ const db = getDb();
44
+ const { where, params } = buildWhereClause(period, body.project, body.source);
45
+ return streamSSE(c, async (stream) => {
46
+ const abortSignal = c.req.raw.signal;
47
+ try {
48
+ await stream.writeSSE({
49
+ event: 'progress',
50
+ data: JSON.stringify({ phase: 'aggregating', message: 'Aggregating facets...' }),
51
+ });
52
+ const aggregated = getAggregatedData(db, where, params, body.project, body.source);
53
+ if (aggregated.totalSessions === 0) {
54
+ await stream.writeSSE({
55
+ event: 'error',
56
+ data: JSON.stringify({ error: 'No sessions with facets found. Run analysis first.' }),
57
+ });
58
+ return;
59
+ }
60
+ if (aggregated.totalSessions < MIN_FACETS_FOR_REFLECT) {
61
+ await stream.writeSSE({
62
+ event: 'error',
63
+ data: JSON.stringify({
64
+ error: `Need at least ${MIN_FACETS_FOR_REFLECT} analyzed sessions for meaningful pattern synthesis. Currently have ${aggregated.totalSessions}. Run session analysis on more sessions first.`,
65
+ code: 'INSUFFICIENT_FACETS',
66
+ current: aggregated.totalSessions,
67
+ required: MIN_FACETS_FOR_REFLECT,
68
+ }),
69
+ });
70
+ return;
71
+ }
72
+ const client = createLLMClient();
73
+ const results = {};
74
+ const targetTool = detectTargetTool(db);
75
+ for (const section of sections) {
76
+ if (abortSignal.aborted)
77
+ break;
78
+ await stream.writeSSE({
79
+ event: 'progress',
80
+ data: JSON.stringify({ phase: 'synthesizing', section, message: `Generating ${section}...` }),
81
+ });
82
+ if (section === 'friction-wins') {
83
+ const prompt = generateFrictionWinsPrompt({
84
+ frictionCategories: aggregated.frictionCategories,
85
+ effectivePatterns: aggregated.effectivePatterns,
86
+ totalSessions: aggregated.totalSessions,
87
+ period,
88
+ pqSignals: (aggregated.pqDeficits.length || aggregated.pqStrengths.length)
89
+ ? { deficits: aggregated.pqDeficits, strengths: aggregated.pqStrengths }
90
+ : undefined,
91
+ });
92
+ const response = await client.chat([
93
+ { role: 'system', content: FRICTION_WINS_SYSTEM_PROMPT },
94
+ { role: 'user', content: prompt },
95
+ ], { signal: abortSignal });
96
+ const parsed = parseLLMJson(response.content);
97
+ results['friction-wins'] = {
98
+ section: 'friction-wins',
99
+ ...(parsed ?? {}),
100
+ frictionCategories: aggregated.frictionCategories,
101
+ effectivePatterns: aggregated.effectivePatterns,
102
+ pqDeficits: aggregated.pqDeficits,
103
+ pqStrengths: aggregated.pqStrengths,
104
+ generatedAt: new Date().toISOString(),
105
+ };
106
+ }
107
+ else if (section === 'rules-skills') {
108
+ // Only include patterns with sufficient occurrence counts for actionable artifacts
109
+ const recurringFriction = aggregated.frictionCategories.filter(fc => fc.count >= 3);
110
+ const recurringPatterns = aggregated.effectivePatterns.filter(ep => ep.frequency >= 2);
111
+ const prompt = generateRulesSkillsPrompt({
112
+ recurringFriction,
113
+ effectivePatterns: recurringPatterns,
114
+ targetTool,
115
+ });
116
+ const response = await client.chat([
117
+ { role: 'system', content: RULES_SKILLS_SYSTEM_PROMPT },
118
+ { role: 'user', content: prompt },
119
+ ], { signal: abortSignal });
120
+ const parsed = parseLLMJson(response.content);
121
+ results['rules-skills'] = {
122
+ section: 'rules-skills',
123
+ ...(parsed ?? {}),
124
+ targetTool,
125
+ generatedAt: new Date().toISOString(),
126
+ };
127
+ }
128
+ else if (section === 'working-style') {
129
+ const prompt = generateWorkingStylePrompt({
130
+ workflowDistribution: aggregated.workflowDistribution,
131
+ outcomeDistribution: aggregated.outcomeDistribution,
132
+ characterDistribution: aggregated.characterDistribution,
133
+ totalSessions: aggregated.totalSessions,
134
+ period,
135
+ frictionFrequency: aggregated.frictionTotal,
136
+ });
137
+ const response = await client.chat([
138
+ { role: 'system', content: WORKING_STYLE_SYSTEM_PROMPT },
139
+ { role: 'user', content: prompt },
140
+ ], { signal: abortSignal });
141
+ const parsed = parseLLMJson(response.content);
142
+ // Sanitize tagline: must be a string ≤40 chars. Non-string LLM outputs
143
+ // (object, array, number) are normalized to undefined so they don't
144
+ // corrupt the snapshot blob stored in SQLite.
145
+ const rawTagline = parsed && parsed['tagline'];
146
+ const tagline = typeof rawTagline === 'string'
147
+ ? rawTagline.slice(0, 40)
148
+ : undefined;
149
+ results['working-style'] = {
150
+ section: 'working-style',
151
+ ...(parsed ?? {}),
152
+ tagline,
153
+ workflowDistribution: aggregated.workflowDistribution,
154
+ outcomeDistribution: aggregated.outcomeDistribution,
155
+ characterDistribution: aggregated.characterDistribution,
156
+ generatedAt: new Date().toISOString(),
157
+ };
158
+ }
159
+ }
160
+ // Only save snapshot if the request was not aborted mid-generation.
161
+ // Saving partial results would cause stale/incomplete data to auto-load on next visit.
162
+ if (!c.req.raw.signal.aborted) {
163
+ // For ISO week periods, use the exact week boundaries as window metadata.
164
+ // This ensures the snapshot metadata accurately reflects the week it covers.
165
+ const isoWeekBounds = parseIsoWeek(period);
166
+ const windowStart = isoWeekBounds
167
+ ? isoWeekBounds.start.toISOString()
168
+ : buildPeriodFilter(period);
169
+ const windowEnd = isoWeekBounds
170
+ ? isoWeekBounds.end.toISOString()
171
+ : new Date().toISOString();
172
+ const projectKey = body.project || '__all__';
173
+ db.prepare(`
174
+ INSERT INTO reflect_snapshots (period, project_id, results_json, generated_at, window_start, window_end, session_count, facet_count)
175
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
176
+ ON CONFLICT(period, project_id) DO UPDATE SET
177
+ results_json = excluded.results_json,
178
+ generated_at = excluded.generated_at,
179
+ window_start = excluded.window_start,
180
+ window_end = excluded.window_end,
181
+ session_count = excluded.session_count,
182
+ facet_count = excluded.facet_count
183
+ `).run(period, projectKey, JSON.stringify(results), windowEnd, windowStart, windowEnd, aggregated.totalSessions, aggregated.frictionTotal);
184
+ }
185
+ await stream.writeSSE({
186
+ event: 'complete',
187
+ data: JSON.stringify({ results }),
188
+ });
189
+ }
190
+ catch (err) {
191
+ const message = err instanceof Error ? err.message : 'Unknown error';
192
+ await stream.writeSSE({
193
+ event: 'error',
194
+ data: JSON.stringify({ error: message }),
195
+ }).catch(() => { });
196
+ }
197
+ });
198
+ });
199
+ // GET /api/reflect/results
200
+ // Returns raw aggregated facet data without LLM synthesis (fast, no cost).
201
+ // The full synthesized view requires POST /api/reflect/generate.
202
+ app.get('/results', (c) => {
203
+ const db = getDb();
204
+ const period = c.req.query('period') || '30d';
205
+ const project = c.req.query('project');
206
+ const source = c.req.query('source');
207
+ const { where, params } = buildWhereClause(period, project, source);
208
+ const aggregated = getAggregatedData(db, where, params, project, source);
209
+ return c.json(aggregated);
210
+ });
211
+ // GET /api/reflect/weeks
212
+ // Returns all ISO weeks from the earliest session through the current week, with snapshot status.
213
+ // Used by the WeekSelector component to show which weeks have reflections.
214
+ // Week list is data-driven (not capped at 8) so users with long history can navigate all weeks.
215
+ app.get('/weeks', (c) => {
216
+ const db = getDb();
217
+ const project = c.req.query('project');
218
+ const projectCondition = project ? 'AND project_id = ?' : '';
219
+ const projectParams = project ? [project] : [];
220
+ // Find the earliest session to determine the full week range.
221
+ // If no sessions exist, return empty array.
222
+ const earliestRow = db.prepare(`
223
+ SELECT MIN(started_at) as earliest
224
+ FROM sessions
225
+ WHERE deleted_at IS NULL
226
+ ${projectCondition}
227
+ `).get(...projectParams);
228
+ if (!earliestRow?.earliest) {
229
+ return c.json({ weeks: [] });
230
+ }
231
+ // Compute the UTC midnight of the Monday of the current ISO week.
232
+ // Truncating to midnight ensures the while-loop comparison is stable
233
+ // regardless of the time-of-day component in started_at values.
234
+ const now = new Date();
235
+ const nowDay = now.getUTCDay(); // 0=Sun
236
+ const daysToMonday = nowDay === 0 ? 6 : nowDay - 1;
237
+ const thisMondayMs = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) - daysToMonday * 86400000;
238
+ // Compute the UTC midnight of the Monday of the earliest session's ISO week.
239
+ const earliestDate = new Date(earliestRow.earliest);
240
+ const earliestDay = earliestDate.getUTCDay();
241
+ const daysToEarliestMonday = earliestDay === 0 ? 6 : earliestDay - 1;
242
+ const earliestMondayMs = Date.UTC(earliestDate.getUTCFullYear(), earliestDate.getUTCMonth(), earliestDate.getUTCDate()) - daysToEarliestMonday * 86400000;
243
+ // Generate all weeks from earliest through current (most recent first).
244
+ // Cap at 520 weeks (~10 years) to stay under SQLite's 999 bind-variable limit
245
+ // for the snapshot IN (...) query (each week uses 1 placeholder + 1 for project_id).
246
+ const MAX_WEEKS = 520;
247
+ const weekEntries = [];
248
+ let weekMondayMs = thisMondayMs;
249
+ while (weekMondayMs >= earliestMondayMs && weekEntries.length < MAX_WEEKS) {
250
+ const weekMonday = new Date(weekMondayMs);
251
+ const week = formatIsoWeek(weekMonday);
252
+ const bounds = parseIsoWeek(week);
253
+ weekEntries.push({ week, start: bounds.start.toISOString(), end: bounds.end.toISOString() });
254
+ weekMondayMs -= 7 * 86400000;
255
+ }
256
+ const projectKey = project || '__all__';
257
+ // Query 1: session counts per ISO week using GROUP BY.
258
+ // The Thursday trick: 'weekday 4' advances to the ISO week's Thursday (or stays if already
259
+ // Thursday), then '-3 days' gives that week's Monday. This handles all 7 days correctly —
260
+ // unlike 'weekday 1, -7 days' which overshoots by one week for sessions starting on Monday.
261
+ // Returns one row per week that has sessions — O(sessions), not O(weeks).
262
+ const rangeStart = weekEntries[weekEntries.length - 1].start;
263
+ const rangeEnd = weekEntries[0].end;
264
+ const sessionCountRaw = db.prepare(`
265
+ SELECT
266
+ date(s.started_at, 'weekday 4', '-3 days') as week_monday,
267
+ COUNT(*) as cnt
268
+ FROM sessions s
269
+ WHERE s.deleted_at IS NULL
270
+ AND s.started_at >= ?
271
+ AND s.started_at < ?
272
+ ${projectCondition}
273
+ GROUP BY week_monday
274
+ `).all(rangeStart, rangeEnd, ...projectParams);
275
+ // Build a map from ISO week string -> session count.
276
+ // week_monday is a YYYY-MM-DD string (SQLite date()); parse it and derive the ISO week string.
277
+ const sessionCountMap = new Map();
278
+ for (const row of sessionCountRaw) {
279
+ const monday = new Date(row.week_monday + 'T00:00:00Z');
280
+ const weekStr = formatIsoWeek(monday);
281
+ // Accumulate in case formatIsoWeek maps two slightly-different Mondays to the same week
282
+ sessionCountMap.set(weekStr, (sessionCountMap.get(weekStr) ?? 0) + row.cnt);
283
+ }
284
+ // Query 2: all snapshots for these weeks in one query
285
+ const weekPlaceholders = weekEntries.map(() => '?').join(', ');
286
+ const snapshotRows = db.prepare(`
287
+ SELECT period, generated_at
288
+ FROM reflect_snapshots
289
+ WHERE period IN (${weekPlaceholders}) AND project_id = ?
290
+ `).all(...weekEntries.map(e => e.week), projectKey);
291
+ const snapshotMap = new Map(snapshotRows.map(r => [r.period, r.generated_at]));
292
+ const weeks = weekEntries.map((entry) => ({
293
+ week: entry.week,
294
+ sessionCount: sessionCountMap.get(entry.week) ?? 0,
295
+ hasSnapshot: snapshotMap.has(entry.week),
296
+ generatedAt: snapshotMap.get(entry.week) ?? null,
297
+ }));
298
+ return c.json({ weeks });
299
+ });
300
+ // GET /api/reflect/snapshot
301
+ // Returns cached reflect results for a given period/project combo.
302
+ app.get('/snapshot', (c) => {
303
+ const db = getDb();
304
+ const period = c.req.query('period') || '30d';
305
+ const project = c.req.query('project') || '__all__';
306
+ const row = db.prepare(`SELECT * FROM reflect_snapshots WHERE period = ? AND project_id = ?`).get(period, project);
307
+ if (!row) {
308
+ return c.json({ snapshot: null });
309
+ }
310
+ let results;
311
+ try {
312
+ results = JSON.parse(row.results_json);
313
+ }
314
+ catch {
315
+ // Corrupted snapshot data — treat as if no snapshot exists
316
+ return c.json({ snapshot: null });
317
+ }
318
+ return c.json({
319
+ snapshot: {
320
+ period: row.period,
321
+ projectId: row.project_id,
322
+ results,
323
+ generatedAt: row.generated_at,
324
+ windowStart: row.window_start,
325
+ windowEnd: row.window_end,
326
+ sessionCount: row.session_count,
327
+ facetCount: row.facet_count,
328
+ },
329
+ });
330
+ });
331
+ export default app;
332
+ //# sourceMappingURL=reflect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reflect.js","sourceRoot":"","sources":["../../src/routes/reflect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import type { Context, MiddlewareHandler } from 'hono';
2
+ import { streamSSE } from 'hono/streaming';
3
+ import { getDb } from '@code-insights/cli/db/client';
4
+ import type { AnalysisResult, AnalysisOptions } from '../llm/analysis.js';
5
+ import type { SessionData } from '../llm/analysis-db.js';
6
+ import type { SQLiteMessageRow } from '../llm/prompt-types.js';
7
+ /**
8
+ * Load a session row for LLM analysis. Returns undefined if the session doesn't exist
9
+ * or has been soft-deleted. The selected columns match exactly what the analysis engine
10
+ * expects via the SessionData interface.
11
+ */
12
+ export declare function loadSessionForAnalysis(db: ReturnType<typeof getDb>, sessionId: string): SessionData | undefined;
13
+ /**
14
+ * Load all messages for a session, ordered by timestamp ascending.
15
+ * The selected columns match the SQLiteMessageRow interface consumed by the analysis engine.
16
+ */
17
+ export declare function loadSessionMessages(db: ReturnType<typeof getDb>, sessionId: string): SQLiteMessageRow[];
18
+ /**
19
+ * Hono middleware factory that short-circuits with a 400 if no LLM provider is configured.
20
+ * Apply per-route: app.post('/route', requireLLM(), async (c) => { ... })
21
+ * The error shape { success: false, error: '...' } matches the analysis endpoint convention.
22
+ */
23
+ export declare function requireLLM(): MiddlewareHandler;
24
+ /**
25
+ * Options for trackAnalysisResult. Callers may supply an onSuccess callback for
26
+ * any handler-specific events (e.g., insight_generated) that fire only on success.
27
+ */
28
+ interface TrackAnalysisOptions {
29
+ /** Called after the success-path analysis_run event — emit extra events here. */
30
+ onSuccess?: () => void;
31
+ }
32
+ /**
33
+ * Emit the 'analysis_run' telemetry event (and captureError on failure) for a
34
+ * completed analysis call. Encapsulates the boilerplate shared across the session,
35
+ * prompt-quality, and recurring handlers in analysis.ts.
36
+ *
37
+ * Usage:
38
+ * trackAnalysisResult('session', result, startTime, {
39
+ * onSuccess: () => trackEvent('insight_generated', { type: 'session', count: result.insights.length }),
40
+ * });
41
+ */
42
+ export declare function trackAnalysisResult(analysisType: string, result: Pick<AnalysisResult, 'success' | 'error' | 'error_type' | 'response_preview'>, startTime: number, options?: TrackAnalysisOptions): void;
43
+ /**
44
+ * Build the human-readable progress message for a session analysis stream event.
45
+ * The session analysis handler uses chunk-count information; the prompt-quality
46
+ * handler uses a fixed string. Accept a resolver callback so each caller decides.
47
+ */
48
+ type ProgressMessageFn = (progress: {
49
+ phase: string;
50
+ currentChunk?: number;
51
+ totalChunks?: number;
52
+ }) => string;
53
+ /**
54
+ * Options for streamSessionAnalysis.
55
+ */
56
+ interface StreamSessionAnalysisOptions {
57
+ /**
58
+ * Human-readable telemetry type string (e.g. 'session', 'prompt-quality').
59
+ * Used as the `type` field in the analysis_run event.
60
+ */
61
+ analysisType: string;
62
+ /**
63
+ * The analysis function to call. Must match the signature of analyzeSession /
64
+ * analyzePromptQuality — accepts (session, messages, options?) and returns AnalysisResult.
65
+ */
66
+ analysisFn: (session: SessionData, messages: SQLiteMessageRow[], options?: AnalysisOptions) => Promise<AnalysisResult>;
67
+ /**
68
+ * Resolve the progress event message from the current progress state.
69
+ * Called inside the onProgress callback to build the human-readable 'message' field.
70
+ */
71
+ progressMessage: ProgressMessageFn;
72
+ /**
73
+ * Called on the success path after tracking telemetry (e.g. to apply a generated title
74
+ * or emit an insight_generated event). Receives the successful AnalysisResult.
75
+ */
76
+ onSuccess?: (result: AnalysisResult) => void;
77
+ }
78
+ /**
79
+ * Shared SSE lifecycle for single-session analysis stream endpoints.
80
+ * Handles the streamSSE wrapper, abort signal, progress events, result telemetry,
81
+ * and complete/error SSE events. The two analysis stream handlers (session and
82
+ * prompt-quality) differed only in their progress message and analysis function —
83
+ * those are injected via options.
84
+ *
85
+ * Preserves the exact SSE event names and complete/error payload shapes the
86
+ * dashboard expects: progress → { phase, message, ...progress }, complete →
87
+ * { success, insightCount, tokenUsage }, error → { error }.
88
+ */
89
+ export declare function streamSessionAnalysis(c: Context, session: SessionData, messages: SQLiteMessageRow[], opts: StreamSessionAnalysisOptions): ReturnType<typeof streamSSE>;
90
+ /**
91
+ * Options for streamBatchBackfill. The two backfill handlers (facets and PQ) share
92
+ * the same loop structure but differ in how they check for existing work and which
93
+ * analysis function they call.
94
+ */
95
+ export interface StreamBatchBackfillOptions {
96
+ /**
97
+ * Returns true if the session should be skipped (work already done).
98
+ * Only called when force=false; callers implement the appropriate SQL check.
99
+ */
100
+ shouldSkip: (sessionId: string) => boolean;
101
+ /**
102
+ * The analysis function to run for each session. Receives the loaded session,
103
+ * its messages, and an AbortSignal. Returns a result with a success boolean.
104
+ */
105
+ analysisFn: (session: SessionData, messages: SQLiteMessageRow[], options: {
106
+ signal: AbortSignal;
107
+ }) => Promise<{
108
+ success: boolean;
109
+ error?: string;
110
+ }>;
111
+ }
112
+ /**
113
+ * Shared SSE lifecycle for batch backfill endpoints (facets backfill and PQ backfill).
114
+ * Iterates sessionIds one-by-one, streaming per-session progress events and a final
115
+ * complete event. Preserves the exact SSE payload shapes the dashboard expects:
116
+ * progress → { completed, failed, total, currentSessionId, error? }
117
+ * complete → { completed, failed, total }
118
+ *
119
+ * The caller is responsible for request body validation (sessionIds array present,
120
+ * length ≤ MAX_BACKFILL_SESSIONS) before calling this helper.
121
+ */
122
+ export declare function streamBatchBackfill(c: Context, sessionIds: string[], force: boolean, opts: StreamBatchBackfillOptions): ReturnType<typeof streamSSE>;
123
+ export {};
124
+ //# sourceMappingURL=route-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../src/routes/route-helpers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAIrD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAM/G;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAKvG;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,iBAAiB,CAU9C;AAID;;;GAGG;AACH,UAAU,oBAAoB;IAC5B,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,kBAAkB,CAAC,EACrF,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,IAAI,CAuBN;AAID;;;;GAIG;AACH,KAAK,iBAAiB,GAAG,CAAC,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,MAAM,CAAC;AAE9G;;GAEG;AACH,UAAU,4BAA4B;IACpC;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,EAAE,CACV,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,OAAO,CAAC,EAAE,eAAe,KACtB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B;;;OAGG;IACH,eAAe,EAAE,iBAAiB,CAAC;IACnC;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,EAAE,OAAO,EACV,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,IAAI,EAAE,4BAA4B,GACjC,UAAU,CAAC,OAAO,SAAS,CAAC,CA0F9B;AAID;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C;;;OAGG;IACH,UAAU,EAAE,CACV,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,OAAO,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,KAC7B,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,OAAO,EACV,UAAU,EAAE,MAAM,EAAE,EACpB,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,0BAA0B,GAC/B,UAAU,CAAC,OAAO,SAAS,CAAC,CA2D9B"}