@massu/core 0.1.1 → 0.4.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/commands/_shared-preamble.md +76 -0
- package/commands/massu-audit-deps.md +211 -0
- package/commands/massu-changelog.md +174 -0
- package/commands/massu-cleanup.md +315 -0
- package/commands/massu-commit.md +481 -0
- package/commands/massu-create-plan.md +752 -0
- package/commands/massu-dead-code.md +131 -0
- package/commands/massu-debug.md +484 -0
- package/commands/massu-deploy.md +91 -0
- package/commands/massu-deps.md +374 -0
- package/commands/massu-doc-gen.md +279 -0
- package/commands/massu-docs.md +364 -0
- package/commands/massu-estimate.md +313 -0
- package/commands/massu-golden-path.md +973 -0
- package/commands/massu-guide.md +167 -0
- package/commands/massu-hotfix.md +480 -0
- package/commands/massu-loop-playwright.md +837 -0
- package/commands/massu-loop.md +775 -0
- package/commands/massu-new-feature.md +511 -0
- package/commands/massu-parity.md +214 -0
- package/commands/massu-plan.md +456 -0
- package/commands/massu-push-light.md +207 -0
- package/commands/massu-push.md +434 -0
- package/commands/massu-refactor.md +410 -0
- package/commands/massu-release.md +363 -0
- package/commands/massu-review.md +238 -0
- package/commands/massu-simplify.md +281 -0
- package/commands/massu-status.md +278 -0
- package/commands/massu-tdd.md +201 -0
- package/commands/massu-test.md +516 -0
- package/commands/massu-verify-playwright.md +281 -0
- package/commands/massu-verify.md +667 -0
- package/dist/cli.js +7772 -3140
- package/dist/hooks/cost-tracker.js +103 -40
- package/dist/hooks/post-edit-context.js +74 -8
- package/dist/hooks/post-tool-use.js +268 -106
- package/dist/hooks/pre-compact.js +167 -43
- package/dist/hooks/pre-delete-check.js +159 -42
- package/dist/hooks/quality-event.js +103 -40
- package/dist/hooks/security-gate.js +29 -0
- package/dist/hooks/session-end.js +143 -84
- package/dist/hooks/session-start.js +186 -49
- package/dist/hooks/user-prompt.js +189 -43
- package/package.json +10 -15
- package/src/adr-generator.ts +9 -2
- package/src/analytics.ts +9 -3
- package/src/audit-trail.ts +10 -3
- package/src/backfill-sessions.ts +5 -4
- package/src/cli.ts +6 -0
- package/src/cloud-sync.ts +14 -18
- package/src/commands/doctor.ts +193 -6
- package/src/commands/init.ts +230 -5
- package/src/commands/install-commands.ts +137 -0
- package/src/config.ts +68 -2
- package/src/cost-tracker.ts +11 -6
- package/src/db.ts +115 -2
- package/src/dependency-scorer.ts +9 -2
- package/src/docs-tools.ts +21 -16
- package/src/hooks/post-edit-context.ts +4 -4
- package/src/hooks/post-tool-use.ts +130 -0
- package/src/hooks/pre-compact.ts +23 -1
- package/src/hooks/pre-delete-check.ts +92 -4
- package/src/hooks/security-gate.ts +32 -0
- package/src/hooks/session-end.ts +3 -3
- package/src/hooks/session-start.ts +99 -6
- package/src/hooks/user-prompt.ts +46 -1
- package/src/import-resolver.ts +2 -1
- package/src/knowledge-db.ts +169 -0
- package/src/knowledge-indexer.ts +704 -0
- package/src/knowledge-tools.ts +1413 -0
- package/src/license.ts +482 -0
- package/src/memory-db.ts +1364 -23
- package/src/memory-tools.ts +14 -15
- package/src/observability-tools.ts +13 -2
- package/src/observation-extractor.ts +11 -4
- package/src/page-deps.ts +3 -2
- package/src/prompt-analyzer.ts +9 -2
- package/src/python/coupling-detector.ts +124 -0
- package/src/python/domain-enforcer.ts +83 -0
- package/src/python/impact-analyzer.ts +95 -0
- package/src/python/import-parser.ts +244 -0
- package/src/python/import-resolver.ts +135 -0
- package/src/python/migration-indexer.ts +115 -0
- package/src/python/migration-parser.ts +332 -0
- package/src/python/model-indexer.ts +70 -0
- package/src/python/model-parser.ts +279 -0
- package/src/python/route-indexer.ts +58 -0
- package/src/python/route-parser.ts +317 -0
- package/src/python-tools.ts +629 -0
- package/src/regression-detector.ts +9 -3
- package/src/security-scorer.ts +9 -2
- package/src/sentinel-db.ts +45 -89
- package/src/sentinel-tools.ts +8 -11
- package/src/server.ts +29 -7
- package/src/session-archiver.ts +4 -5
- package/src/team-knowledge.ts +9 -2
- package/src/tools.ts +1032 -44
- package/src/validate-features-runner.ts +0 -1
- package/src/validation-engine.ts +9 -2
- package/README.md +0 -40
- 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/sentinel-db.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
ComponentRole,
|
|
24
24
|
} from './sentinel-types.ts';
|
|
25
25
|
import { getProjectRoot } from './config.ts';
|
|
26
|
+
import { sanitizeFts5Query } from './memory-db.ts';
|
|
26
27
|
|
|
27
28
|
// ============================================================
|
|
28
29
|
// Sentinel: Feature Registry Data Access Layer
|
|
@@ -135,7 +136,7 @@ export function searchFeatures(
|
|
|
135
136
|
JOIN massu_sentinel_fts fts ON s.id = fts.rowid
|
|
136
137
|
WHERE massu_sentinel_fts MATCH ?
|
|
137
138
|
`;
|
|
138
|
-
params.push(query);
|
|
139
|
+
params.push(sanitizeFts5Query(query));
|
|
139
140
|
} else {
|
|
140
141
|
sql = `
|
|
141
142
|
SELECT s.*,
|
|
@@ -246,41 +247,17 @@ export function getOrphanedFeatures(db: Database.Database): Feature[] {
|
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
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
250
|
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);
|
|
251
|
+
const affectedFeatureIds = new Set<number>();
|
|
252
|
+
|
|
253
|
+
// Find all features linked to these files
|
|
254
|
+
for (const filePath of filePaths) {
|
|
255
|
+
const links = db.prepare(
|
|
256
|
+
'SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?'
|
|
257
|
+
).all(filePath) as { feature_id: number }[];
|
|
258
|
+
for (const link of links) {
|
|
259
|
+
affectedFeatureIds.add(link.feature_id);
|
|
260
|
+
}
|
|
284
261
|
}
|
|
285
262
|
|
|
286
263
|
const orphaned: ImpactItem[] = [];
|
|
@@ -288,12 +265,15 @@ export function getFeatureImpact(db: Database.Database, filePaths: string[]): Im
|
|
|
288
265
|
const unaffected: ImpactItem[] = [];
|
|
289
266
|
|
|
290
267
|
for (const featureId of affectedFeatureIds) {
|
|
291
|
-
const feature =
|
|
292
|
-
if (!feature) continue;
|
|
268
|
+
const feature = getFeatureById(db, featureId);
|
|
269
|
+
if (!feature || feature.status !== 'active') continue;
|
|
293
270
|
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
271
|
+
const allComponents = db.prepare(
|
|
272
|
+
'SELECT component_file, is_primary FROM massu_sentinel_components WHERE feature_id = ?'
|
|
273
|
+
).all(featureId) as { component_file: string; is_primary: number }[];
|
|
274
|
+
|
|
275
|
+
const affected = allComponents.filter(c => fileSet.has(c.component_file));
|
|
276
|
+
const remaining = allComponents.filter(c => !fileSet.has(c.component_file));
|
|
297
277
|
const primaryAffected = affected.some(c => c.is_primary);
|
|
298
278
|
|
|
299
279
|
const item: ImpactItem = {
|
|
@@ -403,7 +383,7 @@ export function validateFeatures(db: Database.Database, domainFilter?: string):
|
|
|
403
383
|
sql += ' AND domain = ?';
|
|
404
384
|
params.push(domainFilter);
|
|
405
385
|
}
|
|
406
|
-
sql += ' ORDER BY domain, feature_key
|
|
386
|
+
sql += ' ORDER BY domain, feature_key';
|
|
407
387
|
|
|
408
388
|
const features = db.prepare(sql).all(...params) as Record<string, unknown>[];
|
|
409
389
|
const details: ValidationItem[] = [];
|
|
@@ -489,63 +469,39 @@ export function validateFeatures(db: Database.Database, domainFilter?: string):
|
|
|
489
469
|
// ============================================================
|
|
490
470
|
|
|
491
471
|
export function checkParity(db: Database.Database, oldFiles: string[], newFiles: string[]): ParityReport {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
472
|
+
const oldFileSet = new Set(oldFiles);
|
|
473
|
+
const newFileSet = new Set(newFiles);
|
|
495
474
|
|
|
496
|
-
//
|
|
475
|
+
// Find features linked to old files
|
|
497
476
|
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);
|
|
477
|
+
for (const file of oldFiles) {
|
|
478
|
+
const links = db.prepare('SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?').all(file) as { feature_id: number }[];
|
|
479
|
+
for (const link of links) {
|
|
480
|
+
oldFeatureIds.add(link.feature_id);
|
|
521
481
|
}
|
|
522
482
|
}
|
|
523
483
|
|
|
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);
|
|
484
|
+
// Find features linked to new files
|
|
485
|
+
const newFeatureIds = new Set<number>();
|
|
486
|
+
for (const file of newFiles) {
|
|
487
|
+
const links = db.prepare('SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?').all(file) as { feature_id: number }[];
|
|
488
|
+
for (const link of links) {
|
|
489
|
+
newFeatureIds.add(link.feature_id);
|
|
533
490
|
}
|
|
534
491
|
}
|
|
535
492
|
|
|
536
|
-
const oldFileSet = new Set(oldFiles);
|
|
537
|
-
const newFileSet = new Set(newFiles);
|
|
538
493
|
const done: ParityItem[] = [];
|
|
539
494
|
const gaps: ParityItem[] = [];
|
|
540
|
-
const
|
|
495
|
+
const newFeatures: ParityItem[] = [];
|
|
541
496
|
|
|
497
|
+
// Features in old that are also in new = DONE
|
|
498
|
+
// Features in old but NOT in new = GAP
|
|
542
499
|
for (const fId of oldFeatureIds) {
|
|
543
|
-
const feature =
|
|
500
|
+
const feature = getFeatureById(db, fId);
|
|
544
501
|
if (!feature) continue;
|
|
545
502
|
|
|
546
|
-
const
|
|
547
|
-
const
|
|
548
|
-
const newComps = comps.filter(c => newFileSet.has(c.component_file));
|
|
503
|
+
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 }[];
|
|
504
|
+
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
505
|
|
|
550
506
|
const item: ParityItem = {
|
|
551
507
|
feature_key: feature.feature_key,
|
|
@@ -562,15 +518,15 @@ export function checkParity(db: Database.Database, oldFiles: string[], newFiles:
|
|
|
562
518
|
}
|
|
563
519
|
}
|
|
564
520
|
|
|
521
|
+
// Features only in new = NEW
|
|
565
522
|
for (const fId of newFeatureIds) {
|
|
566
523
|
if (oldFeatureIds.has(fId)) continue;
|
|
567
|
-
const feature =
|
|
524
|
+
const feature = getFeatureById(db, fId);
|
|
568
525
|
if (!feature) continue;
|
|
569
526
|
|
|
570
|
-
const
|
|
571
|
-
const newComps = comps.filter(c => newFileSet.has(c.component_file));
|
|
527
|
+
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
528
|
|
|
573
|
-
|
|
529
|
+
newFeatures.push({
|
|
574
530
|
feature_key: feature.feature_key,
|
|
575
531
|
title: feature.title,
|
|
576
532
|
status: 'NEW',
|
|
@@ -582,7 +538,7 @@ export function checkParity(db: Database.Database, oldFiles: string[], newFiles:
|
|
|
582
538
|
const total = done.length + gaps.length;
|
|
583
539
|
const parityPercentage = total > 0 ? Math.round((done.length / total) * 100) : 100;
|
|
584
540
|
|
|
585
|
-
return { done, gaps, new_features:
|
|
541
|
+
return { done, gaps, new_features: newFeatures, parity_percentage: parityPercentage };
|
|
586
542
|
}
|
|
587
543
|
|
|
588
544
|
// ============================================================
|
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
|
@@ -11,10 +11,24 @@
|
|
|
11
11
|
* Tool names are configurable via massu.config.yaml toolPrefix.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { readFileSync } from 'fs';
|
|
15
|
+
import { resolve, dirname } from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
14
17
|
import { getCodeGraphDb, getDataDb } from './db.ts';
|
|
15
18
|
import { getConfig } from './config.ts';
|
|
16
19
|
import { getToolDefinitions, handleToolCall } from './tools.ts';
|
|
17
20
|
import { getMemoryDb, pruneOldConversationTurns, pruneOldObservations } from './memory-db.ts';
|
|
21
|
+
import { getCurrentTier } from './license.ts';
|
|
22
|
+
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const PKG_VERSION = (() => {
|
|
25
|
+
try {
|
|
26
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
27
|
+
return pkg.version ?? '0.0.0';
|
|
28
|
+
} catch {
|
|
29
|
+
return '0.0.0';
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
18
32
|
|
|
19
33
|
interface JsonRpcRequest {
|
|
20
34
|
jsonrpc: '2.0';
|
|
@@ -40,7 +54,7 @@ function getDb() {
|
|
|
40
54
|
return { codegraphDb, dataDb: dataDb };
|
|
41
55
|
}
|
|
42
56
|
|
|
43
|
-
function handleRequest(request: JsonRpcRequest): JsonRpcResponse {
|
|
57
|
+
async function handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
|
|
44
58
|
const { method, params, id } = request;
|
|
45
59
|
|
|
46
60
|
switch (method) {
|
|
@@ -54,8 +68,8 @@ function handleRequest(request: JsonRpcRequest): JsonRpcResponse {
|
|
|
54
68
|
tools: {},
|
|
55
69
|
},
|
|
56
70
|
serverInfo: {
|
|
57
|
-
name: 'massu',
|
|
58
|
-
version:
|
|
71
|
+
name: getConfig().toolPrefix || 'massu',
|
|
72
|
+
version: PKG_VERSION,
|
|
59
73
|
},
|
|
60
74
|
},
|
|
61
75
|
};
|
|
@@ -80,7 +94,7 @@ function handleRequest(request: JsonRpcRequest): JsonRpcResponse {
|
|
|
80
94
|
const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
|
|
81
95
|
|
|
82
96
|
const { codegraphDb: cgDb, dataDb: lDb } = getDb();
|
|
83
|
-
const result = handleToolCall(toolName, toolArgs, lDb, cgDb);
|
|
97
|
+
const result = await handleToolCall(toolName, toolArgs, lDb, cgDb);
|
|
84
98
|
|
|
85
99
|
return {
|
|
86
100
|
jsonrpc: '2.0',
|
|
@@ -133,12 +147,21 @@ function pruneMemoryOnStartup(): void {
|
|
|
133
147
|
|
|
134
148
|
pruneMemoryOnStartup();
|
|
135
149
|
|
|
150
|
+
// === License init: pre-cache tier status ===
|
|
151
|
+
getCurrentTier().then(tier => {
|
|
152
|
+
process.stderr.write(`massu: License tier: ${tier}\n`);
|
|
153
|
+
}).catch(error => {
|
|
154
|
+
process.stderr.write(
|
|
155
|
+
`massu: License check failed (non-fatal): ${error instanceof Error ? error.message : String(error)}\n`
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
136
159
|
// === stdio JSON-RPC transport ===
|
|
137
160
|
|
|
138
161
|
let buffer = '';
|
|
139
162
|
|
|
140
163
|
process.stdin.setEncoding('utf-8');
|
|
141
|
-
process.stdin.on('data', (chunk: string) => {
|
|
164
|
+
process.stdin.on('data', async (chunk: string) => {
|
|
142
165
|
buffer += chunk;
|
|
143
166
|
|
|
144
167
|
// Process complete messages (newline-delimited JSON-RPC)
|
|
@@ -151,7 +174,7 @@ process.stdin.on('data', (chunk: string) => {
|
|
|
151
174
|
|
|
152
175
|
try {
|
|
153
176
|
const request = JSON.parse(line) as JsonRpcRequest;
|
|
154
|
-
const response = handleRequest(request);
|
|
177
|
+
const response = await handleRequest(request);
|
|
155
178
|
|
|
156
179
|
// Don't send responses for notifications (no id)
|
|
157
180
|
if (request.id !== undefined) {
|
|
@@ -182,7 +205,6 @@ process.stdin.on('end', () => {
|
|
|
182
205
|
// Handle errors gracefully
|
|
183
206
|
process.on('uncaughtException', (error) => {
|
|
184
207
|
process.stderr.write(`massu: Uncaught exception: ${error.message}\n`);
|
|
185
|
-
process.exit(1);
|
|
186
208
|
});
|
|
187
209
|
|
|
188
210
|
process.on('unhandledRejection', (reason) => {
|
package/src/session-archiver.ts
CHANGED
|
@@ -5,14 +5,12 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from '
|
|
|
5
5
|
import { resolve, dirname } from 'path';
|
|
6
6
|
import type Database from 'better-sqlite3';
|
|
7
7
|
import { generateCurrentMd } from './session-state-generator.ts';
|
|
8
|
-
import {
|
|
8
|
+
import { getResolvedPaths } from './config.ts';
|
|
9
9
|
|
|
10
10
|
// ============================================================
|
|
11
11
|
// P5-002: Session Archiver
|
|
12
12
|
// ============================================================
|
|
13
13
|
|
|
14
|
-
const PROJECT_ROOT = getProjectRoot();
|
|
15
|
-
|
|
16
14
|
/**
|
|
17
15
|
* Archive the current CURRENT.md and generate a new one from memory DB.
|
|
18
16
|
*/
|
|
@@ -21,8 +19,9 @@ export function archiveAndRegenerate(db: Database.Database, sessionId: string):
|
|
|
21
19
|
archivePath?: string;
|
|
22
20
|
newContent: string;
|
|
23
21
|
} {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
22
|
+
const resolved = getResolvedPaths();
|
|
23
|
+
const currentMdPath = resolved.sessionStatePath;
|
|
24
|
+
const archiveDir = resolved.sessionArchivePath;
|
|
26
25
|
let archived = false;
|
|
27
26
|
let archivePath: string | undefined;
|
|
28
27
|
|
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
|
+
}
|