@massu/core 0.6.3 → 0.8.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/README.md CHANGED
@@ -14,8 +14,8 @@ This sets up the MCP server, configuration, and lifecycle hooks in one command.
14
14
 
15
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
- - **51 MCP Tools** — quality analytics, cost tracking, security scoring, dependency analysis, and more
18
- - **11 Lifecycle Hooks** — pre-commit gates, security scanning, intent suggestion, and session management
17
+ - **73 MCP Tools** — quality analytics, cost tracking, security scoring, dependency analysis, and more
18
+ - **15 Lifecycle Hooks** — pre-commit gates, security scanning, intent suggestion, session management, and auto-learning pipeline
19
19
  - **3-Database Architecture** — code graph (read-only), data (imports/mappings), and memory (sessions/analytics)
20
20
  - **Config-Driven** — all project-specific data lives in `massu.config.yaml`
21
21
 
package/dist/cli.js CHANGED
@@ -125,7 +125,7 @@ function getResolvedPaths() {
125
125
  settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
126
126
  };
127
127
  }
128
- var DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, RawConfigSchema, _config, _projectRoot;
128
+ var DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, AutoLearningConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, RawConfigSchema, _config, _projectRoot;
129
129
  var init_config = __esm({
130
130
  "src/config.ts"() {
131
131
  "use strict";
@@ -248,6 +248,44 @@ var init_config = __esm({
248
248
  warning: z.number().default(50)
249
249
  }).optional()
250
250
  }).optional();
251
+ AutoLearningConfigSchema = z.object({
252
+ enabled: z.boolean().default(true),
253
+ incidentDir: z.string().default("docs/incidents"),
254
+ memoryDir: z.string().default("memory"),
255
+ memoryIndexFile: z.string().default("MEMORY.md"),
256
+ enforcementHooksDir: z.string().default("scripts/hooks"),
257
+ fixDetection: z.object({
258
+ enabled: z.boolean().default(true),
259
+ lookbackDays: z.number().default(7),
260
+ signals: z.array(z.string()).default([
261
+ "removed_broken_code",
262
+ "added_error_handling",
263
+ "method_name_correction",
264
+ "auth_fix",
265
+ "nil_handling_fix",
266
+ "concurrency_fix",
267
+ "async_pattern_fix",
268
+ "added_missing_import"
269
+ ])
270
+ }).default({}),
271
+ failureClassification: z.object({
272
+ enabled: z.boolean().default(true),
273
+ thresholds: z.object({
274
+ known: z.number().default(5),
275
+ similar: z.number().default(3)
276
+ }).default({}),
277
+ scoring: z.object({
278
+ diffPatternWeight: z.number().default(3),
279
+ filePatternWeight: z.number().default(2),
280
+ promptKeywordWeight: z.number().default(2)
281
+ }).default({})
282
+ }).default({}),
283
+ pipeline: z.object({
284
+ requireIncidentReport: z.boolean().default(true),
285
+ requirePreventionRule: z.boolean().default(true),
286
+ requireEnforcement: z.boolean().default(true)
287
+ }).default({})
288
+ }).optional();
251
289
  CloudConfigSchema = z.object({
252
290
  enabled: z.boolean().default(false),
253
291
  apiKey: z.string().optional(),
@@ -337,7 +375,8 @@ var init_config = __esm({
337
375
  regression: RegressionConfigSchema,
338
376
  cloud: CloudConfigSchema,
339
377
  conventions: ConventionsConfigSchema,
340
- python: PythonConfigSchema
378
+ python: PythonConfigSchema,
379
+ autoLearning: AutoLearningConfigSchema
341
380
  }).passthrough();
342
381
  _config = null;
343
382
  _projectRoot = null;
@@ -348,10 +387,12 @@ var init_config = __esm({
348
387
  var memory_db_exports = {};
349
388
  __export(memory_db_exports, {
350
389
  addConversationTurn: () => addConversationTurn,
390
+ addFailureClass: () => addFailureClass,
351
391
  addObservation: () => addObservation,
352
392
  addSummary: () => addSummary,
353
393
  addToolCallDetail: () => addToolCallDetail,
354
394
  addUserPrompt: () => addUserPrompt,
395
+ appendIncidentToFailureClass: () => appendIncidentToFailureClass,
355
396
  assignImportance: () => assignImportance,
356
397
  autoDetectTaskId: () => autoDetectTaskId,
357
398
  createSession: () => createSession,
@@ -363,6 +404,7 @@ __export(memory_db_exports, {
363
404
  getCrossTaskProgress: () => getCrossTaskProgress,
364
405
  getDecisionsAbout: () => getDecisionsAbout,
365
406
  getFailedAttempts: () => getFailedAttempts,
407
+ getFailureClasses: () => getFailureClasses,
366
408
  getLastProcessedLine: () => getLastProcessedLine,
367
409
  getMemoryDb: () => getMemoryDb,
368
410
  getObservabilityDbSize: () => getObservabilityDbSize,
@@ -379,6 +421,7 @@ __export(memory_db_exports, {
379
421
  pruneOldObservations: () => pruneOldObservations,
380
422
  removePendingSync: () => removePendingSync,
381
423
  sanitizeFts5Query: () => sanitizeFts5Query,
424
+ scoreFailureClasses: () => scoreFailureClasses,
382
425
  searchConversationTurns: () => searchConversationTurns,
383
426
  searchObservations: () => searchObservations,
384
427
  setLastProcessedLine: () => setLastProcessedLine
@@ -878,6 +921,25 @@ function initMemorySchema(db) {
878
921
  features TEXT DEFAULT '[]'
879
922
  );
880
923
  `);
924
+ db.exec(`
925
+ CREATE TABLE IF NOT EXISTS failure_classes (
926
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
927
+ name TEXT NOT NULL UNIQUE,
928
+ description TEXT NOT NULL,
929
+ diff_patterns TEXT NOT NULL DEFAULT '[]',
930
+ file_patterns TEXT NOT NULL DEFAULT '[]',
931
+ prompt_keywords TEXT NOT NULL DEFAULT '[]',
932
+ incidents TEXT NOT NULL DEFAULT '[]',
933
+ rules TEXT NOT NULL DEFAULT '[]',
934
+ scanner_checks TEXT NOT NULL DEFAULT '[]',
935
+ known_message TEXT NOT NULL DEFAULT '',
936
+ needs_review INTEGER NOT NULL DEFAULT 0,
937
+ created_at TEXT DEFAULT (datetime('now')),
938
+ updated_at TEXT DEFAULT (datetime('now'))
939
+ );
940
+ CREATE INDEX IF NOT EXISTS idx_fc_name ON failure_classes(name);
941
+ CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
942
+ `);
881
943
  }
882
944
  function enqueueSyncPayload(db, payload) {
883
945
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
@@ -1311,6 +1373,108 @@ function getObservabilityDbSize(db) {
1311
1373
  estimated_size_mb: Math.round(pageCount * pageSize / (1024 * 1024) * 100) / 100
1312
1374
  };
1313
1375
  }
1376
+ function addFailureClass(db, opts) {
1377
+ const result = db.prepare(`
1378
+ INSERT OR IGNORE INTO failure_classes (name, description, diff_patterns, file_patterns, prompt_keywords, incidents, rules, scanner_checks, known_message, needs_review)
1379
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1380
+ `).run(
1381
+ opts.name,
1382
+ opts.description,
1383
+ JSON.stringify(opts.diffPatterns ?? []),
1384
+ JSON.stringify(opts.filePatterns ?? []),
1385
+ JSON.stringify(opts.promptKeywords ?? []),
1386
+ JSON.stringify(opts.incidents ?? []),
1387
+ JSON.stringify(opts.rules ?? []),
1388
+ JSON.stringify(opts.scannerChecks ?? []),
1389
+ opts.knownMessage ?? "",
1390
+ opts.needsReview ? 1 : 0
1391
+ );
1392
+ return Number(result.lastInsertRowid);
1393
+ }
1394
+ function getFailureClasses(db) {
1395
+ const rows = db.prepare("SELECT * FROM failure_classes ORDER BY name").all();
1396
+ return rows.map((row) => ({
1397
+ id: row.id,
1398
+ name: row.name,
1399
+ description: row.description,
1400
+ diff_patterns: JSON.parse(row.diff_patterns || "[]"),
1401
+ file_patterns: JSON.parse(row.file_patterns || "[]"),
1402
+ prompt_keywords: JSON.parse(row.prompt_keywords || "[]"),
1403
+ incidents: JSON.parse(row.incidents || "[]"),
1404
+ rules: JSON.parse(row.rules || "[]"),
1405
+ scanner_checks: JSON.parse(row.scanner_checks || "[]"),
1406
+ known_message: row.known_message,
1407
+ needs_review: !!row.needs_review
1408
+ }));
1409
+ }
1410
+ function appendIncidentToFailureClass(db, className, incidentId) {
1411
+ const row = db.prepare("SELECT incidents FROM failure_classes WHERE name = ?").get(className);
1412
+ if (!row) return;
1413
+ const incidents = JSON.parse(row.incidents || "[]");
1414
+ if (!incidents.includes(incidentId)) {
1415
+ incidents.push(incidentId);
1416
+ db.prepare("UPDATE failure_classes SET incidents = ?, updated_at = datetime('now') WHERE name = ?").run(JSON.stringify(incidents), className);
1417
+ }
1418
+ }
1419
+ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
1420
+ const classes = getFailureClasses(db);
1421
+ if (classes.length === 0) return null;
1422
+ const diffWeight = weights?.diffPatternWeight ?? 3;
1423
+ const fileWeight = weights?.filePatternWeight ?? 2;
1424
+ const promptWeight = weights?.promptKeywordWeight ?? 2;
1425
+ let bestMatch = null;
1426
+ for (const fc of classes) {
1427
+ let score = 0;
1428
+ for (const pattern of fc.diff_patterns) {
1429
+ if (!pattern) continue;
1430
+ try {
1431
+ if (new RegExp(pattern, "i").test(matchText)) {
1432
+ score += diffWeight;
1433
+ }
1434
+ } catch {
1435
+ if (matchText.toLowerCase().includes(pattern.toLowerCase())) {
1436
+ score += diffWeight;
1437
+ }
1438
+ }
1439
+ }
1440
+ for (const pattern of fc.file_patterns) {
1441
+ if (!pattern) continue;
1442
+ try {
1443
+ if (new RegExp(pattern).test(filePath)) {
1444
+ score += fileWeight;
1445
+ }
1446
+ } catch {
1447
+ if (filePath.includes(pattern)) {
1448
+ score += fileWeight;
1449
+ }
1450
+ }
1451
+ }
1452
+ if (promptContext) {
1453
+ for (const keyword of fc.prompt_keywords) {
1454
+ if (!keyword) continue;
1455
+ try {
1456
+ if (new RegExp(keyword, "i").test(promptContext)) {
1457
+ score += promptWeight;
1458
+ }
1459
+ } catch {
1460
+ if (promptContext.toLowerCase().includes(keyword.toLowerCase())) {
1461
+ score += promptWeight;
1462
+ }
1463
+ }
1464
+ }
1465
+ }
1466
+ if (!bestMatch || score > bestMatch.score) {
1467
+ bestMatch = {
1468
+ name: fc.name,
1469
+ score,
1470
+ incidentCount: fc.incidents.length,
1471
+ rules: fc.rules,
1472
+ knownMessage: fc.known_message
1473
+ };
1474
+ }
1475
+ }
1476
+ return bestMatch;
1477
+ }
1314
1478
  var init_memory_db = __esm({
1315
1479
  "src/memory-db.ts"() {
1316
1480
  "use strict";
@@ -1808,14 +1972,24 @@ function buildHooksConfig(hooksDir) {
1808
1972
  {
1809
1973
  matcher: "Edit|Write",
1810
1974
  hooks: [
1811
- { type: "command", command: hookCmd(hooksDir, "post-edit-context.js"), timeout: 5 }
1975
+ { type: "command", command: hookCmd(hooksDir, "post-edit-context.js"), timeout: 5 },
1976
+ { type: "command", command: hookCmd(hooksDir, "fix-detector.js"), timeout: 5 },
1977
+ { type: "command", command: hookCmd(hooksDir, "classify-failure.js"), timeout: 5 }
1978
+ ]
1979
+ },
1980
+ {
1981
+ matcher: "Write",
1982
+ hooks: [
1983
+ { type: "command", command: hookCmd(hooksDir, "incident-pipeline.js"), timeout: 5 },
1984
+ { type: "command", command: hookCmd(hooksDir, "rule-enforcement-pipeline.js"), timeout: 5 }
1812
1985
  ]
1813
1986
  }
1814
1987
  ],
1815
1988
  Stop: [
1816
1989
  {
1817
1990
  hooks: [
1818
- { type: "command", command: hookCmd(hooksDir, "session-end.js"), timeout: 15 }
1991
+ { type: "command", command: hookCmd(hooksDir, "session-end.js"), timeout: 15 },
1992
+ { type: "command", command: hookCmd(hooksDir, "auto-learning-pipeline.js"), timeout: 10 }
1819
1993
  ]
1820
1994
  }
1821
1995
  ],
@@ -2195,7 +2369,7 @@ var init_license = __esm({
2195
2369
  enterprise: 3
2196
2370
  };
2197
2371
  TOOL_TIER_MAP = {
2198
- // --- Free tier (12 tools: core navigation + basic memory + regression + license) ---
2372
+ // --- Free tier (13 tools: core navigation + basic memory + regression + license) ---
2199
2373
  sync: "free",
2200
2374
  context: "free",
2201
2375
  impact: "free",
@@ -2205,6 +2379,7 @@ var init_license = __esm({
2205
2379
  coupling_check: "free",
2206
2380
  memory_search: "free",
2207
2381
  memory_ingest: "free",
2382
+ memory_backfill: "free",
2208
2383
  regression_risk: "free",
2209
2384
  feature_health: "free",
2210
2385
  license_status: "free",