@massu/core 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/commands/README.md +122 -0
- package/commands/massu-deploy.python.md +200 -0
- package/commands/massu-scaffold-page.md +172 -59
- package/commands/massu-scaffold-page.swift.md +121 -0
- package/commands/massu-scaffold-router.python.md +143 -0
- package/dist/cli.js +562 -231
- package/dist/hooks/auto-learning-pipeline.js +8 -4
- package/dist/hooks/classify-failure.js +8 -4
- package/dist/hooks/cost-tracker.js +8 -4
- package/dist/hooks/fix-detector.js +8 -4
- package/dist/hooks/incident-pipeline.js +8 -4
- package/dist/hooks/post-edit-context.js +8 -4
- package/dist/hooks/post-tool-use.js +8 -4
- package/dist/hooks/pre-compact.js +8 -4
- package/dist/hooks/pre-delete-check.js +8 -4
- package/dist/hooks/quality-event.js +8 -4
- package/dist/hooks/rule-enforcement-pipeline.js +8 -4
- package/dist/hooks/session-end.js +8 -4
- package/dist/hooks/session-start.js +20 -7
- package/dist/hooks/user-prompt.js +8 -4
- package/package.json +1 -1
- package/src/cli.ts +6 -0
- package/src/commands/init.ts +89 -4
- package/src/commands/install-commands.ts +366 -42
- package/src/commands/show-template.ts +65 -0
- package/src/config.ts +11 -3
- package/src/detect/index.ts +10 -1
- package/src/detect/migrate.ts +52 -1
- package/src/detect/source-dir-detector.ts +28 -2
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -225,6 +225,7 @@ var PythonConfigSchema = z.object({
|
|
|
225
225
|
var PathsConfigSchema = z.object({
|
|
226
226
|
source: z.string().default("src"),
|
|
227
227
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
228
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
228
229
|
routers: z.string().optional(),
|
|
229
230
|
routerRoot: z.string().optional(),
|
|
230
231
|
pages: z.string().optional(),
|
|
@@ -383,13 +384,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
383
384
|
name: parsed.project.name,
|
|
384
385
|
root: projectRoot
|
|
385
386
|
},
|
|
387
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
388
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
389
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
390
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
391
|
+
// top-level passthrough language blocks.
|
|
386
392
|
framework: {
|
|
387
|
-
|
|
393
|
+
...fw,
|
|
388
394
|
router,
|
|
389
395
|
orm,
|
|
390
|
-
ui
|
|
391
|
-
primary: fw.primary,
|
|
392
|
-
languages: fw.languages
|
|
396
|
+
ui
|
|
393
397
|
},
|
|
394
398
|
paths: parsed.paths,
|
|
395
399
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -224,6 +224,7 @@ var PythonConfigSchema = z.object({
|
|
|
224
224
|
var PathsConfigSchema = z.object({
|
|
225
225
|
source: z.string().default("src"),
|
|
226
226
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
227
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
227
228
|
routers: z.string().optional(),
|
|
228
229
|
routerRoot: z.string().optional(),
|
|
229
230
|
pages: z.string().optional(),
|
|
@@ -382,13 +383,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
382
383
|
name: parsed.project.name,
|
|
383
384
|
root: projectRoot
|
|
384
385
|
},
|
|
386
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
387
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
388
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
389
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
390
|
+
// top-level passthrough language blocks.
|
|
385
391
|
framework: {
|
|
386
|
-
|
|
392
|
+
...fw,
|
|
387
393
|
router,
|
|
388
394
|
orm,
|
|
389
|
-
ui
|
|
390
|
-
primary: fw.primary,
|
|
391
|
-
languages: fw.languages
|
|
395
|
+
ui
|
|
392
396
|
},
|
|
393
397
|
paths: parsed.paths,
|
|
394
398
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -225,6 +225,7 @@ var PythonConfigSchema = z.object({
|
|
|
225
225
|
var PathsConfigSchema = z.object({
|
|
226
226
|
source: z.string().default("src"),
|
|
227
227
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
228
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
228
229
|
routers: z.string().optional(),
|
|
229
230
|
routerRoot: z.string().optional(),
|
|
230
231
|
pages: z.string().optional(),
|
|
@@ -383,13 +384,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
383
384
|
name: parsed.project.name,
|
|
384
385
|
root: projectRoot
|
|
385
386
|
},
|
|
387
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
388
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
389
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
390
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
391
|
+
// top-level passthrough language blocks.
|
|
386
392
|
framework: {
|
|
387
|
-
|
|
393
|
+
...fw,
|
|
388
394
|
router,
|
|
389
395
|
orm,
|
|
390
|
-
ui
|
|
391
|
-
primary: fw.primary,
|
|
392
|
-
languages: fw.languages
|
|
396
|
+
ui
|
|
393
397
|
},
|
|
394
398
|
paths: parsed.paths,
|
|
395
399
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -224,6 +224,7 @@ var PythonConfigSchema = z.object({
|
|
|
224
224
|
var PathsConfigSchema = z.object({
|
|
225
225
|
source: z.string().default("src"),
|
|
226
226
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
227
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
227
228
|
routers: z.string().optional(),
|
|
228
229
|
routerRoot: z.string().optional(),
|
|
229
230
|
pages: z.string().optional(),
|
|
@@ -382,13 +383,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
382
383
|
name: parsed.project.name,
|
|
383
384
|
root: projectRoot
|
|
384
385
|
},
|
|
386
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
387
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
388
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
389
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
390
|
+
// top-level passthrough language blocks.
|
|
385
391
|
framework: {
|
|
386
|
-
|
|
392
|
+
...fw,
|
|
387
393
|
router,
|
|
388
394
|
orm,
|
|
389
|
-
ui
|
|
390
|
-
primary: fw.primary,
|
|
391
|
-
languages: fw.languages
|
|
395
|
+
ui
|
|
392
396
|
},
|
|
393
397
|
paths: parsed.paths,
|
|
394
398
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -6002,6 +6002,7 @@ var PythonConfigSchema = z.object({
|
|
|
6002
6002
|
var PathsConfigSchema = z.object({
|
|
6003
6003
|
source: z.string().default("src"),
|
|
6004
6004
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
6005
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
6005
6006
|
routers: z.string().optional(),
|
|
6006
6007
|
routerRoot: z.string().optional(),
|
|
6007
6008
|
pages: z.string().optional(),
|
|
@@ -6160,13 +6161,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
6160
6161
|
name: parsed.project.name,
|
|
6161
6162
|
root: projectRoot
|
|
6162
6163
|
},
|
|
6164
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
6165
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
6166
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
6167
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
6168
|
+
// top-level passthrough language blocks.
|
|
6163
6169
|
framework: {
|
|
6164
|
-
|
|
6170
|
+
...fw,
|
|
6165
6171
|
router,
|
|
6166
6172
|
orm,
|
|
6167
|
-
ui
|
|
6168
|
-
primary: fw.primary,
|
|
6169
|
-
languages: fw.languages
|
|
6173
|
+
ui
|
|
6170
6174
|
},
|
|
6171
6175
|
paths: parsed.paths,
|
|
6172
6176
|
toolPrefix: parsed.toolPrefix,
|
|
@@ -8263,6 +8267,13 @@ var TEST_DIR_KEYWORDS = ["tests", "test", "__tests__", "spec", "specs"];
|
|
|
8263
8267
|
function extsFor(language) {
|
|
8264
8268
|
return EXTENSIONS[language] ?? [];
|
|
8265
8269
|
}
|
|
8270
|
+
function extsWithFallback(language, fallbackTsForJs) {
|
|
8271
|
+
const base = extsFor(language);
|
|
8272
|
+
if (language === "javascript" && fallbackTsForJs) {
|
|
8273
|
+
return [...base, "ts", "tsx"];
|
|
8274
|
+
}
|
|
8275
|
+
return base;
|
|
8276
|
+
}
|
|
8266
8277
|
function isTestPath(language, path) {
|
|
8267
8278
|
const segments = path.split("/");
|
|
8268
8279
|
for (const seg of segments) {
|
|
@@ -8284,10 +8295,11 @@ function isInsideRoot(root, candidate) {
|
|
|
8284
8295
|
return false;
|
|
8285
8296
|
}
|
|
8286
8297
|
}
|
|
8287
|
-
function detectSourceDirs(projectRoot, languages) {
|
|
8298
|
+
function detectSourceDirs(projectRoot, languages, opts) {
|
|
8299
|
+
const fallbackTsForJs = opts?.fallbackTsForJs ?? false;
|
|
8288
8300
|
const out = {};
|
|
8289
8301
|
for (const lang of languages) {
|
|
8290
|
-
const exts =
|
|
8302
|
+
const exts = extsWithFallback(lang, fallbackTsForJs);
|
|
8291
8303
|
if (exts.length === 0) continue;
|
|
8292
8304
|
const patterns = exts.map((e) => `**/*.${e}`);
|
|
8293
8305
|
let files;
|
|
@@ -8808,8 +8820,9 @@ async function runDetection(projectRoot, overrides) {
|
|
|
8808
8820
|
const languages = Array.from(
|
|
8809
8821
|
new Set(pkg.manifests.map((m) => m.language))
|
|
8810
8822
|
);
|
|
8823
|
+
const fallbackTsForJs = languages.includes("javascript") && !languages.includes("typescript");
|
|
8811
8824
|
const [sourceDirs, monorepo] = await Promise.all([
|
|
8812
|
-
Promise.resolve(detectSourceDirs(projectRoot, languages)),
|
|
8825
|
+
Promise.resolve(detectSourceDirs(projectRoot, languages, { fallbackTsForJs })),
|
|
8813
8826
|
Promise.resolve(detectMonorepo(projectRoot))
|
|
8814
8827
|
]);
|
|
8815
8828
|
const domains = inferDomains(projectRoot, monorepo, sourceDirs);
|
|
@@ -226,6 +226,7 @@ var PythonConfigSchema = z.object({
|
|
|
226
226
|
var PathsConfigSchema = z.object({
|
|
227
227
|
source: z.string().default("src"),
|
|
228
228
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
229
|
+
monorepo_roots: z.array(z.string()).optional(),
|
|
229
230
|
routers: z.string().optional(),
|
|
230
231
|
routerRoot: z.string().optional(),
|
|
231
232
|
pages: z.string().optional(),
|
|
@@ -384,13 +385,16 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
384
385
|
name: parsed.project.name,
|
|
385
386
|
root: projectRoot
|
|
386
387
|
},
|
|
388
|
+
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
389
|
+
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
390
|
+
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
391
|
+
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
392
|
+
// top-level passthrough language blocks.
|
|
387
393
|
framework: {
|
|
388
|
-
|
|
394
|
+
...fw,
|
|
389
395
|
router,
|
|
390
396
|
orm,
|
|
391
|
-
ui
|
|
392
|
-
primary: fw.primary,
|
|
393
|
-
languages: fw.languages
|
|
397
|
+
ui
|
|
394
398
|
},
|
|
395
399
|
paths: parsed.paths,
|
|
396
400
|
toolPrefix: parsed.toolPrefix,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 72 total), 55+ workflow commands, 11 agents, 20+ patterns",
|
|
6
6
|
"main": "src/server.ts",
|
package/src/cli.ts
CHANGED
|
@@ -47,6 +47,11 @@ async function main(): Promise<void> {
|
|
|
47
47
|
await runInstallCommands();
|
|
48
48
|
break;
|
|
49
49
|
}
|
|
50
|
+
case 'show-template': {
|
|
51
|
+
const { runShowTemplate } = await import('./commands/show-template.ts');
|
|
52
|
+
await runShowTemplate(args.slice(1));
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
50
55
|
case 'validate-config': {
|
|
51
56
|
const { runValidateConfig } = await import('./commands/doctor.ts');
|
|
52
57
|
await runValidateConfig();
|
|
@@ -136,6 +141,7 @@ Commands:
|
|
|
136
141
|
doctor Check installation health
|
|
137
142
|
install-hooks Install/update Claude Code hooks
|
|
138
143
|
install-commands Install/update slash commands
|
|
144
|
+
show-template Print the resolved variant of a bundled template (e.g. for diffs)
|
|
139
145
|
validate-config Validate massu.config.yaml (alias: config validate)
|
|
140
146
|
config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
|
|
141
147
|
|
package/src/commands/init.ts
CHANGED
|
@@ -231,7 +231,18 @@ export function detectPython(projectRoot: string): PythonDetection {
|
|
|
231
231
|
// Legacy Config File Generation (preserved for cli.test.ts)
|
|
232
232
|
// ============================================================
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* @deprecated Since @massu/core@1.2.1. Use {@link buildConfigFromDetection}
|
|
236
|
+
* with {@link runDetection} for monorepo-aware path resolution and
|
|
237
|
+
* schema_version=2 output. This path hardcodes `paths.source = 'src'` and
|
|
238
|
+
* cannot emit `paths.monorepo_roots`, so it would roll back on every
|
|
239
|
+
* non-`src/` layout. Kept only for the legacy `cli.test.ts` smoke tests;
|
|
240
|
+
* new callers must use the v2 builder.
|
|
241
|
+
*/
|
|
234
242
|
export function generateConfig(projectRoot: string, framework: FrameworkDetection): boolean {
|
|
243
|
+
console.warn(
|
|
244
|
+
'[@massu/core] generateConfig() is deprecated since 1.2.1 — use buildConfigFromDetection instead. It cannot produce valid configs for monorepos.'
|
|
245
|
+
);
|
|
235
246
|
const configPath = resolve(projectRoot, 'massu.config.yaml');
|
|
236
247
|
|
|
237
248
|
if (existsSync(configPath)) {
|
|
@@ -294,6 +305,37 @@ ${yamlStringify(config)}`;
|
|
|
294
305
|
// V2 Config Builder (detection-driven)
|
|
295
306
|
// ============================================================
|
|
296
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Return the common top-level parent directory across every workspace
|
|
310
|
+
* package. Returns `'.'` when packages span multiple parents (e.g. a repo
|
|
311
|
+
* with both `apps/*` and `packages/*`) — the project root is always a valid
|
|
312
|
+
* paths.source value (see validateWrittenConfig at init.ts:572).
|
|
313
|
+
*/
|
|
314
|
+
function monorepoCommonRoot(
|
|
315
|
+
packages: ReadonlyArray<{ path: string }>
|
|
316
|
+
): string {
|
|
317
|
+
const roots = monorepoDistinctRoots(packages);
|
|
318
|
+
return roots.length === 1 ? roots[0] : '.';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Return the distinct top-level parent directories of every workspace
|
|
323
|
+
* package (e.g. `['apps', 'packages']` when both are present). Sorted for
|
|
324
|
+
* determinism. Excludes root-level ('.') workspaces.
|
|
325
|
+
*/
|
|
326
|
+
function monorepoDistinctRoots(
|
|
327
|
+
packages: ReadonlyArray<{ path: string }>
|
|
328
|
+
): string[] {
|
|
329
|
+
const set = new Set<string>();
|
|
330
|
+
for (const p of packages) {
|
|
331
|
+
const parts = p.path.split('/');
|
|
332
|
+
if (parts.length > 1 && parts[0] !== '' && parts[0] !== '.') {
|
|
333
|
+
set.add(parts[0]);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return [...set].sort();
|
|
337
|
+
}
|
|
338
|
+
|
|
297
339
|
/**
|
|
298
340
|
* Build a schema_version=2 config object from a DetectionResult.
|
|
299
341
|
*
|
|
@@ -364,11 +406,38 @@ export function buildConfigFromDetection(
|
|
|
364
406
|
const legacyUi = (primaryEntry?.ui as string | undefined) ?? 'none';
|
|
365
407
|
|
|
366
408
|
// Determine paths.source from primary language's dominant source dir.
|
|
409
|
+
// P1-003: when the primary language has no detectable source dir AND the
|
|
410
|
+
// repo is a monorepo, fall back to the common parent of workspace packages
|
|
411
|
+
// (e.g. 'apps' for turbo + apps/*, 'packages' for pnpm + packages/*). This
|
|
412
|
+
// prevents the validator from rejecting a nonexistent top-level 'src/' on
|
|
413
|
+
// monorepo shapes where code actually lives under apps/ or packages/.
|
|
367
414
|
let pathsSource = 'src';
|
|
368
415
|
if (primary) {
|
|
369
416
|
const primaryDirs = detection.sourceDirs[primary]?.source_dirs ?? [];
|
|
370
417
|
if (primaryDirs.length > 0) {
|
|
371
418
|
pathsSource = primaryDirs[0];
|
|
419
|
+
} else if (
|
|
420
|
+
detection.monorepo.type !== 'single' &&
|
|
421
|
+
detection.monorepo.packages.length > 0
|
|
422
|
+
) {
|
|
423
|
+
pathsSource = monorepoCommonRoot(detection.monorepo.packages);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// P1-005: emit `paths.monorepo_roots` as the distinct parent directories of
|
|
428
|
+
// every workspace package when this is a monorepo. Optional + additive;
|
|
429
|
+
// v1 consumers ignore it. When detection identified a monorepo type
|
|
430
|
+
// (turbo/nx/pnpm/etc) but no manifested workspace packages were found
|
|
431
|
+
// (e.g. fresh-install fixtures with apps/*/main.py that haven't declared
|
|
432
|
+
// sub-manifests yet), fall back to deriving roots from the resolved
|
|
433
|
+
// paths.source so the field is still accurate for monorepo-aware tools.
|
|
434
|
+
let monorepoRoots: string[] | undefined;
|
|
435
|
+
if (detection.monorepo.type !== 'single') {
|
|
436
|
+
if (detection.monorepo.packages.length > 0) {
|
|
437
|
+
monorepoRoots = monorepoDistinctRoots(detection.monorepo.packages);
|
|
438
|
+
} else if (pathsSource !== 'src' && pathsSource !== '.') {
|
|
439
|
+
// Derive from paths.source when no workspace manifests exist.
|
|
440
|
+
monorepoRoots = [pathsSource];
|
|
372
441
|
}
|
|
373
442
|
}
|
|
374
443
|
|
|
@@ -411,6 +480,14 @@ export function buildConfigFromDetection(
|
|
|
411
480
|
frameworkBlock.languages = languageEntries;
|
|
412
481
|
}
|
|
413
482
|
|
|
483
|
+
const pathsBlock: Record<string, unknown> = {
|
|
484
|
+
source: pathsSource,
|
|
485
|
+
aliases: { '@': pathsSource },
|
|
486
|
+
};
|
|
487
|
+
if (monorepoRoots && monorepoRoots.length > 0) {
|
|
488
|
+
pathsBlock.monorepo_roots = monorepoRoots;
|
|
489
|
+
}
|
|
490
|
+
|
|
414
491
|
const config: Record<string, unknown> = {
|
|
415
492
|
schema_version: 2,
|
|
416
493
|
project: {
|
|
@@ -418,10 +495,7 @@ export function buildConfigFromDetection(
|
|
|
418
495
|
root: 'auto',
|
|
419
496
|
},
|
|
420
497
|
framework: frameworkBlock,
|
|
421
|
-
paths:
|
|
422
|
-
source: pathsSource,
|
|
423
|
-
aliases: { '@': pathsSource },
|
|
424
|
-
},
|
|
498
|
+
paths: pathsBlock,
|
|
425
499
|
toolPrefix: 'massu',
|
|
426
500
|
domains,
|
|
427
501
|
rules: [],
|
|
@@ -588,6 +662,17 @@ export function validateWrittenConfig(
|
|
|
588
662
|
}
|
|
589
663
|
}
|
|
590
664
|
}
|
|
665
|
+
// P2-001: verify paths.monorepo_roots entries exist on disk (parity
|
|
666
|
+
// with paths.source existence check at line 624-631 above).
|
|
667
|
+
const mRoots = (cfg.paths as Record<string, unknown>).monorepo_roots;
|
|
668
|
+
if (Array.isArray(mRoots)) {
|
|
669
|
+
for (const r of mRoots) {
|
|
670
|
+
if (typeof r !== 'string' || r === '.') continue;
|
|
671
|
+
if (!existsSync(resolve(projectRoot, r))) {
|
|
672
|
+
return `paths.monorepo_roots '${r}' does not exist on disk`;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
591
676
|
}
|
|
592
677
|
} catch (err) {
|
|
593
678
|
return err instanceof Error ? err.message : String(err);
|