@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,481 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// src/hooks/auto-learning-pipeline.ts
|
|
11
|
+
import { execSync } from "child_process";
|
|
12
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, unlinkSync, readdirSync } from "fs";
|
|
13
|
+
import { tmpdir } from "os";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
|
|
16
|
+
// src/config.ts
|
|
17
|
+
import { resolve, dirname } from "path";
|
|
18
|
+
import { existsSync, readFileSync } from "fs";
|
|
19
|
+
import { parse as parseYaml } from "yaml";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
var DomainConfigSchema = z.object({
|
|
22
|
+
name: z.string().default("Unknown"),
|
|
23
|
+
routers: z.array(z.string()).default([]),
|
|
24
|
+
pages: z.array(z.string()).default([]),
|
|
25
|
+
tables: z.array(z.string()).default([]),
|
|
26
|
+
allowedImportsFrom: z.array(z.string()).default([])
|
|
27
|
+
});
|
|
28
|
+
var PatternRuleConfigSchema = z.object({
|
|
29
|
+
pattern: z.string().default("**"),
|
|
30
|
+
rules: z.array(z.string()).default([])
|
|
31
|
+
});
|
|
32
|
+
var CostModelSchema = z.object({
|
|
33
|
+
input_per_million: z.number(),
|
|
34
|
+
output_per_million: z.number(),
|
|
35
|
+
cache_read_per_million: z.number().optional(),
|
|
36
|
+
cache_write_per_million: z.number().optional()
|
|
37
|
+
});
|
|
38
|
+
var AnalyticsConfigSchema = z.object({
|
|
39
|
+
quality: z.object({
|
|
40
|
+
weights: z.record(z.string(), z.number()).default({
|
|
41
|
+
bug_found: -5,
|
|
42
|
+
vr_failure: -10,
|
|
43
|
+
incident: -20,
|
|
44
|
+
cr_violation: -3,
|
|
45
|
+
vr_pass: 2,
|
|
46
|
+
clean_commit: 5,
|
|
47
|
+
successful_verification: 3
|
|
48
|
+
}),
|
|
49
|
+
categories: z.array(z.string()).default(["security", "architecture", "coupling", "tests", "rule_compliance"])
|
|
50
|
+
}).optional(),
|
|
51
|
+
cost: z.object({
|
|
52
|
+
models: z.record(z.string(), CostModelSchema).default({}),
|
|
53
|
+
currency: z.string().default("USD")
|
|
54
|
+
}).optional(),
|
|
55
|
+
prompts: z.object({
|
|
56
|
+
success_indicators: z.array(z.string()).default(["committed", "approved", "looks good", "perfect", "great", "thanks"]),
|
|
57
|
+
failure_indicators: z.array(z.string()).default(["revert", "wrong", "that's not", "undo", "incorrect"]),
|
|
58
|
+
max_turns_for_success: z.number().default(2)
|
|
59
|
+
}).optional()
|
|
60
|
+
}).optional();
|
|
61
|
+
var CustomPatternSchema = z.object({
|
|
62
|
+
pattern: z.string(),
|
|
63
|
+
severity: z.string(),
|
|
64
|
+
message: z.string()
|
|
65
|
+
});
|
|
66
|
+
var GovernanceConfigSchema = z.object({
|
|
67
|
+
audit: z.object({
|
|
68
|
+
formats: z.array(z.string()).default(["summary", "detailed", "soc2"]),
|
|
69
|
+
retention_days: z.number().default(365),
|
|
70
|
+
auto_log: z.record(z.string(), z.boolean()).default({
|
|
71
|
+
code_changes: true,
|
|
72
|
+
rule_enforcement: true,
|
|
73
|
+
approvals: true,
|
|
74
|
+
commits: true
|
|
75
|
+
})
|
|
76
|
+
}).optional(),
|
|
77
|
+
validation: z.object({
|
|
78
|
+
realtime: z.boolean().default(true),
|
|
79
|
+
checks: z.record(z.string(), z.boolean()).default({
|
|
80
|
+
rule_compliance: true,
|
|
81
|
+
import_existence: true,
|
|
82
|
+
naming_conventions: true
|
|
83
|
+
}),
|
|
84
|
+
custom_patterns: z.array(CustomPatternSchema).default([])
|
|
85
|
+
}).optional(),
|
|
86
|
+
adr: z.object({
|
|
87
|
+
detection_phrases: z.array(z.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
|
|
88
|
+
template: z.string().default("default"),
|
|
89
|
+
storage: z.string().default("database"),
|
|
90
|
+
output_dir: z.string().default("docs/adr")
|
|
91
|
+
}).optional()
|
|
92
|
+
}).optional();
|
|
93
|
+
var SecurityPatternSchema = z.object({
|
|
94
|
+
pattern: z.string(),
|
|
95
|
+
severity: z.string(),
|
|
96
|
+
category: z.string(),
|
|
97
|
+
description: z.string()
|
|
98
|
+
});
|
|
99
|
+
var SecurityConfigSchema = z.object({
|
|
100
|
+
patterns: z.array(SecurityPatternSchema).default([]),
|
|
101
|
+
auto_score_on_edit: z.boolean().default(true),
|
|
102
|
+
score_threshold_alert: z.number().default(50),
|
|
103
|
+
severity_weights: z.record(z.string(), z.number()).optional(),
|
|
104
|
+
restrictive_licenses: z.array(z.string()).optional(),
|
|
105
|
+
dep_alternatives: z.record(z.string(), z.array(z.string())).optional(),
|
|
106
|
+
dependencies: z.object({
|
|
107
|
+
package_manager: z.string().default("npm"),
|
|
108
|
+
blocked_packages: z.array(z.string()).default([]),
|
|
109
|
+
preferred_packages: z.record(z.string(), z.string()).default({}),
|
|
110
|
+
max_bundle_size_kb: z.number().default(500)
|
|
111
|
+
}).optional()
|
|
112
|
+
}).optional();
|
|
113
|
+
var TeamConfigSchema = z.object({
|
|
114
|
+
enabled: z.boolean().default(false),
|
|
115
|
+
sync_backend: z.string().default("local"),
|
|
116
|
+
developer_id: z.string().default("auto"),
|
|
117
|
+
share_by_default: z.boolean().default(false),
|
|
118
|
+
expertise_weights: z.object({
|
|
119
|
+
session: z.number().default(20),
|
|
120
|
+
observation: z.number().default(10)
|
|
121
|
+
}).optional(),
|
|
122
|
+
privacy: z.object({
|
|
123
|
+
share_file_paths: z.boolean().default(true),
|
|
124
|
+
share_code_snippets: z.boolean().default(false),
|
|
125
|
+
share_observations: z.boolean().default(true)
|
|
126
|
+
}).optional()
|
|
127
|
+
}).optional();
|
|
128
|
+
var RegressionConfigSchema = z.object({
|
|
129
|
+
test_patterns: z.array(z.string()).default([
|
|
130
|
+
"{dir}/__tests__/{name}.test.{ext}",
|
|
131
|
+
"{dir}/{name}.spec.{ext}",
|
|
132
|
+
"tests/{path}.test.{ext}"
|
|
133
|
+
]),
|
|
134
|
+
test_runner: z.string().default("npm test"),
|
|
135
|
+
health_thresholds: z.object({
|
|
136
|
+
healthy: z.number().default(80),
|
|
137
|
+
warning: z.number().default(50)
|
|
138
|
+
}).optional()
|
|
139
|
+
}).optional();
|
|
140
|
+
var AutoLearningConfigSchema = z.object({
|
|
141
|
+
enabled: z.boolean().default(true),
|
|
142
|
+
incidentDir: z.string().default("docs/incidents"),
|
|
143
|
+
memoryDir: z.string().default("memory"),
|
|
144
|
+
memoryIndexFile: z.string().default("MEMORY.md"),
|
|
145
|
+
enforcementHooksDir: z.string().default("scripts/hooks"),
|
|
146
|
+
fixDetection: z.object({
|
|
147
|
+
enabled: z.boolean().default(true),
|
|
148
|
+
lookbackDays: z.number().default(7),
|
|
149
|
+
signals: z.array(z.string()).default([
|
|
150
|
+
"removed_broken_code",
|
|
151
|
+
"added_error_handling",
|
|
152
|
+
"method_name_correction",
|
|
153
|
+
"auth_fix",
|
|
154
|
+
"nil_handling_fix",
|
|
155
|
+
"concurrency_fix",
|
|
156
|
+
"async_pattern_fix",
|
|
157
|
+
"added_missing_import"
|
|
158
|
+
])
|
|
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({}),
|
|
172
|
+
pipeline: z.object({
|
|
173
|
+
requireIncidentReport: z.boolean().default(true),
|
|
174
|
+
requirePreventionRule: z.boolean().default(true),
|
|
175
|
+
requireEnforcement: z.boolean().default(true)
|
|
176
|
+
}).default({})
|
|
177
|
+
}).optional();
|
|
178
|
+
var CloudConfigSchema = z.object({
|
|
179
|
+
enabled: z.boolean().default(false),
|
|
180
|
+
apiKey: z.string().optional(),
|
|
181
|
+
endpoint: z.string().optional(),
|
|
182
|
+
sync: z.object({
|
|
183
|
+
memory: z.boolean().default(true),
|
|
184
|
+
analytics: z.boolean().default(true),
|
|
185
|
+
audit: z.boolean().default(true)
|
|
186
|
+
}).default({ memory: true, analytics: true, audit: true })
|
|
187
|
+
}).optional();
|
|
188
|
+
var ConventionsConfigSchema = z.object({
|
|
189
|
+
claudeDirName: z.string().default(".claude").refine(
|
|
190
|
+
(s) => !s.includes("..") && !s.startsWith("/"),
|
|
191
|
+
{ message: 'claudeDirName must not contain ".." or start with "/"' }
|
|
192
|
+
),
|
|
193
|
+
sessionStatePath: z.string().default(".claude/session-state/CURRENT.md").refine(
|
|
194
|
+
(s) => !s.includes("..") && !s.startsWith("/"),
|
|
195
|
+
{ message: 'sessionStatePath must not contain ".." or start with "/"' }
|
|
196
|
+
),
|
|
197
|
+
sessionArchivePath: z.string().default(".claude/session-state/archive").refine(
|
|
198
|
+
(s) => !s.includes("..") && !s.startsWith("/"),
|
|
199
|
+
{ message: 'sessionArchivePath must not contain ".." or start with "/"' }
|
|
200
|
+
),
|
|
201
|
+
knowledgeCategories: z.array(z.string()).default([
|
|
202
|
+
"patterns",
|
|
203
|
+
"commands",
|
|
204
|
+
"incidents",
|
|
205
|
+
"reference",
|
|
206
|
+
"protocols",
|
|
207
|
+
"checklists",
|
|
208
|
+
"playbooks",
|
|
209
|
+
"critical",
|
|
210
|
+
"scripts",
|
|
211
|
+
"status",
|
|
212
|
+
"templates",
|
|
213
|
+
"loop-state",
|
|
214
|
+
"session-state",
|
|
215
|
+
"agents"
|
|
216
|
+
]),
|
|
217
|
+
knowledgeSourceFiles: z.array(z.string()).default(["CLAUDE.md", "MEMORY.md", "corrections.md"]),
|
|
218
|
+
excludePatterns: z.array(z.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
|
|
219
|
+
}).optional();
|
|
220
|
+
var PythonDomainConfigSchema = z.object({
|
|
221
|
+
name: z.string(),
|
|
222
|
+
packages: z.array(z.string()),
|
|
223
|
+
allowed_imports_from: z.array(z.string()).default([])
|
|
224
|
+
});
|
|
225
|
+
var PythonConfigSchema = z.object({
|
|
226
|
+
root: z.string(),
|
|
227
|
+
alembic_dir: z.string().optional(),
|
|
228
|
+
domains: z.array(PythonDomainConfigSchema).default([]),
|
|
229
|
+
exclude_dirs: z.array(z.string()).default(["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"])
|
|
230
|
+
}).optional();
|
|
231
|
+
var PathsConfigSchema = z.object({
|
|
232
|
+
source: z.string().default("src"),
|
|
233
|
+
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
234
|
+
routers: z.string().optional(),
|
|
235
|
+
routerRoot: z.string().optional(),
|
|
236
|
+
pages: z.string().optional(),
|
|
237
|
+
middleware: z.string().optional(),
|
|
238
|
+
schema: z.string().optional(),
|
|
239
|
+
components: z.string().optional(),
|
|
240
|
+
hooks: z.string().optional()
|
|
241
|
+
});
|
|
242
|
+
var RawConfigSchema = z.object({
|
|
243
|
+
project: z.object({
|
|
244
|
+
name: z.string().default("my-project"),
|
|
245
|
+
root: z.string().default("auto")
|
|
246
|
+
}).default({ name: "my-project", root: "auto" }),
|
|
247
|
+
framework: z.object({
|
|
248
|
+
type: z.string().default("typescript"),
|
|
249
|
+
router: z.string().default("none"),
|
|
250
|
+
orm: z.string().default("none"),
|
|
251
|
+
ui: z.string().default("none")
|
|
252
|
+
}).default({ type: "typescript", router: "none", orm: "none", ui: "none" }),
|
|
253
|
+
paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
|
|
254
|
+
toolPrefix: z.string().default("massu"),
|
|
255
|
+
dbAccessPattern: z.string().optional(),
|
|
256
|
+
knownMismatches: z.record(z.string(), z.record(z.string(), z.string())).optional(),
|
|
257
|
+
accessScopes: z.array(z.string()).optional(),
|
|
258
|
+
domains: z.array(DomainConfigSchema).default([]),
|
|
259
|
+
rules: z.array(PatternRuleConfigSchema).default([]),
|
|
260
|
+
analytics: AnalyticsConfigSchema,
|
|
261
|
+
governance: GovernanceConfigSchema,
|
|
262
|
+
security: SecurityConfigSchema,
|
|
263
|
+
team: TeamConfigSchema,
|
|
264
|
+
regression: RegressionConfigSchema,
|
|
265
|
+
cloud: CloudConfigSchema,
|
|
266
|
+
conventions: ConventionsConfigSchema,
|
|
267
|
+
python: PythonConfigSchema,
|
|
268
|
+
autoLearning: AutoLearningConfigSchema
|
|
269
|
+
}).passthrough();
|
|
270
|
+
var _config = null;
|
|
271
|
+
var _projectRoot = null;
|
|
272
|
+
function findProjectRoot() {
|
|
273
|
+
const cwd = process.cwd();
|
|
274
|
+
let dir = cwd;
|
|
275
|
+
while (true) {
|
|
276
|
+
if (existsSync(resolve(dir, "massu.config.yaml"))) {
|
|
277
|
+
return dir;
|
|
278
|
+
}
|
|
279
|
+
const parent = dirname(dir);
|
|
280
|
+
if (parent === dir) break;
|
|
281
|
+
dir = parent;
|
|
282
|
+
}
|
|
283
|
+
dir = cwd;
|
|
284
|
+
while (true) {
|
|
285
|
+
if (existsSync(resolve(dir, "package.json"))) {
|
|
286
|
+
return dir;
|
|
287
|
+
}
|
|
288
|
+
if (existsSync(resolve(dir, ".git"))) {
|
|
289
|
+
return dir;
|
|
290
|
+
}
|
|
291
|
+
const parent = dirname(dir);
|
|
292
|
+
if (parent === dir) break;
|
|
293
|
+
dir = parent;
|
|
294
|
+
}
|
|
295
|
+
return cwd;
|
|
296
|
+
}
|
|
297
|
+
function getProjectRoot() {
|
|
298
|
+
if (!_projectRoot) {
|
|
299
|
+
_projectRoot = findProjectRoot();
|
|
300
|
+
}
|
|
301
|
+
return _projectRoot;
|
|
302
|
+
}
|
|
303
|
+
function getConfig() {
|
|
304
|
+
if (_config) return _config;
|
|
305
|
+
const root = getProjectRoot();
|
|
306
|
+
const configPath = resolve(root, "massu.config.yaml");
|
|
307
|
+
let rawYaml = {};
|
|
308
|
+
if (existsSync(configPath)) {
|
|
309
|
+
const content = readFileSync(configPath, "utf-8");
|
|
310
|
+
rawYaml = parseYaml(content) ?? {};
|
|
311
|
+
}
|
|
312
|
+
const parsed = RawConfigSchema.parse(rawYaml);
|
|
313
|
+
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
|
|
314
|
+
_config = {
|
|
315
|
+
project: {
|
|
316
|
+
name: parsed.project.name,
|
|
317
|
+
root: projectRoot
|
|
318
|
+
},
|
|
319
|
+
framework: parsed.framework,
|
|
320
|
+
paths: parsed.paths,
|
|
321
|
+
toolPrefix: parsed.toolPrefix,
|
|
322
|
+
dbAccessPattern: parsed.dbAccessPattern,
|
|
323
|
+
knownMismatches: parsed.knownMismatches,
|
|
324
|
+
accessScopes: parsed.accessScopes,
|
|
325
|
+
domains: parsed.domains,
|
|
326
|
+
rules: parsed.rules,
|
|
327
|
+
analytics: parsed.analytics,
|
|
328
|
+
governance: parsed.governance,
|
|
329
|
+
security: parsed.security,
|
|
330
|
+
team: parsed.team,
|
|
331
|
+
regression: parsed.regression,
|
|
332
|
+
cloud: parsed.cloud,
|
|
333
|
+
conventions: parsed.conventions,
|
|
334
|
+
python: parsed.python
|
|
335
|
+
};
|
|
336
|
+
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
337
|
+
_config.cloud = {
|
|
338
|
+
enabled: true,
|
|
339
|
+
sync: { memory: true, analytics: true, audit: true },
|
|
340
|
+
..._config.cloud,
|
|
341
|
+
apiKey: process.env.MASSU_API_KEY
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return _config;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/hooks/auto-learning-pipeline.ts
|
|
348
|
+
function getSessionFlagPath(sessionId) {
|
|
349
|
+
return join(tmpdir(), "massu-auto-learning", `fixes-${sessionId.slice(0, 12)}.jsonl`);
|
|
350
|
+
}
|
|
351
|
+
async function main() {
|
|
352
|
+
try {
|
|
353
|
+
const input = await readStdin();
|
|
354
|
+
const hookInput = JSON.parse(input);
|
|
355
|
+
const config = getConfig();
|
|
356
|
+
if (config.autoLearning?.enabled === false) {
|
|
357
|
+
process.exit(0);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const root = getProjectRoot();
|
|
361
|
+
const incidentDir = config.autoLearning?.incidentDir ?? "docs/incidents";
|
|
362
|
+
const memoryDir = config.autoLearning?.memoryDir ?? "memory";
|
|
363
|
+
const autoLearn = config.autoLearning;
|
|
364
|
+
const flagPath = getSessionFlagPath(hookInput.session_id);
|
|
365
|
+
let sessionFixes = [];
|
|
366
|
+
if (existsSync2(flagPath)) {
|
|
367
|
+
try {
|
|
368
|
+
sessionFixes = readFileSync2(flagPath, "utf-8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
let uncommittedFix = false;
|
|
373
|
+
try {
|
|
374
|
+
const diff = execSync("git diff --name-only", { cwd: root, timeout: 3e3, encoding: "utf-8" });
|
|
375
|
+
if (diff.trim()) {
|
|
376
|
+
const fullDiff = execSync("git diff", { cwd: root, timeout: 5e3, encoding: "utf-8" });
|
|
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;
|
|
379
|
+
if (fixPatterns > 3 || removedBroken > 1) {
|
|
380
|
+
uncommittedFix = true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
if (sessionFixes.length === 0 && !uncommittedFix) {
|
|
386
|
+
cleanup(flagPath);
|
|
387
|
+
process.exit(0);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const lines = [];
|
|
391
|
+
lines.push("");
|
|
392
|
+
lines.push("============================================================================");
|
|
393
|
+
lines.push(" MASSU AUTO-LEARNING PIPELINE \u2014 ACTION REQUIRED BEFORE SESSION END");
|
|
394
|
+
lines.push("============================================================================");
|
|
395
|
+
if (sessionFixes.length > 0) {
|
|
396
|
+
lines.push("");
|
|
397
|
+
lines.push(` ${sessionFixes.length} bug fix(es) detected during this session:`);
|
|
398
|
+
lines.push("");
|
|
399
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
400
|
+
for (const fix of sessionFixes) {
|
|
401
|
+
const existing = byFile.get(fix.file) ?? [];
|
|
402
|
+
existing.push(...fix.signals);
|
|
403
|
+
byFile.set(fix.file, [...new Set(existing)]);
|
|
404
|
+
}
|
|
405
|
+
for (const [file, signals] of byFile) {
|
|
406
|
+
lines.push(` - ${file} (${signals.join(", ")})`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (uncommittedFix) {
|
|
410
|
+
lines.push("");
|
|
411
|
+
lines.push(" Additional uncommitted fix patterns detected in git diff.");
|
|
412
|
+
}
|
|
413
|
+
lines.push("");
|
|
414
|
+
lines.push(" Complete these steps before this session ends:");
|
|
415
|
+
lines.push("");
|
|
416
|
+
if (autoLearn?.pipeline?.requireIncidentReport !== false) {
|
|
417
|
+
lines.push(" STEP 1: INCIDENT REPORT");
|
|
418
|
+
lines.push(` For each distinct bug fixed, create: ${incidentDir}/YYYY-MM-DD-<slug>.md`);
|
|
419
|
+
lines.push(" Include: Date, Severity, Symptoms, Root Cause, Fix, Files Changed, Prevention Rules");
|
|
420
|
+
lines.push("");
|
|
421
|
+
}
|
|
422
|
+
if (autoLearn?.pipeline?.requirePreventionRule !== false) {
|
|
423
|
+
lines.push(" STEP 2: PREVENTION RULE");
|
|
424
|
+
lines.push(` For each incident, create: ${memoryDir}/feedback_<rule_name>.md`);
|
|
425
|
+
lines.push(" Include frontmatter (name, description, type: feedback) + Why + How to apply");
|
|
426
|
+
lines.push(` Update ${config.autoLearning?.memoryIndexFile ?? "MEMORY.md"} index`);
|
|
427
|
+
lines.push("");
|
|
428
|
+
}
|
|
429
|
+
if (autoLearn?.pipeline?.requireEnforcement !== false) {
|
|
430
|
+
lines.push(" STEP 3: ENFORCEMENT PLACEMENT");
|
|
431
|
+
lines.push(" For each new rule, determine enforcement layer(s):");
|
|
432
|
+
lines.push(" a) If statically detectable \u2192 add to pattern-feedback hook");
|
|
433
|
+
lines.push(" b) If about editing certain files \u2192 add to blast-radius hook");
|
|
434
|
+
lines.push(" c) If about dangerous commands \u2192 add to dangerous-command hook");
|
|
435
|
+
lines.push(" d) If critical \u2192 add to pre-commit hook");
|
|
436
|
+
lines.push(" e) If needs runtime monitoring \u2192 create monitoring producer");
|
|
437
|
+
lines.push("");
|
|
438
|
+
}
|
|
439
|
+
lines.push(" STEP 4: VERIFY");
|
|
440
|
+
lines.push(" Test any new enforcement hooks to confirm they detect violations.");
|
|
441
|
+
lines.push("");
|
|
442
|
+
lines.push("============================================================================");
|
|
443
|
+
lines.push("");
|
|
444
|
+
console.log(lines.join("\n"));
|
|
445
|
+
cleanup(flagPath);
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
process.exit(0);
|
|
449
|
+
}
|
|
450
|
+
function cleanup(flagPath) {
|
|
451
|
+
try {
|
|
452
|
+
if (existsSync2(flagPath)) unlinkSync(flagPath);
|
|
453
|
+
const dir = join(tmpdir(), "massu-auto-learning");
|
|
454
|
+
if (existsSync2(dir)) {
|
|
455
|
+
const now = Date.now();
|
|
456
|
+
for (const file of readdirSync(dir)) {
|
|
457
|
+
const fullPath = join(dir, file);
|
|
458
|
+
try {
|
|
459
|
+
const stat = __require("fs").statSync(fullPath);
|
|
460
|
+
if (now - stat.mtimeMs > 864e5) {
|
|
461
|
+
unlinkSync(fullPath);
|
|
462
|
+
}
|
|
463
|
+
} catch {
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function readStdin() {
|
|
471
|
+
return new Promise((resolve2) => {
|
|
472
|
+
let data = "";
|
|
473
|
+
process.stdin.setEncoding("utf-8");
|
|
474
|
+
process.stdin.on("data", (chunk) => {
|
|
475
|
+
data += chunk;
|
|
476
|
+
});
|
|
477
|
+
process.stdin.on("end", () => resolve2(data));
|
|
478
|
+
setTimeout(() => resolve2(data), 5e3);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
main();
|