@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
package/src/memory-tools.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
|
-
import type { ToolDefinition, ToolResult } from './
|
|
6
|
-
import { p, text } from './tool-helpers.ts';
|
|
5
|
+
import type { ToolDefinition, ToolResult } from './tools.ts';
|
|
7
6
|
import {
|
|
8
7
|
searchObservations,
|
|
9
8
|
getRecentObservations,
|
|
@@ -16,21 +15,15 @@ import {
|
|
|
16
15
|
} from './memory-db.ts';
|
|
17
16
|
import { getConfig } from './config.ts';
|
|
18
17
|
|
|
18
|
+
/** Prefix a base tool name with the configured tool prefix. */
|
|
19
|
+
function p(baseName: string): string {
|
|
20
|
+
return `${getConfig().toolPrefix}_${baseName}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
// ============================================================
|
|
20
24
|
// P4-001 through P4-006: MCP Memory Tools
|
|
21
25
|
// ============================================================
|
|
22
26
|
|
|
23
|
-
const MEMORY_BASE_NAMES = new Set([
|
|
24
|
-
'memory_search', 'memory_timeline', 'memory_detail',
|
|
25
|
-
'memory_sessions', 'memory_failures', 'memory_ingest',
|
|
26
|
-
]);
|
|
27
|
-
|
|
28
|
-
export function isMemoryTool(name: string): boolean {
|
|
29
|
-
const pfx = getConfig().toolPrefix + '_';
|
|
30
|
-
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
31
|
-
return MEMORY_BASE_NAMES.has(baseName);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
27
|
/**
|
|
35
28
|
* Get all memory tool definitions.
|
|
36
29
|
*/
|
|
@@ -45,7 +38,7 @@ export function getMemoryToolDefinitions(): ToolDefinition[] {
|
|
|
45
38
|
properties: {
|
|
46
39
|
query: { type: 'string', description: 'Search text (FTS5 query syntax supported)' },
|
|
47
40
|
type: { type: 'string', description: 'Filter by observation type (decision, bugfix, feature, failed_attempt, cr_violation, vr_check, etc.)' },
|
|
48
|
-
cr_rule: { type: 'string', description: 'Filter by CR rule (e.g., CR-
|
|
41
|
+
cr_rule: { type: 'string', description: 'Filter by CR rule (e.g., CR-9)' },
|
|
49
42
|
date_from: { type: 'string', description: 'Start date (ISO format)' },
|
|
50
43
|
limit: { type: 'number', description: 'Max results (default: 20)' },
|
|
51
44
|
},
|
|
@@ -122,7 +115,7 @@ export function getMemoryToolDefinitions(): ToolDefinition[] {
|
|
|
122
115
|
title: { type: 'string', description: 'Short description' },
|
|
123
116
|
detail: { type: 'string', description: 'Full context' },
|
|
124
117
|
importance: { type: 'number', description: 'Override importance (1-5, default: auto-assigned)' },
|
|
125
|
-
cr_rule: { type: 'string', description: 'Link to CR rule (e.g., CR-
|
|
118
|
+
cr_rule: { type: 'string', description: 'Link to CR rule (e.g., CR-9)' },
|
|
126
119
|
plan_item: { type: 'string', description: 'Link to plan item (e.g., P2-003)' },
|
|
127
120
|
files: {
|
|
128
121
|
type: 'array',
|
|
@@ -381,7 +374,13 @@ function handleIngest(args: Record<string, unknown>, db: Database.Database): Too
|
|
|
381
374
|
return text(`Observation #${id} recorded successfully.\nType: ${type}\nTitle: ${title}\nImportance: ${importance}\nSession: ${activeSession.session_id.slice(0, 8)}...`);
|
|
382
375
|
}
|
|
383
376
|
|
|
377
|
+
// ============================================================
|
|
378
|
+
// Helpers
|
|
379
|
+
// ============================================================
|
|
384
380
|
|
|
381
|
+
function text(content: string): ToolResult {
|
|
382
|
+
return { content: [{ type: 'text', text: content }] };
|
|
383
|
+
}
|
|
385
384
|
|
|
386
385
|
function safeParseJson(json: string, fallback: unknown): unknown {
|
|
387
386
|
try {
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
|
-
import type { ToolDefinition, ToolResult } from './
|
|
6
|
-
import { p, text } from './tool-helpers.ts';
|
|
5
|
+
import type { ToolDefinition, ToolResult } from './tools.ts';
|
|
7
6
|
import {
|
|
8
7
|
getConversationTurns,
|
|
9
8
|
searchConversationTurns,
|
|
@@ -14,6 +13,11 @@ import {
|
|
|
14
13
|
} from './memory-db.ts';
|
|
15
14
|
import { getConfig } from './config.ts';
|
|
16
15
|
|
|
16
|
+
/** Prefix a base tool name with the configured tool prefix. */
|
|
17
|
+
function p(baseName: string): string {
|
|
18
|
+
return `${getConfig().toolPrefix}_${baseName}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
// ============================================================
|
|
18
22
|
// Observability MCP Tools (P3-001 through P3-004)
|
|
19
23
|
// ============================================================
|
|
@@ -330,3 +334,10 @@ function handleSessionStats(args: Record<string, unknown>, db: Database.Database
|
|
|
330
334
|
return text(lines.join('\n'));
|
|
331
335
|
}
|
|
332
336
|
|
|
337
|
+
// ============================================================
|
|
338
|
+
// Helpers
|
|
339
|
+
// ============================================================
|
|
340
|
+
|
|
341
|
+
function text(content: string): ToolResult {
|
|
342
|
+
return { content: [{ type: 'text', text: content }] };
|
|
343
|
+
}
|
package/src/prompt-analyzer.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
|
-
import type { ToolDefinition, ToolResult } from './
|
|
6
|
-
import { p, text } from './tool-helpers.ts';
|
|
5
|
+
import type { ToolDefinition, ToolResult } from './tools.ts';
|
|
7
6
|
import { createHash } from 'crypto';
|
|
8
7
|
import { getConfig } from './config.ts';
|
|
9
8
|
import { escapeRegex, redactSensitiveContent } from './security-utils.ts';
|
|
@@ -12,6 +11,11 @@ import { escapeRegex, redactSensitiveContent } from './security-utils.ts';
|
|
|
12
11
|
// Prompt Effectiveness Analysis
|
|
13
12
|
// ============================================================
|
|
14
13
|
|
|
14
|
+
/** Prefix a base tool name with the configured tool prefix. */
|
|
15
|
+
function p(baseName: string): string {
|
|
16
|
+
return `${getConfig().toolPrefix}_${baseName}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
/** Default success/failure indicators. Can be overridden via config.analytics.prompts */
|
|
16
20
|
const DEFAULT_SUCCESS_INDICATORS = ['committed', 'approved', 'looks good', 'perfect', 'great', 'thanks'];
|
|
17
21
|
const DEFAULT_FAILURE_INDICATORS = ['revert', 'wrong', "that's not", 'undo', 'incorrect'];
|
|
@@ -323,3 +327,6 @@ function handleSuggestions(args: Record<string, unknown>, db: Database.Database)
|
|
|
323
327
|
return text(lines.join('\n'));
|
|
324
328
|
}
|
|
325
329
|
|
|
330
|
+
function text(content: string): ToolResult {
|
|
331
|
+
return { content: [{ type: 'text', text: content }] };
|
|
332
|
+
}
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
|
-
import type { ToolDefinition, ToolResult } from './
|
|
6
|
-
import { p, text } from './tool-helpers.ts';
|
|
5
|
+
import type { ToolDefinition, ToolResult } from './tools.ts';
|
|
7
6
|
import { getConfig } from './config.ts';
|
|
8
7
|
|
|
9
8
|
// ============================================================
|
|
10
9
|
// Regression Detection
|
|
11
10
|
// ============================================================
|
|
12
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
|
+
|
|
13
17
|
/** Default health thresholds. Configurable via regression.health_thresholds */
|
|
14
18
|
const DEFAULT_HEALTH_THRESHOLDS = {
|
|
15
19
|
healthy: 80,
|
|
@@ -267,7 +271,6 @@ function handleRegressionCheck(_args: Record<string, unknown>, db: Database.Data
|
|
|
267
271
|
FROM feature_health
|
|
268
272
|
WHERE modifications_since_test > 0
|
|
269
273
|
ORDER BY modifications_since_test DESC
|
|
270
|
-
LIMIT 500
|
|
271
274
|
`).all() as Array<Record<string, unknown>>;
|
|
272
275
|
|
|
273
276
|
if (recentlyModified.length === 0) {
|
|
@@ -311,3 +314,6 @@ function handleRegressionCheck(_args: Record<string, unknown>, db: Database.Data
|
|
|
311
314
|
return text(lines.join('\n'));
|
|
312
315
|
}
|
|
313
316
|
|
|
317
|
+
function text(content: string): ToolResult {
|
|
318
|
+
return { content: [{ type: 'text', text: content }] };
|
|
319
|
+
}
|
package/src/security-scorer.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
|
-
import type { ToolDefinition, ToolResult } from './
|
|
6
|
-
import { p, text } from './tool-helpers.ts';
|
|
5
|
+
import type { ToolDefinition, ToolResult } from './tools.ts';
|
|
7
6
|
import { getConfig } from './config.ts';
|
|
8
7
|
import { existsSync, readFileSync } from 'fs';
|
|
9
8
|
import { ensureWithinRoot, enforceSeverityFloors } from './security-utils.ts';
|
|
@@ -12,6 +11,11 @@ import { ensureWithinRoot, enforceSeverityFloors } from './security-utils.ts';
|
|
|
12
11
|
// Security Risk Scoring
|
|
13
12
|
// ============================================================
|
|
14
13
|
|
|
14
|
+
/** Prefix a base tool name with the configured tool prefix. */
|
|
15
|
+
function p(baseName: string): string {
|
|
16
|
+
return `${getConfig().toolPrefix}_${baseName}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
export interface SecurityFinding {
|
|
16
20
|
pattern: string;
|
|
17
21
|
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
@@ -396,3 +400,6 @@ function handleSecurityTrend(args: Record<string, unknown>, db: Database.Databas
|
|
|
396
400
|
return text(lines.join('\n'));
|
|
397
401
|
}
|
|
398
402
|
|
|
403
|
+
function text(content: string): ToolResult {
|
|
404
|
+
return { content: [{ type: 'text', text: content }] };
|
|
405
|
+
}
|
package/src/sentinel-db.ts
CHANGED
|
@@ -246,41 +246,17 @@ export function getOrphanedFeatures(db: Database.Database): Feature[] {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
export function getFeatureImpact(db: Database.Database, filePaths: string[]): ImpactReport {
|
|
249
|
-
if (filePaths.length === 0) {
|
|
250
|
-
return { files_analyzed: [], orphaned: [], degraded: [], unaffected: [], blocked: false, block_reason: null };
|
|
251
|
-
}
|
|
252
|
-
|
|
253
249
|
const fileSet = new Set(filePaths);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Batch: load all affected features in a single query
|
|
267
|
-
const featurePlaceholders = affectedFeatureIds.map(() => '?').join(',');
|
|
268
|
-
const featureRows = db.prepare(
|
|
269
|
-
`SELECT * FROM massu_sentinel WHERE id IN (${featurePlaceholders}) AND status = 'active'`
|
|
270
|
-
).all(...affectedFeatureIds) as Record<string, unknown>[];
|
|
271
|
-
const featuresById = new Map(featureRows.map(r => [r.id as number, toFeature(r)]));
|
|
272
|
-
|
|
273
|
-
// Batch: load all components for affected features in a single query
|
|
274
|
-
const allComponents = db.prepare(
|
|
275
|
-
`SELECT feature_id, component_file, is_primary FROM massu_sentinel_components WHERE feature_id IN (${featurePlaceholders})`
|
|
276
|
-
).all(...affectedFeatureIds) as { feature_id: number; component_file: string; is_primary: number }[];
|
|
277
|
-
|
|
278
|
-
// Group components by feature
|
|
279
|
-
const componentsByFeature = new Map<number, typeof allComponents>();
|
|
280
|
-
for (const comp of allComponents) {
|
|
281
|
-
let list = componentsByFeature.get(comp.feature_id);
|
|
282
|
-
if (!list) { list = []; componentsByFeature.set(comp.feature_id, list); }
|
|
283
|
-
list.push(comp);
|
|
250
|
+
const affectedFeatureIds = new Set<number>();
|
|
251
|
+
|
|
252
|
+
// Find all features linked to these files
|
|
253
|
+
for (const filePath of filePaths) {
|
|
254
|
+
const links = db.prepare(
|
|
255
|
+
'SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?'
|
|
256
|
+
).all(filePath) as { feature_id: number }[];
|
|
257
|
+
for (const link of links) {
|
|
258
|
+
affectedFeatureIds.add(link.feature_id);
|
|
259
|
+
}
|
|
284
260
|
}
|
|
285
261
|
|
|
286
262
|
const orphaned: ImpactItem[] = [];
|
|
@@ -288,12 +264,15 @@ export function getFeatureImpact(db: Database.Database, filePaths: string[]): Im
|
|
|
288
264
|
const unaffected: ImpactItem[] = [];
|
|
289
265
|
|
|
290
266
|
for (const featureId of affectedFeatureIds) {
|
|
291
|
-
const feature =
|
|
292
|
-
if (!feature) continue;
|
|
267
|
+
const feature = getFeatureById(db, featureId);
|
|
268
|
+
if (!feature || feature.status !== 'active') continue;
|
|
293
269
|
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
270
|
+
const allComponents = db.prepare(
|
|
271
|
+
'SELECT component_file, is_primary FROM massu_sentinel_components WHERE feature_id = ?'
|
|
272
|
+
).all(featureId) as { component_file: string; is_primary: number }[];
|
|
273
|
+
|
|
274
|
+
const affected = allComponents.filter(c => fileSet.has(c.component_file));
|
|
275
|
+
const remaining = allComponents.filter(c => !fileSet.has(c.component_file));
|
|
297
276
|
const primaryAffected = affected.some(c => c.is_primary);
|
|
298
277
|
|
|
299
278
|
const item: ImpactItem = {
|
|
@@ -403,7 +382,7 @@ export function validateFeatures(db: Database.Database, domainFilter?: string):
|
|
|
403
382
|
sql += ' AND domain = ?';
|
|
404
383
|
params.push(domainFilter);
|
|
405
384
|
}
|
|
406
|
-
sql += ' ORDER BY domain, feature_key
|
|
385
|
+
sql += ' ORDER BY domain, feature_key';
|
|
407
386
|
|
|
408
387
|
const features = db.prepare(sql).all(...params) as Record<string, unknown>[];
|
|
409
388
|
const details: ValidationItem[] = [];
|
|
@@ -489,63 +468,39 @@ export function validateFeatures(db: Database.Database, domainFilter?: string):
|
|
|
489
468
|
// ============================================================
|
|
490
469
|
|
|
491
470
|
export function checkParity(db: Database.Database, oldFiles: string[], newFiles: string[]): ParityReport {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
471
|
+
const oldFileSet = new Set(oldFiles);
|
|
472
|
+
const newFileSet = new Set(newFiles);
|
|
495
473
|
|
|
496
|
-
//
|
|
474
|
+
// Find features linked to old files
|
|
497
475
|
const oldFeatureIds = new Set<number>();
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Batch: find features linked to new files in a single query
|
|
505
|
-
const newFeatureIds = new Set<number>();
|
|
506
|
-
if (newFiles.length > 0) {
|
|
507
|
-
const ph = newFiles.map(() => '?').join(',');
|
|
508
|
-
const links = db.prepare(`SELECT DISTINCT feature_id FROM massu_sentinel_components WHERE component_file IN (${ph})`).all(...newFiles) as { feature_id: number }[];
|
|
509
|
-
for (const link of links) newFeatureIds.add(link.feature_id);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Batch: load all referenced features in a single query
|
|
513
|
-
const allIds = [...new Set([...oldFeatureIds, ...newFeatureIds])];
|
|
514
|
-
const featuresById = new Map<number, Feature>();
|
|
515
|
-
if (allIds.length > 0) {
|
|
516
|
-
const ph = allIds.map(() => '?').join(',');
|
|
517
|
-
const rows = db.prepare(`SELECT * FROM massu_sentinel WHERE id IN (${ph})`).all(...allIds) as Record<string, unknown>[];
|
|
518
|
-
for (const row of rows) {
|
|
519
|
-
const f = toFeature(row);
|
|
520
|
-
featuresById.set(f.id, f);
|
|
476
|
+
for (const file of oldFiles) {
|
|
477
|
+
const links = db.prepare('SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?').all(file) as { feature_id: number }[];
|
|
478
|
+
for (const link of links) {
|
|
479
|
+
oldFeatureIds.add(link.feature_id);
|
|
521
480
|
}
|
|
522
481
|
}
|
|
523
482
|
|
|
524
|
-
//
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
let list = componentsByFeature.get(comp.feature_id);
|
|
531
|
-
if (!list) { list = []; componentsByFeature.set(comp.feature_id, list); }
|
|
532
|
-
list.push(comp);
|
|
483
|
+
// Find features linked to new files
|
|
484
|
+
const newFeatureIds = new Set<number>();
|
|
485
|
+
for (const file of newFiles) {
|
|
486
|
+
const links = db.prepare('SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?').all(file) as { feature_id: number }[];
|
|
487
|
+
for (const link of links) {
|
|
488
|
+
newFeatureIds.add(link.feature_id);
|
|
533
489
|
}
|
|
534
490
|
}
|
|
535
491
|
|
|
536
|
-
const oldFileSet = new Set(oldFiles);
|
|
537
|
-
const newFileSet = new Set(newFiles);
|
|
538
492
|
const done: ParityItem[] = [];
|
|
539
493
|
const gaps: ParityItem[] = [];
|
|
540
|
-
const
|
|
494
|
+
const newFeatures: ParityItem[] = [];
|
|
541
495
|
|
|
496
|
+
// Features in old that are also in new = DONE
|
|
497
|
+
// Features in old but NOT in new = GAP
|
|
542
498
|
for (const fId of oldFeatureIds) {
|
|
543
|
-
const feature =
|
|
499
|
+
const feature = getFeatureById(db, fId);
|
|
544
500
|
if (!feature) continue;
|
|
545
501
|
|
|
546
|
-
const
|
|
547
|
-
const
|
|
548
|
-
const newComps = comps.filter(c => newFileSet.has(c.component_file));
|
|
502
|
+
const oldComps = db.prepare('SELECT component_file FROM massu_sentinel_components WHERE feature_id = ? AND component_file IN (' + oldFiles.map(() => '?').join(',') + ')').all(fId, ...oldFiles) as { component_file: string }[];
|
|
503
|
+
const newComps = db.prepare('SELECT component_file FROM massu_sentinel_components WHERE feature_id = ? AND component_file IN (' + newFiles.map(() => '?').join(',') + ')').all(fId, ...newFiles) as { component_file: string }[];
|
|
549
504
|
|
|
550
505
|
const item: ParityItem = {
|
|
551
506
|
feature_key: feature.feature_key,
|
|
@@ -562,15 +517,15 @@ export function checkParity(db: Database.Database, oldFiles: string[], newFiles:
|
|
|
562
517
|
}
|
|
563
518
|
}
|
|
564
519
|
|
|
520
|
+
// Features only in new = NEW
|
|
565
521
|
for (const fId of newFeatureIds) {
|
|
566
522
|
if (oldFeatureIds.has(fId)) continue;
|
|
567
|
-
const feature =
|
|
523
|
+
const feature = getFeatureById(db, fId);
|
|
568
524
|
if (!feature) continue;
|
|
569
525
|
|
|
570
|
-
const
|
|
571
|
-
const newComps = comps.filter(c => newFileSet.has(c.component_file));
|
|
526
|
+
const newComps = db.prepare('SELECT component_file FROM massu_sentinel_components WHERE feature_id = ? AND component_file IN (' + newFiles.map(() => '?').join(',') + ')').all(fId, ...newFiles) as { component_file: string }[];
|
|
572
527
|
|
|
573
|
-
|
|
528
|
+
newFeatures.push({
|
|
574
529
|
feature_key: feature.feature_key,
|
|
575
530
|
title: feature.title,
|
|
576
531
|
status: 'NEW',
|
|
@@ -582,7 +537,7 @@ export function checkParity(db: Database.Database, oldFiles: string[], newFiles:
|
|
|
582
537
|
const total = done.length + gaps.length;
|
|
583
538
|
const parityPercentage = total > 0 ? Math.round((done.length / total) * 100) : 100;
|
|
584
539
|
|
|
585
|
-
return { done, gaps, new_features:
|
|
540
|
+
return { done, gaps, new_features: newFeatures, parity_percentage: parityPercentage };
|
|
586
541
|
}
|
|
587
542
|
|
|
588
543
|
// ============================================================
|
package/src/sentinel-tools.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
|
-
import type { ToolDefinition, ToolResult } from './
|
|
6
|
-
import { p, text } from './tool-helpers.ts';
|
|
5
|
+
import type { ToolDefinition, ToolResult } from './tools.ts';
|
|
7
6
|
import {
|
|
8
7
|
searchFeatures,
|
|
9
8
|
getFeatureDetail,
|
|
@@ -19,19 +18,17 @@ import {
|
|
|
19
18
|
import type { ComponentRole, FeatureStatus, FeaturePriority } from './sentinel-types.ts';
|
|
20
19
|
import { getConfig } from './config.ts';
|
|
21
20
|
|
|
21
|
+
/** Prefix a base tool name with the configured tool prefix. */
|
|
22
|
+
function p(baseName: string): string {
|
|
23
|
+
return `${getConfig().toolPrefix}_${baseName}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
// ============================================================
|
|
23
27
|
// Sentinel: MCP Tool Definitions & Handlers
|
|
24
28
|
// ============================================================
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'sentinel_validate', 'sentinel_register', 'sentinel_parity',
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
export function isSentinelTool(name: string): boolean {
|
|
32
|
-
const pfx = getConfig().toolPrefix + '_';
|
|
33
|
-
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
34
|
-
return SENTINEL_BASE_NAMES.has(baseName);
|
|
30
|
+
function text(content: string): ToolResult {
|
|
31
|
+
return { content: [{ type: 'text', text: content }] };
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
export function getSentinelToolDefinitions(): ToolDefinition[] {
|
package/src/server.ts
CHANGED
|
@@ -55,7 +55,7 @@ function handleRequest(request: JsonRpcRequest): JsonRpcResponse {
|
|
|
55
55
|
},
|
|
56
56
|
serverInfo: {
|
|
57
57
|
name: 'massu',
|
|
58
|
-
version: '
|
|
58
|
+
version: '1.0.0',
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
};
|
|
@@ -182,7 +182,6 @@ process.stdin.on('end', () => {
|
|
|
182
182
|
// Handle errors gracefully
|
|
183
183
|
process.on('uncaughtException', (error) => {
|
|
184
184
|
process.stderr.write(`massu: Uncaught exception: ${error.message}\n`);
|
|
185
|
-
process.exit(1);
|
|
186
185
|
});
|
|
187
186
|
|
|
188
187
|
process.on('unhandledRejection', (reason) => {
|
package/src/team-knowledge.ts
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
|
-
import type { ToolDefinition, ToolResult } from './
|
|
6
|
-
import { p, text } from './tool-helpers.ts';
|
|
5
|
+
import type { ToolDefinition, ToolResult } from './tools.ts';
|
|
7
6
|
import { getConfig } from './config.ts';
|
|
8
7
|
|
|
9
8
|
// ============================================================
|
|
10
9
|
// Team Knowledge Graph
|
|
11
10
|
// ============================================================
|
|
12
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
|
+
|
|
13
17
|
/**
|
|
14
18
|
* Calculate expertise score for a developer in a module.
|
|
15
19
|
* Based on session depth (how many sessions) and observation quality.
|
|
@@ -398,3 +402,6 @@ function handleTeamConflicts(args: Record<string, unknown>, db: Database.Databas
|
|
|
398
402
|
return text(lines.join('\n'));
|
|
399
403
|
}
|
|
400
404
|
|
|
405
|
+
function text(content: string): ToolResult {
|
|
406
|
+
return { content: [{ type: 'text', text: content }] };
|
|
407
|
+
}
|