@massu/core 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +2 -2
  2. package/dist/hooks/cost-tracker.js +23 -35
  3. package/dist/hooks/post-edit-context.js +2 -2
  4. package/dist/hooks/post-tool-use.js +43 -58
  5. package/dist/hooks/pre-compact.js +23 -38
  6. package/dist/hooks/pre-delete-check.js +18 -31
  7. package/dist/hooks/quality-event.js +23 -35
  8. package/dist/hooks/session-end.js +62 -78
  9. package/dist/hooks/session-start.js +33 -42
  10. package/dist/hooks/user-prompt.js +23 -38
  11. package/package.json +8 -14
  12. package/src/adr-generator.ts +9 -2
  13. package/src/analytics.ts +9 -3
  14. package/src/audit-trail.ts +10 -3
  15. package/src/cloud-sync.ts +14 -18
  16. package/src/commands/init.ts +1 -5
  17. package/src/cost-tracker.ts +11 -6
  18. package/src/dependency-scorer.ts +9 -2
  19. package/src/docs-tools.ts +13 -10
  20. package/src/hooks/post-edit-context.ts +3 -3
  21. package/src/hooks/session-end.ts +3 -3
  22. package/src/hooks/session-start.ts +2 -2
  23. package/src/memory-db.ts +1351 -23
  24. package/src/memory-tools.ts +14 -15
  25. package/src/observability-tools.ts +13 -2
  26. package/src/prompt-analyzer.ts +9 -2
  27. package/src/regression-detector.ts +9 -3
  28. package/src/security-scorer.ts +9 -2
  29. package/src/sentinel-db.ts +43 -88
  30. package/src/sentinel-tools.ts +8 -11
  31. package/src/server.ts +1 -2
  32. package/src/team-knowledge.ts +9 -2
  33. package/src/tools.ts +771 -35
  34. package/src/validate-features-runner.ts +0 -1
  35. package/src/validation-engine.ts +9 -2
  36. package/dist/cli.js +0 -7890
  37. package/dist/server.js +0 -7008
  38. package/src/__tests__/adr-generator.test.ts +0 -260
  39. package/src/__tests__/analytics.test.ts +0 -282
  40. package/src/__tests__/audit-trail.test.ts +0 -382
  41. package/src/__tests__/backfill-sessions.test.ts +0 -690
  42. package/src/__tests__/cli.test.ts +0 -290
  43. package/src/__tests__/cloud-sync.test.ts +0 -261
  44. package/src/__tests__/config-sections.test.ts +0 -359
  45. package/src/__tests__/config.test.ts +0 -732
  46. package/src/__tests__/cost-tracker.test.ts +0 -348
  47. package/src/__tests__/db.test.ts +0 -177
  48. package/src/__tests__/dependency-scorer.test.ts +0 -325
  49. package/src/__tests__/docs-integration.test.ts +0 -178
  50. package/src/__tests__/docs-tools.test.ts +0 -199
  51. package/src/__tests__/domains.test.ts +0 -236
  52. package/src/__tests__/hooks.test.ts +0 -221
  53. package/src/__tests__/import-resolver.test.ts +0 -95
  54. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  55. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  56. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  57. package/src/__tests__/memory-db.test.ts +0 -404
  58. package/src/__tests__/memory-enhancements.test.ts +0 -316
  59. package/src/__tests__/memory-tools.test.ts +0 -199
  60. package/src/__tests__/middleware-tree.test.ts +0 -177
  61. package/src/__tests__/observability-tools.test.ts +0 -595
  62. package/src/__tests__/observability.test.ts +0 -437
  63. package/src/__tests__/observation-extractor.test.ts +0 -167
  64. package/src/__tests__/page-deps.test.ts +0 -60
  65. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  66. package/src/__tests__/regression-detector.test.ts +0 -295
  67. package/src/__tests__/rules.test.ts +0 -87
  68. package/src/__tests__/schema-mapper.test.ts +0 -29
  69. package/src/__tests__/security-scorer.test.ts +0 -238
  70. package/src/__tests__/security-utils.test.ts +0 -175
  71. package/src/__tests__/sentinel-db.test.ts +0 -491
  72. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  73. package/src/__tests__/sentinel-tools.test.ts +0 -324
  74. package/src/__tests__/sentinel-types.test.ts +0 -750
  75. package/src/__tests__/server.test.ts +0 -452
  76. package/src/__tests__/session-archiver.test.ts +0 -524
  77. package/src/__tests__/session-state-generator.test.ts +0 -900
  78. package/src/__tests__/team-knowledge.test.ts +0 -327
  79. package/src/__tests__/tools.test.ts +0 -340
  80. package/src/__tests__/transcript-parser.test.ts +0 -195
  81. package/src/__tests__/trpc-index.test.ts +0 -25
  82. package/src/__tests__/validate-features-runner.test.ts +0 -517
  83. package/src/__tests__/validation-engine.test.ts +0 -300
  84. package/src/core-tools.ts +0 -685
  85. package/src/memory-queries.ts +0 -804
  86. package/src/memory-schema.ts +0 -546
  87. package/src/tool-helpers.ts +0 -41
@@ -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 './tool-helpers.ts';
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-16)' },
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-16)' },
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 './tool-helpers.ts';
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
+ }
@@ -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 './tool-helpers.ts';
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 './tool-helpers.ts';
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
+ }
@@ -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 './tool-helpers.ts';
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
+ }
@@ -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
- // Batch: find all features linked to these files in a single query
256
- const placeholders = filePaths.map(() => '?').join(',');
257
- const featureLinks = db.prepare(
258
- `SELECT DISTINCT feature_id FROM massu_sentinel_components WHERE component_file IN (${placeholders})`
259
- ).all(...filePaths) as { feature_id: number }[];
260
- const affectedFeatureIds = featureLinks.map(l => l.feature_id);
261
-
262
- if (affectedFeatureIds.length === 0) {
263
- return { files_analyzed: filePaths, orphaned: [], degraded: [], unaffected: [], blocked: false, block_reason: null };
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 = featuresById.get(featureId);
292
- if (!feature) continue;
267
+ const feature = getFeatureById(db, featureId);
268
+ if (!feature || feature.status !== 'active') continue;
293
269
 
294
- const comps = componentsByFeature.get(featureId) ?? [];
295
- const affected = comps.filter(c => fileSet.has(c.component_file));
296
- const remaining = comps.filter(c => !fileSet.has(c.component_file));
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 LIMIT 1000';
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
- if (oldFiles.length === 0 && newFiles.length === 0) {
493
- return { done: [], gaps: [], new_features: [], parity_percentage: 100 };
494
- }
471
+ const oldFileSet = new Set(oldFiles);
472
+ const newFileSet = new Set(newFiles);
495
473
 
496
- // Batch: find features linked to old files in a single query
474
+ // Find features linked to old files
497
475
  const oldFeatureIds = new Set<number>();
498
- if (oldFiles.length > 0) {
499
- const ph = oldFiles.map(() => '?').join(',');
500
- const links = db.prepare(`SELECT DISTINCT feature_id FROM massu_sentinel_components WHERE component_file IN (${ph})`).all(...oldFiles) as { feature_id: number }[];
501
- for (const link of links) oldFeatureIds.add(link.feature_id);
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
- // Batch: load all components for referenced features
525
- const componentsByFeature = new Map<number, Array<{ component_file: string }>>();
526
- if (allIds.length > 0) {
527
- const ph = allIds.map(() => '?').join(',');
528
- const comps = db.prepare(`SELECT feature_id, component_file FROM massu_sentinel_components WHERE feature_id IN (${ph})`).all(...allIds) as { feature_id: number; component_file: string }[];
529
- for (const comp of comps) {
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 newFeaturesList: ParityItem[] = [];
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 = featuresById.get(fId);
499
+ const feature = getFeatureById(db, fId);
544
500
  if (!feature) continue;
545
501
 
546
- const comps = componentsByFeature.get(fId) ?? [];
547
- const oldComps = comps.filter(c => oldFileSet.has(c.component_file));
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 = featuresById.get(fId);
523
+ const feature = getFeatureById(db, fId);
568
524
  if (!feature) continue;
569
525
 
570
- const comps = componentsByFeature.get(fId) ?? [];
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
- newFeaturesList.push({
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: newFeaturesList, parity_percentage: parityPercentage };
540
+ return { done, gaps, new_features: newFeatures, parity_percentage: parityPercentage };
586
541
  }
587
542
 
588
543
  // ============================================================
@@ -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 './tool-helpers.ts';
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
- const SENTINEL_BASE_NAMES = new Set([
27
- 'sentinel_search', 'sentinel_detail', 'sentinel_impact',
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: '0.1.0',
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) => {
@@ -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 './tool-helpers.ts';
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
+ }