@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,327 +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
- getTeamToolDefinitions,
8
- isTeamTool,
9
- calculateExpertise,
10
- updateExpertise,
11
- detectConflicts,
12
- shareObservation,
13
- handleTeamToolCall,
14
- } from '../team-knowledge.ts';
15
-
16
- function createTestDb(): Database.Database {
17
- const db = new Database(':memory:');
18
- db.exec(`
19
- CREATE TABLE developer_expertise (
20
- id INTEGER PRIMARY KEY AUTOINCREMENT,
21
- developer_id TEXT NOT NULL,
22
- module TEXT NOT NULL,
23
- session_count INTEGER NOT NULL DEFAULT 0,
24
- observation_count INTEGER NOT NULL DEFAULT 0,
25
- expertise_score INTEGER NOT NULL DEFAULT 0,
26
- last_active TEXT DEFAULT (datetime('now')),
27
- UNIQUE(developer_id, module)
28
- );
29
-
30
- CREATE TABLE shared_observations (
31
- id INTEGER PRIMARY KEY AUTOINCREMENT,
32
- original_id INTEGER,
33
- developer_id TEXT NOT NULL,
34
- project TEXT NOT NULL,
35
- observation_type TEXT NOT NULL,
36
- summary TEXT NOT NULL,
37
- file_path TEXT,
38
- module TEXT,
39
- severity INTEGER NOT NULL DEFAULT 3,
40
- is_shared INTEGER NOT NULL DEFAULT 0,
41
- shared_at TEXT,
42
- created_at TEXT DEFAULT (datetime('now'))
43
- );
44
-
45
- CREATE TABLE knowledge_conflicts (
46
- id INTEGER PRIMARY KEY AUTOINCREMENT,
47
- file_path TEXT NOT NULL,
48
- developer_a TEXT NOT NULL,
49
- developer_b TEXT NOT NULL,
50
- conflict_type TEXT NOT NULL DEFAULT 'concurrent_edit',
51
- resolved INTEGER NOT NULL DEFAULT 0,
52
- detected_at TEXT DEFAULT (datetime('now'))
53
- );
54
-
55
- CREATE TABLE observations (
56
- id INTEGER PRIMARY KEY AUTOINCREMENT,
57
- session_id TEXT NOT NULL,
58
- type TEXT NOT NULL,
59
- title TEXT NOT NULL,
60
- detail TEXT,
61
- files_involved TEXT DEFAULT '[]',
62
- created_at_epoch INTEGER NOT NULL
63
- );
64
- `);
65
- return db;
66
- }
67
-
68
- describe('team-knowledge', () => {
69
- let db: Database.Database;
70
-
71
- beforeEach(() => {
72
- db = createTestDb();
73
- });
74
-
75
- afterEach(() => {
76
- db.close();
77
- });
78
-
79
- describe('getTeamToolDefinitions', () => {
80
- it('returns 3 tool definitions', () => {
81
- const tools = getTeamToolDefinitions();
82
- expect(tools).toHaveLength(3);
83
- expect(tools.map(t => t.name.split('_').slice(-2).join('_'))).toEqual([
84
- 'team_search',
85
- 'team_expertise',
86
- 'team_conflicts',
87
- ]);
88
- });
89
-
90
- it('team_search requires query', () => {
91
- const tools = getTeamToolDefinitions();
92
- const searchTool = tools.find(t => t.name.endsWith('_team_search'));
93
- expect(searchTool?.inputSchema.required).toEqual(['query']);
94
- });
95
- });
96
-
97
- describe('isTeamTool', () => {
98
- it('returns true for team tool names', () => {
99
- expect(isTeamTool('massu_team_search')).toBe(true);
100
- expect(isTeamTool('massu_team_expertise')).toBe(true);
101
- expect(isTeamTool('massu_team_conflicts')).toBe(true);
102
- });
103
-
104
- it('returns false for non-team tool names', () => {
105
- expect(isTeamTool('massu_security_score')).toBe(false);
106
- expect(isTeamTool('massu_unknown')).toBe(false);
107
- });
108
- });
109
-
110
- describe('calculateExpertise', () => {
111
- it('calculates expertise from session and observation counts', () => {
112
- const score = calculateExpertise(10, 50);
113
- expect(score).toBeGreaterThan(0);
114
- expect(score).toBeLessThanOrEqual(100);
115
- });
116
-
117
- it('returns higher score for more sessions', () => {
118
- const score1 = calculateExpertise(5, 10);
119
- const score2 = calculateExpertise(20, 10);
120
- expect(score2).toBeGreaterThan(score1);
121
- });
122
-
123
- it('returns higher score for more observations', () => {
124
- const score1 = calculateExpertise(5, 10);
125
- const score2 = calculateExpertise(5, 50);
126
- expect(score2).toBeGreaterThan(score1);
127
- });
128
-
129
- it('caps score at 100', () => {
130
- const score = calculateExpertise(1000, 1000);
131
- expect(score).toBeLessThanOrEqual(100);
132
- });
133
-
134
- it('handles zero counts', () => {
135
- const score = calculateExpertise(0, 0);
136
- expect(score).toBe(0);
137
- });
138
- });
139
-
140
- describe('updateExpertise', () => {
141
- it('creates expertise record for new developer-module pair', () => {
142
- // Insert observation with file changes
143
- db.prepare(`
144
- INSERT INTO observations (session_id, type, title, detail, files_involved, created_at_epoch)
145
- VALUES (?, ?, ?, ?, ?, ?)
146
- `).run('session-1', 'feature', 'Added feature', 'Details', JSON.stringify(['src/routers/orders.ts']), Date.now());
147
-
148
- updateExpertise(db, 'dev-alice', 'session-1');
149
-
150
- const expertise = db.prepare(
151
- 'SELECT * FROM developer_expertise WHERE developer_id = ? AND module = ?'
152
- ).get('dev-alice', 'orders') as Record<string, unknown> | undefined;
153
-
154
- expect(expertise).toBeDefined();
155
- expect(expertise!.session_count).toBe(1);
156
- expect(expertise!.expertise_score).toBeGreaterThan(0);
157
- });
158
-
159
- it('updates existing expertise record', () => {
160
- // Create initial expertise
161
- db.prepare(`
162
- INSERT INTO developer_expertise (developer_id, module, session_count, observation_count, expertise_score)
163
- VALUES (?, ?, ?, ?, ?)
164
- `).run('dev-bob', 'products', 5, 20, 50);
165
-
166
- // Add new observation
167
- db.prepare(`
168
- INSERT INTO observations (session_id, type, title, detail, files_involved, created_at_epoch)
169
- VALUES (?, ?, ?, ?, ?, ?)
170
- `).run('session-2', 'bugfix', 'Fixed bug', 'Details', JSON.stringify(['src/routers/products.ts']), Date.now());
171
-
172
- updateExpertise(db, 'dev-bob', 'session-2');
173
-
174
- const expertise = db.prepare(
175
- 'SELECT * FROM developer_expertise WHERE developer_id = ? AND module = ?'
176
- ).get('dev-bob', 'products') as Record<string, unknown>;
177
-
178
- expect(expertise.session_count).toBe(6);
179
- expect(expertise.observation_count).toBeGreaterThan(20);
180
- });
181
- });
182
-
183
- describe('detectConflicts', () => {
184
- it('returns empty array when no conflicts', () => {
185
- const conflicts = detectConflicts(db, 7);
186
- expect(conflicts).toEqual([]);
187
- });
188
-
189
- it('detects concurrent edits by different developers', () => {
190
- // Add observations for two developers on same file
191
- db.prepare(`
192
- INSERT INTO shared_observations (developer_id, project, observation_type, summary, file_path, is_shared, created_at)
193
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
194
- `).run('dev-alice', 'myproject', 'edit', 'Alice edited file', 'src/app.ts', 1);
195
-
196
- db.prepare(`
197
- INSERT INTO shared_observations (developer_id, project, observation_type, summary, file_path, is_shared, created_at)
198
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
199
- `).run('dev-bob', 'myproject', 'edit', 'Bob edited file', 'src/app.ts', 1);
200
-
201
- const conflicts = detectConflicts(db, 7);
202
- expect(conflicts.length).toBeGreaterThan(0);
203
- expect(conflicts[0].filePath).toBe('src/app.ts');
204
- expect(conflicts[0].conflictType).toBe('concurrent_edit');
205
- });
206
-
207
- it('respects days back parameter', () => {
208
- // Add old observation
209
- db.prepare(`
210
- INSERT INTO shared_observations (developer_id, project, observation_type, summary, file_path, is_shared, created_at)
211
- VALUES (?, ?, ?, ?, ?, ?, datetime('now', '-10 days'))
212
- `).run('dev-alice', 'myproject', 'edit', 'Old edit', 'src/old.ts', 1);
213
-
214
- db.prepare(`
215
- INSERT INTO shared_observations (developer_id, project, observation_type, summary, file_path, is_shared, created_at)
216
- VALUES (?, ?, ?, ?, ?, ?, datetime('now', '-10 days'))
217
- `).run('dev-bob', 'myproject', 'edit', 'Old edit', 'src/old.ts', 1);
218
-
219
- const conflicts = detectConflicts(db, 5); // Only look back 5 days
220
- expect(conflicts).toEqual([]);
221
- });
222
- });
223
-
224
- describe('shareObservation', () => {
225
- it('creates shared observation record', () => {
226
- const id = shareObservation(
227
- db,
228
- 'dev-alice',
229
- 'myproject',
230
- 'discovery',
231
- 'Found interesting pattern',
232
- {
233
- filePath: 'src/utils.ts',
234
- module: 'utils',
235
- severity: 4,
236
- }
237
- );
238
-
239
- expect(id).toBeGreaterThan(0);
240
-
241
- const obs = db.prepare('SELECT * FROM shared_observations WHERE id = ?').get(id) as Record<string, unknown>;
242
- expect(obs.developer_id).toBe('dev-alice');
243
- expect(obs.observation_type).toBe('discovery');
244
- expect(obs.summary).toBe('Found interesting pattern');
245
- expect(obs.file_path).toBe('src/utils.ts');
246
- expect(obs.module).toBe('utils');
247
- expect(obs.severity).toBe(4);
248
- expect(obs.is_shared).toBe(1);
249
- });
250
-
251
- it('uses default severity when not provided', () => {
252
- const id = shareObservation(
253
- db,
254
- 'dev-bob',
255
- 'myproject',
256
- 'bugfix',
257
- 'Fixed critical bug'
258
- );
259
-
260
- const obs = db.prepare('SELECT severity FROM shared_observations WHERE id = ?').get(id) as { severity: number };
261
- expect(obs.severity).toBe(3);
262
- });
263
- });
264
-
265
- describe('handleTeamToolCall', () => {
266
- it('handles team_search with no results', () => {
267
- const result = handleTeamToolCall('massu_team_search', { query: 'nonexistent' }, db);
268
- const text = result.content[0].text;
269
- expect(text).toContain('No shared observations found');
270
- });
271
-
272
- it('handles team_search with results', () => {
273
- shareObservation(db, 'dev-alice', 'myproject', 'discovery', 'Found authentication bug', {
274
- module: 'auth',
275
- });
276
-
277
- const result = handleTeamToolCall('massu_team_search', { query: 'authentication' }, db);
278
- const text = result.content[0].text;
279
- expect(text).toContain('Team Knowledge');
280
- expect(text).toContain('dev-alice');
281
- expect(text).toContain('discovery');
282
- });
283
-
284
- it('handles team_expertise overview when no module specified', () => {
285
- db.prepare(`
286
- INSERT INTO developer_expertise (developer_id, module, session_count, observation_count, expertise_score)
287
- VALUES (?, ?, ?, ?, ?)
288
- `).run('dev-alice', 'orders', 10, 50, 75);
289
-
290
- const result = handleTeamToolCall('massu_team_expertise', {}, db);
291
- const text = result.content[0].text;
292
- expect(text).toContain('Team Expertise Overview');
293
- expect(text).toContain('orders');
294
- });
295
-
296
- it('handles team_expertise for specific module', () => {
297
- db.prepare(`
298
- INSERT INTO developer_expertise (developer_id, module, session_count, observation_count, expertise_score)
299
- VALUES (?, ?, ?, ?, ?)
300
- `).run('dev-alice', 'products', 15, 60, 85);
301
-
302
- db.prepare(`
303
- INSERT INTO developer_expertise (developer_id, module, session_count, observation_count, expertise_score)
304
- VALUES (?, ?, ?, ?, ?)
305
- `).run('dev-bob', 'products', 8, 30, 55);
306
-
307
- const result = handleTeamToolCall('massu_team_expertise', { module: 'products' }, db);
308
- const text = result.content[0].text;
309
- expect(text).toContain('Expertise: products');
310
- expect(text).toContain('dev-alice');
311
- expect(text).toContain('dev-bob');
312
- expect(text).toContain('85'); // Alice's score should appear first (higher)
313
- });
314
-
315
- it('handles team_conflicts with no conflicts', () => {
316
- const result = handleTeamToolCall('massu_team_conflicts', {}, db);
317
- const text = result.content[0].text;
318
- expect(text).toContain('No concurrent work conflicts');
319
- });
320
-
321
- it('handles unknown tool name', () => {
322
- const result = handleTeamToolCall('massu_team_unknown', {}, db);
323
- const text = result.content[0].text;
324
- expect(text).toContain('Unknown team tool');
325
- });
326
- });
327
- });
@@ -1,340 +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 { getToolDefinitions, handleToolCall } from '../tools.ts';
7
-
8
- // Mock all dependencies
9
- vi.mock('../config.ts', () => ({
10
- getConfig: () => ({
11
- toolPrefix: 'massu',
12
- framework: { type: 'typescript', router: 'trpc', orm: 'prisma' },
13
- paths: {
14
- source: 'src',
15
- routers: 'src/server/api/routers',
16
- middleware: 'src/middleware.ts',
17
- },
18
- domains: [
19
- {
20
- name: 'test',
21
- routers: ['test'],
22
- pages: ['src/app/test/**'],
23
- tables: ['test_table'],
24
- allowedImportsFrom: ['*'],
25
- },
26
- ],
27
- }),
28
- getProjectRoot: () => '/test/project',
29
- getResolvedPaths: () => ({
30
- codegraphDbPath: '/test/codegraph.db',
31
- dataDbPath: '/test/data.db',
32
- }),
33
- }));
34
-
35
- vi.mock('../memory-db.ts', () => ({
36
- getMemoryDb: () => createMockDb(),
37
- }));
38
-
39
- vi.mock('../db.ts', async (importOriginal) => {
40
- const actual = await importOriginal() as Record<string, unknown>;
41
- return {
42
- ...actual,
43
- isDataStale: () => false,
44
- updateBuildTimestamp: vi.fn(),
45
- };
46
- });
47
-
48
- vi.mock('../import-resolver.ts', () => ({
49
- buildImportIndex: () => 0,
50
- }));
51
-
52
- vi.mock('../trpc-index.ts', () => ({
53
- buildTrpcIndex: () => ({ totalProcedures: 0, withCallers: 0, withoutCallers: 0 }),
54
- }));
55
-
56
- vi.mock('../page-deps.ts', () => ({
57
- buildPageDeps: () => 0,
58
- findAffectedPages: () => [],
59
- }));
60
-
61
- vi.mock('../middleware-tree.ts', () => ({
62
- buildMiddlewareTree: () => 0,
63
- isInMiddlewareTree: () => false,
64
- getMiddlewareTree: () => [],
65
- }));
66
-
67
- vi.mock('../rules.ts', () => ({
68
- matchRules: () => [],
69
- globMatch: () => false,
70
- }));
71
-
72
- vi.mock('../sentinel-scanner.ts', () => ({
73
- runFeatureScan: () => ({ registered: 0, fromProcedures: 0, fromPages: 0, fromComponents: 0 }),
74
- }));
75
-
76
- function createMockDb(): Database.Database {
77
- const db = new Database(':memory:');
78
- db.pragma('journal_mode = WAL');
79
-
80
- db.exec(`
81
- CREATE TABLE IF NOT EXISTS massu_imports (
82
- id INTEGER PRIMARY KEY AUTOINCREMENT,
83
- source_file TEXT NOT NULL,
84
- target_file TEXT NOT NULL,
85
- import_type TEXT NOT NULL,
86
- imported_names TEXT NOT NULL DEFAULT '[]',
87
- line INTEGER NOT NULL DEFAULT 0
88
- );
89
-
90
- CREATE TABLE IF NOT EXISTS massu_trpc_procedures (
91
- id INTEGER PRIMARY KEY AUTOINCREMENT,
92
- router_file TEXT NOT NULL,
93
- router_name TEXT NOT NULL,
94
- procedure_name TEXT NOT NULL,
95
- procedure_type TEXT NOT NULL,
96
- has_ui_caller INTEGER NOT NULL DEFAULT 0
97
- );
98
-
99
- CREATE TABLE IF NOT EXISTS massu_trpc_call_sites (
100
- id INTEGER PRIMARY KEY AUTOINCREMENT,
101
- procedure_id INTEGER NOT NULL,
102
- file TEXT NOT NULL,
103
- line INTEGER NOT NULL DEFAULT 0,
104
- call_pattern TEXT NOT NULL,
105
- FOREIGN KEY (procedure_id) REFERENCES massu_trpc_procedures(id)
106
- );
107
-
108
- CREATE TABLE IF NOT EXISTS massu_middleware_tree (
109
- id INTEGER PRIMARY KEY AUTOINCREMENT,
110
- file TEXT NOT NULL UNIQUE
111
- );
112
-
113
- CREATE TABLE IF NOT EXISTS massu_meta (
114
- key TEXT PRIMARY KEY,
115
- value TEXT NOT NULL
116
- );
117
- `);
118
-
119
- return db;
120
- }
121
-
122
- function createMockCodeGraphDb(): Database.Database {
123
- const db = new Database(':memory:');
124
- db.pragma('journal_mode = WAL');
125
-
126
- db.exec(`
127
- CREATE TABLE IF NOT EXISTS files (
128
- id INTEGER PRIMARY KEY AUTOINCREMENT,
129
- path TEXT UNIQUE NOT NULL,
130
- indexed_at INTEGER NOT NULL DEFAULT 0
131
- );
132
-
133
- CREATE TABLE IF NOT EXISTS nodes (
134
- id INTEGER PRIMARY KEY AUTOINCREMENT,
135
- file_path TEXT NOT NULL,
136
- name TEXT NOT NULL,
137
- kind TEXT NOT NULL,
138
- start_line INTEGER,
139
- end_line INTEGER
140
- );
141
- `);
142
-
143
- // Seed with at least one file to pass staleness check
144
- db.prepare('INSERT INTO files (path, indexed_at) VALUES (?, ?)').run('test.ts', Math.floor(Date.now() / 1000));
145
-
146
- return db;
147
- }
148
-
149
- describe('Tools Module', () => {
150
- let dataDb: Database.Database;
151
- let codegraphDb: Database.Database;
152
-
153
- beforeEach(() => {
154
- dataDb = createMockDb();
155
- codegraphDb = createMockCodeGraphDb();
156
- });
157
-
158
- afterEach(() => {
159
- dataDb.close();
160
- codegraphDb.close();
161
- });
162
-
163
- describe('getToolDefinitions', () => {
164
- it('returns an array of tool definitions', () => {
165
- const tools = getToolDefinitions();
166
- expect(Array.isArray(tools)).toBe(true);
167
- expect(tools.length).toBeGreaterThan(0);
168
- });
169
-
170
- it('includes core tools', () => {
171
- const tools = getToolDefinitions();
172
- const toolNames = tools.map(t => t.name);
173
- expect(toolNames).toContain('massu_sync');
174
- expect(toolNames).toContain('massu_context');
175
- expect(toolNames).toContain('massu_impact');
176
- });
177
-
178
- it('includes trpc tools when framework.router is trpc', () => {
179
- const tools = getToolDefinitions();
180
- const toolNames = tools.map(t => t.name);
181
- expect(toolNames).toContain('massu_trpc_map');
182
- expect(toolNames).toContain('massu_coupling_check');
183
- });
184
-
185
- it('includes domain tools when domains are configured', () => {
186
- const tools = getToolDefinitions();
187
- const toolNames = tools.map(t => t.name);
188
- expect(toolNames).toContain('massu_domains');
189
- });
190
-
191
- it('includes schema tools when framework.orm is prisma', () => {
192
- const tools = getToolDefinitions();
193
- const toolNames = tools.map(t => t.name);
194
- expect(toolNames).toContain('massu_schema');
195
- });
196
-
197
- it('each tool has required properties', () => {
198
- const tools = getToolDefinitions();
199
- for (const tool of tools) {
200
- expect(tool.name).toBeTruthy();
201
- expect(tool.description).toBeTruthy();
202
- expect(tool.inputSchema).toBeTruthy();
203
- expect(typeof tool.inputSchema).toBe('object');
204
- }
205
- });
206
- });
207
-
208
- describe('handleToolCall - massu_sync', () => {
209
- it('rebuilds indexes and returns summary', () => {
210
- const result = handleToolCall('massu_sync', {}, dataDb, codegraphDb);
211
- expect(result.content[0].type).toBe('text');
212
- expect(result.content[0].text).toContain('Indexes rebuilt');
213
- });
214
- });
215
-
216
- describe('handleToolCall - massu_context', () => {
217
- beforeEach(() => {
218
- codegraphDb.prepare(`
219
- INSERT INTO nodes (file_path, name, kind, start_line, end_line)
220
- VALUES ('src/test.ts', 'testFunction', 'function', 1, 10)
221
- `).run();
222
-
223
- dataDb.prepare(`
224
- INSERT INTO massu_imports (source_file, target_file, import_type)
225
- VALUES ('src/test.ts', 'src/utils.ts', 'named')
226
- `).run();
227
- });
228
-
229
- it('returns context for a file', () => {
230
- const result = handleToolCall('massu_context', { file: 'src/test.ts' }, dataDb, codegraphDb);
231
- expect(result.content[0].text).toContain('CodeGraph Nodes');
232
- expect(result.content[0].text).toContain('testFunction');
233
- });
234
-
235
- it('includes domain classification', () => {
236
- const result = handleToolCall('massu_context', { file: 'src/test.ts' }, dataDb, codegraphDb);
237
- expect(result.content[0].text).toContain('Domain:');
238
- });
239
-
240
- it('includes import information', () => {
241
- const result = handleToolCall('massu_context', { file: 'src/test.ts' }, dataDb, codegraphDb);
242
- expect(result.content[0].text).toContain('Imports');
243
- });
244
- });
245
-
246
- describe('handleToolCall - massu_impact', () => {
247
- it('returns impact analysis for a file', () => {
248
- const result = handleToolCall('massu_impact', { file: 'src/test.ts' }, dataDb, codegraphDb);
249
- expect(result.content[0].text).toContain('Impact Analysis');
250
- expect(result.content[0].text).toContain('src/test.ts');
251
- });
252
-
253
- it('includes middleware tree check', () => {
254
- const result = handleToolCall('massu_impact', { file: 'src/test.ts' }, dataDb, codegraphDb);
255
- expect(result.content[0].text).toContain('Middleware');
256
- });
257
-
258
- it('includes domain information', () => {
259
- const result = handleToolCall('massu_impact', { file: 'src/test.ts' }, dataDb, codegraphDb);
260
- expect(result.content[0].text).toContain('Domain:');
261
- });
262
- });
263
-
264
- describe('handleToolCall - massu_trpc_map', () => {
265
- beforeEach(() => {
266
- dataDb.prepare(`
267
- INSERT INTO massu_trpc_procedures (router_file, router_name, procedure_name, procedure_type, has_ui_caller)
268
- VALUES
269
- ('src/server/api/routers/test.ts', 'test', 'getTest', 'query', 1),
270
- ('src/server/api/routers/test.ts', 'test', 'updateTest', 'mutation', 0)
271
- `).run();
272
- });
273
-
274
- it('returns summary without arguments', () => {
275
- const result = handleToolCall('massu_trpc_map', {}, dataDb, codegraphDb);
276
- expect(result.content[0].text).toContain('tRPC Procedure Summary');
277
- expect(result.content[0].text).toContain('Total procedures');
278
- });
279
-
280
- it('filters by router', () => {
281
- const result = handleToolCall('massu_trpc_map', { router: 'test' }, dataDb, codegraphDb);
282
- expect(result.content[0].text).toContain('Router: test');
283
- expect(result.content[0].text).toContain('getTest');
284
- expect(result.content[0].text).toContain('updateTest');
285
- });
286
-
287
- it('shows uncoupled procedures', () => {
288
- const result = handleToolCall('massu_trpc_map', { uncoupled: true }, dataDb, codegraphDb);
289
- expect(result.content[0].text).toContain('Uncoupled Procedures');
290
- expect(result.content[0].text).toContain('updateTest');
291
- });
292
- });
293
-
294
- describe('handleToolCall - massu_domains', () => {
295
- beforeEach(() => {
296
- dataDb.prepare(`
297
- INSERT INTO massu_imports (source_file, target_file, import_type)
298
- VALUES ('src/app/test/page.tsx', 'src/utils/helper.ts', 'named')
299
- `).run();
300
-
301
- codegraphDb.prepare(`
302
- INSERT INTO files (path, indexed_at)
303
- VALUES ('src/app/test/page.tsx', ?)
304
- `).run(Math.floor(Date.now() / 1000));
305
- });
306
-
307
- it('classifies a file', () => {
308
- const result = handleToolCall('massu_domains', { file: 'src/app/test/page.tsx' }, dataDb, codegraphDb);
309
- expect(result.content[0].text).toContain('Domain:');
310
- });
311
-
312
- it('shows domain summary without arguments', () => {
313
- const result = handleToolCall('massu_domains', {}, dataDb, codegraphDb);
314
- expect(result.content[0].text).toContain('Domain Summary');
315
- });
316
-
317
- it('lists files in domain', () => {
318
- const result = handleToolCall('massu_domains', { domain: 'test' }, dataDb, codegraphDb);
319
- expect(result.content[0].text).toContain('Domain: test');
320
- });
321
- });
322
-
323
- describe('handleToolCall - unknown tool', () => {
324
- it('returns error for unknown tool', () => {
325
- const result = handleToolCall('massu_unknown_tool', {}, dataDb, codegraphDb);
326
- expect(result.content[0].text).toContain('Unknown tool');
327
- });
328
- });
329
-
330
- describe('handleToolCall - error handling', () => {
331
- it('catches and returns errors', () => {
332
- // Force an error by passing invalid database
333
- const badDb = new Database(':memory:');
334
- badDb.close(); // Closed DB will cause errors
335
-
336
- const result = handleToolCall('massu_context', { file: 'test.ts' }, badDb, codegraphDb);
337
- expect(result.content[0].text).toContain('Error');
338
- });
339
- });
340
- });