@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,236 +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 { classifyRouter, classifyFile, findCrossDomainImports, getFilesInDomain } from '../domains.ts';
7
-
8
- // Mock config
9
- vi.mock('../config.ts', () => ({
10
- getConfig: () => ({
11
- toolPrefix: 'massu',
12
- framework: { type: 'typescript', router: 'trpc', orm: 'prisma' },
13
- paths: { source: 'src', routers: 'src/server/api/routers' },
14
- domains: [
15
- {
16
- name: 'auth',
17
- routers: ['auth', 'user'],
18
- pages: ['src/app/auth/**'],
19
- tables: ['users', 'sessions'],
20
- allowedImportsFrom: ['*'],
21
- },
22
- {
23
- name: 'product',
24
- routers: ['product', 'catalog'],
25
- pages: ['src/app/products/**'],
26
- tables: ['products'],
27
- allowedImportsFrom: ['auth'],
28
- },
29
- {
30
- name: 'order',
31
- routers: ['order*'],
32
- pages: ['src/app/orders/**'],
33
- tables: ['orders'],
34
- allowedImportsFrom: ['auth', 'product'],
35
- },
36
- ],
37
- }),
38
- getProjectRoot: () => '/test/project',
39
- getResolvedPaths: () => ({
40
- codegraphDbPath: '/test/codegraph.db',
41
- dataDbPath: '/test/data.db',
42
- }),
43
- }));
44
-
45
- function createTestDataDb(): Database.Database {
46
- const db = new Database(':memory:');
47
- db.pragma('journal_mode = WAL');
48
- db.pragma('foreign_keys = ON');
49
-
50
- db.exec(`
51
- CREATE TABLE IF NOT EXISTS massu_imports (
52
- id INTEGER PRIMARY KEY AUTOINCREMENT,
53
- source_file TEXT NOT NULL,
54
- target_file TEXT NOT NULL,
55
- import_type TEXT NOT NULL,
56
- imported_names TEXT NOT NULL DEFAULT '[]',
57
- line INTEGER NOT NULL DEFAULT 0
58
- );
59
- `);
60
-
61
- return db;
62
- }
63
-
64
- function createTestCodeGraphDb(): Database.Database {
65
- const db = new Database(':memory:');
66
- db.pragma('journal_mode = WAL');
67
-
68
- db.exec(`
69
- CREATE TABLE IF NOT EXISTS files (
70
- id INTEGER PRIMARY KEY AUTOINCREMENT,
71
- path TEXT UNIQUE NOT NULL
72
- );
73
- `);
74
-
75
- return db;
76
- }
77
-
78
- describe('Domains Module', () => {
79
- let dataDb: Database.Database;
80
- let codegraphDb: Database.Database;
81
-
82
- beforeEach(() => {
83
- dataDb = createTestDataDb();
84
- codegraphDb = createTestCodeGraphDb();
85
- });
86
-
87
- afterEach(() => {
88
- dataDb.close();
89
- codegraphDb.close();
90
- });
91
-
92
- describe('classifyRouter', () => {
93
- it('classifies router by exact match', () => {
94
- expect(classifyRouter('auth')).toBe('auth');
95
- expect(classifyRouter('user')).toBe('auth');
96
- expect(classifyRouter('product')).toBe('product');
97
- });
98
-
99
- it('classifies router by wildcard pattern', () => {
100
- expect(classifyRouter('orders')).toBe('order');
101
- expect(classifyRouter('orderHistory')).toBe('order');
102
- });
103
-
104
- it('returns Unknown for unmatched router', () => {
105
- expect(classifyRouter('unknownRouter')).toBe('Unknown');
106
- });
107
- });
108
-
109
- describe('classifyFile', () => {
110
- it('classifies file by page pattern', () => {
111
- expect(classifyFile('src/app/auth/login/page.tsx')).toBe('auth');
112
- expect(classifyFile('src/app/products/list/page.tsx')).toBe('product');
113
- expect(classifyFile('src/app/orders/[id]/page.tsx')).toBe('order');
114
- });
115
-
116
- it('classifies router file by path', () => {
117
- expect(classifyFile('src/server/api/routers/auth.ts')).toBe('auth');
118
- expect(classifyFile('src/server/api/routers/product.ts')).toBe('product');
119
- expect(classifyFile('src/server/api/routers/orders.ts')).toBe('order');
120
- });
121
-
122
- it('classifies component file by directory name', () => {
123
- expect(classifyFile('src/components/auth/LoginForm.tsx')).toBe('auth');
124
- expect(classifyFile('src/components/product/ProductCard.tsx')).toBe('product');
125
- });
126
-
127
- it('returns Unknown for unclassifiable file', () => {
128
- expect(classifyFile('src/utils/helpers.ts')).toBe('Unknown');
129
- });
130
- });
131
-
132
- describe('findCrossDomainImports', () => {
133
- beforeEach(() => {
134
- // Seed test imports
135
- dataDb.prepare(`
136
- INSERT INTO massu_imports (source_file, target_file, import_type, imported_names)
137
- VALUES
138
- ('src/app/orders/page.tsx', 'src/app/auth/hooks.ts', 'named', '["useAuth"]'),
139
- ('src/app/orders/page.tsx', 'src/app/products/api.ts', 'named', '["getProduct"]'),
140
- ('src/app/products/page.tsx', 'src/app/orders/utils.ts', 'named', '["formatOrder"]'),
141
- ('src/app/auth/page.tsx', 'src/app/auth/login.tsx', 'named', '["LoginForm"]')
142
- `).run();
143
- });
144
-
145
- it('identifies cross-domain imports', () => {
146
- const crossings = findCrossDomainImports(dataDb);
147
- expect(crossings.length).toBeGreaterThan(0);
148
-
149
- const orderToAuth = crossings.find(c => c.sourceDomain === 'order' && c.targetDomain === 'auth');
150
- expect(orderToAuth).toBeTruthy();
151
- });
152
-
153
- it('marks allowed imports correctly', () => {
154
- const crossings = findCrossDomainImports(dataDb);
155
- const orderToAuth = crossings.find(c => c.sourceDomain === 'order' && c.targetDomain === 'auth');
156
- expect(orderToAuth?.allowed).toBe(true); // order allows imports from auth
157
- });
158
-
159
- it('marks disallowed imports as violations', () => {
160
- const crossings = findCrossDomainImports(dataDb);
161
- const productToOrder = crossings.find(c => c.sourceDomain === 'product' && c.targetDomain === 'order');
162
- if (productToOrder) {
163
- expect(productToOrder.allowed).toBe(false); // product doesn't allow imports from order
164
- }
165
- });
166
-
167
- it('ignores same-domain imports', () => {
168
- const crossings = findCrossDomainImports(dataDb);
169
- const authToAuth = crossings.find(c => c.sourceDomain === 'auth' && c.targetDomain === 'auth');
170
- expect(authToAuth).toBeUndefined();
171
- });
172
-
173
- it('ignores Unknown domain imports', () => {
174
- dataDb.prepare(`
175
- INSERT INTO massu_imports (source_file, target_file, import_type)
176
- VALUES ('src/utils/helper.ts', 'src/app/auth/page.tsx', 'named')
177
- `).run();
178
-
179
- const crossings = findCrossDomainImports(dataDb);
180
- const unknownToAuth = crossings.find(c => c.sourceDomain === 'Unknown');
181
- expect(unknownToAuth).toBeUndefined();
182
- });
183
- });
184
-
185
- describe('getFilesInDomain', () => {
186
- beforeEach(() => {
187
- // Seed test files
188
- codegraphDb.prepare(`
189
- INSERT INTO files (path) VALUES
190
- ('src/server/api/routers/auth.ts'),
191
- ('src/server/api/routers/user.ts'),
192
- ('src/server/api/routers/product.ts'),
193
- ('src/app/auth/login/page.tsx'),
194
- ('src/app/auth/register/page.tsx'),
195
- ('src/app/products/list/page.tsx'),
196
- ('src/components/auth/LoginForm.tsx'),
197
- ('src/components/product/ProductCard.tsx')
198
- `).run();
199
- });
200
-
201
- it('returns routers in domain', () => {
202
- const files = getFilesInDomain(dataDb, codegraphDb, 'auth');
203
- expect(files.routers.length).toBeGreaterThan(0);
204
- expect(files.routers).toContain('src/server/api/routers/auth.ts');
205
- expect(files.routers).toContain('src/server/api/routers/user.ts');
206
- });
207
-
208
- it('returns pages in domain', () => {
209
- const files = getFilesInDomain(dataDb, codegraphDb, 'auth');
210
- expect(files.pages.length).toBeGreaterThan(0);
211
- expect(files.pages).toContain('src/app/auth/login/page.tsx');
212
- expect(files.pages).toContain('src/app/auth/register/page.tsx');
213
- });
214
-
215
- it('returns components in domain', () => {
216
- const files = getFilesInDomain(dataDb, codegraphDb, 'auth');
217
- expect(files.components.length).toBeGreaterThan(0);
218
- expect(files.components).toContain('src/components/auth/LoginForm.tsx');
219
- });
220
-
221
- it('returns empty for nonexistent domain', () => {
222
- const files = getFilesInDomain(dataDb, codegraphDb, 'nonexistent');
223
- expect(files.routers).toEqual([]);
224
- expect(files.pages).toEqual([]);
225
- expect(files.components).toEqual([]);
226
- });
227
-
228
- it('filters files correctly by domain', () => {
229
- const authFiles = getFilesInDomain(dataDb, codegraphDb, 'auth');
230
- const productFiles = getFilesInDomain(dataDb, codegraphDb, 'product');
231
-
232
- expect(authFiles.routers).not.toContain('src/server/api/routers/product.ts');
233
- expect(productFiles.routers).toContain('src/server/api/routers/product.ts');
234
- });
235
- });
236
- });
@@ -1,221 +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 { resolve } from 'path';
7
- import { unlinkSync, existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
8
- import {
9
- createSession,
10
- addObservation,
11
- addSummary,
12
- addUserPrompt,
13
- getMemoryDb,
14
- getFailedAttempts,
15
- getRecentObservations,
16
- getSessionSummaries,
17
- } from '../memory-db.ts';
18
- import { generateCurrentMd } from '../session-state-generator.ts';
19
- import { archiveAndRegenerate } from '../session-archiver.ts';
20
-
21
- // P7-004: Hook Handler Tests
22
- // Tests the core logic that hooks use (not the stdin/stdout plumbing)
23
-
24
- const TEST_DB_PATH = resolve(__dirname, '../test-hooks.db');
25
-
26
- function createTestDb(): Database.Database {
27
- if (existsSync(TEST_DB_PATH)) unlinkSync(TEST_DB_PATH);
28
- const db = new Database(TEST_DB_PATH);
29
- db.pragma('journal_mode = WAL');
30
- db.pragma('foreign_keys = ON');
31
-
32
- // Minimal schema for hook tests
33
- db.exec(`
34
- CREATE TABLE IF NOT EXISTS sessions (
35
- id INTEGER PRIMARY KEY AUTOINCREMENT,
36
- session_id TEXT UNIQUE NOT NULL,
37
- project TEXT NOT NULL DEFAULT 'my-project',
38
- git_branch TEXT,
39
- started_at TEXT NOT NULL,
40
- started_at_epoch INTEGER NOT NULL,
41
- ended_at TEXT,
42
- ended_at_epoch INTEGER,
43
- status TEXT CHECK(status IN ('active', 'completed', 'abandoned')) NOT NULL DEFAULT 'active',
44
- plan_file TEXT,
45
- plan_phase TEXT,
46
- task_id TEXT
47
- );
48
- CREATE TABLE IF NOT EXISTS observations (
49
- id INTEGER PRIMARY KEY AUTOINCREMENT,
50
- session_id TEXT NOT NULL,
51
- type TEXT NOT NULL CHECK(type IN (
52
- 'decision', 'bugfix', 'feature', 'refactor', 'discovery',
53
- 'cr_violation', 'vr_check', 'pattern_compliance', 'failed_attempt',
54
- 'file_change', 'incident_near_miss'
55
- )),
56
- title TEXT NOT NULL,
57
- detail TEXT,
58
- files_involved TEXT DEFAULT '[]',
59
- plan_item TEXT,
60
- cr_rule TEXT,
61
- vr_type TEXT,
62
- evidence TEXT,
63
- importance INTEGER NOT NULL DEFAULT 3 CHECK(importance BETWEEN 1 AND 5),
64
- recurrence_count INTEGER NOT NULL DEFAULT 1,
65
- original_tokens INTEGER DEFAULT 0,
66
- created_at TEXT NOT NULL,
67
- created_at_epoch INTEGER NOT NULL,
68
- FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
69
- );
70
- CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
71
- title, detail, evidence, content='observations', content_rowid='id'
72
- );
73
- CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
74
- INSERT INTO observations_fts(rowid, title, detail, evidence) VALUES (new.id, new.title, new.detail, new.evidence);
75
- END;
76
- CREATE TABLE IF NOT EXISTS session_summaries (
77
- id INTEGER PRIMARY KEY AUTOINCREMENT,
78
- session_id TEXT NOT NULL,
79
- request TEXT, investigated TEXT, decisions TEXT, completed TEXT,
80
- failed_attempts TEXT, next_steps TEXT,
81
- files_created TEXT DEFAULT '[]', files_modified TEXT DEFAULT '[]',
82
- verification_results TEXT DEFAULT '{}', plan_progress TEXT DEFAULT '{}',
83
- created_at TEXT NOT NULL, created_at_epoch INTEGER NOT NULL,
84
- FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
85
- );
86
- CREATE TABLE IF NOT EXISTS user_prompts (
87
- id INTEGER PRIMARY KEY AUTOINCREMENT,
88
- session_id TEXT NOT NULL,
89
- prompt_text TEXT NOT NULL,
90
- prompt_number INTEGER NOT NULL DEFAULT 1,
91
- created_at TEXT NOT NULL, created_at_epoch INTEGER NOT NULL,
92
- FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
93
- );
94
- CREATE VIRTUAL TABLE IF NOT EXISTS user_prompts_fts USING fts5(
95
- prompt_text, content='user_prompts', content_rowid='id'
96
- );
97
- CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
98
- INSERT INTO user_prompts_fts(rowid, prompt_text) VALUES (new.id, new.prompt_text);
99
- END;
100
- CREATE TABLE IF NOT EXISTS memory_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
101
- `);
102
-
103
- return db;
104
- }
105
-
106
- describe('Hook Logic', () => {
107
- let db: Database.Database;
108
-
109
- beforeEach(() => {
110
- db = createTestDb();
111
- });
112
-
113
- afterEach(() => {
114
- db.close();
115
- if (existsSync(TEST_DB_PATH)) unlinkSync(TEST_DB_PATH);
116
- });
117
-
118
- describe('Session Creation (P3-001, P3-004)', () => {
119
- it('creates session on first hook call', () => {
120
- createSession(db, 'hook-session-1', { branch: 'feature-x' });
121
- const session = db.prepare('SELECT * FROM sessions WHERE session_id = ?').get('hook-session-1') as Record<string, unknown>;
122
- expect(session).toBeTruthy();
123
- expect(session.git_branch).toBe('feature-x');
124
- });
125
-
126
- it('is idempotent across hooks', () => {
127
- createSession(db, 'hook-session-1');
128
- createSession(db, 'hook-session-1');
129
- createSession(db, 'hook-session-1');
130
- const count = db.prepare('SELECT COUNT(*) as c FROM sessions WHERE session_id = ?').get('hook-session-1') as { c: number };
131
- expect(count.c).toBe(1);
132
- });
133
- });
134
-
135
- describe('Observation Capture (P3-002)', () => {
136
- it('stores file_change observations', () => {
137
- createSession(db, 'hook-session-1');
138
- addObservation(db, 'hook-session-1', 'file_change', 'Edited: src/lib/auth.ts', null, {
139
- filesInvolved: ['src/lib/auth.ts'],
140
- });
141
- const obs = getRecentObservations(db, 10, 'hook-session-1');
142
- expect(obs.length).toBe(1);
143
- expect(obs[0].type).toBe('file_change');
144
- });
145
-
146
- it('stores vr_check observations', () => {
147
- createSession(db, 'hook-session-1');
148
- addObservation(db, 'hook-session-1', 'vr_check', 'VR-BUILD: PASS', null, {
149
- vrType: 'VR-BUILD',
150
- evidence: 'Build succeeded',
151
- importance: 2,
152
- });
153
- const obs = getRecentObservations(db, 10, 'hook-session-1');
154
- expect(obs[0].type).toBe('vr_check');
155
- expect(obs[0].importance).toBe(2);
156
- });
157
- });
158
-
159
- describe('Summary Generation (P3-003)', () => {
160
- it('generates summary from observations', () => {
161
- createSession(db, 'hook-session-1');
162
- addObservation(db, 'hook-session-1', 'decision', 'Use esbuild for hooks', 'Faster than tsc');
163
- addObservation(db, 'hook-session-1', 'feature', 'Implemented memory DB', null);
164
- addObservation(db, 'hook-session-1', 'failed_attempt', 'process.cwd() wrong', 'Returns test dir');
165
-
166
- addSummary(db, 'hook-session-1', {
167
- request: 'Implement memory system',
168
- decisions: '- Use esbuild for hooks',
169
- completed: '- Implemented memory DB',
170
- failedAttempts: '- process.cwd() wrong',
171
- });
172
-
173
- const summaries = getSessionSummaries(db, 5);
174
- expect(summaries.length).toBe(1);
175
- expect(summaries[0].request).toBe('Implement memory system');
176
- });
177
- });
178
-
179
- describe('Context Injection Format (P3-001)', () => {
180
- it('includes failed attempts in context', () => {
181
- createSession(db, 'hook-session-1');
182
- addObservation(db, 'hook-session-1', 'failed_attempt', 'Regex parser fails on nested braces', null, {
183
- importance: 5,
184
- });
185
-
186
- const failures = getFailedAttempts(db);
187
- expect(failures.length).toBe(1);
188
- expect(failures[0].title).toContain('Regex parser');
189
- });
190
- });
191
-
192
- describe('CURRENT.md Generation (P3-003 + P5-001)', () => {
193
- it('generates valid markdown', () => {
194
- createSession(db, 'hook-session-1', { branch: 'main' });
195
- addUserPrompt(db, 'hook-session-1', 'Fix the auth bug', 1);
196
- addObservation(db, 'hook-session-1', 'decision', 'Use JWT tokens', 'More secure');
197
- addObservation(db, 'hook-session-1', 'file_change', 'Edited: src/auth.ts', null, {
198
- filesInvolved: ['src/auth.ts'],
199
- });
200
-
201
- const md = generateCurrentMd(db, 'hook-session-1');
202
- expect(md).toContain('# Session State');
203
- expect(md).toContain('auto-generated from massu-memory');
204
- expect(md).toContain('hook-session-1');
205
- expect(md).toContain('Use JWT tokens');
206
- });
207
- });
208
-
209
- describe('User Prompt Capture (P3-004)', () => {
210
- it('stores prompts with incrementing numbers', () => {
211
- createSession(db, 'hook-session-1');
212
- addUserPrompt(db, 'hook-session-1', 'First prompt', 1);
213
- addUserPrompt(db, 'hook-session-1', 'Second prompt', 2);
214
-
215
- const prompts = db.prepare('SELECT * FROM user_prompts WHERE session_id = ? ORDER BY prompt_number').all('hook-session-1') as Array<Record<string, unknown>>;
216
- expect(prompts.length).toBe(2);
217
- expect(prompts[0].prompt_number).toBe(1);
218
- expect(prompts[1].prompt_number).toBe(2);
219
- });
220
- });
221
- });
@@ -1,95 +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 } from 'vitest';
5
- import { parseImports, resolveImportPath } from '../import-resolver.ts';
6
-
7
- describe('parseImports', () => {
8
- it('parses named imports', () => {
9
- const source = `import { Foo, Bar } from '@/components/shared/Foo';`;
10
- const imports = parseImports(source);
11
- expect(imports).toHaveLength(1);
12
- expect(imports[0].type).toBe('named');
13
- expect(imports[0].names).toEqual(['Foo', 'Bar']);
14
- expect(imports[0].specifier).toBe('@/components/shared/Foo');
15
- });
16
-
17
- it('parses default imports', () => {
18
- const source = `import Database from 'better-sqlite3';`;
19
- const imports = parseImports(source);
20
- expect(imports).toHaveLength(1);
21
- expect(imports[0].type).toBe('default');
22
- expect(imports[0].names).toEqual(['Database']);
23
- });
24
-
25
- it('parses namespace imports', () => {
26
- const source = `import * as utils from './utils';`;
27
- const imports = parseImports(source);
28
- expect(imports).toHaveLength(1);
29
- expect(imports[0].type).toBe('namespace');
30
- expect(imports[0].names).toEqual(['utils']);
31
- });
32
-
33
- it('parses type imports', () => {
34
- const source = `import type { Database } from 'better-sqlite3';`;
35
- const imports = parseImports(source);
36
- expect(imports).toHaveLength(1);
37
- expect(imports[0].type).toBe('named');
38
- expect(imports[0].names).toEqual(['Database']);
39
- });
40
-
41
- it('parses side effect imports', () => {
42
- const source = `import './globals.css';`;
43
- const imports = parseImports(source);
44
- expect(imports).toHaveLength(1);
45
- expect(imports[0].type).toBe('side_effect');
46
- expect(imports[0].specifier).toBe('./globals.css');
47
- });
48
-
49
- it('parses dynamic imports', () => {
50
- const source = `const jsdom = await import('jsdom');`;
51
- const imports = parseImports(source);
52
- expect(imports).toHaveLength(1);
53
- expect(imports[0].type).toBe('dynamic');
54
- expect(imports[0].specifier).toBe('jsdom');
55
- });
56
-
57
- it('parses aliased imports', () => {
58
- const source = `import { Foo as Bar, Baz } from './module';`;
59
- const imports = parseImports(source);
60
- expect(imports).toHaveLength(1);
61
- expect(imports[0].names).toEqual(['Foo', 'Baz']);
62
- });
63
-
64
- it('handles multiple imports', () => {
65
- const source = `
66
- import { Button } from '@/components/ui/button';
67
- import { useRouter } from 'next/navigation';
68
- import type { FC } from 'react';
69
- import './styles.css';
70
- `;
71
- const imports = parseImports(source);
72
- expect(imports).toHaveLength(4);
73
- });
74
-
75
- it('skips comments', () => {
76
- const source = `
77
- // import { Foo } from './foo';
78
- import { Bar } from './bar';
79
- `;
80
- const imports = parseImports(source);
81
- expect(imports).toHaveLength(1);
82
- expect(imports[0].names).toEqual(['Bar']);
83
- });
84
- });
85
-
86
- describe('resolveImportPath', () => {
87
- it('returns null for bare/external imports', () => {
88
- expect(resolveImportPath('react', '/some/file.ts')).toBeNull();
89
- expect(resolveImportPath('next/navigation', '/some/file.ts')).toBeNull();
90
- expect(resolveImportPath('better-sqlite3', '/some/file.ts')).toBeNull();
91
- });
92
-
93
- // Note: resolveImportPath depends on file system, so we only test external filtering here.
94
- // Full integration tests would need actual file paths.
95
- });
@@ -1,134 +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, vi } from 'vitest';
5
- import { handleToolCall } from '../../tools.ts';
6
-
7
- // Mock all external dependencies
8
- vi.mock('../../config.ts', () => ({
9
- getConfig: () => ({
10
- toolPrefix: 'massu',
11
- framework: { type: 'typescript', router: 'trpc', orm: 'prisma' },
12
- paths: {
13
- source: 'src',
14
- routers: 'src/server/api/routers',
15
- middleware: 'src/middleware.ts',
16
- },
17
- domains: [],
18
- }),
19
- getProjectRoot: () => '/test/project',
20
- getResolvedPaths: () => ({
21
- codegraphDbPath: '/test/codegraph.db',
22
- dataDbPath: '/test/data.db',
23
- }),
24
- }));
25
-
26
- vi.mock('../../memory-db.ts', () => {
27
- const Database = require('better-sqlite3');
28
- return {
29
- getMemoryDb: () => {
30
- const db = new Database(':memory:');
31
- db.exec(`
32
- CREATE TABLE IF NOT EXISTS sessions (id TEXT PRIMARY KEY, created_at TEXT, updated_at TEXT, context TEXT);
33
- CREATE TABLE IF NOT EXISTS observations (id INTEGER PRIMARY KEY, session_id TEXT, content TEXT, category TEXT, confidence REAL, created_at TEXT);
34
- CREATE TABLE IF NOT EXISTS analytics_events (id INTEGER PRIMARY KEY, session_id TEXT, event_type TEXT, data TEXT, created_at TEXT);
35
- CREATE TABLE IF NOT EXISTS cost_records (id INTEGER PRIMARY KEY, session_id TEXT, model TEXT, input_tokens INTEGER, output_tokens INTEGER, cache_read_tokens INTEGER DEFAULT 0, cache_write_tokens INTEGER DEFAULT 0, cost REAL, created_at TEXT);
36
- CREATE TABLE IF NOT EXISTS prompt_analyses (id INTEGER PRIMARY KEY, session_id TEXT, tool_name TEXT, prompt_text TEXT, analysis TEXT, created_at TEXT);
37
- CREATE TABLE IF NOT EXISTS audit_trail (id INTEGER PRIMARY KEY, session_id TEXT, action TEXT, details TEXT, created_at TEXT);
38
- CREATE TABLE IF NOT EXISTS validation_results (id INTEGER PRIMARY KEY, session_id TEXT, rule_id TEXT, result TEXT, details TEXT, created_at TEXT);
39
- CREATE TABLE IF NOT EXISTS adrs (id INTEGER PRIMARY KEY, session_id TEXT, title TEXT, status TEXT, context TEXT, decision TEXT, consequences TEXT, created_at TEXT);
40
- CREATE TABLE IF NOT EXISTS security_scores (id INTEGER PRIMARY KEY, session_id TEXT, file_path TEXT, score REAL, findings TEXT, created_at TEXT);
41
- CREATE TABLE IF NOT EXISTS dependency_scores (id INTEGER PRIMARY KEY, session_id TEXT, package_name TEXT, score REAL, details TEXT, created_at TEXT);
42
- CREATE TABLE IF NOT EXISTS team_knowledge (id INTEGER PRIMARY KEY, session_id TEXT, topic TEXT, content TEXT, author TEXT, created_at TEXT);
43
- CREATE TABLE IF NOT EXISTS regression_baselines (id INTEGER PRIMARY KEY, session_id TEXT, metric TEXT, value REAL, created_at TEXT);
44
- CREATE TABLE IF NOT EXISTS observability_spans (id INTEGER PRIMARY KEY, session_id TEXT, trace_id TEXT, span_id TEXT, parent_span_id TEXT, name TEXT, start_time TEXT, end_time TEXT, duration_ms REAL, status TEXT, attributes TEXT);
45
- CREATE TABLE IF NOT EXISTS observability_metrics (id INTEGER PRIMARY KEY, session_id TEXT, name TEXT, value REAL, unit TEXT, dimensions TEXT, timestamp TEXT);
46
- CREATE TABLE IF NOT EXISTS observability_logs (id INTEGER PRIMARY KEY, session_id TEXT, level TEXT, message TEXT, context TEXT, timestamp TEXT);
47
- `);
48
- return db;
49
- },
50
- };
51
- });
52
-
53
- vi.mock('../../db.ts', async (importOriginal) => {
54
- const actual = await importOriginal() as Record<string, unknown>;
55
- return {
56
- ...actual,
57
- isDataStale: () => false,
58
- updateBuildTimestamp: vi.fn(),
59
- };
60
- });
61
-
62
- vi.mock('../../import-resolver.ts', () => ({
63
- buildImportIndex: () => 0,
64
- }));
65
-
66
- vi.mock('../../trpc-index.ts', () => ({
67
- buildTrpcIndex: () => ({ totalProcedures: 0, withCallers: 0, withoutCallers: 0 }),
68
- }));
69
-
70
- vi.mock('../../page-deps.ts', () => ({
71
- buildPageDeps: () => 0,
72
- findAffectedPages: () => [],
73
- }));
74
-
75
- vi.mock('../../middleware-tree.ts', () => ({
76
- buildMiddlewareTree: () => 0,
77
- isInMiddlewareTree: () => false,
78
- getMiddlewareTree: () => [],
79
- }));
80
-
81
- vi.mock('../../domains.ts', () => ({
82
- classifyFile: () => ({ domain: 'unknown', layer: 'unknown' }),
83
- classifyRouter: () => 'unknown',
84
- findCrossDomainImports: () => [],
85
- getFilesInDomain: () => [],
86
- }));
87
-
88
- vi.mock('../../schema-mapper.ts', () => ({
89
- parsePrismaSchema: () => [],
90
- detectMismatches: () => [],
91
- findColumnUsageInRouters: () => [],
92
- }));
93
-
94
- vi.mock('../../sentinel-scanner.ts', () => ({
95
- runFeatureScan: () => ({ newFeatures: 0, updatedFeatures: 0 }),
96
- }));
97
-
98
- describe('Integration: Path Traversal Prevention', () => {
99
- const traversalPaths = [
100
- '../../etc/passwd',
101
- '/etc/passwd',
102
- '../../../.env',
103
- '/Users/someone/.ssh/id_rsa',
104
- 'src/../../secret.txt',
105
- ];
106
-
107
- it('massu_context rejects paths with directory traversal', () => {
108
- for (const maliciousPath of traversalPaths) {
109
- try {
110
- const result = handleToolCall('massu_context', { file_path: maliciousPath });
111
- const text = result.content[0]?.text || '';
112
- // Should either error or not return actual file contents
113
- // The tool should not silently succeed with external file contents
114
- expect(text).not.toContain('root:x:0:0');
115
- expect(text).not.toContain('BEGIN RSA PRIVATE KEY');
116
- } catch {
117
- // Throwing is acceptable - it means the tool rejected the input
118
- }
119
- }
120
- });
121
-
122
- it('docs tools reject paths outside project root', () => {
123
- for (const maliciousPath of traversalPaths) {
124
- try {
125
- const result = handleToolCall('massu_docs_read', { path: maliciousPath });
126
- const text = result.content[0]?.text || '';
127
- expect(text).not.toContain('root:x:0:0');
128
- expect(text).not.toContain('BEGIN RSA PRIVATE KEY');
129
- } catch {
130
- // Throwing is acceptable
131
- }
132
- }
133
- });
134
- });