@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
|
@@ -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
|
-
|
|
989
|
+
15 hooks monitoring this session. Type "${getConfig().toolPrefix ?? "massu"}_sync" to index your codebase.
|
|
932
990
|
=== END MASSU ===
|
|
933
991
|
|
|
934
992
|
`
|