@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.
Files changed (53) hide show
  1. package/dist/cli.js +11182 -1559
  2. package/dist/hooks/auto-learning-pipeline.js +99 -19
  3. package/dist/hooks/classify-failure.js +99 -19
  4. package/dist/hooks/cost-tracker.js +97 -11
  5. package/dist/hooks/fix-detector.js +99 -19
  6. package/dist/hooks/incident-pipeline.js +97 -11
  7. package/dist/hooks/post-edit-context.js +97 -11
  8. package/dist/hooks/post-tool-use.js +101 -20
  9. package/dist/hooks/pre-compact.js +97 -11
  10. package/dist/hooks/pre-delete-check.js +97 -11
  11. package/dist/hooks/quality-event.js +97 -11
  12. package/dist/hooks/rule-enforcement-pipeline.js +97 -11
  13. package/dist/hooks/session-end.js +97 -11
  14. package/dist/hooks/session-start.js +8803 -782
  15. package/dist/hooks/user-prompt.js +98 -43
  16. package/package.json +13 -3
  17. package/reference/hook-execution-order.md +17 -25
  18. package/src/cli.ts +81 -2
  19. package/src/commands/config-check-drift.ts +132 -0
  20. package/src/commands/config-refresh.ts +224 -0
  21. package/src/commands/config-upgrade.ts +126 -0
  22. package/src/commands/doctor.ts +1 -29
  23. package/src/commands/init.ts +756 -216
  24. package/src/config.ts +168 -12
  25. package/src/detect/domain-inferrer.ts +142 -0
  26. package/src/detect/drift.ts +199 -0
  27. package/src/detect/framework-detector.ts +281 -0
  28. package/src/detect/index.ts +174 -0
  29. package/src/detect/migrate.ts +278 -0
  30. package/src/detect/monorepo-detector.ts +347 -0
  31. package/src/detect/package-detector.ts +728 -0
  32. package/src/detect/source-dir-detector.ts +264 -0
  33. package/src/detect/vr-command-map.ts +167 -0
  34. package/src/hooks/auto-learning-pipeline.ts +2 -2
  35. package/src/hooks/classify-failure.ts +2 -2
  36. package/src/hooks/fix-detector.ts +2 -2
  37. package/src/hooks/session-start.ts +43 -2
  38. package/src/hooks/user-prompt.ts +1 -21
  39. package/src/knowledge-indexer.ts +1 -1
  40. package/src/license.ts +1 -2
  41. package/src/memory-db.ts +0 -5
  42. package/src/memory-file-ingest.ts +6 -13
  43. package/src/tools.ts +0 -8
  44. package/templates/multi-runtime/massu.config.yaml +80 -0
  45. package/templates/python-django/massu.config.yaml +51 -0
  46. package/templates/python-fastapi/massu.config.yaml +50 -0
  47. package/templates/rust-actix/massu.config.yaml +38 -0
  48. package/templates/swift-ios/massu.config.yaml +37 -0
  49. package/templates/ts-nestjs/massu.config.yaml +43 -0
  50. package/templates/ts-nextjs/massu.config.yaml +43 -0
  51. package/README.md +0 -40
  52. package/src/claude-md-templates.ts +0 -342
  53. 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: 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" }),
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
- autoLearning: AutoLearningConfigSchema
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 parsed = RawConfigSchema.parse(rawYaml);
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: parsed.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
- python: parsed.python
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 = __require("fs").statSync(fullPath);
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: 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" }),
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
- autoLearning: AutoLearningConfigSchema
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 parsed = RawConfigSchema.parse(rawYaml);
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: parsed.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
- python: parsed.python
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
- __require("fs").writeFileSync(dedupeMarker, "1");
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: z.object({
242
- type: z.string().default("typescript"),
243
- router: z.string().default("none"),
244
- orm: z.string().default("none"),
245
- ui: z.string().default("none")
246
- }).default({ type: "typescript", router: "none", orm: "none", ui: "none" }),
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
- autoLearning: AutoLearningConfigSchema
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 parsed = RawConfigSchema.parse(rawYaml);
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: parsed.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
- python: parsed.python
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: 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" }),
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
- autoLearning: AutoLearningConfigSchema
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 parsed = RawConfigSchema.parse(rawYaml);
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: parsed.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
- python: parsed.python
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 = __require("fs").readFileSync(flagPath, "utf-8").split("\n").filter(Boolean);
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.`