@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,517 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- // ============================================================
5
- // validate-features-runner.ts tests
6
- // The module is a standalone script (no exports). We test its
7
- // logic by exercising the conditional branches directly in unit
8
- // tests, mocking better-sqlite3, fs, and config.
9
- // ============================================================
10
-
11
- import { describe, it, expect, vi, beforeEach } from 'vitest';
12
-
13
- // ------------------------------------
14
- // Mock dependencies
15
- // ------------------------------------
16
-
17
- vi.mock('fs', async (importOriginal) => {
18
- const actual = await importOriginal<typeof import('fs')>();
19
- return {
20
- ...actual,
21
- existsSync: vi.fn(),
22
- };
23
- });
24
-
25
- vi.mock('../config.ts', () => ({
26
- getProjectRoot: vi.fn(() => '/home/user/my-project'),
27
- getResolvedPaths: vi.fn(() => ({
28
- dataDbPath: '/home/user/my-project/.massu/data.db',
29
- memoryDbPath: '/home/user/my-project/.massu/memory.db',
30
- codegraphDbPath: '/home/user/my-project/.massu/codegraph.db',
31
- srcDir: '/home/user/my-project/src',
32
- pathAlias: {},
33
- extensions: ['.ts'],
34
- indexFiles: ['index.ts'],
35
- patternsDir: '/home/user/my-project/.claude/patterns',
36
- claudeMdPath: '/home/user/my-project/.claude/CLAUDE.md',
37
- docsMapPath: '/home/user/my-project/.massu/docs-map.json',
38
- helpSitePath: '/home/user/my-project/help',
39
- prismaSchemaPath: '/home/user/my-project/prisma/schema.prisma',
40
- rootRouterPath: '/home/user/my-project/src/server/api/root.ts',
41
- routersDir: '/home/user/my-project/src/server/api/routers',
42
- })),
43
- getConfig: vi.fn(() => ({
44
- toolPrefix: 'massu',
45
- project: { name: 'my-project', root: '/home/user/my-project' },
46
- })),
47
- }));
48
-
49
- // Import mocks
50
- import { existsSync } from 'fs';
51
- import { getProjectRoot, getResolvedPaths } from '../config.ts';
52
-
53
- const mockExistsSync = vi.mocked(existsSync);
54
- const mockGetProjectRoot = vi.mocked(getProjectRoot);
55
- const mockGetResolvedPaths = vi.mocked(getResolvedPaths);
56
-
57
- // ------------------------------------
58
- // Helper: build a mock DB statement
59
- // ------------------------------------
60
-
61
- function makeStmt(returnValue: unknown) {
62
- return { get: vi.fn().mockReturnValue(returnValue), all: vi.fn().mockReturnValue([]) };
63
- }
64
-
65
- function makeAllStmt(returnValue: unknown[]) {
66
- return { get: vi.fn(), all: vi.fn().mockReturnValue(returnValue) };
67
- }
68
-
69
- // ------------------------------------
70
- // Scenario: no data DB found
71
- // ------------------------------------
72
-
73
- describe('scenario: no data DB found', () => {
74
- beforeEach(() => {
75
- vi.clearAllMocks();
76
- mockExistsSync.mockReturnValue(false);
77
- });
78
-
79
- it('exits 0 when dbPath does not exist', () => {
80
- const dbPath = mockGetResolvedPaths().dataDbPath;
81
- const dbExists = mockExistsSync(dbPath);
82
- expect(dbExists).toBe(false);
83
-
84
- // The script would call process.exit(0) here — we verify the condition
85
- expect(dbPath).toBe('/home/user/my-project/.massu/data.db');
86
- });
87
-
88
- it('prints skipping message when no db found', () => {
89
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
90
-
91
- const dbPath = mockGetResolvedPaths().dataDbPath;
92
- if (!mockExistsSync(dbPath)) {
93
- console.log('Sentinel: No data DB found - skipping feature validation (run sync first)');
94
- }
95
-
96
- expect(consoleSpy).toHaveBeenCalledWith(
97
- 'Sentinel: No data DB found - skipping feature validation (run sync first)'
98
- );
99
- consoleSpy.mockRestore();
100
- });
101
- });
102
-
103
- // ------------------------------------
104
- // Scenario: sentinel tables do not exist
105
- // ------------------------------------
106
-
107
- describe('scenario: no sentinel tables', () => {
108
- beforeEach(() => {
109
- vi.clearAllMocks();
110
- mockExistsSync.mockReturnValue(true);
111
- });
112
-
113
- it('exits 0 when massu_sentinel table is missing', () => {
114
- const tableExistsStmt = makeStmt(undefined); // undefined = no table found
115
- const tableExists = tableExistsStmt.get();
116
- expect(tableExists).toBeUndefined();
117
-
118
- // The script would exit(0) and log skipping message
119
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
120
- if (!tableExists) {
121
- console.log('Sentinel: Feature registry not initialized - skipping (run sync first)');
122
- }
123
- expect(consoleSpy).toHaveBeenCalledWith(
124
- 'Sentinel: Feature registry not initialized - skipping (run sync first)'
125
- );
126
- consoleSpy.mockRestore();
127
- });
128
-
129
- it('proceeds when massu_sentinel table exists', () => {
130
- const tableExistsStmt = makeStmt({ name: 'massu_sentinel' });
131
- const tableExists = tableExistsStmt.get();
132
- expect(tableExists).toBeTruthy();
133
- expect((tableExists as { name: string }).name).toBe('massu_sentinel');
134
- });
135
- });
136
-
137
- // ------------------------------------
138
- // Scenario: no active features
139
- // ------------------------------------
140
-
141
- describe('scenario: no active features', () => {
142
- beforeEach(() => {
143
- vi.clearAllMocks();
144
- mockExistsSync.mockReturnValue(true);
145
- });
146
-
147
- it('exits 0 when active feature count is 0', () => {
148
- const countStmt = makeStmt({ count: 0 });
149
- const totalActive = countStmt.get() as { count: number };
150
- expect(totalActive.count).toBe(0);
151
-
152
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
153
- if (totalActive.count === 0) {
154
- console.log('Sentinel: No active features registered - skipping validation');
155
- }
156
- expect(consoleSpy).toHaveBeenCalledWith(
157
- 'Sentinel: No active features registered - skipping validation'
158
- );
159
- consoleSpy.mockRestore();
160
- });
161
-
162
- it('proceeds when active features exist', () => {
163
- const countStmt = makeStmt({ count: 5 });
164
- const totalActive = countStmt.get() as { count: number };
165
- expect(totalActive.count).toBe(5);
166
- expect(totalActive.count > 0).toBe(true);
167
- });
168
- });
169
-
170
- // ------------------------------------
171
- // Scenario: all primary components exist
172
- // ------------------------------------
173
-
174
- describe('scenario: all features have living primary components', () => {
175
- beforeEach(() => {
176
- vi.clearAllMocks();
177
- mockExistsSync.mockReturnValue(true);
178
- });
179
-
180
- it('exits 0 and prints PASS when all component files exist', () => {
181
- const projectRoot = mockGetProjectRoot();
182
- const rows = [
183
- { feature_key: 'auth.login', title: 'User Login', priority: 'critical', component_file: 'src/Login.tsx' },
184
- { feature_key: 'auth.register', title: 'User Register', priority: 'standard', component_file: 'src/Register.tsx' },
185
- ];
186
-
187
- const allStmt = makeAllStmt(rows);
188
- const orphanedRows = allStmt.all() as typeof rows;
189
-
190
- // All files exist
191
- mockExistsSync.mockImplementation(() => true);
192
-
193
- const missingFeatures: typeof rows = [];
194
- for (const row of orphanedRows) {
195
- const absPath = `${projectRoot}/${row.component_file}`;
196
- if (!mockExistsSync(absPath)) {
197
- missingFeatures.push(row);
198
- }
199
- }
200
-
201
- expect(missingFeatures).toHaveLength(0);
202
-
203
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
204
- if (missingFeatures.length === 0) {
205
- console.log('Sentinel: All active features have living primary components. PASS');
206
- }
207
- expect(consoleSpy).toHaveBeenCalledWith(
208
- 'Sentinel: All active features have living primary components. PASS'
209
- );
210
- consoleSpy.mockRestore();
211
- });
212
- });
213
-
214
- // ------------------------------------
215
- // Scenario: orphaned features (non-critical)
216
- // ------------------------------------
217
-
218
- describe('scenario: orphaned features - non-critical only', () => {
219
- beforeEach(() => {
220
- vi.clearAllMocks();
221
- });
222
-
223
- it('exits 0 with WARN when only non-critical features are orphaned', () => {
224
- const projectRoot = '/home/user/my-project';
225
- const rows = [
226
- { feature_key: 'product.search', title: 'Product Search', priority: 'standard', component_file: 'src/Search.tsx' },
227
- { feature_key: 'product.filter', title: 'Product Filter', priority: 'nice-to-have', component_file: 'src/Filter.tsx' },
228
- ];
229
-
230
- // Files do not exist
231
- mockExistsSync.mockImplementation((p) => {
232
- // DB file itself exists
233
- if (p === '/home/user/my-project/.massu/data.db') return true;
234
- return false;
235
- });
236
-
237
- const missingFeatures: { feature_key: string; title: string; priority: string; missing_file: string }[] = [];
238
- for (const row of rows) {
239
- const absPath = `${projectRoot}/${row.component_file}`;
240
- if (!mockExistsSync(absPath)) {
241
- missingFeatures.push({
242
- feature_key: row.feature_key,
243
- title: row.title,
244
- priority: row.priority,
245
- missing_file: row.component_file,
246
- });
247
- }
248
- }
249
-
250
- expect(missingFeatures).toHaveLength(2);
251
-
252
- const criticalCount = missingFeatures.filter(f => f.priority === 'critical').length;
253
- expect(criticalCount).toBe(0);
254
-
255
- // Non-critical orphans are warnings, not blockers -> exit(0)
256
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
257
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
258
-
259
- if (criticalCount > 0) {
260
- console.error(`\nFAIL: ${criticalCount} CRITICAL features are orphaned. Fix before committing.`);
261
- } else {
262
- console.warn(`\nWARN: ${missingFeatures.length} features are orphaned (non-critical). Consider updating registry.`);
263
- }
264
-
265
- expect(consoleSpy).toHaveBeenCalledWith(
266
- '\nWARN: 2 features are orphaned (non-critical). Consider updating registry.'
267
- );
268
- expect(errorSpy).not.toHaveBeenCalled();
269
-
270
- consoleSpy.mockRestore();
271
- errorSpy.mockRestore();
272
- });
273
-
274
- it('logs each orphaned feature with priority and missing file', () => {
275
- const missingFeatures = [
276
- { feature_key: 'product.search', title: 'Product Search', priority: 'standard', missing_file: 'src/Search.tsx' },
277
- ];
278
-
279
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
280
-
281
- console.error(`Sentinel: ${missingFeatures.length} features have MISSING primary components:`);
282
- for (const f of missingFeatures) {
283
- console.error(` [${f.priority}] ${f.feature_key}: ${f.title}`);
284
- console.error(` Missing: ${f.missing_file}`);
285
- }
286
-
287
- expect(errorSpy).toHaveBeenCalledWith('Sentinel: 1 features have MISSING primary components:');
288
- expect(errorSpy).toHaveBeenCalledWith(' [standard] product.search: Product Search');
289
- expect(errorSpy).toHaveBeenCalledWith(' Missing: src/Search.tsx');
290
-
291
- errorSpy.mockRestore();
292
- });
293
- });
294
-
295
- // ------------------------------------
296
- // Scenario: critical features are orphaned
297
- // ------------------------------------
298
-
299
- describe('scenario: orphaned features - critical present', () => {
300
- beforeEach(() => {
301
- vi.clearAllMocks();
302
- });
303
-
304
- it('exits 1 with FAIL message when critical features are orphaned', () => {
305
- const projectRoot = '/home/user/my-project';
306
- const rows = [
307
- { feature_key: 'auth.login', title: 'User Login', priority: 'critical', component_file: 'src/Login.tsx' },
308
- { feature_key: 'product.search', title: 'Product Search', priority: 'standard', component_file: 'src/Search.tsx' },
309
- ];
310
-
311
- // All component files missing
312
- mockExistsSync.mockImplementation((p) => {
313
- if (p === '/home/user/my-project/.massu/data.db') return true;
314
- return false;
315
- });
316
-
317
- const missingFeatures: { feature_key: string; title: string; priority: string; missing_file: string }[] = [];
318
- for (const row of rows) {
319
- const absPath = `${projectRoot}/${row.component_file}`;
320
- if (!mockExistsSync(absPath)) {
321
- missingFeatures.push({
322
- feature_key: row.feature_key,
323
- title: row.title,
324
- priority: row.priority,
325
- missing_file: row.component_file,
326
- });
327
- }
328
- }
329
-
330
- const criticalCount = missingFeatures.filter(f => f.priority === 'critical').length;
331
- expect(criticalCount).toBe(1);
332
-
333
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
334
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
335
-
336
- if (criticalCount > 0) {
337
- console.error(`\nFAIL: ${criticalCount} CRITICAL features are orphaned. Fix before committing.`);
338
- } else {
339
- console.warn(`\nWARN: ${missingFeatures.length} features are orphaned (non-critical). Consider updating registry.`);
340
- }
341
-
342
- expect(errorSpy).toHaveBeenCalledWith(
343
- '\nFAIL: 1 CRITICAL features are orphaned. Fix before committing.'
344
- );
345
- expect(warnSpy).not.toHaveBeenCalled();
346
-
347
- errorSpy.mockRestore();
348
- warnSpy.mockRestore();
349
- });
350
-
351
- it('counts critical orphaned features correctly', () => {
352
- const missingFeatures = [
353
- { feature_key: 'auth.login', title: 'User Login', priority: 'critical', missing_file: 'src/Login.tsx' },
354
- { feature_key: 'auth.register', title: 'Registration', priority: 'critical', missing_file: 'src/Register.tsx' },
355
- { feature_key: 'product.search', title: 'Search', priority: 'standard', missing_file: 'src/Search.tsx' },
356
- ];
357
-
358
- const criticalCount = missingFeatures.filter(f => f.priority === 'critical').length;
359
- expect(criticalCount).toBe(2);
360
- });
361
-
362
- it('distinguishes between critical and non-critical in mixed scenario', () => {
363
- const missingFeatures = [
364
- { priority: 'critical' },
365
- { priority: 'standard' },
366
- { priority: 'nice-to-have' },
367
- ];
368
-
369
- const criticalCount = missingFeatures.filter(f => f.priority === 'critical').length;
370
- const nonCriticalCount = missingFeatures.filter(f => f.priority !== 'critical').length;
371
-
372
- expect(criticalCount).toBe(1);
373
- expect(nonCriticalCount).toBe(2);
374
- expect(criticalCount > 0).toBe(true); // -> exit(1) path
375
- });
376
- });
377
-
378
- // ------------------------------------
379
- // Status logging tests
380
- // ------------------------------------
381
-
382
- describe('status logging', () => {
383
- it('logs active feature count before checking components', () => {
384
- const totalActive = { count: 8 };
385
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
386
-
387
- console.log(`Sentinel: ${totalActive.count} active features, checking primary components...`);
388
-
389
- expect(consoleSpy).toHaveBeenCalledWith(
390
- 'Sentinel: 8 active features, checking primary components...'
391
- );
392
- consoleSpy.mockRestore();
393
- });
394
-
395
- it('formats singular vs plural active feature count correctly', () => {
396
- // The script uses the raw count in the message - verify the message format
397
- const count1 = 1;
398
- const count5 = 5;
399
- const msg1 = `Sentinel: ${count1} active features, checking primary components...`;
400
- const msg5 = `Sentinel: ${count5} active features, checking primary components...`;
401
- expect(msg1).toContain('1 active features');
402
- expect(msg5).toContain('5 active features');
403
- });
404
- });
405
-
406
- // ------------------------------------
407
- // Path resolution tests
408
- // ------------------------------------
409
-
410
- describe('path resolution for component files', () => {
411
- beforeEach(() => {
412
- vi.clearAllMocks();
413
- });
414
-
415
- it('resolves component_file relative to PROJECT_ROOT', () => {
416
- const projectRoot = '/home/user/my-project';
417
- const componentFile = 'src/components/Login.tsx';
418
-
419
- // The script uses: resolve(PROJECT_ROOT, row.component_file)
420
- // which is equivalent to path.join for relative paths
421
- const absPath = `${projectRoot}/${componentFile}`;
422
- expect(absPath).toBe('/home/user/my-project/src/components/Login.tsx');
423
- });
424
-
425
- it('checks each component file with existsSync', () => {
426
- const projectRoot = '/home/user/my-project';
427
- const rows = [
428
- { component_file: 'src/Login.tsx' },
429
- { component_file: 'src/Register.tsx' },
430
- ];
431
-
432
- const checkedPaths: string[] = [];
433
- mockExistsSync.mockImplementation((p) => {
434
- checkedPaths.push(p as string);
435
- return true;
436
- });
437
-
438
- for (const row of rows) {
439
- existsSync(`${projectRoot}/${row.component_file}`);
440
- }
441
-
442
- expect(checkedPaths).toEqual([
443
- '/home/user/my-project/src/Login.tsx',
444
- '/home/user/my-project/src/Register.tsx',
445
- ]);
446
- });
447
-
448
- it('correctly identifies missing vs existing files', () => {
449
- const existingFiles = new Set(['/home/user/my-project/src/Login.tsx']);
450
- mockExistsSync.mockImplementation((p) => existingFiles.has(p as string));
451
-
452
- expect(mockExistsSync('/home/user/my-project/src/Login.tsx')).toBe(true);
453
- expect(mockExistsSync('/home/user/my-project/src/Search.tsx')).toBe(false);
454
- });
455
- });
456
-
457
- // ------------------------------------
458
- // DB open/close correctness tests
459
- // ------------------------------------
460
-
461
- describe('database open and close', () => {
462
- it('verifies readonly mode is used for data DB', () => {
463
- // The script opens: new Database(dbPath, { readonly: true })
464
- // Verify the options pattern
465
- const options = { readonly: true };
466
- expect(options.readonly).toBe(true);
467
- });
468
-
469
- it('verifies WAL pragma is set', () => {
470
- // The script calls: db.pragma('journal_mode = WAL')
471
- const pragma = 'journal_mode = WAL';
472
- expect(pragma).toBe('journal_mode = WAL');
473
- });
474
-
475
- it('verifies sentinel table query uses correct table name', () => {
476
- const query = "SELECT name FROM sqlite_master WHERE type='table' AND name='massu_sentinel'";
477
- expect(query).toContain('massu_sentinel');
478
- expect(query).toContain("type='table'");
479
- });
480
-
481
- it('verifies active features query uses correct status filter', () => {
482
- const query = "SELECT COUNT(*) as count FROM massu_sentinel WHERE status = 'active'";
483
- expect(query).toContain("status = 'active'");
484
- expect(query).toContain('COUNT(*)');
485
- });
486
-
487
- it('verifies orphaned feature query joins correct tables', () => {
488
- const query = `
489
- SELECT s.feature_key, s.title, s.priority, c.component_file
490
- FROM massu_sentinel s
491
- JOIN massu_sentinel_components c ON c.feature_id = s.id AND c.is_primary = 1
492
- WHERE s.status = 'active'
493
- ORDER BY s.priority DESC, s.domain, s.feature_key
494
- `;
495
- expect(query).toContain('massu_sentinel s');
496
- expect(query).toContain('massu_sentinel_components c');
497
- expect(query).toContain('c.is_primary = 1');
498
- expect(query).toContain("s.status = 'active'");
499
- expect(query).toContain('s.priority DESC');
500
- });
501
- });
502
-
503
- // ------------------------------------
504
- // getResolvedPaths integration tests
505
- // ------------------------------------
506
-
507
- describe('config integration', () => {
508
- it('reads dbPath from getResolvedPaths()', () => {
509
- const paths = mockGetResolvedPaths();
510
- expect(paths.dataDbPath).toBe('/home/user/my-project/.massu/data.db');
511
- });
512
-
513
- it('reads PROJECT_ROOT from getProjectRoot()', () => {
514
- const root = mockGetProjectRoot();
515
- expect(root).toBe('/home/user/my-project');
516
- });
517
- });