@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.
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
3
+
4
+ // src/hooks/rule-enforcement-pipeline.ts
5
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
6
+ import { basename, resolve as resolve2 } from "path";
7
+
8
+ // src/config.ts
9
+ import { resolve, dirname } from "path";
10
+ import { existsSync, readFileSync } from "fs";
11
+ import { parse as parseYaml } from "yaml";
12
+ import { z } from "zod";
13
+ var DomainConfigSchema = z.object({
14
+ name: z.string().default("Unknown"),
15
+ routers: z.array(z.string()).default([]),
16
+ pages: z.array(z.string()).default([]),
17
+ tables: z.array(z.string()).default([]),
18
+ allowedImportsFrom: z.array(z.string()).default([])
19
+ });
20
+ var PatternRuleConfigSchema = z.object({
21
+ pattern: z.string().default("**"),
22
+ rules: z.array(z.string()).default([])
23
+ });
24
+ var CostModelSchema = z.object({
25
+ input_per_million: z.number(),
26
+ output_per_million: z.number(),
27
+ cache_read_per_million: z.number().optional(),
28
+ cache_write_per_million: z.number().optional()
29
+ });
30
+ var AnalyticsConfigSchema = z.object({
31
+ quality: z.object({
32
+ weights: z.record(z.string(), z.number()).default({
33
+ bug_found: -5,
34
+ vr_failure: -10,
35
+ incident: -20,
36
+ cr_violation: -3,
37
+ vr_pass: 2,
38
+ clean_commit: 5,
39
+ successful_verification: 3
40
+ }),
41
+ categories: z.array(z.string()).default(["security", "architecture", "coupling", "tests", "rule_compliance"])
42
+ }).optional(),
43
+ cost: z.object({
44
+ models: z.record(z.string(), CostModelSchema).default({}),
45
+ currency: z.string().default("USD")
46
+ }).optional(),
47
+ prompts: z.object({
48
+ success_indicators: z.array(z.string()).default(["committed", "approved", "looks good", "perfect", "great", "thanks"]),
49
+ failure_indicators: z.array(z.string()).default(["revert", "wrong", "that's not", "undo", "incorrect"]),
50
+ max_turns_for_success: z.number().default(2)
51
+ }).optional()
52
+ }).optional();
53
+ var CustomPatternSchema = z.object({
54
+ pattern: z.string(),
55
+ severity: z.string(),
56
+ message: z.string()
57
+ });
58
+ var GovernanceConfigSchema = z.object({
59
+ audit: z.object({
60
+ formats: z.array(z.string()).default(["summary", "detailed", "soc2"]),
61
+ retention_days: z.number().default(365),
62
+ auto_log: z.record(z.string(), z.boolean()).default({
63
+ code_changes: true,
64
+ rule_enforcement: true,
65
+ approvals: true,
66
+ commits: true
67
+ })
68
+ }).optional(),
69
+ validation: z.object({
70
+ realtime: z.boolean().default(true),
71
+ checks: z.record(z.string(), z.boolean()).default({
72
+ rule_compliance: true,
73
+ import_existence: true,
74
+ naming_conventions: true
75
+ }),
76
+ custom_patterns: z.array(CustomPatternSchema).default([])
77
+ }).optional(),
78
+ adr: z.object({
79
+ detection_phrases: z.array(z.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
80
+ template: z.string().default("default"),
81
+ storage: z.string().default("database"),
82
+ output_dir: z.string().default("docs/adr")
83
+ }).optional()
84
+ }).optional();
85
+ var SecurityPatternSchema = z.object({
86
+ pattern: z.string(),
87
+ severity: z.string(),
88
+ category: z.string(),
89
+ description: z.string()
90
+ });
91
+ var SecurityConfigSchema = z.object({
92
+ patterns: z.array(SecurityPatternSchema).default([]),
93
+ auto_score_on_edit: z.boolean().default(true),
94
+ score_threshold_alert: z.number().default(50),
95
+ severity_weights: z.record(z.string(), z.number()).optional(),
96
+ restrictive_licenses: z.array(z.string()).optional(),
97
+ dep_alternatives: z.record(z.string(), z.array(z.string())).optional(),
98
+ dependencies: z.object({
99
+ package_manager: z.string().default("npm"),
100
+ blocked_packages: z.array(z.string()).default([]),
101
+ preferred_packages: z.record(z.string(), z.string()).default({}),
102
+ max_bundle_size_kb: z.number().default(500)
103
+ }).optional()
104
+ }).optional();
105
+ var TeamConfigSchema = z.object({
106
+ enabled: z.boolean().default(false),
107
+ sync_backend: z.string().default("local"),
108
+ developer_id: z.string().default("auto"),
109
+ share_by_default: z.boolean().default(false),
110
+ expertise_weights: z.object({
111
+ session: z.number().default(20),
112
+ observation: z.number().default(10)
113
+ }).optional(),
114
+ privacy: z.object({
115
+ share_file_paths: z.boolean().default(true),
116
+ share_code_snippets: z.boolean().default(false),
117
+ share_observations: z.boolean().default(true)
118
+ }).optional()
119
+ }).optional();
120
+ var RegressionConfigSchema = z.object({
121
+ test_patterns: z.array(z.string()).default([
122
+ "{dir}/__tests__/{name}.test.{ext}",
123
+ "{dir}/{name}.spec.{ext}",
124
+ "tests/{path}.test.{ext}"
125
+ ]),
126
+ test_runner: z.string().default("npm test"),
127
+ health_thresholds: z.object({
128
+ healthy: z.number().default(80),
129
+ warning: z.number().default(50)
130
+ }).optional()
131
+ }).optional();
132
+ var AutoLearningConfigSchema = z.object({
133
+ enabled: z.boolean().default(true),
134
+ incidentDir: z.string().default("docs/incidents"),
135
+ memoryDir: z.string().default("memory"),
136
+ memoryIndexFile: z.string().default("MEMORY.md"),
137
+ enforcementHooksDir: z.string().default("scripts/hooks"),
138
+ fixDetection: z.object({
139
+ enabled: z.boolean().default(true),
140
+ lookbackDays: z.number().default(7),
141
+ signals: z.array(z.string()).default([
142
+ "removed_broken_code",
143
+ "added_error_handling",
144
+ "method_name_correction",
145
+ "auth_fix",
146
+ "nil_handling_fix",
147
+ "concurrency_fix",
148
+ "async_pattern_fix",
149
+ "added_missing_import"
150
+ ])
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({}),
164
+ pipeline: z.object({
165
+ requireIncidentReport: z.boolean().default(true),
166
+ requirePreventionRule: z.boolean().default(true),
167
+ requireEnforcement: z.boolean().default(true)
168
+ }).default({})
169
+ }).optional();
170
+ var CloudConfigSchema = z.object({
171
+ enabled: z.boolean().default(false),
172
+ apiKey: z.string().optional(),
173
+ endpoint: z.string().optional(),
174
+ sync: z.object({
175
+ memory: z.boolean().default(true),
176
+ analytics: z.boolean().default(true),
177
+ audit: z.boolean().default(true)
178
+ }).default({ memory: true, analytics: true, audit: true })
179
+ }).optional();
180
+ var ConventionsConfigSchema = z.object({
181
+ claudeDirName: z.string().default(".claude").refine(
182
+ (s) => !s.includes("..") && !s.startsWith("/"),
183
+ { message: 'claudeDirName must not contain ".." or start with "/"' }
184
+ ),
185
+ sessionStatePath: z.string().default(".claude/session-state/CURRENT.md").refine(
186
+ (s) => !s.includes("..") && !s.startsWith("/"),
187
+ { message: 'sessionStatePath must not contain ".." or start with "/"' }
188
+ ),
189
+ sessionArchivePath: z.string().default(".claude/session-state/archive").refine(
190
+ (s) => !s.includes("..") && !s.startsWith("/"),
191
+ { message: 'sessionArchivePath must not contain ".." or start with "/"' }
192
+ ),
193
+ knowledgeCategories: z.array(z.string()).default([
194
+ "patterns",
195
+ "commands",
196
+ "incidents",
197
+ "reference",
198
+ "protocols",
199
+ "checklists",
200
+ "playbooks",
201
+ "critical",
202
+ "scripts",
203
+ "status",
204
+ "templates",
205
+ "loop-state",
206
+ "session-state",
207
+ "agents"
208
+ ]),
209
+ knowledgeSourceFiles: z.array(z.string()).default(["CLAUDE.md", "MEMORY.md", "corrections.md"]),
210
+ excludePatterns: z.array(z.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
211
+ }).optional();
212
+ var PythonDomainConfigSchema = z.object({
213
+ name: z.string(),
214
+ packages: z.array(z.string()),
215
+ allowed_imports_from: z.array(z.string()).default([])
216
+ });
217
+ var PythonConfigSchema = z.object({
218
+ root: z.string(),
219
+ alembic_dir: z.string().optional(),
220
+ domains: z.array(PythonDomainConfigSchema).default([]),
221
+ exclude_dirs: z.array(z.string()).default(["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"])
222
+ }).optional();
223
+ var PathsConfigSchema = z.object({
224
+ source: z.string().default("src"),
225
+ aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
226
+ routers: z.string().optional(),
227
+ routerRoot: z.string().optional(),
228
+ pages: z.string().optional(),
229
+ middleware: z.string().optional(),
230
+ schema: z.string().optional(),
231
+ components: z.string().optional(),
232
+ hooks: z.string().optional()
233
+ });
234
+ var RawConfigSchema = z.object({
235
+ project: z.object({
236
+ name: z.string().default("my-project"),
237
+ root: z.string().default("auto")
238
+ }).default({ name: "my-project", root: "auto" }),
239
+ framework: z.object({
240
+ type: z.string().default("typescript"),
241
+ router: z.string().default("none"),
242
+ orm: z.string().default("none"),
243
+ ui: z.string().default("none")
244
+ }).default({ type: "typescript", router: "none", orm: "none", ui: "none" }),
245
+ paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
246
+ toolPrefix: z.string().default("massu"),
247
+ dbAccessPattern: z.string().optional(),
248
+ knownMismatches: z.record(z.string(), z.record(z.string(), z.string())).optional(),
249
+ accessScopes: z.array(z.string()).optional(),
250
+ domains: z.array(DomainConfigSchema).default([]),
251
+ rules: z.array(PatternRuleConfigSchema).default([]),
252
+ analytics: AnalyticsConfigSchema,
253
+ governance: GovernanceConfigSchema,
254
+ security: SecurityConfigSchema,
255
+ team: TeamConfigSchema,
256
+ regression: RegressionConfigSchema,
257
+ cloud: CloudConfigSchema,
258
+ conventions: ConventionsConfigSchema,
259
+ python: PythonConfigSchema,
260
+ autoLearning: AutoLearningConfigSchema
261
+ }).passthrough();
262
+ var _config = null;
263
+ var _projectRoot = null;
264
+ function findProjectRoot() {
265
+ const cwd = process.cwd();
266
+ let dir = cwd;
267
+ while (true) {
268
+ if (existsSync(resolve(dir, "massu.config.yaml"))) {
269
+ return dir;
270
+ }
271
+ const parent = dirname(dir);
272
+ if (parent === dir) break;
273
+ dir = parent;
274
+ }
275
+ dir = cwd;
276
+ while (true) {
277
+ if (existsSync(resolve(dir, "package.json"))) {
278
+ return dir;
279
+ }
280
+ if (existsSync(resolve(dir, ".git"))) {
281
+ return dir;
282
+ }
283
+ const parent = dirname(dir);
284
+ if (parent === dir) break;
285
+ dir = parent;
286
+ }
287
+ return cwd;
288
+ }
289
+ function getProjectRoot() {
290
+ if (!_projectRoot) {
291
+ _projectRoot = findProjectRoot();
292
+ }
293
+ return _projectRoot;
294
+ }
295
+ function getConfig() {
296
+ if (_config) return _config;
297
+ const root = getProjectRoot();
298
+ const configPath = resolve(root, "massu.config.yaml");
299
+ let rawYaml = {};
300
+ if (existsSync(configPath)) {
301
+ const content = readFileSync(configPath, "utf-8");
302
+ rawYaml = parseYaml(content) ?? {};
303
+ }
304
+ const parsed = RawConfigSchema.parse(rawYaml);
305
+ const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
306
+ _config = {
307
+ project: {
308
+ name: parsed.project.name,
309
+ root: projectRoot
310
+ },
311
+ framework: parsed.framework,
312
+ paths: parsed.paths,
313
+ toolPrefix: parsed.toolPrefix,
314
+ dbAccessPattern: parsed.dbAccessPattern,
315
+ knownMismatches: parsed.knownMismatches,
316
+ accessScopes: parsed.accessScopes,
317
+ domains: parsed.domains,
318
+ rules: parsed.rules,
319
+ analytics: parsed.analytics,
320
+ governance: parsed.governance,
321
+ security: parsed.security,
322
+ team: parsed.team,
323
+ regression: parsed.regression,
324
+ cloud: parsed.cloud,
325
+ conventions: parsed.conventions,
326
+ python: parsed.python
327
+ };
328
+ if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
329
+ _config.cloud = {
330
+ enabled: true,
331
+ sync: { memory: true, analytics: true, audit: true },
332
+ ..._config.cloud,
333
+ apiKey: process.env.MASSU_API_KEY
334
+ };
335
+ }
336
+ return _config;
337
+ }
338
+
339
+ // src/hooks/rule-enforcement-pipeline.ts
340
+ async function main() {
341
+ try {
342
+ const input = await readStdin();
343
+ const hookInput = JSON.parse(input);
344
+ const filePath = hookInput.tool_input?.file_path;
345
+ if (!filePath) {
346
+ process.exit(0);
347
+ return;
348
+ }
349
+ const config = getConfig();
350
+ if (config.autoLearning?.enabled === false) {
351
+ process.exit(0);
352
+ return;
353
+ }
354
+ const root = getProjectRoot();
355
+ const memoryDir = config.autoLearning?.memoryDir ?? "memory";
356
+ const enforcementDir = config.autoLearning?.enforcementHooksDir ?? "scripts/hooks";
357
+ const relPath = filePath.startsWith(root + "/") ? filePath.slice(root.length + 1) : filePath;
358
+ const fileName = basename(filePath);
359
+ if (!fileName.startsWith("feedback_") || !fileName.endsWith(".md")) {
360
+ process.exit(0);
361
+ return;
362
+ }
363
+ const claudeDir = config.conventions?.claudeDirName ?? ".claude";
364
+ if (!relPath.includes(memoryDir) && !relPath.includes("memory/") && !relPath.includes(claudeDir + "/")) {
365
+ process.exit(0);
366
+ return;
367
+ }
368
+ if (!existsSync2(filePath)) {
369
+ process.exit(0);
370
+ return;
371
+ }
372
+ if (config.autoLearning?.pipeline?.requireEnforcement === false) {
373
+ process.exit(0);
374
+ return;
375
+ }
376
+ const content = readFileSync2(filePath, "utf-8");
377
+ const nameMatch = content.match(/^name:\s*(.+)/m);
378
+ const descMatch = content.match(/^description:\s*(.+)/m);
379
+ const ruleName = nameMatch?.[1]?.trim() ?? fileName;
380
+ const ruleDesc = descMatch?.[1]?.trim() ?? "";
381
+ const enforcementDirAbs = resolve2(root, enforcementDir);
382
+ let hasEnforcement = false;
383
+ if (existsSync2(enforcementDirAbs)) {
384
+ const hookFiles = readdirSync(enforcementDirAbs).filter((f) => f.endsWith(".sh") || f.endsWith(".ts") || f.endsWith(".js"));
385
+ for (const hookFile of hookFiles) {
386
+ try {
387
+ const hookContent = readFileSync2(resolve2(enforcementDirAbs, hookFile), "utf-8");
388
+ if (hookContent.includes(fileName)) {
389
+ hasEnforcement = true;
390
+ break;
391
+ }
392
+ } catch {
393
+ }
394
+ }
395
+ }
396
+ if (hasEnforcement) {
397
+ process.exit(0);
398
+ return;
399
+ }
400
+ const lines = [];
401
+ lines.push("");
402
+ lines.push("============================================================================");
403
+ lines.push(" AUTO-LEARNING: New Rule Created \u2014 Enforcement Placement Required");
404
+ lines.push("============================================================================");
405
+ lines.push("");
406
+ lines.push(` Rule: ${ruleName}`);
407
+ lines.push(` File: ${filePath}`);
408
+ if (ruleDesc) {
409
+ lines.push(` Description: ${ruleDesc}`);
410
+ }
411
+ lines.push("");
412
+ lines.push(" This rule has NO automated enforcement yet. Add it now.");
413
+ lines.push("");
414
+ lines.push(" ANALYZE the rule and determine enforcement layer(s):");
415
+ lines.push("");
416
+ lines.push(" 1. STATICALLY DETECTABLE? (grep/regex can find violations in code)");
417
+ lines.push(` \u2192 Add check to: ${enforcementDir}/pattern-feedback hook`);
418
+ lines.push(` \u2192 Also add to pre-commit hook if critical`);
419
+ lines.push("");
420
+ lines.push(" 2. ABOUT EDITING CERTAIN FILES? (auth, infra, routers, etc.)");
421
+ lines.push(` \u2192 Add warning to: ${enforcementDir}/blast-radius hook`);
422
+ lines.push("");
423
+ lines.push(" 3. ABOUT DANGEROUS COMMANDS? (kill, rm, destructive ops)");
424
+ lines.push(` \u2192 Add block to: ${enforcementDir}/dangerous-command hook`);
425
+ lines.push("");
426
+ lines.push(" 4. NEEDS RUNTIME MONITORING? (can only be detected at runtime)");
427
+ lines.push(" \u2192 Create a monitoring/audit producer");
428
+ lines.push("");
429
+ lines.push(" 5. AI-GUIDANCE ONLY? (philosophy, process, judgment calls)");
430
+ lines.push(" \u2192 Memory rule is sufficient (already created)");
431
+ lines.push("");
432
+ lines.push(" AFTER adding enforcement, test the hook to verify it detects violations.");
433
+ lines.push("");
434
+ lines.push(" This step is MANDATORY per the auto-learning pipeline.");
435
+ lines.push("============================================================================");
436
+ lines.push("");
437
+ console.log(lines.join("\n"));
438
+ } catch {
439
+ }
440
+ process.exit(0);
441
+ }
442
+ function readStdin() {
443
+ return new Promise((resolve3) => {
444
+ let data = "";
445
+ process.stdin.setEncoding("utf-8");
446
+ process.stdin.on("data", (chunk) => {
447
+ data += chunk;
448
+ });
449
+ process.stdin.on("end", () => resolve3(data));
450
+ setTimeout(() => resolve3(data), 3e3);
451
+ });
452
+ }
453
+ main();
@@ -131,6 +131,44 @@ var RegressionConfigSchema = z.object({
131
131
  warning: z.number().default(50)
132
132
  }).optional()
133
133
  }).optional();
134
+ var AutoLearningConfigSchema = z.object({
135
+ enabled: z.boolean().default(true),
136
+ incidentDir: z.string().default("docs/incidents"),
137
+ memoryDir: z.string().default("memory"),
138
+ memoryIndexFile: z.string().default("MEMORY.md"),
139
+ enforcementHooksDir: z.string().default("scripts/hooks"),
140
+ fixDetection: z.object({
141
+ enabled: z.boolean().default(true),
142
+ lookbackDays: z.number().default(7),
143
+ signals: z.array(z.string()).default([
144
+ "removed_broken_code",
145
+ "added_error_handling",
146
+ "method_name_correction",
147
+ "auth_fix",
148
+ "nil_handling_fix",
149
+ "concurrency_fix",
150
+ "async_pattern_fix",
151
+ "added_missing_import"
152
+ ])
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({}),
166
+ pipeline: z.object({
167
+ requireIncidentReport: z.boolean().default(true),
168
+ requirePreventionRule: z.boolean().default(true),
169
+ requireEnforcement: z.boolean().default(true)
170
+ }).default({})
171
+ }).optional();
134
172
  var CloudConfigSchema = z.object({
135
173
  enabled: z.boolean().default(false),
136
174
  apiKey: z.string().optional(),
@@ -220,7 +258,8 @@ var RawConfigSchema = z.object({
220
258
  regression: RegressionConfigSchema,
221
259
  cloud: CloudConfigSchema,
222
260
  conventions: ConventionsConfigSchema,
223
- python: PythonConfigSchema
261
+ python: PythonConfigSchema,
262
+ autoLearning: AutoLearningConfigSchema
224
263
  }).passthrough();
225
264
  var _config = null;
226
265
  var _projectRoot = null;
@@ -821,6 +860,25 @@ function initMemorySchema(db) {
821
860
  features TEXT DEFAULT '[]'
822
861
  );
823
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
+ `);
824
882
  }
825
883
  function enqueueSyncPayload(db, payload) {
826
884
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
@@ -131,6 +131,44 @@ var RegressionConfigSchema = z.object({
131
131
  warning: z.number().default(50)
132
132
  }).optional()
133
133
  }).optional();
134
+ var AutoLearningConfigSchema = z.object({
135
+ enabled: z.boolean().default(true),
136
+ incidentDir: z.string().default("docs/incidents"),
137
+ memoryDir: z.string().default("memory"),
138
+ memoryIndexFile: z.string().default("MEMORY.md"),
139
+ enforcementHooksDir: z.string().default("scripts/hooks"),
140
+ fixDetection: z.object({
141
+ enabled: z.boolean().default(true),
142
+ lookbackDays: z.number().default(7),
143
+ signals: z.array(z.string()).default([
144
+ "removed_broken_code",
145
+ "added_error_handling",
146
+ "method_name_correction",
147
+ "auth_fix",
148
+ "nil_handling_fix",
149
+ "concurrency_fix",
150
+ "async_pattern_fix",
151
+ "added_missing_import"
152
+ ])
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({}),
166
+ pipeline: z.object({
167
+ requireIncidentReport: z.boolean().default(true),
168
+ requirePreventionRule: z.boolean().default(true),
169
+ requireEnforcement: z.boolean().default(true)
170
+ }).default({})
171
+ }).optional();
134
172
  var CloudConfigSchema = z.object({
135
173
  enabled: z.boolean().default(false),
136
174
  apiKey: z.string().optional(),
@@ -220,7 +258,8 @@ var RawConfigSchema = z.object({
220
258
  regression: RegressionConfigSchema,
221
259
  cloud: CloudConfigSchema,
222
260
  conventions: ConventionsConfigSchema,
223
- python: PythonConfigSchema
261
+ python: PythonConfigSchema,
262
+ autoLearning: AutoLearningConfigSchema
224
263
  }).passthrough();
225
264
  var _config = null;
226
265
  var _projectRoot = null;
@@ -827,6 +866,25 @@ function initMemorySchema(db) {
827
866
  features TEXT DEFAULT '[]'
828
867
  );
829
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
+ `);
830
888
  }
831
889
  function autoDetectTaskId(planFile) {
832
890
  if (!planFile) return null;
@@ -928,7 +986,7 @@ async function main() {
928
986
  process.stdout.write(
929
987
  `=== MASSU AI: Active ===
930
988
  Session memory, code intelligence, and governance are now active.
931
- 11 hooks monitoring this session. Type "${getConfig().toolPrefix ?? "massu"}_sync" to index your codebase.
989
+ 15 hooks monitoring this session. Type "${getConfig().toolPrefix ?? "massu"}_sync" to index your codebase.
932
990
  === END MASSU ===
933
991
 
934
992
  `