@massu/core 0.7.0 → 0.8.1
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 +163 -19
- 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 +40 -4
- 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 +19 -16
- 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 -2
- package/src/memory-db.ts +201 -0
- package/src/memory-file-ingest.ts +13 -6
|
@@ -149,6 +149,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
149
149
|
"added_missing_import"
|
|
150
150
|
])
|
|
151
151
|
}).default({}),
|
|
152
|
+
failureClassification: z.object({
|
|
153
|
+
enabled: z.boolean().default(true),
|
|
154
|
+
thresholds: z.object({
|
|
155
|
+
known: z.number().default(5),
|
|
156
|
+
similar: z.number().default(3)
|
|
157
|
+
}).default({}),
|
|
158
|
+
scoring: z.object({
|
|
159
|
+
diffPatternWeight: z.number().default(3),
|
|
160
|
+
filePatternWeight: z.number().default(2),
|
|
161
|
+
promptKeywordWeight: z.number().default(2)
|
|
162
|
+
}).default({})
|
|
163
|
+
}).default({}),
|
|
152
164
|
pipeline: z.object({
|
|
153
165
|
requireIncidentReport: z.boolean().default(true),
|
|
154
166
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -151,6 +151,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
151
151
|
"added_missing_import"
|
|
152
152
|
])
|
|
153
153
|
}).default({}),
|
|
154
|
+
failureClassification: z.object({
|
|
155
|
+
enabled: z.boolean().default(true),
|
|
156
|
+
thresholds: z.object({
|
|
157
|
+
known: z.number().default(5),
|
|
158
|
+
similar: z.number().default(3)
|
|
159
|
+
}).default({}),
|
|
160
|
+
scoring: z.object({
|
|
161
|
+
diffPatternWeight: z.number().default(3),
|
|
162
|
+
filePatternWeight: z.number().default(2),
|
|
163
|
+
promptKeywordWeight: z.number().default(2)
|
|
164
|
+
}).default({})
|
|
165
|
+
}).default({}),
|
|
154
166
|
pipeline: z.object({
|
|
155
167
|
requireIncidentReport: z.boolean().default(true),
|
|
156
168
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -848,6 +860,25 @@ function initMemorySchema(db) {
|
|
|
848
860
|
features TEXT DEFAULT '[]'
|
|
849
861
|
);
|
|
850
862
|
`);
|
|
863
|
+
db.exec(`
|
|
864
|
+
CREATE TABLE IF NOT EXISTS failure_classes (
|
|
865
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
866
|
+
name TEXT NOT NULL UNIQUE,
|
|
867
|
+
description TEXT NOT NULL,
|
|
868
|
+
diff_patterns TEXT NOT NULL DEFAULT '[]',
|
|
869
|
+
file_patterns TEXT NOT NULL DEFAULT '[]',
|
|
870
|
+
prompt_keywords TEXT NOT NULL DEFAULT '[]',
|
|
871
|
+
incidents TEXT NOT NULL DEFAULT '[]',
|
|
872
|
+
rules TEXT NOT NULL DEFAULT '[]',
|
|
873
|
+
scanner_checks TEXT NOT NULL DEFAULT '[]',
|
|
874
|
+
known_message TEXT NOT NULL DEFAULT '',
|
|
875
|
+
needs_review INTEGER NOT NULL DEFAULT 0,
|
|
876
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
877
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
878
|
+
);
|
|
879
|
+
CREATE INDEX IF NOT EXISTS idx_fc_name ON failure_classes(name);
|
|
880
|
+
CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
|
|
881
|
+
`);
|
|
851
882
|
}
|
|
852
883
|
function assignImportance(type, vrResult) {
|
|
853
884
|
switch (type) {
|
|
@@ -1635,11 +1666,10 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
|
|
|
1635
1666
|
// src/hooks/post-tool-use.ts
|
|
1636
1667
|
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
1637
1668
|
import { join as join2 } from "path";
|
|
1638
|
-
import { parse as
|
|
1669
|
+
import { parse as parseYaml2 } from "yaml";
|
|
1639
1670
|
|
|
1640
1671
|
// src/memory-file-ingest.ts
|
|
1641
1672
|
import { readFileSync as readFileSync5, existsSync as existsSync6, readdirSync } from "fs";
|
|
1642
|
-
import { parse as parseYaml2 } from "yaml";
|
|
1643
1673
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
1644
1674
|
if (!existsSync6(filePath)) return "skipped";
|
|
1645
1675
|
const content = readFileSync5(filePath, "utf-8");
|
|
@@ -1651,7 +1681,13 @@ function ingestMemoryFile(db, sessionId, filePath) {
|
|
|
1651
1681
|
let confidence;
|
|
1652
1682
|
if (frontmatterMatch) {
|
|
1653
1683
|
try {
|
|
1654
|
-
const fm =
|
|
1684
|
+
const fm = {};
|
|
1685
|
+
for (const line of frontmatterMatch[1].split("\n")) {
|
|
1686
|
+
const sep = line.indexOf(":");
|
|
1687
|
+
if (sep > 0) {
|
|
1688
|
+
fm[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1655
1691
|
name = fm.name ?? basename2;
|
|
1656
1692
|
description = fm.description ?? "";
|
|
1657
1693
|
type = fm.type ?? "discovery";
|
|
@@ -1866,7 +1902,7 @@ function readConventions(cwd) {
|
|
|
1866
1902
|
const configPath = join2(projectRoot, "massu.config.yaml");
|
|
1867
1903
|
if (!existsSync7(configPath)) return defaults;
|
|
1868
1904
|
const content = readFileSync6(configPath, "utf-8");
|
|
1869
|
-
const parsed =
|
|
1905
|
+
const parsed = parseYaml2(content);
|
|
1870
1906
|
if (!parsed || typeof parsed !== "object") return defaults;
|
|
1871
1907
|
const conventions = parsed.conventions;
|
|
1872
1908
|
if (!conventions || typeof conventions !== "object") return defaults;
|
|
@@ -151,6 +151,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
151
151
|
"added_missing_import"
|
|
152
152
|
])
|
|
153
153
|
}).default({}),
|
|
154
|
+
failureClassification: z.object({
|
|
155
|
+
enabled: z.boolean().default(true),
|
|
156
|
+
thresholds: z.object({
|
|
157
|
+
known: z.number().default(5),
|
|
158
|
+
similar: z.number().default(3)
|
|
159
|
+
}).default({}),
|
|
160
|
+
scoring: z.object({
|
|
161
|
+
diffPatternWeight: z.number().default(3),
|
|
162
|
+
filePatternWeight: z.number().default(2),
|
|
163
|
+
promptKeywordWeight: z.number().default(2)
|
|
164
|
+
}).default({})
|
|
165
|
+
}).default({}),
|
|
154
166
|
pipeline: z.object({
|
|
155
167
|
requireIncidentReport: z.boolean().default(true),
|
|
156
168
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -848,6 +860,25 @@ function initMemorySchema(db) {
|
|
|
848
860
|
features TEXT DEFAULT '[]'
|
|
849
861
|
);
|
|
850
862
|
`);
|
|
863
|
+
db.exec(`
|
|
864
|
+
CREATE TABLE IF NOT EXISTS failure_classes (
|
|
865
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
866
|
+
name TEXT NOT NULL UNIQUE,
|
|
867
|
+
description TEXT NOT NULL,
|
|
868
|
+
diff_patterns TEXT NOT NULL DEFAULT '[]',
|
|
869
|
+
file_patterns TEXT NOT NULL DEFAULT '[]',
|
|
870
|
+
prompt_keywords TEXT NOT NULL DEFAULT '[]',
|
|
871
|
+
incidents TEXT NOT NULL DEFAULT '[]',
|
|
872
|
+
rules TEXT NOT NULL DEFAULT '[]',
|
|
873
|
+
scanner_checks TEXT NOT NULL DEFAULT '[]',
|
|
874
|
+
known_message TEXT NOT NULL DEFAULT '',
|
|
875
|
+
needs_review INTEGER NOT NULL DEFAULT 0,
|
|
876
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
877
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
878
|
+
);
|
|
879
|
+
CREATE INDEX IF NOT EXISTS idx_fc_name ON failure_classes(name);
|
|
880
|
+
CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
|
|
881
|
+
`);
|
|
851
882
|
}
|
|
852
883
|
function assignImportance(type, vrResult) {
|
|
853
884
|
switch (type) {
|
|
@@ -150,6 +150,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
150
150
|
"added_missing_import"
|
|
151
151
|
])
|
|
152
152
|
}).default({}),
|
|
153
|
+
failureClassification: z.object({
|
|
154
|
+
enabled: z.boolean().default(true),
|
|
155
|
+
thresholds: z.object({
|
|
156
|
+
known: z.number().default(5),
|
|
157
|
+
similar: z.number().default(3)
|
|
158
|
+
}).default({}),
|
|
159
|
+
scoring: z.object({
|
|
160
|
+
diffPatternWeight: z.number().default(3),
|
|
161
|
+
filePatternWeight: z.number().default(2),
|
|
162
|
+
promptKeywordWeight: z.number().default(2)
|
|
163
|
+
}).default({})
|
|
164
|
+
}).default({}),
|
|
153
165
|
pipeline: z.object({
|
|
154
166
|
requireIncidentReport: z.boolean().default(true),
|
|
155
167
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -151,6 +151,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
151
151
|
"added_missing_import"
|
|
152
152
|
])
|
|
153
153
|
}).default({}),
|
|
154
|
+
failureClassification: z.object({
|
|
155
|
+
enabled: z.boolean().default(true),
|
|
156
|
+
thresholds: z.object({
|
|
157
|
+
known: z.number().default(5),
|
|
158
|
+
similar: z.number().default(3)
|
|
159
|
+
}).default({}),
|
|
160
|
+
scoring: z.object({
|
|
161
|
+
diffPatternWeight: z.number().default(3),
|
|
162
|
+
filePatternWeight: z.number().default(2),
|
|
163
|
+
promptKeywordWeight: z.number().default(2)
|
|
164
|
+
}).default({})
|
|
165
|
+
}).default({}),
|
|
154
166
|
pipeline: z.object({
|
|
155
167
|
requireIncidentReport: z.boolean().default(true),
|
|
156
168
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -848,6 +860,25 @@ function initMemorySchema(db) {
|
|
|
848
860
|
features TEXT DEFAULT '[]'
|
|
849
861
|
);
|
|
850
862
|
`);
|
|
863
|
+
db.exec(`
|
|
864
|
+
CREATE TABLE IF NOT EXISTS failure_classes (
|
|
865
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
866
|
+
name TEXT NOT NULL UNIQUE,
|
|
867
|
+
description TEXT NOT NULL,
|
|
868
|
+
diff_patterns TEXT NOT NULL DEFAULT '[]',
|
|
869
|
+
file_patterns TEXT NOT NULL DEFAULT '[]',
|
|
870
|
+
prompt_keywords TEXT NOT NULL DEFAULT '[]',
|
|
871
|
+
incidents TEXT NOT NULL DEFAULT '[]',
|
|
872
|
+
rules TEXT NOT NULL DEFAULT '[]',
|
|
873
|
+
scanner_checks TEXT NOT NULL DEFAULT '[]',
|
|
874
|
+
known_message TEXT NOT NULL DEFAULT '',
|
|
875
|
+
needs_review INTEGER NOT NULL DEFAULT 0,
|
|
876
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
877
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
878
|
+
);
|
|
879
|
+
CREATE INDEX IF NOT EXISTS idx_fc_name ON failure_classes(name);
|
|
880
|
+
CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
|
|
881
|
+
`);
|
|
851
882
|
}
|
|
852
883
|
|
|
853
884
|
// src/hooks/quality-event.ts
|
|
@@ -149,6 +149,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
149
149
|
"added_missing_import"
|
|
150
150
|
])
|
|
151
151
|
}).default({}),
|
|
152
|
+
failureClassification: z.object({
|
|
153
|
+
enabled: z.boolean().default(true),
|
|
154
|
+
thresholds: z.object({
|
|
155
|
+
known: z.number().default(5),
|
|
156
|
+
similar: z.number().default(3)
|
|
157
|
+
}).default({}),
|
|
158
|
+
scoring: z.object({
|
|
159
|
+
diffPatternWeight: z.number().default(3),
|
|
160
|
+
filePatternWeight: z.number().default(2),
|
|
161
|
+
promptKeywordWeight: z.number().default(2)
|
|
162
|
+
}).default({})
|
|
163
|
+
}).default({}),
|
|
152
164
|
pipeline: z.object({
|
|
153
165
|
requireIncidentReport: z.boolean().default(true),
|
|
154
166
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -348,7 +360,8 @@ async function main() {
|
|
|
348
360
|
process.exit(0);
|
|
349
361
|
return;
|
|
350
362
|
}
|
|
351
|
-
|
|
363
|
+
const claudeDir = config.conventions?.claudeDirName ?? ".claude";
|
|
364
|
+
if (!relPath.includes(memoryDir) && !relPath.includes("memory/") && !relPath.includes(claudeDir + "/")) {
|
|
352
365
|
process.exit(0);
|
|
353
366
|
return;
|
|
354
367
|
}
|
|
@@ -151,6 +151,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
151
151
|
"added_missing_import"
|
|
152
152
|
])
|
|
153
153
|
}).default({}),
|
|
154
|
+
failureClassification: z.object({
|
|
155
|
+
enabled: z.boolean().default(true),
|
|
156
|
+
thresholds: z.object({
|
|
157
|
+
known: z.number().default(5),
|
|
158
|
+
similar: z.number().default(3)
|
|
159
|
+
}).default({}),
|
|
160
|
+
scoring: z.object({
|
|
161
|
+
diffPatternWeight: z.number().default(3),
|
|
162
|
+
filePatternWeight: z.number().default(2),
|
|
163
|
+
promptKeywordWeight: z.number().default(2)
|
|
164
|
+
}).default({})
|
|
165
|
+
}).default({}),
|
|
154
166
|
pipeline: z.object({
|
|
155
167
|
requireIncidentReport: z.boolean().default(true),
|
|
156
168
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -848,6 +860,25 @@ function initMemorySchema(db) {
|
|
|
848
860
|
features TEXT DEFAULT '[]'
|
|
849
861
|
);
|
|
850
862
|
`);
|
|
863
|
+
db.exec(`
|
|
864
|
+
CREATE TABLE IF NOT EXISTS failure_classes (
|
|
865
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
866
|
+
name TEXT NOT NULL UNIQUE,
|
|
867
|
+
description TEXT NOT NULL,
|
|
868
|
+
diff_patterns TEXT NOT NULL DEFAULT '[]',
|
|
869
|
+
file_patterns TEXT NOT NULL DEFAULT '[]',
|
|
870
|
+
prompt_keywords TEXT NOT NULL DEFAULT '[]',
|
|
871
|
+
incidents TEXT NOT NULL DEFAULT '[]',
|
|
872
|
+
rules TEXT NOT NULL DEFAULT '[]',
|
|
873
|
+
scanner_checks TEXT NOT NULL DEFAULT '[]',
|
|
874
|
+
known_message TEXT NOT NULL DEFAULT '',
|
|
875
|
+
needs_review INTEGER NOT NULL DEFAULT 0,
|
|
876
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
877
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
878
|
+
);
|
|
879
|
+
CREATE INDEX IF NOT EXISTS idx_fc_name ON failure_classes(name);
|
|
880
|
+
CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
|
|
881
|
+
`);
|
|
851
882
|
}
|
|
852
883
|
function enqueueSyncPayload(db, payload) {
|
|
853
884
|
db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
|
|
@@ -151,6 +151,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
151
151
|
"added_missing_import"
|
|
152
152
|
])
|
|
153
153
|
}).default({}),
|
|
154
|
+
failureClassification: z.object({
|
|
155
|
+
enabled: z.boolean().default(true),
|
|
156
|
+
thresholds: z.object({
|
|
157
|
+
known: z.number().default(5),
|
|
158
|
+
similar: z.number().default(3)
|
|
159
|
+
}).default({}),
|
|
160
|
+
scoring: z.object({
|
|
161
|
+
diffPatternWeight: z.number().default(3),
|
|
162
|
+
filePatternWeight: z.number().default(2),
|
|
163
|
+
promptKeywordWeight: z.number().default(2)
|
|
164
|
+
}).default({})
|
|
165
|
+
}).default({}),
|
|
154
166
|
pipeline: z.object({
|
|
155
167
|
requireIncidentReport: z.boolean().default(true),
|
|
156
168
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -854,6 +866,25 @@ function initMemorySchema(db) {
|
|
|
854
866
|
features TEXT DEFAULT '[]'
|
|
855
867
|
);
|
|
856
868
|
`);
|
|
869
|
+
db.exec(`
|
|
870
|
+
CREATE TABLE IF NOT EXISTS failure_classes (
|
|
871
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
872
|
+
name TEXT NOT NULL UNIQUE,
|
|
873
|
+
description TEXT NOT NULL,
|
|
874
|
+
diff_patterns TEXT NOT NULL DEFAULT '[]',
|
|
875
|
+
file_patterns TEXT NOT NULL DEFAULT '[]',
|
|
876
|
+
prompt_keywords TEXT NOT NULL DEFAULT '[]',
|
|
877
|
+
incidents TEXT NOT NULL DEFAULT '[]',
|
|
878
|
+
rules TEXT NOT NULL DEFAULT '[]',
|
|
879
|
+
scanner_checks TEXT NOT NULL DEFAULT '[]',
|
|
880
|
+
known_message TEXT NOT NULL DEFAULT '',
|
|
881
|
+
needs_review INTEGER NOT NULL DEFAULT 0,
|
|
882
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
883
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
884
|
+
);
|
|
885
|
+
CREATE INDEX IF NOT EXISTS idx_fc_name ON failure_classes(name);
|
|
886
|
+
CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
|
|
887
|
+
`);
|
|
857
888
|
}
|
|
858
889
|
function autoDetectTaskId(planFile) {
|
|
859
890
|
if (!planFile) return null;
|
|
@@ -955,7 +986,7 @@ async function main() {
|
|
|
955
986
|
process.stdout.write(
|
|
956
987
|
`=== MASSU AI: Active ===
|
|
957
988
|
Session memory, code intelligence, and governance are now active.
|
|
958
|
-
|
|
989
|
+
15 hooks monitoring this session. Type "${getConfig().toolPrefix ?? "massu"}_sync" to index your codebase.
|
|
959
990
|
=== END MASSU ===
|
|
960
991
|
|
|
961
992
|
`
|
|
@@ -151,6 +151,18 @@ var AutoLearningConfigSchema = z.object({
|
|
|
151
151
|
"added_missing_import"
|
|
152
152
|
])
|
|
153
153
|
}).default({}),
|
|
154
|
+
failureClassification: z.object({
|
|
155
|
+
enabled: z.boolean().default(true),
|
|
156
|
+
thresholds: z.object({
|
|
157
|
+
known: z.number().default(5),
|
|
158
|
+
similar: z.number().default(3)
|
|
159
|
+
}).default({}),
|
|
160
|
+
scoring: z.object({
|
|
161
|
+
diffPatternWeight: z.number().default(3),
|
|
162
|
+
filePatternWeight: z.number().default(2),
|
|
163
|
+
promptKeywordWeight: z.number().default(2)
|
|
164
|
+
}).default({})
|
|
165
|
+
}).default({}),
|
|
154
166
|
pipeline: z.object({
|
|
155
167
|
requireIncidentReport: z.boolean().default(true),
|
|
156
168
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -848,6 +860,25 @@ function initMemorySchema(db) {
|
|
|
848
860
|
features TEXT DEFAULT '[]'
|
|
849
861
|
);
|
|
850
862
|
`);
|
|
863
|
+
db.exec(`
|
|
864
|
+
CREATE TABLE IF NOT EXISTS failure_classes (
|
|
865
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
866
|
+
name TEXT NOT NULL UNIQUE,
|
|
867
|
+
description TEXT NOT NULL,
|
|
868
|
+
diff_patterns TEXT NOT NULL DEFAULT '[]',
|
|
869
|
+
file_patterns TEXT NOT NULL DEFAULT '[]',
|
|
870
|
+
prompt_keywords TEXT NOT NULL DEFAULT '[]',
|
|
871
|
+
incidents TEXT NOT NULL DEFAULT '[]',
|
|
872
|
+
rules TEXT NOT NULL DEFAULT '[]',
|
|
873
|
+
scanner_checks TEXT NOT NULL DEFAULT '[]',
|
|
874
|
+
known_message TEXT NOT NULL DEFAULT '',
|
|
875
|
+
needs_review INTEGER NOT NULL DEFAULT 0,
|
|
876
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
877
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
878
|
+
);
|
|
879
|
+
CREATE INDEX IF NOT EXISTS idx_fc_name ON failure_classes(name);
|
|
880
|
+
CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
|
|
881
|
+
`);
|
|
851
882
|
}
|
|
852
883
|
function assignImportance(type, vrResult) {
|
|
853
884
|
switch (type) {
|
|
@@ -921,7 +952,9 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
921
952
|
}
|
|
922
953
|
|
|
923
954
|
// src/hooks/user-prompt.ts
|
|
924
|
-
import { existsSync as existsSync3 } from "fs";
|
|
955
|
+
import { existsSync as existsSync3, writeFileSync } from "fs";
|
|
956
|
+
import { tmpdir } from "os";
|
|
957
|
+
import { join } from "path";
|
|
925
958
|
async function main() {
|
|
926
959
|
try {
|
|
927
960
|
const input = await readStdin();
|
|
@@ -994,6 +1027,35 @@ async function main() {
|
|
|
994
1027
|
}
|
|
995
1028
|
} catch (_memoryNagErr) {
|
|
996
1029
|
}
|
|
1030
|
+
try {
|
|
1031
|
+
const failureKeywords = [
|
|
1032
|
+
"bug",
|
|
1033
|
+
"broken",
|
|
1034
|
+
"crash",
|
|
1035
|
+
"error",
|
|
1036
|
+
"fail",
|
|
1037
|
+
"fix",
|
|
1038
|
+
"wrong",
|
|
1039
|
+
"missing",
|
|
1040
|
+
"undefined",
|
|
1041
|
+
"null",
|
|
1042
|
+
"exception",
|
|
1043
|
+
"stack trace",
|
|
1044
|
+
"regression",
|
|
1045
|
+
"revert",
|
|
1046
|
+
"doesn't work",
|
|
1047
|
+
"not working",
|
|
1048
|
+
"stopped working",
|
|
1049
|
+
"broke"
|
|
1050
|
+
];
|
|
1051
|
+
const promptLower = prompt.toLowerCase();
|
|
1052
|
+
const matched = failureKeywords.filter((kw) => promptLower.includes(kw));
|
|
1053
|
+
if (matched.length > 0) {
|
|
1054
|
+
const contextFile = join(tmpdir(), `massu-failure-context-${session_id.slice(0, 8)}-${Date.now()}`);
|
|
1055
|
+
writeFileSync(contextFile, matched.join(" "), "utf-8");
|
|
1056
|
+
}
|
|
1057
|
+
} catch {
|
|
1058
|
+
}
|
|
997
1059
|
} finally {
|
|
998
1060
|
db.close();
|
|
999
1061
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, auto-learning pipeline, tiered tooling (12 free / 72 total), 55+ workflow commands, 15 agents, 20+ patterns",
|
|
6
6
|
"main": "src/server.ts",
|
|
@@ -52,9 +52,9 @@ Security blocking -> advisory warnings -> matcher-specific -> observability.
|
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
55
|
-
## PostToolUse (
|
|
55
|
+
## PostToolUse (14 hooks)
|
|
56
56
|
|
|
57
|
-
Security scan -> immediate feedback -> context tracking -> incident capture -> memory sync -> observability.
|
|
57
|
+
Security scan -> immediate feedback -> context tracking -> fix detection -> incident capture -> pipeline triggers -> memory sync -> observability.
|
|
58
58
|
|
|
59
59
|
| position: 1 | CI monitor | standard | Bash(git push) -- immediate push feedback |
|
|
60
60
|
|---|---|---|---|
|
|
@@ -62,17 +62,23 @@ Security scan -> immediate feedback -> context tracking -> incident capture -> m
|
|
|
62
62
|
| position: 3 | `pattern-feedback.sh` | standard | Edit\|Write -- immediate pattern violation feedback |
|
|
63
63
|
| position: 4 | `post-edit-context.js` | strict | Edit\|Write -- detailed semantic analysis |
|
|
64
64
|
| position: 5 | `post-tool-use.js` | standard | Edit\|Write\|Bash -- structured context tracking |
|
|
65
|
-
| position: 6 | `
|
|
66
|
-
| position: 7 | `
|
|
67
|
-
| position: 8 | `
|
|
68
|
-
| position: 9 | `
|
|
69
|
-
| position: 10 | `
|
|
70
|
-
| position: 11 | `
|
|
65
|
+
| position: 6 | `fix-detector.js` | standard | Edit\|Write -- detect bug fixes via git diff heuristics |
|
|
66
|
+
| position: 7 | `auto-ingest-incident.sh` | strict | Edit\|Write -- auto-capture incident patterns |
|
|
67
|
+
| position: 8 | `incident-pipeline.js` | standard | Write -- trigger rule derivation on incident report writes |
|
|
68
|
+
| position: 9 | `rule-enforcement-pipeline.js` | standard | Write -- trigger enforcement on prevention rule writes |
|
|
69
|
+
| position: 10 | `memory-auto-ingest.sh` | standard | Write -- auto-sync memory files to codegraph SQLite DB |
|
|
70
|
+
| position: 11 | `validate-deliverables.sh` | strict | Bash\|Edit\|Write -- deliverable validation |
|
|
71
|
+
| position: 12 | `pattern-scanner.sh --single-file` | strict | Edit\|Write -- per-file pattern scan |
|
|
72
|
+
| position: 13 | `mcp-usage-tracker.sh` | strict | MCP tools -- append-only MCP audit log |
|
|
73
|
+
| position: 14 | `compaction-advisor.sh` | standard | Bash\|Edit\|Write\|Read\|Grep\|Glob -- context tracking, widest matcher |
|
|
71
74
|
|
|
72
75
|
**Dependencies**:
|
|
73
76
|
- `output-secret-filter.sh` MUST run before any feedback hooks -- security first
|
|
74
77
|
- `pattern-feedback.sh` before `post-tool-use.js` -- immediate feedback before tracking
|
|
75
|
-
- `
|
|
78
|
+
- `fix-detector.js` after `post-tool-use.js` -- needs structured tracking context
|
|
79
|
+
- `incident-pipeline.js` after `auto-ingest-incident.sh` -- incident must be captured first
|
|
80
|
+
- `rule-enforcement-pipeline.js` after `incident-pipeline.js` -- rule derivation before enforcement
|
|
81
|
+
- `memory-auto-ingest.sh` runs after pipeline hooks -- memory sync is data-writing, before validation
|
|
76
82
|
- `compaction-advisor.sh` MUST be last -- widest matcher, just counts tool calls
|
|
77
83
|
|
|
78
84
|
---
|
|
@@ -105,21 +111,23 @@ Quick state capture -> full DB snapshot.
|
|
|
105
111
|
|
|
106
112
|
---
|
|
107
113
|
|
|
108
|
-
## Stop (
|
|
114
|
+
## Stop (8 hooks)
|
|
109
115
|
|
|
110
|
-
Session summary -> warnings -> memory extraction -> review -> validation.
|
|
116
|
+
Session summary -> auto-learning check -> warnings -> memory extraction -> review -> validation.
|
|
111
117
|
|
|
112
118
|
| position: 1 | `session-end.js` | standard | Write session summary to memory DB |
|
|
113
119
|
|---|---|---|---|
|
|
114
|
-
| position: 2 |
|
|
115
|
-
| position: 3 |
|
|
116
|
-
| position: 4 | `auto-
|
|
117
|
-
| position: 5 | `
|
|
118
|
-
| position: 6 | `
|
|
119
|
-
| position: 7 | `
|
|
120
|
+
| position: 2 | `auto-learning-pipeline.js` | standard | Enforce fix→incident→rule→enforcement pipeline completion |
|
|
121
|
+
| position: 3 | Uncommitted changes warning | standard (inline) | Alert user about unstaged work |
|
|
122
|
+
| position: 4 | `memory-auto-extract.sh` | standard | Auto-extract memories from DB observations |
|
|
123
|
+
| position: 5 | `auto-review-on-stop.sh` | strict | Automated code review of session changes |
|
|
124
|
+
| position: 6 | `surface-review-findings.sh` | strict | Display review findings to user |
|
|
125
|
+
| position: 7 | `validate-deliverables.sh` | strict | Final deliverable validation |
|
|
126
|
+
| position: 8 | `pattern-extractor.sh` | advisory | Extract new patterns from session |
|
|
120
127
|
|
|
121
128
|
**Dependencies**:
|
|
122
129
|
- `session-end.js` MUST be position 1 -- writes DB data that `memory-auto-extract.sh` reads
|
|
130
|
+
- `auto-learning-pipeline.js` MUST run early -- needs to output mandatory instructions before session ends
|
|
123
131
|
- `memory-auto-extract.sh` MUST come after `session-end.js` -- depends on DB observations
|
|
124
132
|
- `surface-review-findings.sh` MUST come after `auto-review-on-stop.sh` -- displays its output
|
|
125
133
|
- `pattern-extractor.sh` runs last -- advisory tier (skipped in minimal/standard profiles)
|
package/src/commands/doctor.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 1. massu.config.yaml exists and parses correctly
|
|
9
9
|
* 2. .mcp.json has massu entry
|
|
10
10
|
* 3. .claude/settings.local.json has hooks config
|
|
11
|
-
* 4. All
|
|
11
|
+
* 4. All 15 compiled hook files exist
|
|
12
12
|
* 5. Knowledge DB exists (.massu/memory.db)
|
|
13
13
|
* 6. Memory directory exists (~/.claude/projects/.../memory/)
|
|
14
14
|
* 7. Shell hooks wired in settings.local.json
|
package/src/commands/init.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 1. Detects project framework (scans package.json)
|
|
8
8
|
* 2. Generates massu.config.yaml (or preserves existing)
|
|
9
9
|
* 3. Registers MCP server in .mcp.json (creates or merges)
|
|
10
|
-
* 4. Installs all
|
|
10
|
+
* 4. Installs all 15 hooks in .claude/settings.local.json
|
|
11
11
|
* 5. Installs slash commands into .claude/commands/
|
|
12
12
|
* 6. Initializes memory directory
|
|
13
13
|
* 7. Prints success summary
|
|
@@ -346,25 +346,18 @@ type HooksConfig = Record<string, HookGroup[]>;
|
|
|
346
346
|
* Handles both local development and npm-installed scenarios.
|
|
347
347
|
*/
|
|
348
348
|
export function resolveHooksDir(): string {
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (existsSync(nodeModulesPath)) {
|
|
353
|
-
return 'node_modules/@massu/core/dist/hooks';
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Fall back to finding relative to this source file
|
|
357
|
-
const localPath = resolve(__dirname, '../dist/hooks');
|
|
358
|
-
if (existsSync(localPath)) {
|
|
359
|
-
return localPath;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Default to node_modules path (will be created on npm install)
|
|
349
|
+
// Always use node_modules/@massu/core/dist/hooks relative to project root.
|
|
350
|
+
// hookCmd() wraps each command with a parent-directory walk to find the
|
|
351
|
+
// project root, so hooks resolve correctly even from subdirectories.
|
|
363
352
|
return 'node_modules/@massu/core/dist/hooks';
|
|
364
353
|
}
|
|
365
354
|
|
|
366
355
|
function hookCmd(hooksDir: string, hookFile: string): string {
|
|
367
|
-
|
|
356
|
+
// Walk up from cwd to find the directory containing node_modules/@massu/core,
|
|
357
|
+
// then cd there before running the hook. This handles subdirectories like
|
|
358
|
+
// website/, packages/foo/, etc. where node_modules doesn't exist.
|
|
359
|
+
const hookPath = `${hooksDir}/${hookFile}`;
|
|
360
|
+
return `d="$PWD"; while [ "$d" != "/" ] && [ ! -f "$d/${hookPath}" ]; do d="$(dirname "$d")"; done; cd "$d" && node ${hookPath}`;
|
|
368
361
|
}
|
|
369
362
|
|
|
370
363
|
export function buildHooksConfig(hooksDir: string): HooksConfig {
|
|
@@ -402,6 +395,15 @@ export function buildHooksConfig(hooksDir: string): HooksConfig {
|
|
|
402
395
|
matcher: 'Edit|Write',
|
|
403
396
|
hooks: [
|
|
404
397
|
{ type: 'command', command: hookCmd(hooksDir, 'post-edit-context.js'), timeout: 5 },
|
|
398
|
+
{ type: 'command', command: hookCmd(hooksDir, 'fix-detector.js'), timeout: 5 },
|
|
399
|
+
{ type: 'command', command: hookCmd(hooksDir, 'classify-failure.js'), timeout: 5 },
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
matcher: 'Write',
|
|
404
|
+
hooks: [
|
|
405
|
+
{ type: 'command', command: hookCmd(hooksDir, 'incident-pipeline.js'), timeout: 5 },
|
|
406
|
+
{ type: 'command', command: hookCmd(hooksDir, 'rule-enforcement-pipeline.js'), timeout: 5 },
|
|
405
407
|
],
|
|
406
408
|
},
|
|
407
409
|
],
|
|
@@ -409,6 +411,7 @@ export function buildHooksConfig(hooksDir: string): HooksConfig {
|
|
|
409
411
|
{
|
|
410
412
|
hooks: [
|
|
411
413
|
{ type: 'command', command: hookCmd(hooksDir, 'session-end.js'), timeout: 15 },
|
|
414
|
+
{ type: 'command', command: hookCmd(hooksDir, 'auto-learning-pipeline.js'), timeout: 10 },
|
|
412
415
|
],
|
|
413
416
|
},
|
|
414
417
|
],
|
package/src/config.ts
CHANGED
|
@@ -168,6 +168,18 @@ const AutoLearningConfigSchema = z.object({
|
|
|
168
168
|
'added_missing_import',
|
|
169
169
|
]),
|
|
170
170
|
}).default({}),
|
|
171
|
+
failureClassification: z.object({
|
|
172
|
+
enabled: z.boolean().default(true),
|
|
173
|
+
thresholds: z.object({
|
|
174
|
+
known: z.number().default(5),
|
|
175
|
+
similar: z.number().default(3),
|
|
176
|
+
}).default({}),
|
|
177
|
+
scoring: z.object({
|
|
178
|
+
diffPatternWeight: z.number().default(3),
|
|
179
|
+
filePatternWeight: z.number().default(2),
|
|
180
|
+
promptKeywordWeight: z.number().default(2),
|
|
181
|
+
}).default({}),
|
|
182
|
+
}).default({}),
|
|
171
183
|
pipeline: z.object({
|
|
172
184
|
requireIncidentReport: z.boolean().default(true),
|
|
173
185
|
requirePreventionRule: z.boolean().default(true),
|
|
@@ -67,14 +67,14 @@ async function main(): Promise<void> {
|
|
|
67
67
|
} catch { /* ignore parse errors */ }
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Source 2: Scan uncommitted git diff for fix patterns
|
|
70
|
+
// Source 2: Scan uncommitted git diff for fix patterns (language-agnostic)
|
|
71
71
|
let uncommittedFix = false;
|
|
72
72
|
try {
|
|
73
73
|
const diff = execSync('git diff --name-only', { cwd: root, timeout: 3000, encoding: 'utf-8' });
|
|
74
74
|
if (diff.trim()) {
|
|
75
75
|
const fullDiff = execSync('git diff', { cwd: root, timeout: 5000, encoding: 'utf-8' });
|
|
76
|
-
const fixPatterns = (fullDiff.match(/^\+.*(try|except|catch|guard
|
|
77
|
-
const removedBroken = (fullDiff.match(/^-.*(bug|broken|crash
|
|
76
|
+
const fixPatterns = (fullDiff.match(/^\+.*(try|except|catch|guard|throw|raise|assert|validate|if.*null|if.*nil|if.*None|if.*undefined)/gm) || []).length;
|
|
77
|
+
const removedBroken = (fullDiff.match(/^-.*(bug|broken|crash|wrong|incorrect|typo|fail|error|miss|stale)/gm) || []).length;
|
|
78
78
|
if (fixPatterns > 3 || removedBroken > 1) {
|
|
79
79
|
uncommittedFix = true;
|
|
80
80
|
}
|