@massu/core 1.10.7 → 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.
- package/CHANGELOG.md +1303 -0
- package/dist/adapter.js +6 -4
- package/dist/cli.js +559 -400
- package/dist/detect/adapters/.bundle-shasums.json +4 -7
- package/dist/detect/adapters/rails.js +6 -4
- package/dist/detect/adapters/spring.js +6 -4
- package/dist/detect/adapters/tree-sitter-loader.js +6 -4
- package/dist/hooks/auto-learning-pipeline.js +24 -1
- package/dist/hooks/classify-failure.js +32 -3
- package/dist/hooks/cost-tracker.js +24 -0
- package/dist/hooks/fix-detector.js +97 -1
- package/dist/hooks/incident-pipeline.js +30 -1
- package/dist/hooks/intent-suggester.js +6 -3
- package/dist/hooks/post-edit-context.js +24 -1
- package/dist/hooks/post-tool-use.js +66 -18
- package/dist/hooks/pre-compact.js +27 -2
- package/dist/hooks/pre-delete-check.js +48 -22
- package/dist/hooks/pre-tool-use-gate.js +962 -0
- package/dist/hooks/quality-event.js +24 -0
- package/dist/hooks/rule-enforcement-pipeline.js +24 -1
- package/dist/hooks/security-gate.js +29 -16
- package/dist/hooks/session-end.js +24 -0
- package/dist/hooks/session-start.js +8710 -8460
- package/dist/hooks/user-prompt.js +24 -0
- package/package.json +15 -18
- package/commands/README.md +0 -134
- package/src/adapter.ts +0 -121
- package/src/adr-generator.ts +0 -292
- package/src/analytics.ts +0 -373
- package/src/audit-trail.ts +0 -450
- package/src/backfill-sessions.ts +0 -182
- package/src/changelog-generator.ts +0 -178
- package/src/cli.ts +0 -246
- package/src/cloud-sync.ts +0 -234
- package/src/commands/adapters.ts +0 -824
- package/src/commands/changelog.ts +0 -165
- package/src/commands/config-check-drift.ts +0 -133
- package/src/commands/config-refresh.ts +0 -396
- package/src/commands/config-upgrade.ts +0 -127
- package/src/commands/doctor.ts +0 -485
- package/src/commands/hook-runner.ts +0 -145
- package/src/commands/init.ts +0 -1823
- package/src/commands/install-commands.ts +0 -692
- package/src/commands/install-hooks.ts +0 -27
- package/src/commands/permissions.ts +0 -150
- package/src/commands/refresh-log.ts +0 -37
- package/src/commands/show-template.ts +0 -65
- package/src/commands/template-engine.ts +0 -301
- package/src/commands/watch.ts +0 -430
- package/src/config.ts +0 -765
- package/src/cost-tracker.ts +0 -355
- package/src/db.ts +0 -370
- package/src/dependency-scorer.ts +0 -337
- package/src/detect/adapters/aspnet.ts +0 -4
- package/src/detect/adapters/discover.ts +0 -469
- package/src/detect/adapters/file-sampler.ts +0 -225
- package/src/detect/adapters/go-chi.ts +0 -4
- package/src/detect/adapters/index.ts +0 -51
- package/src/detect/adapters/nextjs-trpc.ts +0 -166
- package/src/detect/adapters/parse-guard.ts +0 -133
- package/src/detect/adapters/phoenix.ts +0 -4
- package/src/detect/adapters/python-django.ts +0 -208
- package/src/detect/adapters/python-fastapi.ts +0 -223
- package/src/detect/adapters/python-flask.ts +0 -235
- package/src/detect/adapters/query-helpers.ts +0 -170
- package/src/detect/adapters/rails.ts +0 -4
- package/src/detect/adapters/runner.ts +0 -284
- package/src/detect/adapters/spring.ts +0 -4
- package/src/detect/adapters/swift-swiftui.ts +0 -171
- package/src/detect/adapters/tree-sitter-loader.ts +0 -517
- package/src/detect/adapters/types.ts +0 -191
- package/src/detect/codebase-introspector.ts +0 -210
- package/src/detect/domain-inferrer.ts +0 -197
- package/src/detect/drift.ts +0 -199
- package/src/detect/framework-detector.ts +0 -307
- package/src/detect/index.ts +0 -209
- package/src/detect/manifest-registry.ts +0 -261
- package/src/detect/migrate.ts +0 -362
- package/src/detect/monorepo-detector.ts +0 -348
- package/src/detect/package-detector.ts +0 -828
- package/src/detect/passthrough.ts +0 -108
- package/src/detect/regex-fallback.ts +0 -449
- package/src/detect/source-dir-detector.ts +0 -297
- package/src/detect/vr-command-map.ts +0 -167
- package/src/docs-map.json +0 -100
- package/src/docs-tools.ts +0 -519
- package/src/domains.ts +0 -182
- package/src/hooks/auto-learning-pipeline.ts +0 -228
- package/src/hooks/classify-failure.ts +0 -259
- package/src/hooks/cost-tracker.ts +0 -66
- package/src/hooks/fix-detector.ts +0 -186
- package/src/hooks/incident-pipeline.ts +0 -190
- package/src/hooks/intent-suggester.ts +0 -131
- package/src/hooks/post-edit-context.ts +0 -91
- package/src/hooks/post-tool-use.ts +0 -413
- package/src/hooks/pre-compact.ts +0 -168
- package/src/hooks/pre-delete-check.ts +0 -243
- package/src/hooks/quality-event.ts +0 -127
- package/src/hooks/rule-enforcement-pipeline.ts +0 -159
- package/src/hooks/security-gate.ts +0 -153
- package/src/hooks/session-end.ts +0 -467
- package/src/hooks/session-start.ts +0 -436
- package/src/hooks/user-prompt.ts +0 -157
- package/src/import-resolver.ts +0 -226
- package/src/knowledge-db.ts +0 -176
- package/src/knowledge-indexer.ts +0 -708
- package/src/knowledge-tools.ts +0 -1414
- package/src/lib/fileLock.ts +0 -203
- package/src/lib/gitToplevel.ts +0 -22
- package/src/lib/hook-registry.ts +0 -43
- package/src/lib/installLock.ts +0 -66
- package/src/lib/memory-path.ts +0 -49
- package/src/lib/pidLiveness.ts +0 -67
- package/src/lib/settings-local.ts +0 -110
- package/src/lib/sql-table-names.ts +0 -71
- package/src/license.ts +0 -547
- package/src/lsp/auto-detect.ts +0 -98
- package/src/lsp/client.ts +0 -776
- package/src/lsp/enrich.ts +0 -127
- package/src/lsp/types.ts +0 -221
- package/src/memory-db.ts +0 -1614
- package/src/memory-file-ingest.ts +0 -128
- package/src/memory-tools.ts +0 -424
- package/src/middleware-tree.ts +0 -71
- package/src/observability-tools.ts +0 -343
- package/src/observation-extractor.ts +0 -418
- package/src/page-deps.ts +0 -285
- package/src/permissions.ts +0 -422
- package/src/prompt-analyzer.ts +0 -332
- package/src/python/coupling-detector.ts +0 -125
- package/src/python/domain-enforcer.ts +0 -84
- package/src/python/impact-analyzer.ts +0 -96
- package/src/python/import-parser.ts +0 -244
- package/src/python/import-resolver.ts +0 -136
- package/src/python/migration-indexer.ts +0 -116
- package/src/python/migration-parser.ts +0 -332
- package/src/python/model-indexer.ts +0 -71
- package/src/python/model-parser.ts +0 -279
- package/src/python/route-indexer.ts +0 -59
- package/src/python/route-parser.ts +0 -317
- package/src/python-tools.ts +0 -630
- package/src/regression-detector.ts +0 -319
- package/src/rules.ts +0 -57
- package/src/schema-mapper.ts +0 -232
- package/src/security/adapter-origin.ts +0 -130
- package/src/security/adapter-verifier.ts +0 -319
- package/src/security/atomic-write.ts +0 -164
- package/src/security/fetcher.ts +0 -200
- package/src/security/install-tracking.ts +0 -319
- package/src/security/license-pubkey.generated.ts +0 -30
- package/src/security/license-response-verifier.ts +0 -149
- package/src/security/local-fingerprint.ts +0 -225
- package/src/security/manifest-cache.ts +0 -333
- package/src/security/manifest-schema.ts +0 -129
- package/src/security/registry-pubkey.generated.ts +0 -35
- package/src/security/telemetry.ts +0 -320
- package/src/security-scorer.ts +0 -405
- package/src/security-utils.ts +0 -133
- package/src/sentinel-db.ts +0 -580
- package/src/sentinel-scanner.ts +0 -406
- package/src/sentinel-tools.ts +0 -513
- package/src/sentinel-types.ts +0 -140
- package/src/server-dispatch.ts +0 -225
- package/src/server.ts +0 -107
- package/src/session-archiver.ts +0 -111
- package/src/session-state-generator.ts +0 -174
- package/src/team-knowledge.ts +0 -407
- package/src/tool-db-needs.ts +0 -232
- package/src/tools.ts +0 -1205
- package/src/transcript-parser.ts +0 -458
- package/src/trpc-index.ts +0 -215
- package/src/validate-features-runner.ts +0 -107
- package/src/validation-engine.ts +0 -358
- package/src/watch/daemon.ts +0 -385
- package/src/watch/lockfile-detector.ts +0 -65
- package/src/watch/paths.ts +0 -279
- package/src/watch/state.ts +0 -178
package/src/team-knowledge.ts
DELETED
|
@@ -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
|
-
}
|
package/src/tool-db-needs.ts
DELETED
|
@@ -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
|
-
}
|