@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.
- package/README.md +2 -2
- package/dist/hooks/cost-tracker.js +23 -35
- package/dist/hooks/post-edit-context.js +2 -2
- package/dist/hooks/post-tool-use.js +43 -58
- package/dist/hooks/pre-compact.js +23 -38
- package/dist/hooks/pre-delete-check.js +18 -31
- package/dist/hooks/quality-event.js +23 -35
- package/dist/hooks/session-end.js +62 -78
- package/dist/hooks/session-start.js +33 -42
- package/dist/hooks/user-prompt.js +23 -38
- package/package.json +8 -14
- package/src/adr-generator.ts +9 -2
- package/src/analytics.ts +9 -3
- package/src/audit-trail.ts +10 -3
- package/src/cloud-sync.ts +14 -18
- package/src/commands/init.ts +1 -5
- package/src/cost-tracker.ts +11 -6
- package/src/dependency-scorer.ts +9 -2
- package/src/docs-tools.ts +13 -10
- package/src/hooks/post-edit-context.ts +3 -3
- package/src/hooks/session-end.ts +3 -3
- package/src/hooks/session-start.ts +2 -2
- package/src/memory-db.ts +1351 -23
- package/src/memory-tools.ts +14 -15
- package/src/observability-tools.ts +13 -2
- package/src/prompt-analyzer.ts +9 -2
- package/src/regression-detector.ts +9 -3
- package/src/security-scorer.ts +9 -2
- package/src/sentinel-db.ts +43 -88
- package/src/sentinel-tools.ts +8 -11
- package/src/server.ts +1 -2
- package/src/team-knowledge.ts +9 -2
- package/src/tools.ts +771 -35
- package/src/validate-features-runner.ts +0 -1
- package/src/validation-engine.ts +9 -2
- package/dist/cli.js +0 -7890
- package/dist/server.js +0 -7008
- package/src/__tests__/adr-generator.test.ts +0 -260
- package/src/__tests__/analytics.test.ts +0 -282
- package/src/__tests__/audit-trail.test.ts +0 -382
- package/src/__tests__/backfill-sessions.test.ts +0 -690
- package/src/__tests__/cli.test.ts +0 -290
- package/src/__tests__/cloud-sync.test.ts +0 -261
- package/src/__tests__/config-sections.test.ts +0 -359
- package/src/__tests__/config.test.ts +0 -732
- package/src/__tests__/cost-tracker.test.ts +0 -348
- package/src/__tests__/db.test.ts +0 -177
- package/src/__tests__/dependency-scorer.test.ts +0 -325
- package/src/__tests__/docs-integration.test.ts +0 -178
- package/src/__tests__/docs-tools.test.ts +0 -199
- package/src/__tests__/domains.test.ts +0 -236
- package/src/__tests__/hooks.test.ts +0 -221
- package/src/__tests__/import-resolver.test.ts +0 -95
- package/src/__tests__/integration/path-traversal.test.ts +0 -134
- package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
- package/src/__tests__/integration/tool-registration.test.ts +0 -146
- package/src/__tests__/memory-db.test.ts +0 -404
- package/src/__tests__/memory-enhancements.test.ts +0 -316
- package/src/__tests__/memory-tools.test.ts +0 -199
- package/src/__tests__/middleware-tree.test.ts +0 -177
- package/src/__tests__/observability-tools.test.ts +0 -595
- package/src/__tests__/observability.test.ts +0 -437
- package/src/__tests__/observation-extractor.test.ts +0 -167
- package/src/__tests__/page-deps.test.ts +0 -60
- package/src/__tests__/prompt-analyzer.test.ts +0 -298
- package/src/__tests__/regression-detector.test.ts +0 -295
- package/src/__tests__/rules.test.ts +0 -87
- package/src/__tests__/schema-mapper.test.ts +0 -29
- package/src/__tests__/security-scorer.test.ts +0 -238
- package/src/__tests__/security-utils.test.ts +0 -175
- package/src/__tests__/sentinel-db.test.ts +0 -491
- package/src/__tests__/sentinel-scanner.test.ts +0 -750
- package/src/__tests__/sentinel-tools.test.ts +0 -324
- package/src/__tests__/sentinel-types.test.ts +0 -750
- package/src/__tests__/server.test.ts +0 -452
- package/src/__tests__/session-archiver.test.ts +0 -524
- package/src/__tests__/session-state-generator.test.ts +0 -900
- package/src/__tests__/team-knowledge.test.ts +0 -327
- package/src/__tests__/tools.test.ts +0 -340
- package/src/__tests__/transcript-parser.test.ts +0 -195
- package/src/__tests__/trpc-index.test.ts +0 -25
- package/src/__tests__/validate-features-runner.test.ts +0 -517
- package/src/__tests__/validation-engine.test.ts +0 -300
- package/src/core-tools.ts +0 -685
- package/src/memory-queries.ts +0 -804
- package/src/memory-schema.ts +0 -546
- 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
|
-
});
|