@massu/core 0.9.1 → 1.0.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/agents/massu-security-reviewer.md +2 -2
- package/dist/cli.js +10519 -1661
- 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 +98 -12
- 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 +2 -1
- package/src/commands/doctor.ts +1 -29
- package/src/commands/init.ts +752 -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 +1 -1
- 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
|
@@ -20,7 +20,8 @@ var DomainConfigSchema = z.object({
|
|
|
20
20
|
});
|
|
21
21
|
var PatternRuleConfigSchema = z.object({
|
|
22
22
|
pattern: z.string().default("**"),
|
|
23
|
-
rules: z.array(z.string()).default([])
|
|
23
|
+
rules: z.array(z.string()).default([]),
|
|
24
|
+
language: z.string().optional()
|
|
24
25
|
});
|
|
25
26
|
var CostModelSchema = z.object({
|
|
26
27
|
input_per_million: z.number(),
|
|
@@ -232,17 +233,59 @@ var PathsConfigSchema = z.object({
|
|
|
232
233
|
components: z.string().optional(),
|
|
233
234
|
hooks: z.string().optional()
|
|
234
235
|
});
|
|
236
|
+
var LanguageFrameworkEntrySchema = z.object({
|
|
237
|
+
framework: z.string().optional(),
|
|
238
|
+
test_framework: z.string().optional(),
|
|
239
|
+
test: z.string().optional(),
|
|
240
|
+
runtime: z.string().optional(),
|
|
241
|
+
orm: z.string().optional(),
|
|
242
|
+
router: z.string().optional(),
|
|
243
|
+
ui: z.string().optional()
|
|
244
|
+
}).passthrough();
|
|
245
|
+
var FrameworkConfigSchema = z.object({
|
|
246
|
+
type: z.string().default("typescript"),
|
|
247
|
+
primary: z.string().optional(),
|
|
248
|
+
router: z.string().default("none"),
|
|
249
|
+
orm: z.string().default("none"),
|
|
250
|
+
ui: z.string().default("none"),
|
|
251
|
+
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
252
|
+
}).passthrough();
|
|
253
|
+
var VerificationEntrySchema = z.object({
|
|
254
|
+
type: z.string().optional(),
|
|
255
|
+
test: z.string().optional(),
|
|
256
|
+
syntax: z.string().optional(),
|
|
257
|
+
lint: z.string().optional(),
|
|
258
|
+
build: z.string().optional()
|
|
259
|
+
}).passthrough();
|
|
260
|
+
var VerificationConfigSchema = z.record(z.string(), VerificationEntrySchema).optional();
|
|
261
|
+
var CanonicalPathsSchema = z.record(z.string(), z.string()).optional();
|
|
262
|
+
var VerificationTypesSchema = z.record(z.string(), z.string()).optional();
|
|
263
|
+
var DetectionRuleEntrySchema = z.object({
|
|
264
|
+
signals: z.array(z.string()).default([]),
|
|
265
|
+
priority: z.number().optional()
|
|
266
|
+
}).passthrough();
|
|
267
|
+
var DetectionConfigSchema = z.object({
|
|
268
|
+
rules: z.record(
|
|
269
|
+
z.string(),
|
|
270
|
+
// language
|
|
271
|
+
z.record(z.string(), DetectionRuleEntrySchema)
|
|
272
|
+
// framework -> rule entry
|
|
273
|
+
).optional(),
|
|
274
|
+
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
275
|
+
disable_builtin: z.boolean().optional()
|
|
276
|
+
}).passthrough().optional();
|
|
235
277
|
var RawConfigSchema = z.object({
|
|
278
|
+
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
236
279
|
project: z.object({
|
|
237
280
|
name: z.string().default("my-project"),
|
|
238
281
|
root: z.string().default("auto")
|
|
239
282
|
}).default({ name: "my-project", root: "auto" }),
|
|
240
|
-
framework:
|
|
241
|
-
type:
|
|
242
|
-
router:
|
|
243
|
-
orm:
|
|
244
|
-
ui:
|
|
245
|
-
})
|
|
283
|
+
framework: FrameworkConfigSchema.default({
|
|
284
|
+
type: "typescript",
|
|
285
|
+
router: "none",
|
|
286
|
+
orm: "none",
|
|
287
|
+
ui: "none"
|
|
288
|
+
}),
|
|
246
289
|
paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
|
|
247
290
|
toolPrefix: z.string().default("massu"),
|
|
248
291
|
dbAccessPattern: z.string().optional(),
|
|
@@ -257,8 +300,13 @@ var RawConfigSchema = z.object({
|
|
|
257
300
|
regression: RegressionConfigSchema,
|
|
258
301
|
cloud: CloudConfigSchema,
|
|
259
302
|
conventions: ConventionsConfigSchema,
|
|
303
|
+
autoLearning: AutoLearningConfigSchema,
|
|
260
304
|
python: PythonConfigSchema,
|
|
261
|
-
|
|
305
|
+
// P2-004 / P2-005 / P2-006 / P2-008: v2 extensions (all optional)
|
|
306
|
+
verification: VerificationConfigSchema,
|
|
307
|
+
canonical_paths: CanonicalPathsSchema,
|
|
308
|
+
verification_types: VerificationTypesSchema,
|
|
309
|
+
detection: DetectionConfigSchema
|
|
262
310
|
}).passthrough();
|
|
263
311
|
var _config = null;
|
|
264
312
|
var _projectRoot = null;
|
|
@@ -302,14 +350,47 @@ function getConfig() {
|
|
|
302
350
|
const content = readFileSync(configPath, "utf-8");
|
|
303
351
|
rawYaml = parseYaml(content) ?? {};
|
|
304
352
|
}
|
|
305
|
-
const
|
|
353
|
+
const result = RawConfigSchema.safeParse(rawYaml);
|
|
354
|
+
if (!result.success) {
|
|
355
|
+
const issues = result.error.issues.map((i) => {
|
|
356
|
+
const path = i.path.length > 0 ? i.path.join(".") : "(root)";
|
|
357
|
+
const received = "received" in i && i.received !== void 0 ? ` (received ${JSON.stringify(i.received)})` : "";
|
|
358
|
+
return ` - ${path}: ${i.message}${received}`;
|
|
359
|
+
}).join("\n");
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Invalid massu.config.yaml at ${configPath}:
|
|
362
|
+
${issues}
|
|
363
|
+
Hint: run \`massu config refresh\` to regenerate a valid config or fix the listed fields manually.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
const parsed = result.data;
|
|
306
367
|
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
|
|
368
|
+
const fw = parsed.framework;
|
|
369
|
+
let router = fw.router;
|
|
370
|
+
let orm = fw.orm;
|
|
371
|
+
let ui = fw.ui;
|
|
372
|
+
if (fw.type === "multi" && fw.primary && fw.languages) {
|
|
373
|
+
const primaryEntry = fw.languages[fw.primary];
|
|
374
|
+
if (primaryEntry) {
|
|
375
|
+
if (router === "none" && primaryEntry.router) router = primaryEntry.router;
|
|
376
|
+
if (orm === "none" && primaryEntry.orm) orm = primaryEntry.orm;
|
|
377
|
+
if (ui === "none" && primaryEntry.ui) ui = primaryEntry.ui;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
307
380
|
_config = {
|
|
381
|
+
schema_version: parsed.schema_version,
|
|
308
382
|
project: {
|
|
309
383
|
name: parsed.project.name,
|
|
310
384
|
root: projectRoot
|
|
311
385
|
},
|
|
312
|
-
framework:
|
|
386
|
+
framework: {
|
|
387
|
+
type: fw.type,
|
|
388
|
+
router,
|
|
389
|
+
orm,
|
|
390
|
+
ui,
|
|
391
|
+
primary: fw.primary,
|
|
392
|
+
languages: fw.languages
|
|
393
|
+
},
|
|
313
394
|
paths: parsed.paths,
|
|
314
395
|
toolPrefix: parsed.toolPrefix,
|
|
315
396
|
dbAccessPattern: parsed.dbAccessPattern,
|
|
@@ -324,7 +405,12 @@ function getConfig() {
|
|
|
324
405
|
regression: parsed.regression,
|
|
325
406
|
cloud: parsed.cloud,
|
|
326
407
|
conventions: parsed.conventions,
|
|
327
|
-
|
|
408
|
+
autoLearning: parsed.autoLearning,
|
|
409
|
+
python: parsed.python,
|
|
410
|
+
verification: parsed.verification,
|
|
411
|
+
canonical_paths: parsed.canonical_paths,
|
|
412
|
+
verification_types: parsed.verification_types,
|
|
413
|
+
detection: parsed.detection
|
|
328
414
|
};
|
|
329
415
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
330
416
|
_config.cloud = {
|
|
@@ -19,7 +19,8 @@ var DomainConfigSchema = z.object({
|
|
|
19
19
|
});
|
|
20
20
|
var PatternRuleConfigSchema = z.object({
|
|
21
21
|
pattern: z.string().default("**"),
|
|
22
|
-
rules: z.array(z.string()).default([])
|
|
22
|
+
rules: z.array(z.string()).default([]),
|
|
23
|
+
language: z.string().optional()
|
|
23
24
|
});
|
|
24
25
|
var CostModelSchema = z.object({
|
|
25
26
|
input_per_million: z.number(),
|
|
@@ -231,17 +232,59 @@ var PathsConfigSchema = z.object({
|
|
|
231
232
|
components: z.string().optional(),
|
|
232
233
|
hooks: z.string().optional()
|
|
233
234
|
});
|
|
235
|
+
var LanguageFrameworkEntrySchema = z.object({
|
|
236
|
+
framework: z.string().optional(),
|
|
237
|
+
test_framework: z.string().optional(),
|
|
238
|
+
test: z.string().optional(),
|
|
239
|
+
runtime: z.string().optional(),
|
|
240
|
+
orm: z.string().optional(),
|
|
241
|
+
router: z.string().optional(),
|
|
242
|
+
ui: z.string().optional()
|
|
243
|
+
}).passthrough();
|
|
244
|
+
var FrameworkConfigSchema = z.object({
|
|
245
|
+
type: z.string().default("typescript"),
|
|
246
|
+
primary: z.string().optional(),
|
|
247
|
+
router: z.string().default("none"),
|
|
248
|
+
orm: z.string().default("none"),
|
|
249
|
+
ui: z.string().default("none"),
|
|
250
|
+
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
251
|
+
}).passthrough();
|
|
252
|
+
var VerificationEntrySchema = z.object({
|
|
253
|
+
type: z.string().optional(),
|
|
254
|
+
test: z.string().optional(),
|
|
255
|
+
syntax: z.string().optional(),
|
|
256
|
+
lint: z.string().optional(),
|
|
257
|
+
build: z.string().optional()
|
|
258
|
+
}).passthrough();
|
|
259
|
+
var VerificationConfigSchema = z.record(z.string(), VerificationEntrySchema).optional();
|
|
260
|
+
var CanonicalPathsSchema = z.record(z.string(), z.string()).optional();
|
|
261
|
+
var VerificationTypesSchema = z.record(z.string(), z.string()).optional();
|
|
262
|
+
var DetectionRuleEntrySchema = z.object({
|
|
263
|
+
signals: z.array(z.string()).default([]),
|
|
264
|
+
priority: z.number().optional()
|
|
265
|
+
}).passthrough();
|
|
266
|
+
var DetectionConfigSchema = z.object({
|
|
267
|
+
rules: z.record(
|
|
268
|
+
z.string(),
|
|
269
|
+
// language
|
|
270
|
+
z.record(z.string(), DetectionRuleEntrySchema)
|
|
271
|
+
// framework -> rule entry
|
|
272
|
+
).optional(),
|
|
273
|
+
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
274
|
+
disable_builtin: z.boolean().optional()
|
|
275
|
+
}).passthrough().optional();
|
|
234
276
|
var RawConfigSchema = z.object({
|
|
277
|
+
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
235
278
|
project: z.object({
|
|
236
279
|
name: z.string().default("my-project"),
|
|
237
280
|
root: z.string().default("auto")
|
|
238
281
|
}).default({ name: "my-project", root: "auto" }),
|
|
239
|
-
framework:
|
|
240
|
-
type:
|
|
241
|
-
router:
|
|
242
|
-
orm:
|
|
243
|
-
ui:
|
|
244
|
-
})
|
|
282
|
+
framework: FrameworkConfigSchema.default({
|
|
283
|
+
type: "typescript",
|
|
284
|
+
router: "none",
|
|
285
|
+
orm: "none",
|
|
286
|
+
ui: "none"
|
|
287
|
+
}),
|
|
245
288
|
paths: PathsConfigSchema.default({ source: "src", aliases: { "@": "src" } }),
|
|
246
289
|
toolPrefix: z.string().default("massu"),
|
|
247
290
|
dbAccessPattern: z.string().optional(),
|
|
@@ -256,8 +299,13 @@ var RawConfigSchema = z.object({
|
|
|
256
299
|
regression: RegressionConfigSchema,
|
|
257
300
|
cloud: CloudConfigSchema,
|
|
258
301
|
conventions: ConventionsConfigSchema,
|
|
302
|
+
autoLearning: AutoLearningConfigSchema,
|
|
259
303
|
python: PythonConfigSchema,
|
|
260
|
-
|
|
304
|
+
// P2-004 / P2-005 / P2-006 / P2-008: v2 extensions (all optional)
|
|
305
|
+
verification: VerificationConfigSchema,
|
|
306
|
+
canonical_paths: CanonicalPathsSchema,
|
|
307
|
+
verification_types: VerificationTypesSchema,
|
|
308
|
+
detection: DetectionConfigSchema
|
|
261
309
|
}).passthrough();
|
|
262
310
|
var _config = null;
|
|
263
311
|
var _projectRoot = null;
|
|
@@ -301,14 +349,47 @@ function getConfig() {
|
|
|
301
349
|
const content = readFileSync(configPath, "utf-8");
|
|
302
350
|
rawYaml = parseYaml(content) ?? {};
|
|
303
351
|
}
|
|
304
|
-
const
|
|
352
|
+
const result = RawConfigSchema.safeParse(rawYaml);
|
|
353
|
+
if (!result.success) {
|
|
354
|
+
const issues = result.error.issues.map((i) => {
|
|
355
|
+
const path = i.path.length > 0 ? i.path.join(".") : "(root)";
|
|
356
|
+
const received = "received" in i && i.received !== void 0 ? ` (received ${JSON.stringify(i.received)})` : "";
|
|
357
|
+
return ` - ${path}: ${i.message}${received}`;
|
|
358
|
+
}).join("\n");
|
|
359
|
+
throw new Error(
|
|
360
|
+
`Invalid massu.config.yaml at ${configPath}:
|
|
361
|
+
${issues}
|
|
362
|
+
Hint: run \`massu config refresh\` to regenerate a valid config or fix the listed fields manually.`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const parsed = result.data;
|
|
305
366
|
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
|
|
367
|
+
const fw = parsed.framework;
|
|
368
|
+
let router = fw.router;
|
|
369
|
+
let orm = fw.orm;
|
|
370
|
+
let ui = fw.ui;
|
|
371
|
+
if (fw.type === "multi" && fw.primary && fw.languages) {
|
|
372
|
+
const primaryEntry = fw.languages[fw.primary];
|
|
373
|
+
if (primaryEntry) {
|
|
374
|
+
if (router === "none" && primaryEntry.router) router = primaryEntry.router;
|
|
375
|
+
if (orm === "none" && primaryEntry.orm) orm = primaryEntry.orm;
|
|
376
|
+
if (ui === "none" && primaryEntry.ui) ui = primaryEntry.ui;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
306
379
|
_config = {
|
|
380
|
+
schema_version: parsed.schema_version,
|
|
307
381
|
project: {
|
|
308
382
|
name: parsed.project.name,
|
|
309
383
|
root: projectRoot
|
|
310
384
|
},
|
|
311
|
-
framework:
|
|
385
|
+
framework: {
|
|
386
|
+
type: fw.type,
|
|
387
|
+
router,
|
|
388
|
+
orm,
|
|
389
|
+
ui,
|
|
390
|
+
primary: fw.primary,
|
|
391
|
+
languages: fw.languages
|
|
392
|
+
},
|
|
312
393
|
paths: parsed.paths,
|
|
313
394
|
toolPrefix: parsed.toolPrefix,
|
|
314
395
|
dbAccessPattern: parsed.dbAccessPattern,
|
|
@@ -323,7 +404,12 @@ function getConfig() {
|
|
|
323
404
|
regression: parsed.regression,
|
|
324
405
|
cloud: parsed.cloud,
|
|
325
406
|
conventions: parsed.conventions,
|
|
326
|
-
|
|
407
|
+
autoLearning: parsed.autoLearning,
|
|
408
|
+
python: parsed.python,
|
|
409
|
+
verification: parsed.verification,
|
|
410
|
+
canonical_paths: parsed.canonical_paths,
|
|
411
|
+
verification_types: parsed.verification_types,
|
|
412
|
+
detection: parsed.detection
|
|
327
413
|
};
|
|
328
414
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
329
415
|
_config.cloud = {
|
|
@@ -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 = {
|
|
@@ -1666,10 +1752,11 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
|
|
|
1666
1752
|
// src/hooks/post-tool-use.ts
|
|
1667
1753
|
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
1668
1754
|
import { join as join2 } from "path";
|
|
1669
|
-
import { parse as
|
|
1755
|
+
import { parse as parseYaml3 } from "yaml";
|
|
1670
1756
|
|
|
1671
1757
|
// src/memory-file-ingest.ts
|
|
1672
1758
|
import { readFileSync as readFileSync5, existsSync as existsSync6, readdirSync } from "fs";
|
|
1759
|
+
import { parse as parseYaml2 } from "yaml";
|
|
1673
1760
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
1674
1761
|
if (!existsSync6(filePath)) return "skipped";
|
|
1675
1762
|
const content = readFileSync5(filePath, "utf-8");
|
|
@@ -1681,13 +1768,7 @@ function ingestMemoryFile(db, sessionId, filePath) {
|
|
|
1681
1768
|
let confidence;
|
|
1682
1769
|
if (frontmatterMatch) {
|
|
1683
1770
|
try {
|
|
1684
|
-
const fm =
|
|
1685
|
-
for (const line of frontmatterMatch[1].split("\n")) {
|
|
1686
|
-
const sep = line.indexOf(":");
|
|
1687
|
-
if (sep > 0) {
|
|
1688
|
-
fm[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1771
|
+
const fm = parseYaml2(frontmatterMatch[1]);
|
|
1691
1772
|
name = fm.name ?? basename2;
|
|
1692
1773
|
description = fm.description ?? "";
|
|
1693
1774
|
type = fm.type ?? "discovery";
|
|
@@ -1902,7 +1983,7 @@ function readConventions(cwd) {
|
|
|
1902
1983
|
const configPath = join2(projectRoot, "massu.config.yaml");
|
|
1903
1984
|
if (!existsSync7(configPath)) return defaults;
|
|
1904
1985
|
const content = readFileSync6(configPath, "utf-8");
|
|
1905
|
-
const parsed =
|
|
1986
|
+
const parsed = parseYaml3(content);
|
|
1906
1987
|
if (!parsed || typeof parsed !== "object") return defaults;
|
|
1907
1988
|
const conventions = parsed.conventions;
|
|
1908
1989
|
if (!conventions || typeof conventions !== "object") return defaults;
|
|
@@ -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 = {
|