@massu/core 0.1.1 → 0.1.2

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 (87) hide show
  1. package/README.md +2 -2
  2. package/dist/hooks/cost-tracker.js +23 -35
  3. package/dist/hooks/post-edit-context.js +2 -2
  4. package/dist/hooks/post-tool-use.js +43 -58
  5. package/dist/hooks/pre-compact.js +23 -38
  6. package/dist/hooks/pre-delete-check.js +18 -31
  7. package/dist/hooks/quality-event.js +23 -35
  8. package/dist/hooks/session-end.js +62 -78
  9. package/dist/hooks/session-start.js +33 -42
  10. package/dist/hooks/user-prompt.js +23 -38
  11. package/package.json +8 -14
  12. package/src/adr-generator.ts +9 -2
  13. package/src/analytics.ts +9 -3
  14. package/src/audit-trail.ts +10 -3
  15. package/src/cloud-sync.ts +14 -18
  16. package/src/commands/init.ts +1 -5
  17. package/src/cost-tracker.ts +11 -6
  18. package/src/dependency-scorer.ts +9 -2
  19. package/src/docs-tools.ts +13 -10
  20. package/src/hooks/post-edit-context.ts +3 -3
  21. package/src/hooks/session-end.ts +3 -3
  22. package/src/hooks/session-start.ts +2 -2
  23. package/src/memory-db.ts +1351 -23
  24. package/src/memory-tools.ts +14 -15
  25. package/src/observability-tools.ts +13 -2
  26. package/src/prompt-analyzer.ts +9 -2
  27. package/src/regression-detector.ts +9 -3
  28. package/src/security-scorer.ts +9 -2
  29. package/src/sentinel-db.ts +43 -88
  30. package/src/sentinel-tools.ts +8 -11
  31. package/src/server.ts +1 -2
  32. package/src/team-knowledge.ts +9 -2
  33. package/src/tools.ts +771 -35
  34. package/src/validate-features-runner.ts +0 -1
  35. package/src/validation-engine.ts +9 -2
  36. package/dist/cli.js +0 -7890
  37. package/dist/server.js +0 -7008
  38. package/src/__tests__/adr-generator.test.ts +0 -260
  39. package/src/__tests__/analytics.test.ts +0 -282
  40. package/src/__tests__/audit-trail.test.ts +0 -382
  41. package/src/__tests__/backfill-sessions.test.ts +0 -690
  42. package/src/__tests__/cli.test.ts +0 -290
  43. package/src/__tests__/cloud-sync.test.ts +0 -261
  44. package/src/__tests__/config-sections.test.ts +0 -359
  45. package/src/__tests__/config.test.ts +0 -732
  46. package/src/__tests__/cost-tracker.test.ts +0 -348
  47. package/src/__tests__/db.test.ts +0 -177
  48. package/src/__tests__/dependency-scorer.test.ts +0 -325
  49. package/src/__tests__/docs-integration.test.ts +0 -178
  50. package/src/__tests__/docs-tools.test.ts +0 -199
  51. package/src/__tests__/domains.test.ts +0 -236
  52. package/src/__tests__/hooks.test.ts +0 -221
  53. package/src/__tests__/import-resolver.test.ts +0 -95
  54. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  55. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  56. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  57. package/src/__tests__/memory-db.test.ts +0 -404
  58. package/src/__tests__/memory-enhancements.test.ts +0 -316
  59. package/src/__tests__/memory-tools.test.ts +0 -199
  60. package/src/__tests__/middleware-tree.test.ts +0 -177
  61. package/src/__tests__/observability-tools.test.ts +0 -595
  62. package/src/__tests__/observability.test.ts +0 -437
  63. package/src/__tests__/observation-extractor.test.ts +0 -167
  64. package/src/__tests__/page-deps.test.ts +0 -60
  65. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  66. package/src/__tests__/regression-detector.test.ts +0 -295
  67. package/src/__tests__/rules.test.ts +0 -87
  68. package/src/__tests__/schema-mapper.test.ts +0 -29
  69. package/src/__tests__/security-scorer.test.ts +0 -238
  70. package/src/__tests__/security-utils.test.ts +0 -175
  71. package/src/__tests__/sentinel-db.test.ts +0 -491
  72. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  73. package/src/__tests__/sentinel-tools.test.ts +0 -324
  74. package/src/__tests__/sentinel-types.test.ts +0 -750
  75. package/src/__tests__/server.test.ts +0 -452
  76. package/src/__tests__/session-archiver.test.ts +0 -524
  77. package/src/__tests__/session-state-generator.test.ts +0 -900
  78. package/src/__tests__/team-knowledge.test.ts +0 -327
  79. package/src/__tests__/tools.test.ts +0 -340
  80. package/src/__tests__/transcript-parser.test.ts +0 -195
  81. package/src/__tests__/trpc-index.test.ts +0 -25
  82. package/src/__tests__/validate-features-runner.test.ts +0 -517
  83. package/src/__tests__/validation-engine.test.ts +0 -300
  84. package/src/core-tools.ts +0 -685
  85. package/src/memory-queries.ts +0 -804
  86. package/src/memory-schema.ts +0 -546
  87. package/src/tool-helpers.ts +0 -41
@@ -1,348 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
- import Database from 'better-sqlite3';
6
- import {
7
- getCostToolDefinitions,
8
- isCostTool,
9
- extractTokenUsage,
10
- calculateCost,
11
- storeSessionCost,
12
- backfillSessionCosts,
13
- handleCostToolCall,
14
- type TokenUsage,
15
- type CostResult,
16
- } from '../cost-tracker.ts';
17
- import type { TranscriptEntry } from '../transcript-parser.ts';
18
-
19
- function createTestDb(): Database.Database {
20
- const db = new Database(':memory:');
21
- db.pragma('journal_mode = WAL');
22
-
23
- db.exec(`
24
- CREATE TABLE sessions (
25
- id INTEGER PRIMARY KEY AUTOINCREMENT,
26
- session_id TEXT UNIQUE NOT NULL,
27
- started_at TEXT NOT NULL,
28
- started_at_epoch INTEGER NOT NULL
29
- );
30
-
31
- CREATE TABLE session_costs (
32
- id INTEGER PRIMARY KEY AUTOINCREMENT,
33
- session_id TEXT NOT NULL UNIQUE,
34
- model TEXT,
35
- input_tokens INTEGER NOT NULL DEFAULT 0,
36
- output_tokens INTEGER NOT NULL DEFAULT 0,
37
- cache_read_tokens INTEGER NOT NULL DEFAULT 0,
38
- cache_write_tokens INTEGER NOT NULL DEFAULT 0,
39
- total_tokens INTEGER NOT NULL DEFAULT 0,
40
- estimated_cost_usd REAL NOT NULL DEFAULT 0.0,
41
- created_at TEXT DEFAULT (datetime('now'))
42
- );
43
-
44
- CREATE TABLE feature_costs (
45
- id INTEGER PRIMARY KEY AUTOINCREMENT,
46
- feature_key TEXT NOT NULL,
47
- session_id TEXT NOT NULL,
48
- tokens_used INTEGER NOT NULL DEFAULT 0,
49
- estimated_cost_usd REAL NOT NULL DEFAULT 0.0,
50
- created_at TEXT DEFAULT (datetime('now'))
51
- );
52
- `);
53
-
54
- return db;
55
- }
56
-
57
- describe('cost-tracker', () => {
58
- let db: Database.Database;
59
-
60
- beforeEach(() => {
61
- db = createTestDb();
62
- });
63
-
64
- afterEach(() => {
65
- db.close();
66
- });
67
-
68
- describe('getCostToolDefinitions', () => {
69
- it('should return tool definitions for cost tools', () => {
70
- const defs = getCostToolDefinitions();
71
- expect(defs.length).toBe(3);
72
-
73
- const names = defs.map(d => d.name);
74
- expect(names.some(n => n.includes('cost_session'))).toBe(true);
75
- expect(names.some(n => n.includes('cost_trend'))).toBe(true);
76
- expect(names.some(n => n.includes('cost_feature'))).toBe(true);
77
-
78
- for (const def of defs) {
79
- expect(def.name).toBeTruthy();
80
- expect(def.description).toBeTruthy();
81
- expect(def.inputSchema).toBeTruthy();
82
- expect(def.inputSchema.type).toBe('object');
83
- }
84
- });
85
- });
86
-
87
- describe('isCostTool', () => {
88
- it('should return true for cost tools', () => {
89
- expect(isCostTool('massu_cost_session')).toBe(true);
90
- expect(isCostTool('massu_cost_trend')).toBe(true);
91
- expect(isCostTool('massu_cost_feature')).toBe(true);
92
- });
93
-
94
- it('should return false for non-cost tools', () => {
95
- expect(isCostTool('massu_quality_score')).toBe(false);
96
- expect(isCostTool('massu_audit_log')).toBe(false);
97
- expect(isCostTool('random_tool')).toBe(false);
98
- });
99
-
100
- it('should handle base names without prefix', () => {
101
- expect(isCostTool('cost_session')).toBe(true);
102
- expect(isCostTool('cost_trend')).toBe(true);
103
- });
104
- });
105
-
106
- describe('extractTokenUsage', () => {
107
- it('should extract token usage from transcript entries', () => {
108
- const entries: TranscriptEntry[] = [
109
- {
110
- type: 'assistant',
111
- message: {
112
- usage: {
113
- input_tokens: 1000,
114
- output_tokens: 500,
115
- cache_read_input_tokens: 200,
116
- cache_creation_input_tokens: 100,
117
- },
118
- model: 'claude-sonnet-4-5',
119
- } as Record<string, unknown>,
120
- } as TranscriptEntry,
121
- {
122
- type: 'assistant',
123
- message: {
124
- usage: {
125
- input_tokens: 800,
126
- output_tokens: 400,
127
- cache_read_tokens: 50,
128
- cache_write_tokens: 25,
129
- },
130
- model: 'claude-sonnet-4-5',
131
- } as Record<string, unknown>,
132
- } as TranscriptEntry,
133
- ];
134
-
135
- const usage = extractTokenUsage(entries);
136
- expect(usage.inputTokens).toBe(1800);
137
- expect(usage.outputTokens).toBe(900);
138
- expect(usage.cacheReadTokens).toBe(250);
139
- expect(usage.cacheWriteTokens).toBe(125);
140
- expect(usage.model).toBe('claude-sonnet-4-5');
141
- });
142
-
143
- it('should return zero tokens for empty entries', () => {
144
- const usage = extractTokenUsage([]);
145
- expect(usage.inputTokens).toBe(0);
146
- expect(usage.outputTokens).toBe(0);
147
- expect(usage.cacheReadTokens).toBe(0);
148
- expect(usage.cacheWriteTokens).toBe(0);
149
- expect(usage.model).toBe('unknown');
150
- });
151
- });
152
-
153
- describe('calculateCost', () => {
154
- it('should calculate cost from token usage', () => {
155
- const usage: TokenUsage = {
156
- inputTokens: 1_000_000,
157
- outputTokens: 500_000,
158
- cacheReadTokens: 200_000,
159
- cacheWriteTokens: 100_000,
160
- model: 'claude-sonnet-4-5',
161
- };
162
-
163
- const cost = calculateCost(usage);
164
-
165
- expect(cost.totalCost).toBeGreaterThan(0);
166
- expect(cost.inputCost).toBeGreaterThan(0);
167
- expect(cost.outputCost).toBeGreaterThan(0);
168
- // Cache costs may be 0 if pricing doesn't include cache_read/write rates
169
- expect(cost.cacheReadCost).toBeGreaterThanOrEqual(0);
170
- expect(cost.cacheWriteCost).toBeGreaterThanOrEqual(0);
171
- expect(cost.currency).toBe('USD');
172
- expect(cost.totalCost).toBe(
173
- cost.inputCost + cost.outputCost + cost.cacheReadCost + cost.cacheWriteCost
174
- );
175
- });
176
-
177
- it('should use default pricing for unknown models', () => {
178
- const usage: TokenUsage = {
179
- inputTokens: 1_000_000,
180
- outputTokens: 500_000,
181
- cacheReadTokens: 0,
182
- cacheWriteTokens: 0,
183
- model: 'unknown-model',
184
- };
185
-
186
- const cost = calculateCost(usage);
187
- expect(cost.totalCost).toBeGreaterThan(0);
188
- });
189
-
190
- it('should handle zero tokens', () => {
191
- const usage: TokenUsage = {
192
- inputTokens: 0,
193
- outputTokens: 0,
194
- cacheReadTokens: 0,
195
- cacheWriteTokens: 0,
196
- model: 'claude-sonnet-4-5',
197
- };
198
-
199
- const cost = calculateCost(usage);
200
- expect(cost.totalCost).toBe(0);
201
- expect(cost.inputCost).toBe(0);
202
- expect(cost.outputCost).toBe(0);
203
- });
204
- });
205
-
206
- describe('storeSessionCost', () => {
207
- it('should store session cost in database', () => {
208
- const sessionId = 'test-session-1';
209
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
210
- sessionId,
211
- new Date().toISOString(),
212
- Math.floor(Date.now() / 1000)
213
- );
214
-
215
- const usage: TokenUsage = {
216
- inputTokens: 1000,
217
- outputTokens: 500,
218
- cacheReadTokens: 200,
219
- cacheWriteTokens: 100,
220
- model: 'claude-sonnet-4-5',
221
- };
222
-
223
- const cost: CostResult = {
224
- totalCost: 0.015,
225
- inputCost: 0.003,
226
- outputCost: 0.0075,
227
- cacheReadCost: 0.0006,
228
- cacheWriteCost: 0.00375,
229
- currency: 'USD',
230
- };
231
-
232
- storeSessionCost(db, sessionId, usage, cost);
233
-
234
- const stored = db.prepare('SELECT * FROM session_costs WHERE session_id = ?').get(sessionId) as Record<string, unknown>;
235
- expect(stored).toBeDefined();
236
- expect(stored.session_id).toBe(sessionId);
237
- expect(stored.model).toBe('claude-sonnet-4-5');
238
- expect(stored.input_tokens).toBe(1000);
239
- expect(stored.output_tokens).toBe(500);
240
- expect(stored.cache_read_tokens).toBe(200);
241
- expect(stored.cache_write_tokens).toBe(100);
242
- expect(stored.total_tokens).toBe(1800);
243
- expect(stored.estimated_cost_usd).toBe(0.015);
244
- });
245
- });
246
-
247
- describe('backfillSessionCosts', () => {
248
- it('should return count of sessions without cost data', () => {
249
- const sessions = ['session-1', 'session-2', 'session-3'];
250
- for (const sid of sessions) {
251
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
252
- sid,
253
- new Date().toISOString(),
254
- Math.floor(Date.now() / 1000)
255
- );
256
- }
257
-
258
- const count = backfillSessionCosts(db);
259
- expect(count).toBe(3);
260
- });
261
-
262
- it('should not count sessions that already have cost data', () => {
263
- const sessionId = 'session-with-cost';
264
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
265
- sessionId,
266
- new Date().toISOString(),
267
- Math.floor(Date.now() / 1000)
268
- );
269
-
270
- const usage: TokenUsage = {
271
- inputTokens: 100,
272
- outputTokens: 50,
273
- cacheReadTokens: 0,
274
- cacheWriteTokens: 0,
275
- model: 'claude-sonnet-4-5',
276
- };
277
-
278
- const cost: CostResult = {
279
- totalCost: 0.001,
280
- inputCost: 0.0003,
281
- outputCost: 0.00075,
282
- cacheReadCost: 0,
283
- cacheWriteCost: 0,
284
- currency: 'USD',
285
- };
286
-
287
- storeSessionCost(db, sessionId, usage, cost);
288
-
289
- const count = backfillSessionCosts(db);
290
- expect(count).toBe(0);
291
- });
292
- });
293
-
294
- describe('handleCostToolCall', () => {
295
- it('should handle cost_session tool call', () => {
296
- const sessionId = 'test-session-2';
297
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
298
- sessionId,
299
- new Date().toISOString(),
300
- Math.floor(Date.now() / 1000)
301
- );
302
-
303
- const usage: TokenUsage = {
304
- inputTokens: 1000,
305
- outputTokens: 500,
306
- cacheReadTokens: 100,
307
- cacheWriteTokens: 50,
308
- model: 'claude-sonnet-4-5',
309
- };
310
-
311
- const cost: CostResult = {
312
- totalCost: 0.01,
313
- inputCost: 0.003,
314
- outputCost: 0.0075,
315
- cacheReadCost: 0.0003,
316
- cacheWriteCost: 0.0001875,
317
- currency: 'USD',
318
- };
319
-
320
- storeSessionCost(db, sessionId, usage, cost);
321
-
322
- const result = handleCostToolCall('massu_cost_session', { session_id: sessionId }, db);
323
-
324
- expect(result.content).toBeDefined();
325
- expect(result.content.length).toBeGreaterThan(0);
326
- expect(result.content[0].type).toBe('text');
327
- const text = result.content[0].text;
328
- expect(text).toContain('Session Cost');
329
- expect(text).toContain('Token Usage');
330
- });
331
-
332
- it('should return error for missing session_id', () => {
333
- const result = handleCostToolCall('massu_cost_session', {}, db);
334
-
335
- expect(result.content).toBeDefined();
336
- expect(result.content[0].type).toBe('text');
337
- expect(result.content[0].text).toContain('Usage');
338
- });
339
-
340
- it('should handle unknown tool name', () => {
341
- const result = handleCostToolCall('massu_unknown_cost_tool', {}, db);
342
-
343
- expect(result.content).toBeDefined();
344
- expect(result.content[0].type).toBe('text');
345
- expect(result.content[0].text).toContain('Unknown cost tool');
346
- });
347
- });
348
- });
@@ -1,177 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
5
- import Database from 'better-sqlite3';
6
- import { isDataStale, updateBuildTimestamp } from '../db.ts';
7
- import { unlinkSync, existsSync } from 'fs';
8
- import { resolve } from 'path';
9
-
10
- const TEST_DATA_DB_PATH = resolve(__dirname, '../test-data-db.db');
11
- const TEST_CODEGRAPH_DB_PATH = resolve(__dirname, '../test-codegraph-db.db');
12
-
13
- function createTestDataDb(): Database.Database {
14
- if (existsSync(TEST_DATA_DB_PATH)) {
15
- unlinkSync(TEST_DATA_DB_PATH);
16
- }
17
-
18
- const db = new Database(TEST_DATA_DB_PATH);
19
- db.pragma('journal_mode = WAL');
20
- db.pragma('foreign_keys = ON');
21
-
22
- db.exec(`
23
- CREATE TABLE IF NOT EXISTS massu_meta (
24
- key TEXT PRIMARY KEY,
25
- value TEXT NOT NULL
26
- );
27
- `);
28
-
29
- return db;
30
- }
31
-
32
- function createTestCodeGraphDb(): Database.Database {
33
- if (existsSync(TEST_CODEGRAPH_DB_PATH)) {
34
- unlinkSync(TEST_CODEGRAPH_DB_PATH);
35
- }
36
-
37
- const db = new Database(TEST_CODEGRAPH_DB_PATH);
38
- db.pragma('journal_mode = WAL');
39
-
40
- db.exec(`
41
- CREATE TABLE IF NOT EXISTS files (
42
- id INTEGER PRIMARY KEY AUTOINCREMENT,
43
- path TEXT UNIQUE NOT NULL,
44
- indexed_at INTEGER NOT NULL
45
- );
46
- `);
47
-
48
- return db;
49
- }
50
-
51
- describe('Database Module', () => {
52
- let dataDb: Database.Database;
53
- let codegraphDb: Database.Database;
54
-
55
- beforeEach(() => {
56
- dataDb = createTestDataDb();
57
- codegraphDb = createTestCodeGraphDb();
58
- });
59
-
60
- afterEach(() => {
61
- dataDb.close();
62
- codegraphDb.close();
63
- if (existsSync(TEST_DATA_DB_PATH)) {
64
- unlinkSync(TEST_DATA_DB_PATH);
65
- }
66
- if (existsSync(TEST_CODEGRAPH_DB_PATH)) {
67
- unlinkSync(TEST_CODEGRAPH_DB_PATH);
68
- }
69
- });
70
-
71
- describe('isDataStale', () => {
72
- it('returns true when no last_build_time exists', () => {
73
- codegraphDb.prepare(`INSERT INTO files (path, indexed_at) VALUES ('test.ts', ?)`).run(
74
- Math.floor(Date.now() / 1000)
75
- );
76
-
77
- const stale = isDataStale(dataDb, codegraphDb);
78
- expect(stale).toBe(true);
79
- });
80
-
81
- it('returns true when codegraph is newer than last build', () => {
82
- const oldTime = new Date(Date.now() - 60000); // 1 minute ago
83
- dataDb.prepare(`INSERT INTO massu_meta (key, value) VALUES ('last_build_time', ?)`).run(
84
- oldTime.toISOString()
85
- );
86
-
87
- const newTimestamp = Math.floor(Date.now() / 1000); // Now
88
- codegraphDb.prepare(`INSERT INTO files (path, indexed_at) VALUES ('test.ts', ?)`).run(newTimestamp);
89
-
90
- const stale = isDataStale(dataDb, codegraphDb);
91
- expect(stale).toBe(true);
92
- });
93
-
94
- it('returns false when data is up to date', () => {
95
- const currentTime = Math.floor(Date.now() / 1000);
96
- const pastTime = currentTime - 60; // 1 minute ago
97
-
98
- codegraphDb.prepare(`INSERT INTO files (path, indexed_at) VALUES ('test.ts', ?)`).run(pastTime);
99
-
100
- dataDb.prepare(`INSERT INTO massu_meta (key, value) VALUES ('last_build_time', ?)`).run(
101
- new Date().toISOString()
102
- );
103
-
104
- const stale = isDataStale(dataDb, codegraphDb);
105
- expect(stale).toBe(false);
106
- });
107
-
108
- it('returns true when no files in codegraph', () => {
109
- dataDb.prepare(`INSERT INTO massu_meta (key, value) VALUES ('last_build_time', ?)`).run(
110
- new Date().toISOString()
111
- );
112
-
113
- const stale = isDataStale(dataDb, codegraphDb);
114
- expect(stale).toBe(true);
115
- });
116
- });
117
-
118
- describe('updateBuildTimestamp', () => {
119
- it('inserts last_build_time when not exists', () => {
120
- updateBuildTimestamp(dataDb);
121
-
122
- const result = dataDb.prepare(`SELECT value FROM massu_meta WHERE key = 'last_build_time'`).get() as { value: string } | undefined;
123
- expect(result).toBeTruthy();
124
- expect(result?.value).toBeTruthy();
125
-
126
- const timestamp = new Date(result!.value);
127
- expect(timestamp.getTime()).toBeGreaterThan(Date.now() - 5000); // Within last 5 seconds
128
- });
129
-
130
- it('updates last_build_time when exists', () => {
131
- const oldTime = new Date(Date.now() - 60000).toISOString(); // 1 minute ago
132
- dataDb.prepare(`INSERT INTO massu_meta (key, value) VALUES ('last_build_time', ?)`).run(oldTime);
133
-
134
- updateBuildTimestamp(dataDb);
135
-
136
- const result = dataDb.prepare(`SELECT value FROM massu_meta WHERE key = 'last_build_time'`).get() as { value: string };
137
- expect(result.value).not.toBe(oldTime);
138
-
139
- const timestamp = new Date(result.value);
140
- expect(timestamp.getTime()).toBeGreaterThan(Date.now() - 5000); // Within last 5 seconds
141
- });
142
-
143
- it('stores timestamp as ISO string', () => {
144
- updateBuildTimestamp(dataDb);
145
-
146
- const result = dataDb.prepare(`SELECT value FROM massu_meta WHERE key = 'last_build_time'`).get() as { value: string };
147
- expect(result.value).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/); // ISO format
148
- });
149
- });
150
-
151
- describe('Data DB schema', () => {
152
- it('creates massu_meta table', () => {
153
- const tables = dataDb.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='massu_meta'`).all();
154
- expect(tables.length).toBe(1);
155
- });
156
-
157
- it('massu_meta has correct columns', () => {
158
- const columns = dataDb.prepare(`PRAGMA table_info(massu_meta)`).all() as { name: string; type: string }[];
159
- const columnNames = columns.map(c => c.name);
160
- expect(columnNames).toContain('key');
161
- expect(columnNames).toContain('value');
162
- });
163
- });
164
-
165
- describe('CodeGraph DB schema', () => {
166
- it('creates files table', () => {
167
- const tables = codegraphDb.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files'`).all();
168
- expect(tables.length).toBe(1);
169
- });
170
-
171
- it('files table has indexed_at column', () => {
172
- const columns = codegraphDb.prepare(`PRAGMA table_info(files)`).all() as { name: string }[];
173
- const columnNames = columns.map(c => c.name);
174
- expect(columnNames).toContain('indexed_at');
175
- });
176
- });
177
- });