@massu/core 0.7.0 → 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 +151 -3
- package/dist/hooks/auto-learning-pipeline.js +14 -2
- package/dist/hooks/classify-failure.js +1146 -0
- package/dist/hooks/cost-tracker.js +31 -0
- package/dist/hooks/fix-detector.js +12 -0
- package/dist/hooks/incident-pipeline.js +698 -10
- package/dist/hooks/post-edit-context.js +12 -0
- package/dist/hooks/post-tool-use.js +31 -0
- package/dist/hooks/pre-compact.js +31 -0
- package/dist/hooks/pre-delete-check.js +12 -0
- package/dist/hooks/quality-event.js +31 -0
- package/dist/hooks/rule-enforcement-pipeline.js +14 -1
- package/dist/hooks/session-end.js +31 -0
- package/dist/hooks/session-start.js +32 -1
- package/dist/hooks/user-prompt.js +63 -1
- package/package.json +1 -1
- 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 +12 -0
- package/src/hooks/auto-learning-pipeline.ts +3 -3
- package/src/hooks/classify-failure.ts +259 -0
- package/src/hooks/incident-pipeline.ts +42 -0
- package/src/hooks/rule-enforcement-pipeline.ts +2 -1
- 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
|
@@ -268,6 +268,18 @@ var init_config = __esm({
|
|
|
268
268
|
"added_missing_import"
|
|
269
269
|
])
|
|
270
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({}),
|
|
271
283
|
pipeline: z.object({
|
|
272
284
|
requireIncidentReport: z.boolean().default(true),
|
|
273
285
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -375,10 +387,12 @@ var init_config = __esm({
|
|
|
375
387
|
var memory_db_exports = {};
|
|
376
388
|
__export(memory_db_exports, {
|
|
377
389
|
addConversationTurn: () => addConversationTurn,
|
|
390
|
+
addFailureClass: () => addFailureClass,
|
|
378
391
|
addObservation: () => addObservation,
|
|
379
392
|
addSummary: () => addSummary,
|
|
380
393
|
addToolCallDetail: () => addToolCallDetail,
|
|
381
394
|
addUserPrompt: () => addUserPrompt,
|
|
395
|
+
appendIncidentToFailureClass: () => appendIncidentToFailureClass,
|
|
382
396
|
assignImportance: () => assignImportance,
|
|
383
397
|
autoDetectTaskId: () => autoDetectTaskId,
|
|
384
398
|
createSession: () => createSession,
|
|
@@ -390,6 +404,7 @@ __export(memory_db_exports, {
|
|
|
390
404
|
getCrossTaskProgress: () => getCrossTaskProgress,
|
|
391
405
|
getDecisionsAbout: () => getDecisionsAbout,
|
|
392
406
|
getFailedAttempts: () => getFailedAttempts,
|
|
407
|
+
getFailureClasses: () => getFailureClasses,
|
|
393
408
|
getLastProcessedLine: () => getLastProcessedLine,
|
|
394
409
|
getMemoryDb: () => getMemoryDb,
|
|
395
410
|
getObservabilityDbSize: () => getObservabilityDbSize,
|
|
@@ -406,6 +421,7 @@ __export(memory_db_exports, {
|
|
|
406
421
|
pruneOldObservations: () => pruneOldObservations,
|
|
407
422
|
removePendingSync: () => removePendingSync,
|
|
408
423
|
sanitizeFts5Query: () => sanitizeFts5Query,
|
|
424
|
+
scoreFailureClasses: () => scoreFailureClasses,
|
|
409
425
|
searchConversationTurns: () => searchConversationTurns,
|
|
410
426
|
searchObservations: () => searchObservations,
|
|
411
427
|
setLastProcessedLine: () => setLastProcessedLine
|
|
@@ -905,6 +921,25 @@ function initMemorySchema(db) {
|
|
|
905
921
|
features TEXT DEFAULT '[]'
|
|
906
922
|
);
|
|
907
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
|
+
`);
|
|
908
943
|
}
|
|
909
944
|
function enqueueSyncPayload(db, payload) {
|
|
910
945
|
db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
|
|
@@ -1338,6 +1373,108 @@ function getObservabilityDbSize(db) {
|
|
|
1338
1373
|
estimated_size_mb: Math.round(pageCount * pageSize / (1024 * 1024) * 100) / 100
|
|
1339
1374
|
};
|
|
1340
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
|
+
}
|
|
1341
1478
|
var init_memory_db = __esm({
|
|
1342
1479
|
"src/memory-db.ts"() {
|
|
1343
1480
|
"use strict";
|
|
@@ -1835,14 +1972,24 @@ function buildHooksConfig(hooksDir) {
|
|
|
1835
1972
|
{
|
|
1836
1973
|
matcher: "Edit|Write",
|
|
1837
1974
|
hooks: [
|
|
1838
|
-
{ 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 }
|
|
1839
1985
|
]
|
|
1840
1986
|
}
|
|
1841
1987
|
],
|
|
1842
1988
|
Stop: [
|
|
1843
1989
|
{
|
|
1844
1990
|
hooks: [
|
|
1845
|
-
{ 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 }
|
|
1846
1993
|
]
|
|
1847
1994
|
}
|
|
1848
1995
|
],
|
|
@@ -2222,7 +2369,7 @@ var init_license = __esm({
|
|
|
2222
2369
|
enterprise: 3
|
|
2223
2370
|
};
|
|
2224
2371
|
TOOL_TIER_MAP = {
|
|
2225
|
-
// --- Free tier (
|
|
2372
|
+
// --- Free tier (13 tools: core navigation + basic memory + regression + license) ---
|
|
2226
2373
|
sync: "free",
|
|
2227
2374
|
context: "free",
|
|
2228
2375
|
impact: "free",
|
|
@@ -2232,6 +2379,7 @@ var init_license = __esm({
|
|
|
2232
2379
|
coupling_check: "free",
|
|
2233
2380
|
memory_search: "free",
|
|
2234
2381
|
memory_ingest: "free",
|
|
2382
|
+
memory_backfill: "free",
|
|
2235
2383
|
regression_risk: "free",
|
|
2236
2384
|
feature_health: "free",
|
|
2237
2385
|
license_status: "free",
|
|
@@ -157,6 +157,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
157
157
|
"added_missing_import"
|
|
158
158
|
])
|
|
159
159
|
}).default({}),
|
|
160
|
+
failureClassification: z.object({
|
|
161
|
+
enabled: z.boolean().default(true),
|
|
162
|
+
thresholds: z.object({
|
|
163
|
+
known: z.number().default(5),
|
|
164
|
+
similar: z.number().default(3)
|
|
165
|
+
}).default({}),
|
|
166
|
+
scoring: z.object({
|
|
167
|
+
diffPatternWeight: z.number().default(3),
|
|
168
|
+
filePatternWeight: z.number().default(2),
|
|
169
|
+
promptKeywordWeight: z.number().default(2)
|
|
170
|
+
}).default({})
|
|
171
|
+
}).default({}),
|
|
160
172
|
pipeline: z.object({
|
|
161
173
|
requireIncidentReport: z.boolean().default(true),
|
|
162
174
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -362,8 +374,8 @@ async function main() {
|
|
|
362
374
|
const diff = execSync("git diff --name-only", { cwd: root, timeout: 3e3, encoding: "utf-8" });
|
|
363
375
|
if (diff.trim()) {
|
|
364
376
|
const fullDiff = execSync("git diff", { cwd: root, timeout: 5e3, encoding: "utf-8" });
|
|
365
|
-
const fixPatterns = (fullDiff.match(/^\+.*(try|except|catch|guard
|
|
366
|
-
const removedBroken = (fullDiff.match(/^-.*(bug|broken|crash
|
|
377
|
+
const fixPatterns = (fullDiff.match(/^\+.*(try|except|catch|guard|throw|raise|assert|validate|if.*null|if.*nil|if.*None|if.*undefined)/gm) || []).length;
|
|
378
|
+
const removedBroken = (fullDiff.match(/^-.*(bug|broken|crash|wrong|incorrect|typo|fail|error|miss|stale)/gm) || []).length;
|
|
367
379
|
if (fixPatterns > 3 || removedBroken > 1) {
|
|
368
380
|
uncommittedFix = true;
|
|
369
381
|
}
|