@massu/core 0.9.2 → 1.1.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/dist/cli.js +11182 -1559
- package/dist/hooks/auto-learning-pipeline.js +99 -19
- package/dist/hooks/classify-failure.js +99 -19
- package/dist/hooks/cost-tracker.js +97 -11
- package/dist/hooks/fix-detector.js +99 -19
- package/dist/hooks/incident-pipeline.js +97 -11
- package/dist/hooks/post-edit-context.js +97 -11
- package/dist/hooks/post-tool-use.js +101 -20
- package/dist/hooks/pre-compact.js +97 -11
- package/dist/hooks/pre-delete-check.js +97 -11
- package/dist/hooks/quality-event.js +97 -11
- package/dist/hooks/rule-enforcement-pipeline.js +97 -11
- package/dist/hooks/session-end.js +97 -11
- package/dist/hooks/session-start.js +8803 -782
- package/dist/hooks/user-prompt.js +98 -43
- package/package.json +13 -3
- package/reference/hook-execution-order.md +17 -25
- package/src/cli.ts +81 -2
- package/src/commands/config-check-drift.ts +132 -0
- package/src/commands/config-refresh.ts +224 -0
- package/src/commands/config-upgrade.ts +126 -0
- package/src/commands/doctor.ts +1 -29
- package/src/commands/init.ts +756 -216
- package/src/config.ts +168 -12
- package/src/detect/domain-inferrer.ts +142 -0
- package/src/detect/drift.ts +199 -0
- package/src/detect/framework-detector.ts +281 -0
- package/src/detect/index.ts +174 -0
- package/src/detect/migrate.ts +278 -0
- package/src/detect/monorepo-detector.ts +347 -0
- package/src/detect/package-detector.ts +728 -0
- package/src/detect/source-dir-detector.ts +264 -0
- package/src/detect/vr-command-map.ts +167 -0
- package/src/hooks/auto-learning-pipeline.ts +2 -2
- package/src/hooks/classify-failure.ts +2 -2
- package/src/hooks/fix-detector.ts +2 -2
- package/src/hooks/session-start.ts +43 -2
- package/src/hooks/user-prompt.ts +1 -21
- package/src/knowledge-indexer.ts +1 -1
- package/src/license.ts +1 -2
- package/src/memory-db.ts +0 -5
- package/src/memory-file-ingest.ts +6 -13
- package/src/tools.ts +0 -8
- package/templates/multi-runtime/massu.config.yaml +80 -0
- package/templates/python-django/massu.config.yaml +51 -0
- package/templates/python-fastapi/massu.config.yaml +50 -0
- package/templates/rust-actix/massu.config.yaml +38 -0
- package/templates/swift-ios/massu.config.yaml +37 -0
- package/templates/ts-nestjs/massu.config.yaml +43 -0
- package/templates/ts-nextjs/massu.config.yaml +43 -0
- package/README.md +0 -40
- package/src/claude-md-templates.ts +0 -342
- package/src/mcp-bridge-tools.ts +0 -458
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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
3
|
|
|
10
4
|
// src/hooks/auto-learning-pipeline.ts
|
|
11
5
|
import { execSync } from "child_process";
|
|
12
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, unlinkSync, readdirSync } from "fs";
|
|
6
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, unlinkSync, readdirSync, statSync } from "fs";
|
|
13
7
|
import { tmpdir } from "os";
|
|
14
8
|
import { join } from "path";
|
|
15
9
|
|
|
@@ -27,7 +21,8 @@ var DomainConfigSchema = z.object({
|
|
|
27
21
|
});
|
|
28
22
|
var PatternRuleConfigSchema = z.object({
|
|
29
23
|
pattern: z.string().default("**"),
|
|
30
|
-
rules: z.array(z.string()).default([])
|
|
24
|
+
rules: z.array(z.string()).default([]),
|
|
25
|
+
language: z.string().optional()
|
|
31
26
|
});
|
|
32
27
|
var CostModelSchema = z.object({
|
|
33
28
|
input_per_million: z.number(),
|
|
@@ -239,17 +234,59 @@ var PathsConfigSchema = z.object({
|
|
|
239
234
|
components: z.string().optional(),
|
|
240
235
|
hooks: z.string().optional()
|
|
241
236
|
});
|
|
237
|
+
var LanguageFrameworkEntrySchema = z.object({
|
|
238
|
+
framework: z.string().optional(),
|
|
239
|
+
test_framework: z.string().optional(),
|
|
240
|
+
test: z.string().optional(),
|
|
241
|
+
runtime: z.string().optional(),
|
|
242
|
+
orm: z.string().optional(),
|
|
243
|
+
router: z.string().optional(),
|
|
244
|
+
ui: z.string().optional()
|
|
245
|
+
}).passthrough();
|
|
246
|
+
var FrameworkConfigSchema = z.object({
|
|
247
|
+
type: z.string().default("typescript"),
|
|
248
|
+
primary: z.string().optional(),
|
|
249
|
+
router: z.string().default("none"),
|
|
250
|
+
orm: z.string().default("none"),
|
|
251
|
+
ui: z.string().default("none"),
|
|
252
|
+
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
253
|
+
}).passthrough();
|
|
254
|
+
var VerificationEntrySchema = z.object({
|
|
255
|
+
type: z.string().optional(),
|
|
256
|
+
test: z.string().optional(),
|
|
257
|
+
syntax: z.string().optional(),
|
|
258
|
+
lint: z.string().optional(),
|
|
259
|
+
build: z.string().optional()
|
|
260
|
+
}).passthrough();
|
|
261
|
+
var VerificationConfigSchema = z.record(z.string(), VerificationEntrySchema).optional();
|
|
262
|
+
var CanonicalPathsSchema = z.record(z.string(), z.string()).optional();
|
|
263
|
+
var VerificationTypesSchema = z.record(z.string(), z.string()).optional();
|
|
264
|
+
var DetectionRuleEntrySchema = z.object({
|
|
265
|
+
signals: z.array(z.string()).default([]),
|
|
266
|
+
priority: z.number().optional()
|
|
267
|
+
}).passthrough();
|
|
268
|
+
var DetectionConfigSchema = z.object({
|
|
269
|
+
rules: z.record(
|
|
270
|
+
z.string(),
|
|
271
|
+
// language
|
|
272
|
+
z.record(z.string(), DetectionRuleEntrySchema)
|
|
273
|
+
// framework -> rule entry
|
|
274
|
+
).optional(),
|
|
275
|
+
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
276
|
+
disable_builtin: z.boolean().optional()
|
|
277
|
+
}).passthrough().optional();
|
|
242
278
|
var RawConfigSchema = z.object({
|
|
279
|
+
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
243
280
|
project: z.object({
|
|
244
281
|
name: z.string().default("my-project"),
|
|
245
282
|
root: z.string().default("auto")
|
|
246
283
|
}).default({ name: "my-project", root: "auto" }),
|
|
247
|
-
framework:
|
|
248
|
-
type:
|
|
249
|
-
router:
|
|
250
|
-
orm:
|
|
251
|
-
ui:
|
|
252
|
-
})
|
|
284
|
+
framework: FrameworkConfigSchema.default({
|
|
285
|
+
type: "typescript",
|
|
286
|
+
router: "none",
|
|
287
|
+
orm: "none",
|
|
288
|
+
ui: "none"
|
|
289
|
+
}),
|
|
253
290
|
paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
|
|
254
291
|
toolPrefix: z.string().default("massu"),
|
|
255
292
|
dbAccessPattern: z.string().optional(),
|
|
@@ -264,8 +301,13 @@ var RawConfigSchema = z.object({
|
|
|
264
301
|
regression: RegressionConfigSchema,
|
|
265
302
|
cloud: CloudConfigSchema,
|
|
266
303
|
conventions: ConventionsConfigSchema,
|
|
304
|
+
autoLearning: AutoLearningConfigSchema,
|
|
267
305
|
python: PythonConfigSchema,
|
|
268
|
-
|
|
306
|
+
// P2-004 / P2-005 / P2-006 / P2-008: v2 extensions (all optional)
|
|
307
|
+
verification: VerificationConfigSchema,
|
|
308
|
+
canonical_paths: CanonicalPathsSchema,
|
|
309
|
+
verification_types: VerificationTypesSchema,
|
|
310
|
+
detection: DetectionConfigSchema
|
|
269
311
|
}).passthrough();
|
|
270
312
|
var _config = null;
|
|
271
313
|
var _projectRoot = null;
|
|
@@ -309,14 +351,47 @@ function getConfig() {
|
|
|
309
351
|
const content = readFileSync(configPath, "utf-8");
|
|
310
352
|
rawYaml = parseYaml(content) ?? {};
|
|
311
353
|
}
|
|
312
|
-
const
|
|
354
|
+
const result = RawConfigSchema.safeParse(rawYaml);
|
|
355
|
+
if (!result.success) {
|
|
356
|
+
const issues = result.error.issues.map((i) => {
|
|
357
|
+
const path = i.path.length > 0 ? i.path.join(".") : "(root)";
|
|
358
|
+
const received = "received" in i && i.received !== void 0 ? ` (received ${JSON.stringify(i.received)})` : "";
|
|
359
|
+
return ` - ${path}: ${i.message}${received}`;
|
|
360
|
+
}).join("\n");
|
|
361
|
+
throw new Error(
|
|
362
|
+
`Invalid massu.config.yaml at ${configPath}:
|
|
363
|
+
${issues}
|
|
364
|
+
Hint: run \`massu config refresh\` to regenerate a valid config or fix the listed fields manually.`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
const parsed = result.data;
|
|
313
368
|
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
|
|
369
|
+
const fw = parsed.framework;
|
|
370
|
+
let router = fw.router;
|
|
371
|
+
let orm = fw.orm;
|
|
372
|
+
let ui = fw.ui;
|
|
373
|
+
if (fw.type === "multi" && fw.primary && fw.languages) {
|
|
374
|
+
const primaryEntry = fw.languages[fw.primary];
|
|
375
|
+
if (primaryEntry) {
|
|
376
|
+
if (router === "none" && primaryEntry.router) router = primaryEntry.router;
|
|
377
|
+
if (orm === "none" && primaryEntry.orm) orm = primaryEntry.orm;
|
|
378
|
+
if (ui === "none" && primaryEntry.ui) ui = primaryEntry.ui;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
314
381
|
_config = {
|
|
382
|
+
schema_version: parsed.schema_version,
|
|
315
383
|
project: {
|
|
316
384
|
name: parsed.project.name,
|
|
317
385
|
root: projectRoot
|
|
318
386
|
},
|
|
319
|
-
framework:
|
|
387
|
+
framework: {
|
|
388
|
+
type: fw.type,
|
|
389
|
+
router,
|
|
390
|
+
orm,
|
|
391
|
+
ui,
|
|
392
|
+
primary: fw.primary,
|
|
393
|
+
languages: fw.languages
|
|
394
|
+
},
|
|
320
395
|
paths: parsed.paths,
|
|
321
396
|
toolPrefix: parsed.toolPrefix,
|
|
322
397
|
dbAccessPattern: parsed.dbAccessPattern,
|
|
@@ -331,7 +406,12 @@ function getConfig() {
|
|
|
331
406
|
regression: parsed.regression,
|
|
332
407
|
cloud: parsed.cloud,
|
|
333
408
|
conventions: parsed.conventions,
|
|
334
|
-
|
|
409
|
+
autoLearning: parsed.autoLearning,
|
|
410
|
+
python: parsed.python,
|
|
411
|
+
verification: parsed.verification,
|
|
412
|
+
canonical_paths: parsed.canonical_paths,
|
|
413
|
+
verification_types: parsed.verification_types,
|
|
414
|
+
detection: parsed.detection
|
|
335
415
|
};
|
|
336
416
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
337
417
|
_config.cloud = {
|
|
@@ -456,7 +536,7 @@ function cleanup(flagPath) {
|
|
|
456
536
|
for (const file of readdirSync(dir)) {
|
|
457
537
|
const fullPath = join(dir, file);
|
|
458
538
|
try {
|
|
459
|
-
const stat =
|
|
539
|
+
const stat = statSync(fullPath);
|
|
460
540
|
if (now - stat.mtimeMs > 864e5) {
|
|
461
541
|
unlinkSync(fullPath);
|
|
462
542
|
}
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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
3
|
|
|
10
4
|
// src/hooks/classify-failure.ts
|
|
11
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, unlinkSync } from "fs";
|
|
5
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
12
6
|
import { tmpdir } from "os";
|
|
13
7
|
import { join, basename as basename2 } from "path";
|
|
14
8
|
|
|
@@ -27,7 +21,8 @@ var DomainConfigSchema = z.object({
|
|
|
27
21
|
});
|
|
28
22
|
var PatternRuleConfigSchema = z.object({
|
|
29
23
|
pattern: z.string().default("**"),
|
|
30
|
-
rules: z.array(z.string()).default([])
|
|
24
|
+
rules: z.array(z.string()).default([]),
|
|
25
|
+
language: z.string().optional()
|
|
31
26
|
});
|
|
32
27
|
var CostModelSchema = z.object({
|
|
33
28
|
input_per_million: z.number(),
|
|
@@ -239,17 +234,59 @@ var PathsConfigSchema = z.object({
|
|
|
239
234
|
components: z.string().optional(),
|
|
240
235
|
hooks: z.string().optional()
|
|
241
236
|
});
|
|
237
|
+
var LanguageFrameworkEntrySchema = z.object({
|
|
238
|
+
framework: z.string().optional(),
|
|
239
|
+
test_framework: z.string().optional(),
|
|
240
|
+
test: z.string().optional(),
|
|
241
|
+
runtime: z.string().optional(),
|
|
242
|
+
orm: z.string().optional(),
|
|
243
|
+
router: z.string().optional(),
|
|
244
|
+
ui: z.string().optional()
|
|
245
|
+
}).passthrough();
|
|
246
|
+
var FrameworkConfigSchema = z.object({
|
|
247
|
+
type: z.string().default("typescript"),
|
|
248
|
+
primary: z.string().optional(),
|
|
249
|
+
router: z.string().default("none"),
|
|
250
|
+
orm: z.string().default("none"),
|
|
251
|
+
ui: z.string().default("none"),
|
|
252
|
+
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
253
|
+
}).passthrough();
|
|
254
|
+
var VerificationEntrySchema = z.object({
|
|
255
|
+
type: z.string().optional(),
|
|
256
|
+
test: z.string().optional(),
|
|
257
|
+
syntax: z.string().optional(),
|
|
258
|
+
lint: z.string().optional(),
|
|
259
|
+
build: z.string().optional()
|
|
260
|
+
}).passthrough();
|
|
261
|
+
var VerificationConfigSchema = z.record(z.string(), VerificationEntrySchema).optional();
|
|
262
|
+
var CanonicalPathsSchema = z.record(z.string(), z.string()).optional();
|
|
263
|
+
var VerificationTypesSchema = z.record(z.string(), z.string()).optional();
|
|
264
|
+
var DetectionRuleEntrySchema = z.object({
|
|
265
|
+
signals: z.array(z.string()).default([]),
|
|
266
|
+
priority: z.number().optional()
|
|
267
|
+
}).passthrough();
|
|
268
|
+
var DetectionConfigSchema = z.object({
|
|
269
|
+
rules: z.record(
|
|
270
|
+
z.string(),
|
|
271
|
+
// language
|
|
272
|
+
z.record(z.string(), DetectionRuleEntrySchema)
|
|
273
|
+
// framework -> rule entry
|
|
274
|
+
).optional(),
|
|
275
|
+
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
276
|
+
disable_builtin: z.boolean().optional()
|
|
277
|
+
}).passthrough().optional();
|
|
242
278
|
var RawConfigSchema = z.object({
|
|
279
|
+
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
243
280
|
project: z.object({
|
|
244
281
|
name: z.string().default("my-project"),
|
|
245
282
|
root: z.string().default("auto")
|
|
246
283
|
}).default({ name: "my-project", root: "auto" }),
|
|
247
|
-
framework:
|
|
248
|
-
type:
|
|
249
|
-
router:
|
|
250
|
-
orm:
|
|
251
|
-
ui:
|
|
252
|
-
})
|
|
284
|
+
framework: FrameworkConfigSchema.default({
|
|
285
|
+
type: "typescript",
|
|
286
|
+
router: "none",
|
|
287
|
+
orm: "none",
|
|
288
|
+
ui: "none"
|
|
289
|
+
}),
|
|
253
290
|
paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
|
|
254
291
|
toolPrefix: z.string().default("massu"),
|
|
255
292
|
dbAccessPattern: z.string().optional(),
|
|
@@ -264,8 +301,13 @@ var RawConfigSchema = z.object({
|
|
|
264
301
|
regression: RegressionConfigSchema,
|
|
265
302
|
cloud: CloudConfigSchema,
|
|
266
303
|
conventions: ConventionsConfigSchema,
|
|
304
|
+
autoLearning: AutoLearningConfigSchema,
|
|
267
305
|
python: PythonConfigSchema,
|
|
268
|
-
|
|
306
|
+
// P2-004 / P2-005 / P2-006 / P2-008: v2 extensions (all optional)
|
|
307
|
+
verification: VerificationConfigSchema,
|
|
308
|
+
canonical_paths: CanonicalPathsSchema,
|
|
309
|
+
verification_types: VerificationTypesSchema,
|
|
310
|
+
detection: DetectionConfigSchema
|
|
269
311
|
}).passthrough();
|
|
270
312
|
var _config = null;
|
|
271
313
|
var _projectRoot = null;
|
|
@@ -309,14 +351,47 @@ function getConfig() {
|
|
|
309
351
|
const content = readFileSync(configPath, "utf-8");
|
|
310
352
|
rawYaml = parseYaml(content) ?? {};
|
|
311
353
|
}
|
|
312
|
-
const
|
|
354
|
+
const result = RawConfigSchema.safeParse(rawYaml);
|
|
355
|
+
if (!result.success) {
|
|
356
|
+
const issues = result.error.issues.map((i) => {
|
|
357
|
+
const path = i.path.length > 0 ? i.path.join(".") : "(root)";
|
|
358
|
+
const received = "received" in i && i.received !== void 0 ? ` (received ${JSON.stringify(i.received)})` : "";
|
|
359
|
+
return ` - ${path}: ${i.message}${received}`;
|
|
360
|
+
}).join("\n");
|
|
361
|
+
throw new Error(
|
|
362
|
+
`Invalid massu.config.yaml at ${configPath}:
|
|
363
|
+
${issues}
|
|
364
|
+
Hint: run \`massu config refresh\` to regenerate a valid config or fix the listed fields manually.`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
const parsed = result.data;
|
|
313
368
|
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
|
|
369
|
+
const fw = parsed.framework;
|
|
370
|
+
let router = fw.router;
|
|
371
|
+
let orm = fw.orm;
|
|
372
|
+
let ui = fw.ui;
|
|
373
|
+
if (fw.type === "multi" && fw.primary && fw.languages) {
|
|
374
|
+
const primaryEntry = fw.languages[fw.primary];
|
|
375
|
+
if (primaryEntry) {
|
|
376
|
+
if (router === "none" && primaryEntry.router) router = primaryEntry.router;
|
|
377
|
+
if (orm === "none" && primaryEntry.orm) orm = primaryEntry.orm;
|
|
378
|
+
if (ui === "none" && primaryEntry.ui) ui = primaryEntry.ui;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
314
381
|
_config = {
|
|
382
|
+
schema_version: parsed.schema_version,
|
|
315
383
|
project: {
|
|
316
384
|
name: parsed.project.name,
|
|
317
385
|
root: projectRoot
|
|
318
386
|
},
|
|
319
|
-
framework:
|
|
387
|
+
framework: {
|
|
388
|
+
type: fw.type,
|
|
389
|
+
router,
|
|
390
|
+
orm,
|
|
391
|
+
ui,
|
|
392
|
+
primary: fw.primary,
|
|
393
|
+
languages: fw.languages
|
|
394
|
+
},
|
|
320
395
|
paths: parsed.paths,
|
|
321
396
|
toolPrefix: parsed.toolPrefix,
|
|
322
397
|
dbAccessPattern: parsed.dbAccessPattern,
|
|
@@ -331,7 +406,12 @@ function getConfig() {
|
|
|
331
406
|
regression: parsed.regression,
|
|
332
407
|
cloud: parsed.cloud,
|
|
333
408
|
conventions: parsed.conventions,
|
|
334
|
-
|
|
409
|
+
autoLearning: parsed.autoLearning,
|
|
410
|
+
python: parsed.python,
|
|
411
|
+
verification: parsed.verification,
|
|
412
|
+
canonical_paths: parsed.canonical_paths,
|
|
413
|
+
verification_types: parsed.verification_types,
|
|
414
|
+
detection: parsed.detection
|
|
335
415
|
};
|
|
336
416
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
337
417
|
_config.cloud = {
|
|
@@ -1070,7 +1150,7 @@ async function main() {
|
|
|
1070
1150
|
return;
|
|
1071
1151
|
}
|
|
1072
1152
|
try {
|
|
1073
|
-
|
|
1153
|
+
writeFileSync(dedupeMarker, "1");
|
|
1074
1154
|
} catch {
|
|
1075
1155
|
}
|
|
1076
1156
|
const db = getMemoryDb();
|
|
@@ -21,7 +21,8 @@ var DomainConfigSchema = z.object({
|
|
|
21
21
|
});
|
|
22
22
|
var PatternRuleConfigSchema = z.object({
|
|
23
23
|
pattern: z.string().default("**"),
|
|
24
|
-
rules: z.array(z.string()).default([])
|
|
24
|
+
rules: z.array(z.string()).default([]),
|
|
25
|
+
language: z.string().optional()
|
|
25
26
|
});
|
|
26
27
|
var CostModelSchema = z.object({
|
|
27
28
|
input_per_million: z.number(),
|
|
@@ -233,17 +234,59 @@ var PathsConfigSchema = z.object({
|
|
|
233
234
|
components: z.string().optional(),
|
|
234
235
|
hooks: z.string().optional()
|
|
235
236
|
});
|
|
237
|
+
var LanguageFrameworkEntrySchema = z.object({
|
|
238
|
+
framework: z.string().optional(),
|
|
239
|
+
test_framework: z.string().optional(),
|
|
240
|
+
test: z.string().optional(),
|
|
241
|
+
runtime: z.string().optional(),
|
|
242
|
+
orm: z.string().optional(),
|
|
243
|
+
router: z.string().optional(),
|
|
244
|
+
ui: z.string().optional()
|
|
245
|
+
}).passthrough();
|
|
246
|
+
var FrameworkConfigSchema = z.object({
|
|
247
|
+
type: z.string().default("typescript"),
|
|
248
|
+
primary: z.string().optional(),
|
|
249
|
+
router: z.string().default("none"),
|
|
250
|
+
orm: z.string().default("none"),
|
|
251
|
+
ui: z.string().default("none"),
|
|
252
|
+
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
253
|
+
}).passthrough();
|
|
254
|
+
var VerificationEntrySchema = z.object({
|
|
255
|
+
type: z.string().optional(),
|
|
256
|
+
test: z.string().optional(),
|
|
257
|
+
syntax: z.string().optional(),
|
|
258
|
+
lint: z.string().optional(),
|
|
259
|
+
build: z.string().optional()
|
|
260
|
+
}).passthrough();
|
|
261
|
+
var VerificationConfigSchema = z.record(z.string(), VerificationEntrySchema).optional();
|
|
262
|
+
var CanonicalPathsSchema = z.record(z.string(), z.string()).optional();
|
|
263
|
+
var VerificationTypesSchema = z.record(z.string(), z.string()).optional();
|
|
264
|
+
var DetectionRuleEntrySchema = z.object({
|
|
265
|
+
signals: z.array(z.string()).default([]),
|
|
266
|
+
priority: z.number().optional()
|
|
267
|
+
}).passthrough();
|
|
268
|
+
var DetectionConfigSchema = z.object({
|
|
269
|
+
rules: z.record(
|
|
270
|
+
z.string(),
|
|
271
|
+
// language
|
|
272
|
+
z.record(z.string(), DetectionRuleEntrySchema)
|
|
273
|
+
// framework -> rule entry
|
|
274
|
+
).optional(),
|
|
275
|
+
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
276
|
+
disable_builtin: z.boolean().optional()
|
|
277
|
+
}).passthrough().optional();
|
|
236
278
|
var RawConfigSchema = z.object({
|
|
279
|
+
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
237
280
|
project: z.object({
|
|
238
281
|
name: z.string().default("my-project"),
|
|
239
282
|
root: z.string().default("auto")
|
|
240
283
|
}).default({ name: "my-project", root: "auto" }),
|
|
241
|
-
framework:
|
|
242
|
-
type:
|
|
243
|
-
router:
|
|
244
|
-
orm:
|
|
245
|
-
ui:
|
|
246
|
-
})
|
|
284
|
+
framework: FrameworkConfigSchema.default({
|
|
285
|
+
type: "typescript",
|
|
286
|
+
router: "none",
|
|
287
|
+
orm: "none",
|
|
288
|
+
ui: "none"
|
|
289
|
+
}),
|
|
247
290
|
paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
|
|
248
291
|
toolPrefix: z.string().default("massu"),
|
|
249
292
|
dbAccessPattern: z.string().optional(),
|
|
@@ -258,8 +301,13 @@ var RawConfigSchema = z.object({
|
|
|
258
301
|
regression: RegressionConfigSchema,
|
|
259
302
|
cloud: CloudConfigSchema,
|
|
260
303
|
conventions: ConventionsConfigSchema,
|
|
304
|
+
autoLearning: AutoLearningConfigSchema,
|
|
261
305
|
python: PythonConfigSchema,
|
|
262
|
-
|
|
306
|
+
// P2-004 / P2-005 / P2-006 / P2-008: v2 extensions (all optional)
|
|
307
|
+
verification: VerificationConfigSchema,
|
|
308
|
+
canonical_paths: CanonicalPathsSchema,
|
|
309
|
+
verification_types: VerificationTypesSchema,
|
|
310
|
+
detection: DetectionConfigSchema
|
|
263
311
|
}).passthrough();
|
|
264
312
|
var _config = null;
|
|
265
313
|
var _projectRoot = null;
|
|
@@ -303,14 +351,47 @@ function getConfig() {
|
|
|
303
351
|
const content = readFileSync(configPath, "utf-8");
|
|
304
352
|
rawYaml = parseYaml(content) ?? {};
|
|
305
353
|
}
|
|
306
|
-
const
|
|
354
|
+
const result = RawConfigSchema.safeParse(rawYaml);
|
|
355
|
+
if (!result.success) {
|
|
356
|
+
const issues = result.error.issues.map((i) => {
|
|
357
|
+
const path = i.path.length > 0 ? i.path.join(".") : "(root)";
|
|
358
|
+
const received = "received" in i && i.received !== void 0 ? ` (received ${JSON.stringify(i.received)})` : "";
|
|
359
|
+
return ` - ${path}: ${i.message}${received}`;
|
|
360
|
+
}).join("\n");
|
|
361
|
+
throw new Error(
|
|
362
|
+
`Invalid massu.config.yaml at ${configPath}:
|
|
363
|
+
${issues}
|
|
364
|
+
Hint: run \`massu config refresh\` to regenerate a valid config or fix the listed fields manually.`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
const parsed = result.data;
|
|
307
368
|
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
|
|
369
|
+
const fw = parsed.framework;
|
|
370
|
+
let router = fw.router;
|
|
371
|
+
let orm = fw.orm;
|
|
372
|
+
let ui = fw.ui;
|
|
373
|
+
if (fw.type === "multi" && fw.primary && fw.languages) {
|
|
374
|
+
const primaryEntry = fw.languages[fw.primary];
|
|
375
|
+
if (primaryEntry) {
|
|
376
|
+
if (router === "none" && primaryEntry.router) router = primaryEntry.router;
|
|
377
|
+
if (orm === "none" && primaryEntry.orm) orm = primaryEntry.orm;
|
|
378
|
+
if (ui === "none" && primaryEntry.ui) ui = primaryEntry.ui;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
308
381
|
_config = {
|
|
382
|
+
schema_version: parsed.schema_version,
|
|
309
383
|
project: {
|
|
310
384
|
name: parsed.project.name,
|
|
311
385
|
root: projectRoot
|
|
312
386
|
},
|
|
313
|
-
framework:
|
|
387
|
+
framework: {
|
|
388
|
+
type: fw.type,
|
|
389
|
+
router,
|
|
390
|
+
orm,
|
|
391
|
+
ui,
|
|
392
|
+
primary: fw.primary,
|
|
393
|
+
languages: fw.languages
|
|
394
|
+
},
|
|
314
395
|
paths: parsed.paths,
|
|
315
396
|
toolPrefix: parsed.toolPrefix,
|
|
316
397
|
dbAccessPattern: parsed.dbAccessPattern,
|
|
@@ -325,7 +406,12 @@ function getConfig() {
|
|
|
325
406
|
regression: parsed.regression,
|
|
326
407
|
cloud: parsed.cloud,
|
|
327
408
|
conventions: parsed.conventions,
|
|
328
|
-
|
|
409
|
+
autoLearning: parsed.autoLearning,
|
|
410
|
+
python: parsed.python,
|
|
411
|
+
verification: parsed.verification,
|
|
412
|
+
canonical_paths: parsed.canonical_paths,
|
|
413
|
+
verification_types: parsed.verification_types,
|
|
414
|
+
detection: parsed.detection
|
|
329
415
|
};
|
|
330
416
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
331
417
|
_config.cloud = {
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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
3
|
|
|
10
4
|
// src/hooks/fix-detector.ts
|
|
11
5
|
import { execSync } from "child_process";
|
|
12
|
-
import { existsSync as existsSync2, appendFileSync, mkdirSync } from "fs";
|
|
6
|
+
import { existsSync as existsSync2, appendFileSync, mkdirSync, readFileSync as readFileSync2 } from "fs";
|
|
13
7
|
import { tmpdir } from "os";
|
|
14
8
|
import { join } from "path";
|
|
15
9
|
|
|
@@ -27,7 +21,8 @@ var DomainConfigSchema = z.object({
|
|
|
27
21
|
});
|
|
28
22
|
var PatternRuleConfigSchema = z.object({
|
|
29
23
|
pattern: z.string().default("**"),
|
|
30
|
-
rules: z.array(z.string()).default([])
|
|
24
|
+
rules: z.array(z.string()).default([]),
|
|
25
|
+
language: z.string().optional()
|
|
31
26
|
});
|
|
32
27
|
var CostModelSchema = z.object({
|
|
33
28
|
input_per_million: z.number(),
|
|
@@ -239,17 +234,59 @@ var PathsConfigSchema = z.object({
|
|
|
239
234
|
components: z.string().optional(),
|
|
240
235
|
hooks: z.string().optional()
|
|
241
236
|
});
|
|
237
|
+
var LanguageFrameworkEntrySchema = z.object({
|
|
238
|
+
framework: z.string().optional(),
|
|
239
|
+
test_framework: z.string().optional(),
|
|
240
|
+
test: z.string().optional(),
|
|
241
|
+
runtime: z.string().optional(),
|
|
242
|
+
orm: z.string().optional(),
|
|
243
|
+
router: z.string().optional(),
|
|
244
|
+
ui: z.string().optional()
|
|
245
|
+
}).passthrough();
|
|
246
|
+
var FrameworkConfigSchema = z.object({
|
|
247
|
+
type: z.string().default("typescript"),
|
|
248
|
+
primary: z.string().optional(),
|
|
249
|
+
router: z.string().default("none"),
|
|
250
|
+
orm: z.string().default("none"),
|
|
251
|
+
ui: z.string().default("none"),
|
|
252
|
+
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
253
|
+
}).passthrough();
|
|
254
|
+
var VerificationEntrySchema = z.object({
|
|
255
|
+
type: z.string().optional(),
|
|
256
|
+
test: z.string().optional(),
|
|
257
|
+
syntax: z.string().optional(),
|
|
258
|
+
lint: z.string().optional(),
|
|
259
|
+
build: z.string().optional()
|
|
260
|
+
}).passthrough();
|
|
261
|
+
var VerificationConfigSchema = z.record(z.string(), VerificationEntrySchema).optional();
|
|
262
|
+
var CanonicalPathsSchema = z.record(z.string(), z.string()).optional();
|
|
263
|
+
var VerificationTypesSchema = z.record(z.string(), z.string()).optional();
|
|
264
|
+
var DetectionRuleEntrySchema = z.object({
|
|
265
|
+
signals: z.array(z.string()).default([]),
|
|
266
|
+
priority: z.number().optional()
|
|
267
|
+
}).passthrough();
|
|
268
|
+
var DetectionConfigSchema = z.object({
|
|
269
|
+
rules: z.record(
|
|
270
|
+
z.string(),
|
|
271
|
+
// language
|
|
272
|
+
z.record(z.string(), DetectionRuleEntrySchema)
|
|
273
|
+
// framework -> rule entry
|
|
274
|
+
).optional(),
|
|
275
|
+
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
276
|
+
disable_builtin: z.boolean().optional()
|
|
277
|
+
}).passthrough().optional();
|
|
242
278
|
var RawConfigSchema = z.object({
|
|
279
|
+
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
243
280
|
project: z.object({
|
|
244
281
|
name: z.string().default("my-project"),
|
|
245
282
|
root: z.string().default("auto")
|
|
246
283
|
}).default({ name: "my-project", root: "auto" }),
|
|
247
|
-
framework:
|
|
248
|
-
type:
|
|
249
|
-
router:
|
|
250
|
-
orm:
|
|
251
|
-
ui:
|
|
252
|
-
})
|
|
284
|
+
framework: FrameworkConfigSchema.default({
|
|
285
|
+
type: "typescript",
|
|
286
|
+
router: "none",
|
|
287
|
+
orm: "none",
|
|
288
|
+
ui: "none"
|
|
289
|
+
}),
|
|
253
290
|
paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
|
|
254
291
|
toolPrefix: z.string().default("massu"),
|
|
255
292
|
dbAccessPattern: z.string().optional(),
|
|
@@ -264,8 +301,13 @@ var RawConfigSchema = z.object({
|
|
|
264
301
|
regression: RegressionConfigSchema,
|
|
265
302
|
cloud: CloudConfigSchema,
|
|
266
303
|
conventions: ConventionsConfigSchema,
|
|
304
|
+
autoLearning: AutoLearningConfigSchema,
|
|
267
305
|
python: PythonConfigSchema,
|
|
268
|
-
|
|
306
|
+
// P2-004 / P2-005 / P2-006 / P2-008: v2 extensions (all optional)
|
|
307
|
+
verification: VerificationConfigSchema,
|
|
308
|
+
canonical_paths: CanonicalPathsSchema,
|
|
309
|
+
verification_types: VerificationTypesSchema,
|
|
310
|
+
detection: DetectionConfigSchema
|
|
269
311
|
}).passthrough();
|
|
270
312
|
var _config = null;
|
|
271
313
|
var _projectRoot = null;
|
|
@@ -309,14 +351,47 @@ function getConfig() {
|
|
|
309
351
|
const content = readFileSync(configPath, "utf-8");
|
|
310
352
|
rawYaml = parseYaml(content) ?? {};
|
|
311
353
|
}
|
|
312
|
-
const
|
|
354
|
+
const result = RawConfigSchema.safeParse(rawYaml);
|
|
355
|
+
if (!result.success) {
|
|
356
|
+
const issues = result.error.issues.map((i) => {
|
|
357
|
+
const path = i.path.length > 0 ? i.path.join(".") : "(root)";
|
|
358
|
+
const received = "received" in i && i.received !== void 0 ? ` (received ${JSON.stringify(i.received)})` : "";
|
|
359
|
+
return ` - ${path}: ${i.message}${received}`;
|
|
360
|
+
}).join("\n");
|
|
361
|
+
throw new Error(
|
|
362
|
+
`Invalid massu.config.yaml at ${configPath}:
|
|
363
|
+
${issues}
|
|
364
|
+
Hint: run \`massu config refresh\` to regenerate a valid config or fix the listed fields manually.`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
const parsed = result.data;
|
|
313
368
|
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
|
|
369
|
+
const fw = parsed.framework;
|
|
370
|
+
let router = fw.router;
|
|
371
|
+
let orm = fw.orm;
|
|
372
|
+
let ui = fw.ui;
|
|
373
|
+
if (fw.type === "multi" && fw.primary && fw.languages) {
|
|
374
|
+
const primaryEntry = fw.languages[fw.primary];
|
|
375
|
+
if (primaryEntry) {
|
|
376
|
+
if (router === "none" && primaryEntry.router) router = primaryEntry.router;
|
|
377
|
+
if (orm === "none" && primaryEntry.orm) orm = primaryEntry.orm;
|
|
378
|
+
if (ui === "none" && primaryEntry.ui) ui = primaryEntry.ui;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
314
381
|
_config = {
|
|
382
|
+
schema_version: parsed.schema_version,
|
|
315
383
|
project: {
|
|
316
384
|
name: parsed.project.name,
|
|
317
385
|
root: projectRoot
|
|
318
386
|
},
|
|
319
|
-
framework:
|
|
387
|
+
framework: {
|
|
388
|
+
type: fw.type,
|
|
389
|
+
router,
|
|
390
|
+
orm,
|
|
391
|
+
ui,
|
|
392
|
+
primary: fw.primary,
|
|
393
|
+
languages: fw.languages
|
|
394
|
+
},
|
|
320
395
|
paths: parsed.paths,
|
|
321
396
|
toolPrefix: parsed.toolPrefix,
|
|
322
397
|
dbAccessPattern: parsed.dbAccessPattern,
|
|
@@ -331,7 +406,12 @@ function getConfig() {
|
|
|
331
406
|
regression: parsed.regression,
|
|
332
407
|
cloud: parsed.cloud,
|
|
333
408
|
conventions: parsed.conventions,
|
|
334
|
-
|
|
409
|
+
autoLearning: parsed.autoLearning,
|
|
410
|
+
python: parsed.python,
|
|
411
|
+
verification: parsed.verification,
|
|
412
|
+
canonical_paths: parsed.canonical_paths,
|
|
413
|
+
verification_types: parsed.verification_types,
|
|
414
|
+
detection: parsed.detection
|
|
335
415
|
};
|
|
336
416
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
337
417
|
_config.cloud = {
|
|
@@ -450,7 +530,7 @@ async function main() {
|
|
|
450
530
|
};
|
|
451
531
|
const flagPath = getSessionFlagPath(hookInput.session_id);
|
|
452
532
|
appendFileSync(flagPath, JSON.stringify(signal) + "\n");
|
|
453
|
-
const lines =
|
|
533
|
+
const lines = readFileSync2(flagPath, "utf-8").split("\n").filter(Boolean);
|
|
454
534
|
if (lines.length === 1) {
|
|
455
535
|
console.log(
|
|
456
536
|
`[Massu Auto-Learning] Bug fix detected in ${filePath} (signals: ${detected.join(", ")}). The auto-learning pipeline will prompt you at session end to create an incident report, derive a prevention rule, and add enforcement.`
|