@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/README.md
CHANGED
|
@@ -12,7 +12,7 @@ This sets up the MCP server, configuration, and lifecycle hooks in one command.
|
|
|
12
12
|
|
|
13
13
|
## What is Massu?
|
|
14
14
|
|
|
15
|
-
Massu is
|
|
15
|
+
Massu is a source-available [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that adds governance capabilities to AI coding assistants like Claude Code. It provides:
|
|
16
16
|
|
|
17
17
|
- **51 MCP Tools** — quality analytics, cost tracking, security scoring, dependency analysis, and more
|
|
18
18
|
- **11 Lifecycle Hooks** — pre-commit gates, security scanning, intent suggestion, and session management
|
|
@@ -37,4 +37,4 @@ Full documentation at [massu.ai](https://massu.ai).
|
|
|
37
37
|
|
|
38
38
|
## License
|
|
39
39
|
|
|
40
|
-
[BSL 1.1](
|
|
40
|
+
[BSL 1.1](https://github.com/massu-ai/massu/blob/main/LICENSE) — source-available. Free to use, modify, and distribute. See LICENSE for full terms.
|
|
@@ -3,7 +3,7 @@ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
|
|
|
3
3
|
|
|
4
4
|
// src/memory-db.ts
|
|
5
5
|
import Database from "better-sqlite3";
|
|
6
|
-
import { dirname as dirname2 } from "path";
|
|
6
|
+
import { dirname as dirname2, basename } from "path";
|
|
7
7
|
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
@@ -268,14 +268,26 @@ function getResolvedPaths() {
|
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
// src/memory-
|
|
271
|
+
// src/memory-db.ts
|
|
272
|
+
function getMemoryDb() {
|
|
273
|
+
const dbPath = getResolvedPaths().memoryDbPath;
|
|
274
|
+
const dir = dirname2(dbPath);
|
|
275
|
+
if (!existsSync2(dir)) {
|
|
276
|
+
mkdirSync(dir, { recursive: true });
|
|
277
|
+
}
|
|
278
|
+
const db = new Database(dbPath);
|
|
279
|
+
db.pragma("journal_mode = WAL");
|
|
280
|
+
db.pragma("foreign_keys = ON");
|
|
281
|
+
initMemorySchema(db);
|
|
282
|
+
return db;
|
|
283
|
+
}
|
|
272
284
|
function initMemorySchema(db) {
|
|
273
285
|
db.exec(`
|
|
274
286
|
-- Sessions table (linked to Claude Code session IDs)
|
|
275
287
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
276
288
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
277
289
|
session_id TEXT UNIQUE NOT NULL,
|
|
278
|
-
project TEXT NOT NULL DEFAULT '
|
|
290
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
279
291
|
git_branch TEXT,
|
|
280
292
|
started_at TEXT NOT NULL,
|
|
281
293
|
started_at_epoch INTEGER NOT NULL,
|
|
@@ -330,9 +342,7 @@ function initMemorySchema(db) {
|
|
|
330
342
|
content_rowid='id'
|
|
331
343
|
);
|
|
332
344
|
`);
|
|
333
|
-
} catch (
|
|
334
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
335
|
-
`);
|
|
345
|
+
} catch (_e) {
|
|
336
346
|
}
|
|
337
347
|
db.exec(`
|
|
338
348
|
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
@@ -392,9 +402,7 @@ function initMemorySchema(db) {
|
|
|
392
402
|
content_rowid='id'
|
|
393
403
|
);
|
|
394
404
|
`);
|
|
395
|
-
} catch (
|
|
396
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
397
|
-
`);
|
|
405
|
+
} catch (_e) {
|
|
398
406
|
}
|
|
399
407
|
db.exec(`
|
|
400
408
|
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
|
|
@@ -464,9 +472,7 @@ function initMemorySchema(db) {
|
|
|
464
472
|
content_rowid=id
|
|
465
473
|
);
|
|
466
474
|
`);
|
|
467
|
-
} catch (
|
|
468
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
469
|
-
`);
|
|
475
|
+
} catch (_e) {
|
|
470
476
|
}
|
|
471
477
|
db.exec(`
|
|
472
478
|
CREATE TRIGGER IF NOT EXISTS ct_fts_insert AFTER INSERT ON conversation_turns BEGIN
|
|
@@ -490,7 +496,7 @@ function initMemorySchema(db) {
|
|
|
490
496
|
CREATE TABLE IF NOT EXISTS session_quality_scores (
|
|
491
497
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
492
498
|
session_id TEXT NOT NULL UNIQUE,
|
|
493
|
-
project TEXT NOT NULL DEFAULT '
|
|
499
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
494
500
|
score INTEGER NOT NULL DEFAULT 100,
|
|
495
501
|
security_score INTEGER NOT NULL DEFAULT 100,
|
|
496
502
|
architecture_score INTEGER NOT NULL DEFAULT 100,
|
|
@@ -513,7 +519,7 @@ function initMemorySchema(db) {
|
|
|
513
519
|
CREATE TABLE IF NOT EXISTS session_costs (
|
|
514
520
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
515
521
|
session_id TEXT NOT NULL UNIQUE,
|
|
516
|
-
project TEXT NOT NULL DEFAULT '
|
|
522
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
517
523
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
518
524
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
519
525
|
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
@@ -742,24 +748,6 @@ function initMemorySchema(db) {
|
|
|
742
748
|
`);
|
|
743
749
|
}
|
|
744
750
|
|
|
745
|
-
// src/memory-db.ts
|
|
746
|
-
var initializedPaths = /* @__PURE__ */ new Set();
|
|
747
|
-
function getMemoryDb() {
|
|
748
|
-
const dbPath = getResolvedPaths().memoryDbPath;
|
|
749
|
-
const dir = dirname2(dbPath);
|
|
750
|
-
if (!existsSync2(dir)) {
|
|
751
|
-
mkdirSync(dir, { recursive: true });
|
|
752
|
-
}
|
|
753
|
-
const db = new Database(dbPath);
|
|
754
|
-
db.pragma("journal_mode = WAL");
|
|
755
|
-
db.pragma("foreign_keys = ON");
|
|
756
|
-
if (!initializedPaths.has(dbPath)) {
|
|
757
|
-
initMemorySchema(db);
|
|
758
|
-
initializedPaths.add(dbPath);
|
|
759
|
-
}
|
|
760
|
-
return db;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
751
|
// src/hooks/cost-tracker.ts
|
|
764
752
|
var CHARS_PER_TOKEN = 4;
|
|
765
753
|
function estimateTokens(text) {
|
|
@@ -787,14 +775,14 @@ async function main() {
|
|
|
787
775
|
process.exit(0);
|
|
788
776
|
}
|
|
789
777
|
function readStdin() {
|
|
790
|
-
return new Promise((
|
|
778
|
+
return new Promise((resolve3) => {
|
|
791
779
|
let data = "";
|
|
792
780
|
process.stdin.setEncoding("utf-8");
|
|
793
781
|
process.stdin.on("data", (chunk) => {
|
|
794
782
|
data += chunk;
|
|
795
783
|
});
|
|
796
|
-
process.stdin.on("end", () =>
|
|
797
|
-
setTimeout(() =>
|
|
784
|
+
process.stdin.on("end", () => resolve3(data));
|
|
785
|
+
setTimeout(() => resolve3(data), 3e3);
|
|
798
786
|
});
|
|
799
787
|
}
|
|
800
788
|
main();
|
|
@@ -319,7 +319,7 @@ async function main() {
|
|
|
319
319
|
const dataDb = new Database(getResolvedPaths().dataDbPath, { readonly: true });
|
|
320
320
|
try {
|
|
321
321
|
if (isInMiddlewareTree(dataDb, rel)) {
|
|
322
|
-
warnings.push("[CRITICAL]
|
|
322
|
+
warnings.push("[CRITICAL] This file is in the middleware import tree. No Node.js deps allowed.");
|
|
323
323
|
}
|
|
324
324
|
} finally {
|
|
325
325
|
dataDb.close();
|
|
@@ -327,7 +327,7 @@ async function main() {
|
|
|
327
327
|
} catch (_e) {
|
|
328
328
|
}
|
|
329
329
|
if (warnings.length > 0) {
|
|
330
|
-
console.log(`[
|
|
330
|
+
console.log(`[Massu] ${warnings.join(" | ")}`);
|
|
331
331
|
}
|
|
332
332
|
} catch (_e) {
|
|
333
333
|
}
|
|
@@ -3,7 +3,7 @@ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
|
|
|
3
3
|
|
|
4
4
|
// src/memory-db.ts
|
|
5
5
|
import Database from "better-sqlite3";
|
|
6
|
-
import { dirname as dirname2 } from "path";
|
|
6
|
+
import { dirname as dirname2, basename } from "path";
|
|
7
7
|
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
@@ -268,14 +268,26 @@ function getResolvedPaths() {
|
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
// src/memory-
|
|
271
|
+
// src/memory-db.ts
|
|
272
|
+
function getMemoryDb() {
|
|
273
|
+
const dbPath = getResolvedPaths().memoryDbPath;
|
|
274
|
+
const dir = dirname2(dbPath);
|
|
275
|
+
if (!existsSync2(dir)) {
|
|
276
|
+
mkdirSync(dir, { recursive: true });
|
|
277
|
+
}
|
|
278
|
+
const db = new Database(dbPath);
|
|
279
|
+
db.pragma("journal_mode = WAL");
|
|
280
|
+
db.pragma("foreign_keys = ON");
|
|
281
|
+
initMemorySchema(db);
|
|
282
|
+
return db;
|
|
283
|
+
}
|
|
272
284
|
function initMemorySchema(db) {
|
|
273
285
|
db.exec(`
|
|
274
286
|
-- Sessions table (linked to Claude Code session IDs)
|
|
275
287
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
276
288
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
277
289
|
session_id TEXT UNIQUE NOT NULL,
|
|
278
|
-
project TEXT NOT NULL DEFAULT '
|
|
290
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
279
291
|
git_branch TEXT,
|
|
280
292
|
started_at TEXT NOT NULL,
|
|
281
293
|
started_at_epoch INTEGER NOT NULL,
|
|
@@ -330,9 +342,7 @@ function initMemorySchema(db) {
|
|
|
330
342
|
content_rowid='id'
|
|
331
343
|
);
|
|
332
344
|
`);
|
|
333
|
-
} catch (
|
|
334
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
335
|
-
`);
|
|
345
|
+
} catch (_e) {
|
|
336
346
|
}
|
|
337
347
|
db.exec(`
|
|
338
348
|
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
@@ -392,9 +402,7 @@ function initMemorySchema(db) {
|
|
|
392
402
|
content_rowid='id'
|
|
393
403
|
);
|
|
394
404
|
`);
|
|
395
|
-
} catch (
|
|
396
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
397
|
-
`);
|
|
405
|
+
} catch (_e) {
|
|
398
406
|
}
|
|
399
407
|
db.exec(`
|
|
400
408
|
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
|
|
@@ -464,9 +472,7 @@ function initMemorySchema(db) {
|
|
|
464
472
|
content_rowid=id
|
|
465
473
|
);
|
|
466
474
|
`);
|
|
467
|
-
} catch (
|
|
468
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
469
|
-
`);
|
|
475
|
+
} catch (_e) {
|
|
470
476
|
}
|
|
471
477
|
db.exec(`
|
|
472
478
|
CREATE TRIGGER IF NOT EXISTS ct_fts_insert AFTER INSERT ON conversation_turns BEGIN
|
|
@@ -490,7 +496,7 @@ function initMemorySchema(db) {
|
|
|
490
496
|
CREATE TABLE IF NOT EXISTS session_quality_scores (
|
|
491
497
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
492
498
|
session_id TEXT NOT NULL UNIQUE,
|
|
493
|
-
project TEXT NOT NULL DEFAULT '
|
|
499
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
494
500
|
score INTEGER NOT NULL DEFAULT 100,
|
|
495
501
|
security_score INTEGER NOT NULL DEFAULT 100,
|
|
496
502
|
architecture_score INTEGER NOT NULL DEFAULT 100,
|
|
@@ -513,7 +519,7 @@ function initMemorySchema(db) {
|
|
|
513
519
|
CREATE TABLE IF NOT EXISTS session_costs (
|
|
514
520
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
515
521
|
session_id TEXT NOT NULL UNIQUE,
|
|
516
|
-
project TEXT NOT NULL DEFAULT '
|
|
522
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
517
523
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
518
524
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
519
525
|
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
@@ -741,9 +747,6 @@ function initMemorySchema(db) {
|
|
|
741
747
|
CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
|
|
742
748
|
`);
|
|
743
749
|
}
|
|
744
|
-
|
|
745
|
-
// src/memory-queries.ts
|
|
746
|
-
import { basename } from "path";
|
|
747
750
|
function assignImportance(type, vrResult) {
|
|
748
751
|
switch (type) {
|
|
749
752
|
case "decision":
|
|
@@ -841,27 +844,9 @@ function deduplicateFailedAttempt(db, sessionId, title, detail, opts) {
|
|
|
841
844
|
});
|
|
842
845
|
}
|
|
843
846
|
|
|
844
|
-
// src/memory-db.ts
|
|
845
|
-
var initializedPaths = /* @__PURE__ */ new Set();
|
|
846
|
-
function getMemoryDb() {
|
|
847
|
-
const dbPath = getResolvedPaths().memoryDbPath;
|
|
848
|
-
const dir = dirname2(dbPath);
|
|
849
|
-
if (!existsSync2(dir)) {
|
|
850
|
-
mkdirSync(dir, { recursive: true });
|
|
851
|
-
}
|
|
852
|
-
const db = new Database(dbPath);
|
|
853
|
-
db.pragma("journal_mode = WAL");
|
|
854
|
-
db.pragma("foreign_keys = ON");
|
|
855
|
-
if (!initializedPaths.has(dbPath)) {
|
|
856
|
-
initMemorySchema(db);
|
|
857
|
-
initializedPaths.add(dbPath);
|
|
858
|
-
}
|
|
859
|
-
return db;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
847
|
// src/transcript-parser.ts
|
|
863
|
-
function estimateTokens(
|
|
864
|
-
return Math.ceil(
|
|
848
|
+
function estimateTokens(text) {
|
|
849
|
+
return Math.ceil(text.length / 4);
|
|
865
850
|
}
|
|
866
851
|
|
|
867
852
|
// src/adr-generator.ts
|
|
@@ -869,9 +854,9 @@ var DEFAULT_DETECTION_PHRASES = ["chose", "decided", "switching to", "moving fro
|
|
|
869
854
|
function getDetectionPhrases() {
|
|
870
855
|
return getConfig().governance?.adr?.detection_phrases ?? DEFAULT_DETECTION_PHRASES;
|
|
871
856
|
}
|
|
872
|
-
function detectDecisionPatterns(
|
|
857
|
+
function detectDecisionPatterns(text) {
|
|
873
858
|
const phrases = getDetectionPhrases();
|
|
874
|
-
const lower =
|
|
859
|
+
const lower = text.toLowerCase();
|
|
875
860
|
return phrases.some((phrase) => lower.includes(phrase));
|
|
876
861
|
}
|
|
877
862
|
|
|
@@ -895,9 +880,9 @@ var PRIVATE_PATTERNS = [
|
|
|
895
880
|
// Stripe keys
|
|
896
881
|
];
|
|
897
882
|
function classifyVisibility(title, detail) {
|
|
898
|
-
const
|
|
883
|
+
const text = `${title} ${detail ?? ""}`;
|
|
899
884
|
for (const pattern of PRIVATE_PATTERNS) {
|
|
900
|
-
if (pattern.test(
|
|
885
|
+
if (pattern.test(text)) return "private";
|
|
901
886
|
}
|
|
902
887
|
return "public";
|
|
903
888
|
}
|
|
@@ -1042,13 +1027,13 @@ function classifyToolCall(tc) {
|
|
|
1042
1027
|
return null;
|
|
1043
1028
|
}
|
|
1044
1029
|
}
|
|
1045
|
-
function extractLinkedReferences(
|
|
1030
|
+
function extractLinkedReferences(text) {
|
|
1046
1031
|
const result = {};
|
|
1047
|
-
const crMatch =
|
|
1032
|
+
const crMatch = text.match(/CR-(\d+)/);
|
|
1048
1033
|
if (crMatch) result.crRule = `CR-${crMatch[1]}`;
|
|
1049
|
-
const vrMatch =
|
|
1034
|
+
const vrMatch = text.match(/VR-([A-Z_]+)/);
|
|
1050
1035
|
if (vrMatch) result.vrType = `VR-${vrMatch[1]}`;
|
|
1051
|
-
const planMatch =
|
|
1036
|
+
const planMatch = text.match(/P(\d+)-(\d+)/);
|
|
1052
1037
|
if (planMatch) result.planItem = `P${planMatch[1]}-${planMatch[2]}`;
|
|
1053
1038
|
return result;
|
|
1054
1039
|
}
|
|
@@ -1175,7 +1160,7 @@ function trackModification(db, featureKey) {
|
|
|
1175
1160
|
|
|
1176
1161
|
// src/import-resolver.ts
|
|
1177
1162
|
import { readFileSync as readFileSync2, existsSync as existsSync3, statSync } from "fs";
|
|
1178
|
-
import { resolve as
|
|
1163
|
+
import { resolve as resolve3, dirname as dirname3, join } from "path";
|
|
1179
1164
|
function resolveImportPath(specifier, fromFile) {
|
|
1180
1165
|
if (!specifier.startsWith(".") && !specifier.startsWith("@/")) {
|
|
1181
1166
|
return null;
|
|
@@ -1183,9 +1168,9 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
1183
1168
|
let basePath;
|
|
1184
1169
|
if (specifier.startsWith("@/")) {
|
|
1185
1170
|
const paths = getResolvedPaths();
|
|
1186
|
-
basePath =
|
|
1171
|
+
basePath = resolve3(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
1187
1172
|
} else {
|
|
1188
|
-
basePath =
|
|
1173
|
+
basePath = resolve3(dirname3(fromFile), specifier);
|
|
1189
1174
|
}
|
|
1190
1175
|
if (existsSync3(basePath) && !isDirectory(basePath)) {
|
|
1191
1176
|
return toRelative(basePath);
|
|
@@ -1224,10 +1209,10 @@ function toRelative(absPath) {
|
|
|
1224
1209
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1225
1210
|
|
|
1226
1211
|
// src/security-utils.ts
|
|
1227
|
-
import { resolve as
|
|
1212
|
+
import { resolve as resolve4, normalize } from "path";
|
|
1228
1213
|
function ensureWithinRoot(filePath, projectRoot) {
|
|
1229
|
-
const resolvedRoot =
|
|
1230
|
-
const resolvedPath =
|
|
1214
|
+
const resolvedRoot = resolve4(projectRoot);
|
|
1215
|
+
const resolvedPath = resolve4(resolvedRoot, filePath);
|
|
1231
1216
|
const normalizedPath = normalize(resolvedPath);
|
|
1232
1217
|
const normalizedRoot = normalize(resolvedRoot);
|
|
1233
1218
|
if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
|
|
@@ -1630,29 +1615,29 @@ function updatePlanProgress(db, sessionId, progress) {
|
|
|
1630
1615
|
if (existing) {
|
|
1631
1616
|
try {
|
|
1632
1617
|
const currentProgress = JSON.parse(existing.plan_progress);
|
|
1633
|
-
for (const
|
|
1634
|
-
currentProgress[
|
|
1618
|
+
for (const p of progress) {
|
|
1619
|
+
currentProgress[p.planItem] = p.status;
|
|
1635
1620
|
}
|
|
1636
1621
|
db.prepare("UPDATE session_summaries SET plan_progress = ? WHERE id = ?").run(JSON.stringify(currentProgress), existing.id);
|
|
1637
1622
|
} catch (_e) {
|
|
1638
1623
|
}
|
|
1639
1624
|
} else {
|
|
1640
1625
|
const progressMap = {};
|
|
1641
|
-
for (const
|
|
1642
|
-
progressMap[
|
|
1626
|
+
for (const p of progress) {
|
|
1627
|
+
progressMap[p.planItem] = p.status;
|
|
1643
1628
|
}
|
|
1644
1629
|
addSummary(db, sessionId, { planProgress: progressMap });
|
|
1645
1630
|
}
|
|
1646
1631
|
}
|
|
1647
1632
|
function readStdin() {
|
|
1648
|
-
return new Promise((
|
|
1633
|
+
return new Promise((resolve5) => {
|
|
1649
1634
|
let data = "";
|
|
1650
1635
|
process.stdin.setEncoding("utf-8");
|
|
1651
1636
|
process.stdin.on("data", (chunk) => {
|
|
1652
1637
|
data += chunk;
|
|
1653
1638
|
});
|
|
1654
|
-
process.stdin.on("end", () =>
|
|
1655
|
-
setTimeout(() =>
|
|
1639
|
+
process.stdin.on("end", () => resolve5(data));
|
|
1640
|
+
setTimeout(() => resolve5(data), 3e3);
|
|
1656
1641
|
});
|
|
1657
1642
|
}
|
|
1658
1643
|
main();
|
|
@@ -3,7 +3,7 @@ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
|
|
|
3
3
|
|
|
4
4
|
// src/memory-db.ts
|
|
5
5
|
import Database from "better-sqlite3";
|
|
6
|
-
import { dirname as dirname2 } from "path";
|
|
6
|
+
import { dirname as dirname2, basename } from "path";
|
|
7
7
|
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
@@ -268,14 +268,26 @@ function getResolvedPaths() {
|
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
// src/memory-
|
|
271
|
+
// src/memory-db.ts
|
|
272
|
+
function getMemoryDb() {
|
|
273
|
+
const dbPath = getResolvedPaths().memoryDbPath;
|
|
274
|
+
const dir = dirname2(dbPath);
|
|
275
|
+
if (!existsSync2(dir)) {
|
|
276
|
+
mkdirSync(dir, { recursive: true });
|
|
277
|
+
}
|
|
278
|
+
const db = new Database(dbPath);
|
|
279
|
+
db.pragma("journal_mode = WAL");
|
|
280
|
+
db.pragma("foreign_keys = ON");
|
|
281
|
+
initMemorySchema(db);
|
|
282
|
+
return db;
|
|
283
|
+
}
|
|
272
284
|
function initMemorySchema(db) {
|
|
273
285
|
db.exec(`
|
|
274
286
|
-- Sessions table (linked to Claude Code session IDs)
|
|
275
287
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
276
288
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
277
289
|
session_id TEXT UNIQUE NOT NULL,
|
|
278
|
-
project TEXT NOT NULL DEFAULT '
|
|
290
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
279
291
|
git_branch TEXT,
|
|
280
292
|
started_at TEXT NOT NULL,
|
|
281
293
|
started_at_epoch INTEGER NOT NULL,
|
|
@@ -330,9 +342,7 @@ function initMemorySchema(db) {
|
|
|
330
342
|
content_rowid='id'
|
|
331
343
|
);
|
|
332
344
|
`);
|
|
333
|
-
} catch (
|
|
334
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
335
|
-
`);
|
|
345
|
+
} catch (_e) {
|
|
336
346
|
}
|
|
337
347
|
db.exec(`
|
|
338
348
|
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
@@ -392,9 +402,7 @@ function initMemorySchema(db) {
|
|
|
392
402
|
content_rowid='id'
|
|
393
403
|
);
|
|
394
404
|
`);
|
|
395
|
-
} catch (
|
|
396
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
397
|
-
`);
|
|
405
|
+
} catch (_e) {
|
|
398
406
|
}
|
|
399
407
|
db.exec(`
|
|
400
408
|
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
|
|
@@ -464,9 +472,7 @@ function initMemorySchema(db) {
|
|
|
464
472
|
content_rowid=id
|
|
465
473
|
);
|
|
466
474
|
`);
|
|
467
|
-
} catch (
|
|
468
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
469
|
-
`);
|
|
475
|
+
} catch (_e) {
|
|
470
476
|
}
|
|
471
477
|
db.exec(`
|
|
472
478
|
CREATE TRIGGER IF NOT EXISTS ct_fts_insert AFTER INSERT ON conversation_turns BEGIN
|
|
@@ -490,7 +496,7 @@ function initMemorySchema(db) {
|
|
|
490
496
|
CREATE TABLE IF NOT EXISTS session_quality_scores (
|
|
491
497
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
492
498
|
session_id TEXT NOT NULL UNIQUE,
|
|
493
|
-
project TEXT NOT NULL DEFAULT '
|
|
499
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
494
500
|
score INTEGER NOT NULL DEFAULT 100,
|
|
495
501
|
security_score INTEGER NOT NULL DEFAULT 100,
|
|
496
502
|
architecture_score INTEGER NOT NULL DEFAULT 100,
|
|
@@ -513,7 +519,7 @@ function initMemorySchema(db) {
|
|
|
513
519
|
CREATE TABLE IF NOT EXISTS session_costs (
|
|
514
520
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
515
521
|
session_id TEXT NOT NULL UNIQUE,
|
|
516
|
-
project TEXT NOT NULL DEFAULT '
|
|
522
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
517
523
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
518
524
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
519
525
|
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
@@ -741,9 +747,6 @@ function initMemorySchema(db) {
|
|
|
741
747
|
CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
|
|
742
748
|
`);
|
|
743
749
|
}
|
|
744
|
-
|
|
745
|
-
// src/memory-queries.ts
|
|
746
|
-
import { basename } from "path";
|
|
747
750
|
function autoDetectTaskId(planFile) {
|
|
748
751
|
if (!planFile) return null;
|
|
749
752
|
const base = basename(planFile);
|
|
@@ -779,24 +782,6 @@ function addSummary(db, sessionId, summary) {
|
|
|
779
782
|
);
|
|
780
783
|
}
|
|
781
784
|
|
|
782
|
-
// src/memory-db.ts
|
|
783
|
-
var initializedPaths = /* @__PURE__ */ new Set();
|
|
784
|
-
function getMemoryDb() {
|
|
785
|
-
const dbPath = getResolvedPaths().memoryDbPath;
|
|
786
|
-
const dir = dirname2(dbPath);
|
|
787
|
-
if (!existsSync2(dir)) {
|
|
788
|
-
mkdirSync(dir, { recursive: true });
|
|
789
|
-
}
|
|
790
|
-
const db = new Database(dbPath);
|
|
791
|
-
db.pragma("journal_mode = WAL");
|
|
792
|
-
db.pragma("foreign_keys = ON");
|
|
793
|
-
if (!initializedPaths.has(dbPath)) {
|
|
794
|
-
initMemorySchema(db);
|
|
795
|
-
initializedPaths.add(dbPath);
|
|
796
|
-
}
|
|
797
|
-
return db;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
785
|
// src/audit-trail.ts
|
|
801
786
|
function logAuditEntry(db, entry) {
|
|
802
787
|
db.prepare(`
|
|
@@ -894,14 +879,14 @@ function safeParseJson(json, fallback) {
|
|
|
894
879
|
}
|
|
895
880
|
}
|
|
896
881
|
function readStdin() {
|
|
897
|
-
return new Promise((
|
|
882
|
+
return new Promise((resolve3) => {
|
|
898
883
|
let data = "";
|
|
899
884
|
process.stdin.setEncoding("utf-8");
|
|
900
885
|
process.stdin.on("data", (chunk) => {
|
|
901
886
|
data += chunk;
|
|
902
887
|
});
|
|
903
|
-
process.stdin.on("end", () =>
|
|
904
|
-
setTimeout(() =>
|
|
888
|
+
process.stdin.on("end", () => resolve3(data));
|
|
889
|
+
setTimeout(() => resolve3(data), 3e3);
|
|
905
890
|
});
|
|
906
891
|
}
|
|
907
892
|
main();
|
|
@@ -294,45 +294,32 @@ function toFeature(row) {
|
|
|
294
294
|
removed_reason: row.removed_reason || null
|
|
295
295
|
};
|
|
296
296
|
}
|
|
297
|
+
function getFeatureById(db, id) {
|
|
298
|
+
const row = db.prepare("SELECT * FROM massu_sentinel WHERE id = ?").get(id);
|
|
299
|
+
return row ? toFeature(row) : null;
|
|
300
|
+
}
|
|
297
301
|
function getFeatureImpact(db, filePaths) {
|
|
298
|
-
if (filePaths.length === 0) {
|
|
299
|
-
return { files_analyzed: [], orphaned: [], degraded: [], unaffected: [], blocked: false, block_reason: null };
|
|
300
|
-
}
|
|
301
302
|
const fileSet = new Set(filePaths);
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
const featurePlaceholders = affectedFeatureIds.map(() => "?").join(",");
|
|
311
|
-
const featureRows = db.prepare(
|
|
312
|
-
`SELECT * FROM massu_sentinel WHERE id IN (${featurePlaceholders}) AND status = 'active'`
|
|
313
|
-
).all(...affectedFeatureIds);
|
|
314
|
-
const featuresById = new Map(featureRows.map((r) => [r.id, toFeature(r)]));
|
|
315
|
-
const allComponents = db.prepare(
|
|
316
|
-
`SELECT feature_id, component_file, is_primary FROM massu_sentinel_components WHERE feature_id IN (${featurePlaceholders})`
|
|
317
|
-
).all(...affectedFeatureIds);
|
|
318
|
-
const componentsByFeature = /* @__PURE__ */ new Map();
|
|
319
|
-
for (const comp of allComponents) {
|
|
320
|
-
let list = componentsByFeature.get(comp.feature_id);
|
|
321
|
-
if (!list) {
|
|
322
|
-
list = [];
|
|
323
|
-
componentsByFeature.set(comp.feature_id, list);
|
|
303
|
+
const affectedFeatureIds = /* @__PURE__ */ new Set();
|
|
304
|
+
for (const filePath of filePaths) {
|
|
305
|
+
const links = db.prepare(
|
|
306
|
+
"SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?"
|
|
307
|
+
).all(filePath);
|
|
308
|
+
for (const link of links) {
|
|
309
|
+
affectedFeatureIds.add(link.feature_id);
|
|
324
310
|
}
|
|
325
|
-
list.push(comp);
|
|
326
311
|
}
|
|
327
312
|
const orphaned = [];
|
|
328
313
|
const degraded = [];
|
|
329
314
|
const unaffected = [];
|
|
330
315
|
for (const featureId of affectedFeatureIds) {
|
|
331
|
-
const feature =
|
|
332
|
-
if (!feature) continue;
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
316
|
+
const feature = getFeatureById(db, featureId);
|
|
317
|
+
if (!feature || feature.status !== "active") continue;
|
|
318
|
+
const allComponents = db.prepare(
|
|
319
|
+
"SELECT component_file, is_primary FROM massu_sentinel_components WHERE feature_id = ?"
|
|
320
|
+
).all(featureId);
|
|
321
|
+
const affected = allComponents.filter((c) => fileSet.has(c.component_file));
|
|
322
|
+
const remaining = allComponents.filter((c) => !fileSet.has(c.component_file));
|
|
336
323
|
const primaryAffected = affected.some((c) => c.is_primary);
|
|
337
324
|
const item = {
|
|
338
325
|
feature,
|