@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 +2 -2
- package/dist/cli.js +180 -5
- package/dist/hooks/auto-learning-pipeline.js +481 -0
- package/dist/hooks/classify-failure.js +1146 -0
- package/dist/hooks/cost-tracker.js +59 -1
- package/dist/hooks/fix-detector.js +474 -0
- package/dist/hooks/incident-pipeline.js +1114 -0
- package/dist/hooks/post-edit-context.js +40 -1
- package/dist/hooks/post-tool-use.js +59 -1
- package/dist/hooks/pre-compact.js +59 -1
- package/dist/hooks/pre-delete-check.js +40 -1
- package/dist/hooks/quality-event.js +59 -1
- package/dist/hooks/rule-enforcement-pipeline.js +453 -0
- package/dist/hooks/session-end.js +59 -1
- package/dist/hooks/session-start.js +60 -2
- package/dist/hooks/user-prompt.js +91 -2
- package/package.json +2 -2
- package/reference/hook-execution-order.md +25 -17
- package/src/commands/doctor.ts +1 -1
- package/src/commands/init.ts +11 -1
- package/src/config.ts +43 -0
- package/src/hooks/auto-learning-pipeline.ts +195 -0
- package/src/hooks/classify-failure.ts +259 -0
- package/src/hooks/fix-detector.ts +186 -0
- package/src/hooks/incident-pipeline.ts +190 -0
- package/src/hooks/rule-enforcement-pipeline.ts +159 -0
- package/src/hooks/session-start.ts +1 -1
- package/src/hooks/user-prompt.ts +21 -1
- package/src/license.ts +2 -1
- package/src/mcp-bridge-tools.ts +1 -1
- package/src/memory-db.ts +201 -0
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
|
-
- **
|
|
18
|
-
- **
|
|
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 (
|
|
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",
|