@massu/core 1.10.8 → 1.12.0

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 (178) hide show
  1. package/CHANGELOG.md +1303 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +517 -397
  4. package/dist/detect/adapters/.bundle-shasums.json +4 -7
  5. package/dist/detect/adapters/rails.js +6 -4
  6. package/dist/detect/adapters/spring.js +6 -4
  7. package/dist/detect/adapters/tree-sitter-loader.js +6 -4
  8. package/dist/hooks/auto-learning-pipeline.js +24 -1
  9. package/dist/hooks/classify-failure.js +32 -3
  10. package/dist/hooks/cost-tracker.js +24 -0
  11. package/dist/hooks/fix-detector.js +97 -1
  12. package/dist/hooks/incident-pipeline.js +30 -1
  13. package/dist/hooks/intent-suggester.js +6 -3
  14. package/dist/hooks/post-edit-context.js +24 -1
  15. package/dist/hooks/post-tool-use.js +66 -18
  16. package/dist/hooks/pre-compact.js +27 -2
  17. package/dist/hooks/pre-delete-check.js +48 -22
  18. package/dist/hooks/pre-tool-use-gate.js +962 -0
  19. package/dist/hooks/quality-event.js +24 -0
  20. package/dist/hooks/rule-enforcement-pipeline.js +24 -1
  21. package/dist/hooks/security-gate.js +29 -16
  22. package/dist/hooks/session-end.js +24 -0
  23. package/dist/hooks/session-start.js +8710 -8460
  24. package/dist/hooks/user-prompt.js +24 -0
  25. package/package.json +15 -18
  26. package/commands/README.md +0 -134
  27. package/src/adapter.ts +0 -121
  28. package/src/adr-generator.ts +0 -292
  29. package/src/analytics.ts +0 -373
  30. package/src/audit-trail.ts +0 -450
  31. package/src/backfill-sessions.ts +0 -182
  32. package/src/changelog-generator.ts +0 -178
  33. package/src/cli.ts +0 -246
  34. package/src/cloud-sync.ts +0 -234
  35. package/src/commands/adapters.ts +0 -824
  36. package/src/commands/changelog.ts +0 -165
  37. package/src/commands/config-check-drift.ts +0 -133
  38. package/src/commands/config-refresh.ts +0 -396
  39. package/src/commands/config-upgrade.ts +0 -127
  40. package/src/commands/doctor.ts +0 -485
  41. package/src/commands/hook-runner.ts +0 -145
  42. package/src/commands/init.ts +0 -1823
  43. package/src/commands/install-commands.ts +0 -692
  44. package/src/commands/install-hooks.ts +0 -27
  45. package/src/commands/permissions.ts +0 -150
  46. package/src/commands/refresh-log.ts +0 -37
  47. package/src/commands/show-template.ts +0 -65
  48. package/src/commands/template-engine.ts +0 -301
  49. package/src/commands/watch.ts +0 -430
  50. package/src/config.ts +0 -765
  51. package/src/cost-tracker.ts +0 -355
  52. package/src/db.ts +0 -370
  53. package/src/dependency-scorer.ts +0 -337
  54. package/src/detect/adapters/aspnet.ts +0 -4
  55. package/src/detect/adapters/discover.ts +0 -469
  56. package/src/detect/adapters/file-sampler.ts +0 -225
  57. package/src/detect/adapters/go-chi.ts +0 -4
  58. package/src/detect/adapters/index.ts +0 -51
  59. package/src/detect/adapters/nextjs-trpc.ts +0 -166
  60. package/src/detect/adapters/parse-guard.ts +0 -133
  61. package/src/detect/adapters/phoenix.ts +0 -4
  62. package/src/detect/adapters/python-django.ts +0 -208
  63. package/src/detect/adapters/python-fastapi.ts +0 -223
  64. package/src/detect/adapters/python-flask.ts +0 -235
  65. package/src/detect/adapters/query-helpers.ts +0 -170
  66. package/src/detect/adapters/rails.ts +0 -4
  67. package/src/detect/adapters/runner.ts +0 -284
  68. package/src/detect/adapters/spring.ts +0 -4
  69. package/src/detect/adapters/swift-swiftui.ts +0 -171
  70. package/src/detect/adapters/tree-sitter-loader.ts +0 -517
  71. package/src/detect/adapters/types.ts +0 -191
  72. package/src/detect/codebase-introspector.ts +0 -210
  73. package/src/detect/domain-inferrer.ts +0 -197
  74. package/src/detect/drift.ts +0 -199
  75. package/src/detect/framework-detector.ts +0 -307
  76. package/src/detect/index.ts +0 -209
  77. package/src/detect/manifest-registry.ts +0 -261
  78. package/src/detect/migrate.ts +0 -362
  79. package/src/detect/monorepo-detector.ts +0 -348
  80. package/src/detect/package-detector.ts +0 -828
  81. package/src/detect/passthrough.ts +0 -108
  82. package/src/detect/regex-fallback.ts +0 -449
  83. package/src/detect/source-dir-detector.ts +0 -297
  84. package/src/detect/vr-command-map.ts +0 -167
  85. package/src/docs-map.json +0 -100
  86. package/src/docs-tools.ts +0 -519
  87. package/src/domains.ts +0 -182
  88. package/src/hooks/auto-learning-pipeline.ts +0 -228
  89. package/src/hooks/classify-failure.ts +0 -259
  90. package/src/hooks/cost-tracker.ts +0 -66
  91. package/src/hooks/fix-detector.ts +0 -186
  92. package/src/hooks/incident-pipeline.ts +0 -190
  93. package/src/hooks/intent-suggester.ts +0 -131
  94. package/src/hooks/post-edit-context.ts +0 -91
  95. package/src/hooks/post-tool-use.ts +0 -413
  96. package/src/hooks/pre-compact.ts +0 -168
  97. package/src/hooks/pre-delete-check.ts +0 -243
  98. package/src/hooks/quality-event.ts +0 -127
  99. package/src/hooks/rule-enforcement-pipeline.ts +0 -159
  100. package/src/hooks/security-gate.ts +0 -153
  101. package/src/hooks/session-end.ts +0 -467
  102. package/src/hooks/session-start.ts +0 -436
  103. package/src/hooks/user-prompt.ts +0 -157
  104. package/src/import-resolver.ts +0 -226
  105. package/src/knowledge-db.ts +0 -176
  106. package/src/knowledge-indexer.ts +0 -708
  107. package/src/knowledge-tools.ts +0 -1414
  108. package/src/lib/fileLock.ts +0 -203
  109. package/src/lib/framework-supports.ts +0 -85
  110. package/src/lib/gitToplevel.ts +0 -22
  111. package/src/lib/hook-registry.ts +0 -43
  112. package/src/lib/installLock.ts +0 -66
  113. package/src/lib/memory-path.ts +0 -49
  114. package/src/lib/pidLiveness.ts +0 -67
  115. package/src/lib/settings-local.ts +0 -110
  116. package/src/lib/sql-table-names.ts +0 -71
  117. package/src/license.ts +0 -547
  118. package/src/lsp/auto-detect.ts +0 -98
  119. package/src/lsp/client.ts +0 -776
  120. package/src/lsp/enrich.ts +0 -127
  121. package/src/lsp/types.ts +0 -221
  122. package/src/memory-db.ts +0 -1614
  123. package/src/memory-file-ingest.ts +0 -128
  124. package/src/memory-tools.ts +0 -424
  125. package/src/middleware-tree.ts +0 -71
  126. package/src/observability-tools.ts +0 -343
  127. package/src/observation-extractor.ts +0 -418
  128. package/src/page-deps.ts +0 -285
  129. package/src/permissions.ts +0 -422
  130. package/src/prompt-analyzer.ts +0 -332
  131. package/src/python/coupling-detector.ts +0 -125
  132. package/src/python/domain-enforcer.ts +0 -84
  133. package/src/python/impact-analyzer.ts +0 -96
  134. package/src/python/import-parser.ts +0 -244
  135. package/src/python/import-resolver.ts +0 -136
  136. package/src/python/migration-indexer.ts +0 -116
  137. package/src/python/migration-parser.ts +0 -332
  138. package/src/python/model-indexer.ts +0 -71
  139. package/src/python/model-parser.ts +0 -279
  140. package/src/python/route-indexer.ts +0 -59
  141. package/src/python/route-parser.ts +0 -317
  142. package/src/python-tools.ts +0 -630
  143. package/src/regression-detector.ts +0 -319
  144. package/src/rules.ts +0 -57
  145. package/src/schema-mapper.ts +0 -232
  146. package/src/security/adapter-origin.ts +0 -130
  147. package/src/security/adapter-verifier.ts +0 -319
  148. package/src/security/atomic-write.ts +0 -164
  149. package/src/security/fetcher.ts +0 -200
  150. package/src/security/install-tracking.ts +0 -319
  151. package/src/security/license-pubkey.generated.ts +0 -30
  152. package/src/security/license-response-verifier.ts +0 -149
  153. package/src/security/local-fingerprint.ts +0 -225
  154. package/src/security/manifest-cache.ts +0 -333
  155. package/src/security/manifest-schema.ts +0 -129
  156. package/src/security/registry-pubkey.generated.ts +0 -35
  157. package/src/security/telemetry.ts +0 -320
  158. package/src/security-scorer.ts +0 -405
  159. package/src/security-utils.ts +0 -133
  160. package/src/sentinel-db.ts +0 -580
  161. package/src/sentinel-scanner.ts +0 -406
  162. package/src/sentinel-tools.ts +0 -513
  163. package/src/sentinel-types.ts +0 -140
  164. package/src/server-dispatch.ts +0 -225
  165. package/src/server.ts +0 -107
  166. package/src/session-archiver.ts +0 -111
  167. package/src/session-state-generator.ts +0 -174
  168. package/src/team-knowledge.ts +0 -407
  169. package/src/tool-db-needs.ts +0 -232
  170. package/src/tools.ts +0 -1206
  171. package/src/transcript-parser.ts +0 -458
  172. package/src/trpc-index.ts +0 -215
  173. package/src/validate-features-runner.ts +0 -107
  174. package/src/validation-engine.ts +0 -358
  175. package/src/watch/daemon.ts +0 -385
  176. package/src/watch/lockfile-detector.ts +0 -65
  177. package/src/watch/paths.ts +0 -279
  178. package/src/watch/state.ts +0 -178
@@ -1,407 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import type Database from 'better-sqlite3';
5
- import type { ToolDefinition, ToolResult } from './tools.ts';
6
- import { getConfig } from './config.ts';
7
-
8
- // ============================================================
9
- // Team Knowledge Graph
10
- // ============================================================
11
-
12
- /** Prefix a base tool name with the configured tool prefix. */
13
- function p(baseName: string): string {
14
- return `${getConfig().toolPrefix}_${baseName}`;
15
- }
16
-
17
- /**
18
- * Calculate expertise score for a developer in a module.
19
- * Based on session depth (how many sessions) and observation quality.
20
- */
21
- export function calculateExpertise(
22
- sessionCount: number,
23
- observationCount: number
24
- ): number {
25
- const config = getConfig();
26
- const sessionWeight = config.team?.expertise_weights?.session ?? 20;
27
- const observationWeight = config.team?.expertise_weights?.observation ?? 10;
28
-
29
- const sessionScore = Math.log2(sessionCount + 1) * sessionWeight;
30
- const obsScore = Math.log2(observationCount + 1) * observationWeight;
31
- return Math.min(100, Math.round(sessionScore + obsScore));
32
- }
33
-
34
- /**
35
- * Update developer expertise based on session observations.
36
- */
37
- export function updateExpertise(
38
- db: Database.Database,
39
- developerId: string,
40
- sessionId: string
41
- ): void {
42
- const fileChanges = db.prepare(`
43
- SELECT DISTINCT files_involved FROM observations
44
- WHERE session_id = ? AND type IN ('file_change', 'feature', 'bugfix', 'refactor')
45
- `).all(sessionId) as Array<{ files_involved: string }>;
46
-
47
- const modules = new Set<string>();
48
- for (const fc of fileChanges) {
49
- try {
50
- const files = JSON.parse(fc.files_involved) as string[];
51
- for (const file of files) {
52
- const module = extractModule(file);
53
- if (module) modules.add(module);
54
- }
55
- } catch { /* skip */ }
56
- }
57
-
58
- for (const module of modules) {
59
- const existing = db.prepare(
60
- 'SELECT session_count, observation_count FROM developer_expertise WHERE developer_id = ? AND module = ?'
61
- ).get(developerId, module) as { session_count: number; observation_count: number } | undefined;
62
-
63
- const sessionCount = (existing?.session_count ?? 0) + 1;
64
- const obsCount = (existing?.observation_count ?? 0) + fileChanges.length;
65
- const score = calculateExpertise(sessionCount, obsCount);
66
-
67
- db.prepare(`
68
- INSERT INTO developer_expertise (developer_id, module, session_count, observation_count, expertise_score, last_active)
69
- VALUES (?, ?, ?, ?, ?, datetime('now'))
70
- ON CONFLICT(developer_id, module) DO UPDATE SET
71
- session_count = ?,
72
- observation_count = ?,
73
- expertise_score = ?,
74
- last_active = datetime('now')
75
- `).run(
76
- developerId, module, sessionCount, obsCount, score,
77
- sessionCount, obsCount, score
78
- );
79
- }
80
- }
81
-
82
- /**
83
- * Detect potential conflicts between developers working on same files.
84
- */
85
- export function detectConflicts(
86
- db: Database.Database,
87
- daysBack: number = 7
88
- ): Array<{
89
- filePath: string;
90
- developerA: string;
91
- developerB: string;
92
- conflictType: string;
93
- }> {
94
- const conflicts = db.prepare(`
95
- SELECT so1.file_path,
96
- so1.developer_id as developer_a,
97
- so2.developer_id as developer_b,
98
- 'concurrent_edit' as conflict_type
99
- FROM shared_observations so1
100
- JOIN shared_observations so2 ON so1.file_path = so2.file_path
101
- WHERE so1.developer_id != so2.developer_id
102
- AND so1.file_path IS NOT NULL
103
- AND so1.created_at >= datetime('now', ?)
104
- AND so2.created_at >= datetime('now', ?)
105
- GROUP BY so1.file_path, so1.developer_id, so2.developer_id
106
- `).all(`-${daysBack} days`, `-${daysBack} days`) as Array<{
107
- file_path: string;
108
- developer_a: string;
109
- developer_b: string;
110
- conflict_type: string;
111
- }>;
112
-
113
- return conflicts.map(c => ({
114
- filePath: c.file_path,
115
- developerA: c.developer_a,
116
- developerB: c.developer_b,
117
- conflictType: c.conflict_type,
118
- }));
119
- }
120
-
121
- /**
122
- * Share an observation for team visibility.
123
- */
124
- export function shareObservation(
125
- db: Database.Database,
126
- developerId: string,
127
- project: string,
128
- observationType: string,
129
- summary: string,
130
- opts?: {
131
- originalId?: number;
132
- filePath?: string;
133
- module?: string;
134
- severity?: number;
135
- }
136
- ): number {
137
- const result = db.prepare(`
138
- INSERT INTO shared_observations
139
- (original_id, developer_id, project, observation_type, summary, file_path, module, severity, is_shared, shared_at)
140
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, TRUE, datetime('now'))
141
- `).run(
142
- opts?.originalId ?? null,
143
- developerId, project, observationType, summary,
144
- opts?.filePath ?? null,
145
- opts?.module ?? null,
146
- opts?.severity ?? 3
147
- );
148
- return Number(result.lastInsertRowid);
149
- }
150
-
151
- /**
152
- * Extract business module from a file path.
153
- * Uses configurable module extraction patterns if provided.
154
- */
155
- function extractModule(filePath: string): string | null {
156
- // Route-based modules
157
- const routerMatch = filePath.match(/routers\/([^/.]+)/);
158
- if (routerMatch) return routerMatch[1];
159
-
160
- // Page-based modules
161
- const pageMatch = filePath.match(/app\/\(([^)]+)\)/);
162
- if (pageMatch) return pageMatch[1];
163
-
164
- // Component-based
165
- const compMatch = filePath.match(/components\/([^/.]+)/);
166
- if (compMatch) return compMatch[1];
167
-
168
- return null;
169
- }
170
-
171
- // ============================================================
172
- // MCP Tool Definitions & Handlers
173
- // ============================================================
174
-
175
- export function getTeamToolDefinitions(): ToolDefinition[] {
176
- return [
177
- {
178
- name: p('team_search'),
179
- description: 'Search team-shared observations. Find what other developers learned about a module or file.',
180
- inputSchema: {
181
- type: 'object',
182
- properties: {
183
- query: { type: 'string', description: 'Search text' },
184
- module: { type: 'string', description: 'Filter by business module' },
185
- },
186
- required: ['query'],
187
- },
188
- },
189
- {
190
- name: p('team_expertise'),
191
- description: 'Who knows what. Shows developers ranked by expertise for a module or file area.',
192
- inputSchema: {
193
- type: 'object',
194
- properties: {
195
- module: { type: 'string', description: 'Business module (e.g., orders, products, design)' },
196
- file_path: { type: 'string', description: 'File path to find experts for' },
197
- },
198
- required: [],
199
- },
200
- },
201
- {
202
- name: p('team_conflicts'),
203
- description: 'Detect concurrent work conflicts. Find areas where multiple developers are making changes.',
204
- inputSchema: {
205
- type: 'object',
206
- properties: {
207
- file_path: { type: 'string', description: 'Check specific file for conflicts' },
208
- days: { type: 'number', description: 'Days to look back (default: 7)' },
209
- },
210
- required: [],
211
- },
212
- },
213
- ];
214
- }
215
-
216
- const TEAM_BASE_NAMES = new Set(['team_search', 'team_expertise', 'team_conflicts']);
217
-
218
- export function isTeamTool(name: string): boolean {
219
- const pfx = getConfig().toolPrefix + '_';
220
- const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
221
- return TEAM_BASE_NAMES.has(baseName);
222
- }
223
-
224
- export function handleTeamToolCall(
225
- name: string,
226
- args: Record<string, unknown>,
227
- memoryDb: Database.Database
228
- ): ToolResult {
229
- try {
230
- const pfx = getConfig().toolPrefix + '_';
231
- const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
232
-
233
- switch (baseName) {
234
- case 'team_search':
235
- return handleTeamSearch(args, memoryDb);
236
- case 'team_expertise':
237
- return handleTeamExpertise(args, memoryDb);
238
- case 'team_conflicts':
239
- return handleTeamConflicts(args, memoryDb);
240
- default:
241
- return text(`Unknown team tool: ${name}`);
242
- }
243
- } catch (error) {
244
- return text(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}\n\nUsage: ${p('team_search')} { query: "pattern" }, ${p('team_expertise')} { module: "tasks" }`);
245
- }
246
- }
247
-
248
- function handleTeamSearch(args: Record<string, unknown>, db: Database.Database): ToolResult {
249
- const query = args.query as string;
250
- if (!query) return text(`Usage: ${p('team_search')} { query: "search term", module: "optional-module" } - Search team-shared observations.`);
251
-
252
- const module = args.module as string | undefined;
253
-
254
- let sql = `
255
- SELECT id, developer_id, observation_type, summary, file_path, module, severity, created_at
256
- FROM shared_observations
257
- WHERE is_shared = TRUE AND summary LIKE ?
258
- `;
259
- const params: (string | number)[] = [`%${query}%`];
260
-
261
- if (module) {
262
- sql += ' AND module = ?';
263
- params.push(module);
264
- }
265
-
266
- sql += ' ORDER BY created_at DESC LIMIT 20';
267
-
268
- const results = db.prepare(sql).all(...params) as Array<Record<string, unknown>>;
269
-
270
- if (results.length === 0) {
271
- return text(`No shared observations found for "${query}". Team knowledge is populated when developers share observations across sessions. Try: ${p('team_expertise')} {} to see module expertise, or broaden your search term.`);
272
- }
273
-
274
- const lines = [
275
- `## Team Knowledge: "${query}" (${results.length} results)`,
276
- '',
277
- '| Developer | Type | Summary | Module | Date |',
278
- '|-----------|------|---------|--------|------|',
279
- ];
280
-
281
- for (const r of results) {
282
- lines.push(
283
- `| ${r.developer_id} | ${r.observation_type} | ${(r.summary as string).slice(0, 60)} | ${r.module ?? '-'} | ${(r.created_at as string).split('T')[0]} |`
284
- );
285
- }
286
-
287
- return text(lines.join('\n'));
288
- }
289
-
290
- function handleTeamExpertise(args: Record<string, unknown>, db: Database.Database): ToolResult {
291
- const module = args.module as string | undefined;
292
- const filePath = args.file_path as string | undefined;
293
-
294
- let targetModule = module;
295
- if (!targetModule && filePath) {
296
- targetModule = extractModule(filePath) ?? undefined;
297
- }
298
-
299
- if (!targetModule) {
300
- const modules = db.prepare(`
301
- SELECT module, COUNT(DISTINCT developer_id) as developers, MAX(expertise_score) as top_score
302
- FROM developer_expertise
303
- GROUP BY module
304
- ORDER BY developers DESC
305
- `).all() as Array<Record<string, unknown>>;
306
-
307
- if (modules.length === 0) {
308
- return text(`No expertise data yet. Expertise is built automatically as developers work on modules across sessions. Try: ${p('team_search')} { query: "keyword" } to search shared observations instead.`);
309
- }
310
-
311
- const lines = [
312
- '## Team Expertise Overview',
313
- '',
314
- '| Module | Developers | Top Score |',
315
- '|--------|-----------|-----------|',
316
- ];
317
-
318
- for (const m of modules) {
319
- lines.push(`| ${m.module} | ${m.developers} | ${m.top_score} |`);
320
- }
321
-
322
- lines.push('');
323
- lines.push(`Use ${p('team_expertise')} { module: "module_name" } to see developers ranked by expertise.`);
324
-
325
- return text(lines.join('\n'));
326
- }
327
-
328
- const experts = db.prepare(`
329
- SELECT developer_id, expertise_score, session_count, observation_count, last_active
330
- FROM developer_expertise
331
- WHERE module = ?
332
- ORDER BY expertise_score DESC
333
- `).all(targetModule) as Array<Record<string, unknown>>;
334
-
335
- if (experts.length === 0) {
336
- return text(`No expertise data for module "${targetModule}". Expertise builds as developers work on files in this module across sessions. Try: ${p('team_expertise')} {} to see all modules with tracked expertise.`);
337
- }
338
-
339
- const lines = [
340
- `## Expertise: ${targetModule}`,
341
- '',
342
- '| Developer | Score | Sessions | Observations | Last Active |',
343
- '|-----------|-------|----------|--------------|-------------|',
344
- ];
345
-
346
- for (const e of experts) {
347
- lines.push(
348
- `| ${e.developer_id} | ${e.expertise_score} | ${e.session_count} | ${e.observation_count} | ${(e.last_active as string).split('T')[0]} |`
349
- );
350
- }
351
-
352
- return text(lines.join('\n'));
353
- }
354
-
355
- function handleTeamConflicts(args: Record<string, unknown>, db: Database.Database): ToolResult {
356
- const days = (args.days as number) ?? 7;
357
- const filePath = args.file_path as string | undefined;
358
-
359
- if (filePath) {
360
- const conflicts = db.prepare(`
361
- SELECT developer_a, developer_b, conflict_type, detected_at, resolved
362
- FROM knowledge_conflicts
363
- WHERE file_path = ?
364
- ORDER BY detected_at DESC LIMIT 10
365
- `).all(filePath) as Array<Record<string, unknown>>;
366
-
367
- if (conflicts.length === 0) {
368
- return text(`No conflicts detected for "${filePath}". Conflicts are detected when multiple developers modify the same file within the lookback window. Try: ${p('team_conflicts')} { days: 30 } to check for conflicts across all files.`);
369
- }
370
-
371
- const lines = [
372
- `## Conflicts: ${filePath}`,
373
- '',
374
- ];
375
-
376
- for (const c of conflicts) {
377
- lines.push(`- ${c.developer_a} vs ${c.developer_b} (${c.conflict_type}) - ${c.resolved ? 'resolved' : 'ACTIVE'}`);
378
- }
379
-
380
- return text(lines.join('\n'));
381
- }
382
-
383
- // General conflict detection
384
- const conflicts = detectConflicts(db, days);
385
-
386
- if (conflicts.length === 0) {
387
- return text(`No concurrent work conflicts detected in the last ${days} days. Conflicts are tracked when multiple developers modify the same files. Try a longer time range: ${p('team_conflicts')} { days: 90 }.`);
388
- }
389
-
390
- const lines = [
391
- `## Work Conflicts (${days} days)`,
392
- `Detected: ${conflicts.length}`,
393
- '',
394
- '| File | Developer A | Developer B | Type |',
395
- '|------|-----------|-----------|------|',
396
- ];
397
-
398
- for (const c of conflicts) {
399
- lines.push(`| ${c.filePath} | ${c.developerA} | ${c.developerB} | ${c.conflictType} |`);
400
- }
401
-
402
- return text(lines.join('\n'));
403
- }
404
-
405
- function text(content: string): ToolResult {
406
- return { content: [{ type: 'text', text: content }] };
407
- }
@@ -1,232 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- /**
5
- * Per-tool SQLite database dependency manifest.
6
- *
7
- * **Role**: SOLE source of truth declaring which SQLite connections each MCP
8
- * tool needs. The dispatcher (`server.ts` → `tools.ts:handleToolCall`) reads
9
- * this map to lazy-resolve connections, opening ONLY the DBs a tool requires.
10
- *
11
- * **Why this exists**:
12
- * Before plan `plan-1.6.2-server-lazy-db-deps`, the dispatcher eagerly opened
13
- * BOTH CodeGraph + Data DBs on every tool/call (see legacy `server.ts:51-55,96`
14
- * and `tools.ts:279`). When `.codegraph/codegraph.db` was missing, EVERY tool
15
- * call failed — even memory/audit/knowledge tools that have no codegraph
16
- * dependency. This manifest makes that bug class structurally impossible:
17
- * a missing peripheral DB only blocks the tools that need it.
18
- *
19
- * **Structural drift-prevention (3 layers)**:
20
- * - L1: TypeScript compile time — exhaustiveness check via `keyof TOOL_DB_NEEDS`.
21
- * - L2: `tool-db-needs-completeness.test.ts` — TypeScript AST walk of every
22
- * tool module verifies declared needs match actual DB access pattern.
23
- * Aliasing/destructuring renames cannot bypass the AST walk.
24
- * - L3: `scripts/massu-pattern-scanner.sh` Check 14 — grep-level enforcement
25
- * that every tool in `getToolDefinitions()` has a manifest entry.
26
- *
27
- * **Adding a new MCP tool**:
28
- * 1. Register in `tools.ts` (CR-11).
29
- * 2. Add an entry here. Missing entries throw `UnknownToolError` at first
30
- * dispatch AND fail L2 + L3 above.
31
- *
32
- * @see `docs/plans/2026-05-10-server-lazy-db-deps.md` (`plan-1.6.2-server-lazy-db-deps`)
33
- */
34
-
35
- /** SQLite connections the MCP server can resolve for a tool call. */
36
- export type DbNeed = 'codegraph' | 'data' | 'memory' | 'knowledge';
37
-
38
- /**
39
- * Custom error thrown when `getToolDbNeeds()` is called with a tool name that
40
- * isn't in the manifest. Caught at the JSON-RPC layer and translated to a
41
- * structured `-32602` (Invalid params) error to the client.
42
- */
43
- export class UnknownToolError extends Error {
44
- readonly toolName: string;
45
- constructor(toolName: string) {
46
- super(`Tool not registered in TOOL_DB_NEEDS manifest: ${toolName}. Add an entry to packages/core/src/tool-db-needs.ts.`);
47
- this.name = 'UnknownToolError';
48
- this.toolName = toolName;
49
- }
50
- }
51
-
52
- /**
53
- * Per-tool DB-need declarations. Keys are tool SHORT-NAMES (without the
54
- * `${toolPrefix}_` prefix). Values are the SQLite connections the handler
55
- * (or its routed module) actually accesses.
56
- *
57
- * Sourced from exhaustive grep of `packages/core/src/{*-tools,analytics,
58
- * cost-tracker,prompt-analyzer,audit-trail,validation-engine,adr-generator,
59
- * security-scorer,dependency-scorer,team-knowledge,regression-detector,
60
- * python-tools,license}.ts` on 2026-05-10. Verified line citations in
61
- * `docs/plans/2026-05-10-server-lazy-db-deps.md §1.4`.
62
- */
63
- export const TOOL_DB_NEEDS = {
64
- // === Core code-intel tools (tools.ts:393-406) ===
65
- // Use CodeGraph DB (read-only AST) + Data DB (Massu's import/trpc/sentinel
66
- // tables). All call `ensureIndexes` directly or via shared infrastructure.
67
- sync: ['codegraph', 'data'],
68
- context: ['codegraph', 'data'],
69
- coupling_check: ['codegraph', 'data'],
70
- impact: ['codegraph', 'data'],
71
- domains: ['codegraph', 'data'],
72
-
73
- // P-H009 (plan-stage-c-high-batch): `trpc_map` queries Data DB tables
74
- // populated by `ensureIndexes(d, codegraphDb)` — without CodeGraph the
75
- // index never builds and the flagship code-intel tool silently returns
76
- // "0 procedures" on fresh installs. Declaring `codegraph` here makes the
77
- // dispatcher open the CodeGraph DB so `buildTrpcIndex` can run; the
78
- // handler also surfaces an actionable hint when no procedures + no
79
- // codegraph (covered by `trpc-map-empty-codegraph-hint.test.ts`).
80
- trpc_map: ['codegraph', 'data'],
81
-
82
- // `schema` reads filesystem (Prisma schema files); no DB access at all.
83
- schema: [],
84
-
85
- // === Memory tools (memory-tools.ts) ===
86
- // Routed via `name.startsWith(pfx + '_memory_')` at tools.ts:284-290.
87
- // Handler opens memory DB per-call (with try/finally close).
88
- memory_search: ['memory'],
89
- memory_timeline: ['memory'],
90
- memory_detail: ['memory'],
91
- memory_sessions: ['memory'],
92
- memory_failures: ['memory'],
93
- memory_ingest: ['memory'],
94
- memory_backfill: ['memory'],
95
-
96
- // === Observability tools (observability-tools.ts) ===
97
- // Routed via `isObservabilityTool(name)` at tools.ts:294-300. Memory DB only.
98
- session_replay: ['memory'],
99
- session_stats: ['memory'],
100
- tool_patterns: ['memory'],
101
- prompt_analysis: ['memory'],
102
-
103
- // === Docs tools (docs-tools.ts) ===
104
- // Routed via `name.startsWith(pfx + '_docs_')` at tools.ts:303-306.
105
- // No DB access — pure filesystem traversal.
106
- docs_audit: [],
107
- docs_coverage: [],
108
-
109
- // === Sentinel registry tools (sentinel-tools.ts:180-184) ===
110
- // Routed via `name.startsWith(pfx + '_sentinel_')` at tools.ts:308.
111
- // Handler signature: `(name, args, dataDb)` — Data DB only.
112
- sentinel_register: ['data'],
113
- sentinel_validate: ['data'],
114
- sentinel_search: ['data'],
115
- sentinel_detail: ['data'],
116
- sentinel_impact: ['data'],
117
- sentinel_parity: ['data'],
118
-
119
- // === Knowledge layer tools (knowledge-tools.ts) ===
120
- // Routed via `isKnowledgeTool(name)` at tools.ts:372-376. Primary DB is
121
- // `knowledgeDb` (separate SQLite file). Handlers ALSO call `getDataDb()`
122
- // (knowledge-tools.ts:1187,1275) and `getMemoryDb()` (knowledge-tools.ts:1332)
123
- // for cross-DB joins — declare all three so the AST completeness test
124
- // (P-B-002) verifies the full access pattern.
125
- knowledge_search: ['knowledge', 'data', 'memory'],
126
- knowledge_pattern: ['knowledge', 'data', 'memory'],
127
- knowledge_rule: ['knowledge', 'data', 'memory'],
128
- knowledge_correct: ['knowledge', 'data', 'memory'],
129
- knowledge_incident: ['knowledge', 'data', 'memory'],
130
- knowledge_plan: ['knowledge', 'data', 'memory'],
131
- knowledge_command: ['knowledge', 'data', 'memory'],
132
- knowledge_gaps: ['knowledge', 'data', 'memory'],
133
- knowledge_verification: ['knowledge', 'data', 'memory'],
134
- knowledge_effectiveness: ['knowledge', 'data', 'memory'],
135
- knowledge_graph: ['knowledge', 'data', 'memory'],
136
- knowledge_schema_check: ['knowledge', 'data', 'memory'],
137
-
138
- // === Analytics / quality (analytics.ts) ===
139
- // Routed via `isAnalyticsTool(name)`. Memory DB only.
140
- quality_score: ['memory'],
141
- quality_report: ['memory'],
142
- quality_trend: ['memory'],
143
-
144
- // === Cost tracker (cost-tracker.ts) ===
145
- cost_session: ['memory'],
146
- cost_feature: ['memory'],
147
- cost_trend: ['memory'],
148
-
149
- // === Prompt analyzer (prompt-analyzer.ts) ===
150
- prompt_effectiveness: ['memory'],
151
- prompt_suggestions: ['memory'],
152
-
153
- // === Audit trail (audit-trail.ts) ===
154
- audit_chain: ['memory'],
155
- audit_log: ['memory'],
156
- audit_report: ['memory'],
157
-
158
- // === Validation engine (validation-engine.ts) ===
159
- validation_check: ['memory'],
160
- validation_report: ['memory'],
161
-
162
- // === ADR generator (adr-generator.ts) ===
163
- adr_create: ['memory'],
164
- adr_list: ['memory'],
165
- adr_detail: ['memory'],
166
-
167
- // === Security scorer (security-scorer.ts) ===
168
- security_score: ['memory'],
169
- security_heatmap: ['memory'],
170
- security_trend: ['memory'],
171
-
172
- // === Dependency scorer (dependency-scorer.ts) ===
173
- dep_score: ['memory'],
174
- dep_alternatives: ['memory'],
175
-
176
- // === Team knowledge (team-knowledge.ts) ===
177
- team_expertise: ['memory'],
178
- team_conflicts: ['memory'],
179
- team_search: ['memory'],
180
-
181
- // === Regression detector (regression-detector.ts) ===
182
- regression_risk: ['memory'],
183
- feature_health: ['memory'],
184
-
185
- // === Python code-intel tools (python-tools.ts) ===
186
- // Routed via `isPythonTool(name)` at tools.ts:379-381. Data DB only.
187
- py_imports: ['data'],
188
- py_routes: ['data'],
189
- py_models: ['data'],
190
- py_migrations: ['data'],
191
- py_coupling: ['data'],
192
- py_context: ['data'],
193
- py_impact: ['data'],
194
- py_domains: ['data'],
195
-
196
- // === License tool (license.ts) ===
197
- license_status: ['memory'],
198
- } as const satisfies Readonly<Record<string, readonly DbNeed[]>>;
199
-
200
- /**
201
- * Configured tool-prefix-stripping helper. Pulled from the runtime config
202
- * so this module stays project-prefix-agnostic.
203
- */
204
- function stripConfiguredPrefix(toolName: string, prefix: string): string {
205
- const pfx = `${prefix}_`;
206
- return toolName.startsWith(pfx) ? toolName.slice(pfx.length) : toolName;
207
- }
208
-
209
- /**
210
- * Look up the DB needs for a tool by its full name (with prefix). Strips the
211
- * configured prefix and consults `TOOL_DB_NEEDS`. Throws `UnknownToolError`
212
- * for tool names not in the manifest — the dispatcher MUST catch this and
213
- * translate to a structured JSON-RPC error.
214
- *
215
- * @param toolName Full tool name including prefix (e.g., `"massu_memory_search"`)
216
- * @param prefix Tool prefix (e.g., `"massu"`) — read from config at dispatch time
217
- * @returns Array of DB connections the tool requires (may be empty)
218
- * @throws {UnknownToolError} if `stripPrefix(toolName)` not in the manifest
219
- */
220
- export function getToolDbNeeds(toolName: string, prefix: string): readonly DbNeed[] {
221
- const shortName = stripConfiguredPrefix(toolName, prefix);
222
- const needs = (TOOL_DB_NEEDS as Record<string, readonly DbNeed[]>)[shortName];
223
- if (needs === undefined) {
224
- throw new UnknownToolError(toolName);
225
- }
226
- return needs;
227
- }
228
-
229
- /** Convenience predicate: does a tool need CodeGraph DB? */
230
- export function toolNeedsCodegraph(toolName: string, prefix: string): boolean {
231
- return getToolDbNeeds(toolName, prefix).includes('codegraph');
232
- }