@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
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 an open-source [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that adds governance capabilities to AI coding assistants like Claude Code. It provides:
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](../../LICENSE) — free for non-production use. See LICENSE for details.
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-schema.ts
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 'unknown',
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 (e) {
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 (e) {
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 (e) {
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 'unknown',
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 'unknown',
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((resolve2) => {
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", () => resolve2(data));
797
- setTimeout(() => resolve2(data), 3e3);
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] CR-16: This file is in the middleware import tree. No Node.js deps allowed.");
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(`[CS CONTEXT] ${warnings.join(" | ")}`);
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-schema.ts
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 'unknown',
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 (e) {
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 (e) {
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 (e) {
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 'unknown',
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 'unknown',
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(text2) {
864
- return Math.ceil(text2.length / 4);
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(text2) {
857
+ function detectDecisionPatterns(text) {
873
858
  const phrases = getDetectionPhrases();
874
- const lower = text2.toLowerCase();
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 text2 = `${title} ${detail ?? ""}`;
883
+ const text = `${title} ${detail ?? ""}`;
899
884
  for (const pattern of PRIVATE_PATTERNS) {
900
- if (pattern.test(text2)) return "private";
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(text2) {
1030
+ function extractLinkedReferences(text) {
1046
1031
  const result = {};
1047
- const crMatch = text2.match(/CR-(\d+)/);
1032
+ const crMatch = text.match(/CR-(\d+)/);
1048
1033
  if (crMatch) result.crRule = `CR-${crMatch[1]}`;
1049
- const vrMatch = text2.match(/VR-([A-Z_]+)/);
1034
+ const vrMatch = text.match(/VR-([A-Z_]+)/);
1050
1035
  if (vrMatch) result.vrType = `VR-${vrMatch[1]}`;
1051
- const planMatch = text2.match(/P(\d+)-(\d+)/);
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 resolve2, dirname as dirname3, join } from "path";
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 = resolve2(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
1171
+ basePath = resolve3(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
1187
1172
  } else {
1188
- basePath = resolve2(dirname3(fromFile), specifier);
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 resolve3, normalize } from "path";
1212
+ import { resolve as resolve4, normalize } from "path";
1228
1213
  function ensureWithinRoot(filePath, projectRoot) {
1229
- const resolvedRoot = resolve3(projectRoot);
1230
- const resolvedPath = resolve3(resolvedRoot, filePath);
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 p2 of progress) {
1634
- currentProgress[p2.planItem] = p2.status;
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 p2 of progress) {
1642
- progressMap[p2.planItem] = p2.status;
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((resolve4) => {
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", () => resolve4(data));
1655
- setTimeout(() => resolve4(data), 3e3);
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-schema.ts
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 'unknown',
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 (e) {
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 (e) {
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 (e) {
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 'unknown',
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 'unknown',
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((resolve2) => {
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", () => resolve2(data));
904
- setTimeout(() => resolve2(data), 3e3);
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 placeholders = filePaths.map(() => "?").join(",");
303
- const featureLinks = db.prepare(
304
- `SELECT DISTINCT feature_id FROM massu_sentinel_components WHERE component_file IN (${placeholders})`
305
- ).all(...filePaths);
306
- const affectedFeatureIds = featureLinks.map((l) => l.feature_id);
307
- if (affectedFeatureIds.length === 0) {
308
- return { files_analyzed: filePaths, orphaned: [], degraded: [], unaffected: [], blocked: false, block_reason: null };
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 = featuresById.get(featureId);
332
- if (!feature) continue;
333
- const comps = componentsByFeature.get(featureId) ?? [];
334
- const affected = comps.filter((c) => fileSet.has(c.component_file));
335
- const remaining = comps.filter((c) => !fileSet.has(c.component_file));
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,