@massu/core 1.3.0 → 1.4.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/commands/README.md +23 -11
- package/commands/massu-deploy.python-docker.md +170 -0
- package/commands/massu-deploy.python-fly.md +189 -0
- package/commands/massu-deploy.python-launchd.md +144 -0
- package/commands/massu-deploy.python-systemd.md +163 -0
- package/commands/massu-scaffold-page.swift.md +10 -10
- package/commands/massu-scaffold-router.python-django.md +153 -0
- package/commands/massu-scaffold-router.python-fastapi.md +145 -0
- package/dist/cli.js +9914 -4133
- package/dist/hooks/auto-learning-pipeline.js +45 -2
- package/dist/hooks/classify-failure.js +45 -2
- package/dist/hooks/cost-tracker.js +45 -2
- package/dist/hooks/fix-detector.js +45 -2
- package/dist/hooks/incident-pipeline.js +45 -2
- package/dist/hooks/post-edit-context.js +45 -2
- package/dist/hooks/post-tool-use.js +45 -2
- package/dist/hooks/pre-compact.js +45 -2
- package/dist/hooks/pre-delete-check.js +45 -2
- package/dist/hooks/quality-event.js +45 -2
- package/dist/hooks/rule-enforcement-pipeline.js +45 -2
- package/dist/hooks/session-end.js +45 -2
- package/dist/hooks/session-start.js +4790 -406
- package/dist/hooks/user-prompt.js +45 -2
- package/package.json +13 -4
- package/src/cli.ts +22 -2
- package/src/commands/config-refresh.ts +91 -23
- package/src/commands/init.ts +131 -24
- package/src/commands/install-commands.ts +142 -26
- package/src/commands/refresh-log.ts +37 -0
- package/src/commands/template-engine.ts +260 -0
- package/src/commands/watch.ts +430 -0
- package/src/config.ts +71 -0
- package/src/detect/adapters/nextjs-trpc.ts +166 -0
- package/src/detect/adapters/parse-guard.ts +133 -0
- package/src/detect/adapters/python-django.ts +208 -0
- package/src/detect/adapters/python-fastapi.ts +223 -0
- package/src/detect/adapters/query-helpers.ts +170 -0
- package/src/detect/adapters/runner.ts +252 -0
- package/src/detect/adapters/swift-swiftui.ts +171 -0
- package/src/detect/adapters/tree-sitter-loader.ts +467 -0
- package/src/detect/adapters/types.ts +173 -0
- package/src/detect/codebase-introspector.ts +190 -0
- package/src/detect/index.ts +28 -2
- package/src/detect/migrate.ts +4 -4
- package/src/detect/regex-fallback.ts +449 -0
- package/src/hooks/session-start.ts +94 -3
- package/src/lib/gitToplevel.ts +22 -0
- package/src/lib/installLock.ts +179 -0
- package/src/lib/pidLiveness.ts +67 -0
- package/src/lsp/auto-detect.ts +98 -0
- package/src/lsp/client.ts +776 -0
- package/src/lsp/enrich.ts +127 -0
- package/src/lsp/types.ts +221 -0
- package/src/watch/daemon.ts +385 -0
- package/src/watch/lockfile-detector.ts +65 -0
- package/src/watch/paths.ts +279 -0
- package/src/watch/state.ts +178 -0
|
@@ -252,6 +252,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
252
252
|
ui: z.string().default("none"),
|
|
253
253
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
254
254
|
}).passthrough();
|
|
255
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
255
256
|
var VerificationEntrySchema = z.object({
|
|
256
257
|
type: z.string().optional(),
|
|
257
258
|
test: z.string().optional(),
|
|
@@ -276,6 +277,39 @@ var DetectionConfigSchema = z.object({
|
|
|
276
277
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
277
278
|
disable_builtin: z.boolean().optional()
|
|
278
279
|
}).passthrough().optional();
|
|
280
|
+
var WatchConfigSchema = z.object({
|
|
281
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
282
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
283
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
284
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
285
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
286
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
287
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
288
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
289
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
290
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
291
|
+
// need root-level watching.
|
|
292
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
293
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
294
|
+
}).passthrough().optional();
|
|
295
|
+
var LSPConfigSchema = z.object({
|
|
296
|
+
enabled: z.boolean().default(false),
|
|
297
|
+
servers: z.array(z.object({
|
|
298
|
+
language: z.string(),
|
|
299
|
+
command: z.string(),
|
|
300
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
301
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
302
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
303
|
+
allow_setuid: z.boolean().default(false),
|
|
304
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
305
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
306
|
+
// Set to 0 to disable the watchdog for this server.
|
|
307
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
308
|
+
})).default([]),
|
|
309
|
+
autoDetect: z.object({
|
|
310
|
+
viaPortScan: z.boolean().default(false)
|
|
311
|
+
}).optional()
|
|
312
|
+
}).passthrough();
|
|
279
313
|
var RawConfigSchema = z.object({
|
|
280
314
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
281
315
|
project: z.object({
|
|
@@ -308,7 +342,13 @@ var RawConfigSchema = z.object({
|
|
|
308
342
|
verification: VerificationConfigSchema,
|
|
309
343
|
canonical_paths: CanonicalPathsSchema,
|
|
310
344
|
verification_types: VerificationTypesSchema,
|
|
311
|
-
detection: DetectionConfigSchema
|
|
345
|
+
detection: DetectionConfigSchema,
|
|
346
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
347
|
+
detected: DetectedConfigSchema,
|
|
348
|
+
// Plan 3a: file-watcher daemon tunables
|
|
349
|
+
watch: WatchConfigSchema,
|
|
350
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
351
|
+
lsp: LSPConfigSchema.optional()
|
|
312
352
|
}).passthrough();
|
|
313
353
|
var _config = null;
|
|
314
354
|
var _projectRoot = null;
|
|
@@ -415,7 +455,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
415
455
|
verification: parsed.verification,
|
|
416
456
|
canonical_paths: parsed.canonical_paths,
|
|
417
457
|
verification_types: parsed.verification_types,
|
|
418
|
-
detection: parsed.detection
|
|
458
|
+
detection: parsed.detection,
|
|
459
|
+
detected: parsed.detected,
|
|
460
|
+
watch: parsed.watch,
|
|
461
|
+
lsp: parsed.lsp
|
|
419
462
|
};
|
|
420
463
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
421
464
|
_config.cloud = {
|
|
@@ -252,6 +252,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
252
252
|
ui: z.string().default("none"),
|
|
253
253
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
254
254
|
}).passthrough();
|
|
255
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
255
256
|
var VerificationEntrySchema = z.object({
|
|
256
257
|
type: z.string().optional(),
|
|
257
258
|
test: z.string().optional(),
|
|
@@ -276,6 +277,39 @@ var DetectionConfigSchema = z.object({
|
|
|
276
277
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
277
278
|
disable_builtin: z.boolean().optional()
|
|
278
279
|
}).passthrough().optional();
|
|
280
|
+
var WatchConfigSchema = z.object({
|
|
281
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
282
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
283
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
284
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
285
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
286
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
287
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
288
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
289
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
290
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
291
|
+
// need root-level watching.
|
|
292
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
293
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
294
|
+
}).passthrough().optional();
|
|
295
|
+
var LSPConfigSchema = z.object({
|
|
296
|
+
enabled: z.boolean().default(false),
|
|
297
|
+
servers: z.array(z.object({
|
|
298
|
+
language: z.string(),
|
|
299
|
+
command: z.string(),
|
|
300
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
301
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
302
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
303
|
+
allow_setuid: z.boolean().default(false),
|
|
304
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
305
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
306
|
+
// Set to 0 to disable the watchdog for this server.
|
|
307
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
308
|
+
})).default([]),
|
|
309
|
+
autoDetect: z.object({
|
|
310
|
+
viaPortScan: z.boolean().default(false)
|
|
311
|
+
}).optional()
|
|
312
|
+
}).passthrough();
|
|
279
313
|
var RawConfigSchema = z.object({
|
|
280
314
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
281
315
|
project: z.object({
|
|
@@ -308,7 +342,13 @@ var RawConfigSchema = z.object({
|
|
|
308
342
|
verification: VerificationConfigSchema,
|
|
309
343
|
canonical_paths: CanonicalPathsSchema,
|
|
310
344
|
verification_types: VerificationTypesSchema,
|
|
311
|
-
detection: DetectionConfigSchema
|
|
345
|
+
detection: DetectionConfigSchema,
|
|
346
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
347
|
+
detected: DetectedConfigSchema,
|
|
348
|
+
// Plan 3a: file-watcher daemon tunables
|
|
349
|
+
watch: WatchConfigSchema,
|
|
350
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
351
|
+
lsp: LSPConfigSchema.optional()
|
|
312
352
|
}).passthrough();
|
|
313
353
|
var _config = null;
|
|
314
354
|
var _projectRoot = null;
|
|
@@ -415,7 +455,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
415
455
|
verification: parsed.verification,
|
|
416
456
|
canonical_paths: parsed.canonical_paths,
|
|
417
457
|
verification_types: parsed.verification_types,
|
|
418
|
-
detection: parsed.detection
|
|
458
|
+
detection: parsed.detection,
|
|
459
|
+
detected: parsed.detected,
|
|
460
|
+
watch: parsed.watch,
|
|
461
|
+
lsp: parsed.lsp
|
|
419
462
|
};
|
|
420
463
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
421
464
|
_config.cloud = {
|
|
@@ -252,6 +252,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
252
252
|
ui: z.string().default("none"),
|
|
253
253
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
254
254
|
}).passthrough();
|
|
255
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
255
256
|
var VerificationEntrySchema = z.object({
|
|
256
257
|
type: z.string().optional(),
|
|
257
258
|
test: z.string().optional(),
|
|
@@ -276,6 +277,39 @@ var DetectionConfigSchema = z.object({
|
|
|
276
277
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
277
278
|
disable_builtin: z.boolean().optional()
|
|
278
279
|
}).passthrough().optional();
|
|
280
|
+
var WatchConfigSchema = z.object({
|
|
281
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
282
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
283
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
284
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
285
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
286
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
287
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
288
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
289
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
290
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
291
|
+
// need root-level watching.
|
|
292
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
293
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
294
|
+
}).passthrough().optional();
|
|
295
|
+
var LSPConfigSchema = z.object({
|
|
296
|
+
enabled: z.boolean().default(false),
|
|
297
|
+
servers: z.array(z.object({
|
|
298
|
+
language: z.string(),
|
|
299
|
+
command: z.string(),
|
|
300
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
301
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
302
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
303
|
+
allow_setuid: z.boolean().default(false),
|
|
304
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
305
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
306
|
+
// Set to 0 to disable the watchdog for this server.
|
|
307
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
308
|
+
})).default([]),
|
|
309
|
+
autoDetect: z.object({
|
|
310
|
+
viaPortScan: z.boolean().default(false)
|
|
311
|
+
}).optional()
|
|
312
|
+
}).passthrough();
|
|
279
313
|
var RawConfigSchema = z.object({
|
|
280
314
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
281
315
|
project: z.object({
|
|
@@ -308,7 +342,13 @@ var RawConfigSchema = z.object({
|
|
|
308
342
|
verification: VerificationConfigSchema,
|
|
309
343
|
canonical_paths: CanonicalPathsSchema,
|
|
310
344
|
verification_types: VerificationTypesSchema,
|
|
311
|
-
detection: DetectionConfigSchema
|
|
345
|
+
detection: DetectionConfigSchema,
|
|
346
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
347
|
+
detected: DetectedConfigSchema,
|
|
348
|
+
// Plan 3a: file-watcher daemon tunables
|
|
349
|
+
watch: WatchConfigSchema,
|
|
350
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
351
|
+
lsp: LSPConfigSchema.optional()
|
|
312
352
|
}).passthrough();
|
|
313
353
|
var _config = null;
|
|
314
354
|
var _projectRoot = null;
|
|
@@ -415,7 +455,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
415
455
|
verification: parsed.verification,
|
|
416
456
|
canonical_paths: parsed.canonical_paths,
|
|
417
457
|
verification_types: parsed.verification_types,
|
|
418
|
-
detection: parsed.detection
|
|
458
|
+
detection: parsed.detection,
|
|
459
|
+
detected: parsed.detected,
|
|
460
|
+
watch: parsed.watch,
|
|
461
|
+
lsp: parsed.lsp
|
|
419
462
|
};
|
|
420
463
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
421
464
|
_config.cloud = {
|
|
@@ -252,6 +252,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
252
252
|
ui: z.string().default("none"),
|
|
253
253
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
254
254
|
}).passthrough();
|
|
255
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
255
256
|
var VerificationEntrySchema = z.object({
|
|
256
257
|
type: z.string().optional(),
|
|
257
258
|
test: z.string().optional(),
|
|
@@ -276,6 +277,39 @@ var DetectionConfigSchema = z.object({
|
|
|
276
277
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
277
278
|
disable_builtin: z.boolean().optional()
|
|
278
279
|
}).passthrough().optional();
|
|
280
|
+
var WatchConfigSchema = z.object({
|
|
281
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
282
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
283
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
284
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
285
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
286
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
287
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
288
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
289
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
290
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
291
|
+
// need root-level watching.
|
|
292
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
293
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
294
|
+
}).passthrough().optional();
|
|
295
|
+
var LSPConfigSchema = z.object({
|
|
296
|
+
enabled: z.boolean().default(false),
|
|
297
|
+
servers: z.array(z.object({
|
|
298
|
+
language: z.string(),
|
|
299
|
+
command: z.string(),
|
|
300
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
301
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
302
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
303
|
+
allow_setuid: z.boolean().default(false),
|
|
304
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
305
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
306
|
+
// Set to 0 to disable the watchdog for this server.
|
|
307
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
308
|
+
})).default([]),
|
|
309
|
+
autoDetect: z.object({
|
|
310
|
+
viaPortScan: z.boolean().default(false)
|
|
311
|
+
}).optional()
|
|
312
|
+
}).passthrough();
|
|
279
313
|
var RawConfigSchema = z.object({
|
|
280
314
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
281
315
|
project: z.object({
|
|
@@ -308,7 +342,13 @@ var RawConfigSchema = z.object({
|
|
|
308
342
|
verification: VerificationConfigSchema,
|
|
309
343
|
canonical_paths: CanonicalPathsSchema,
|
|
310
344
|
verification_types: VerificationTypesSchema,
|
|
311
|
-
detection: DetectionConfigSchema
|
|
345
|
+
detection: DetectionConfigSchema,
|
|
346
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
347
|
+
detected: DetectedConfigSchema,
|
|
348
|
+
// Plan 3a: file-watcher daemon tunables
|
|
349
|
+
watch: WatchConfigSchema,
|
|
350
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
351
|
+
lsp: LSPConfigSchema.optional()
|
|
312
352
|
}).passthrough();
|
|
313
353
|
var _config = null;
|
|
314
354
|
var _projectRoot = null;
|
|
@@ -415,7 +455,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
415
455
|
verification: parsed.verification,
|
|
416
456
|
canonical_paths: parsed.canonical_paths,
|
|
417
457
|
verification_types: parsed.verification_types,
|
|
418
|
-
detection: parsed.detection
|
|
458
|
+
detection: parsed.detection,
|
|
459
|
+
detected: parsed.detected,
|
|
460
|
+
watch: parsed.watch,
|
|
461
|
+
lsp: parsed.lsp
|
|
419
462
|
};
|
|
420
463
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
421
464
|
_config.cloud = {
|
|
@@ -251,6 +251,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
251
251
|
ui: z.string().default("none"),
|
|
252
252
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
253
253
|
}).passthrough();
|
|
254
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
254
255
|
var VerificationEntrySchema = z.object({
|
|
255
256
|
type: z.string().optional(),
|
|
256
257
|
test: z.string().optional(),
|
|
@@ -275,6 +276,39 @@ var DetectionConfigSchema = z.object({
|
|
|
275
276
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
276
277
|
disable_builtin: z.boolean().optional()
|
|
277
278
|
}).passthrough().optional();
|
|
279
|
+
var WatchConfigSchema = z.object({
|
|
280
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
281
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
282
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
283
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
284
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
285
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
286
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
287
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
288
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
289
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
290
|
+
// need root-level watching.
|
|
291
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
292
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
293
|
+
}).passthrough().optional();
|
|
294
|
+
var LSPConfigSchema = z.object({
|
|
295
|
+
enabled: z.boolean().default(false),
|
|
296
|
+
servers: z.array(z.object({
|
|
297
|
+
language: z.string(),
|
|
298
|
+
command: z.string(),
|
|
299
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
300
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
301
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
302
|
+
allow_setuid: z.boolean().default(false),
|
|
303
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
304
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
305
|
+
// Set to 0 to disable the watchdog for this server.
|
|
306
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
307
|
+
})).default([]),
|
|
308
|
+
autoDetect: z.object({
|
|
309
|
+
viaPortScan: z.boolean().default(false)
|
|
310
|
+
}).optional()
|
|
311
|
+
}).passthrough();
|
|
278
312
|
var RawConfigSchema = z.object({
|
|
279
313
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
280
314
|
project: z.object({
|
|
@@ -307,7 +341,13 @@ var RawConfigSchema = z.object({
|
|
|
307
341
|
verification: VerificationConfigSchema,
|
|
308
342
|
canonical_paths: CanonicalPathsSchema,
|
|
309
343
|
verification_types: VerificationTypesSchema,
|
|
310
|
-
detection: DetectionConfigSchema
|
|
344
|
+
detection: DetectionConfigSchema,
|
|
345
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
346
|
+
detected: DetectedConfigSchema,
|
|
347
|
+
// Plan 3a: file-watcher daemon tunables
|
|
348
|
+
watch: WatchConfigSchema,
|
|
349
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
350
|
+
lsp: LSPConfigSchema.optional()
|
|
311
351
|
}).passthrough();
|
|
312
352
|
var _config = null;
|
|
313
353
|
var _projectRoot = null;
|
|
@@ -414,7 +454,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
414
454
|
verification: parsed.verification,
|
|
415
455
|
canonical_paths: parsed.canonical_paths,
|
|
416
456
|
verification_types: parsed.verification_types,
|
|
417
|
-
detection: parsed.detection
|
|
457
|
+
detection: parsed.detection,
|
|
458
|
+
detected: parsed.detected,
|
|
459
|
+
watch: parsed.watch,
|
|
460
|
+
lsp: parsed.lsp
|
|
418
461
|
};
|
|
419
462
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
420
463
|
_config.cloud = {
|
|
@@ -250,6 +250,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
250
250
|
ui: z.string().default("none"),
|
|
251
251
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
252
252
|
}).passthrough();
|
|
253
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
253
254
|
var VerificationEntrySchema = z.object({
|
|
254
255
|
type: z.string().optional(),
|
|
255
256
|
test: z.string().optional(),
|
|
@@ -274,6 +275,39 @@ var DetectionConfigSchema = z.object({
|
|
|
274
275
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
275
276
|
disable_builtin: z.boolean().optional()
|
|
276
277
|
}).passthrough().optional();
|
|
278
|
+
var WatchConfigSchema = z.object({
|
|
279
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
280
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
281
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
282
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
283
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
284
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
285
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
286
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
287
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
288
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
289
|
+
// need root-level watching.
|
|
290
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
291
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
292
|
+
}).passthrough().optional();
|
|
293
|
+
var LSPConfigSchema = z.object({
|
|
294
|
+
enabled: z.boolean().default(false),
|
|
295
|
+
servers: z.array(z.object({
|
|
296
|
+
language: z.string(),
|
|
297
|
+
command: z.string(),
|
|
298
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
299
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
300
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
301
|
+
allow_setuid: z.boolean().default(false),
|
|
302
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
303
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
304
|
+
// Set to 0 to disable the watchdog for this server.
|
|
305
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
306
|
+
})).default([]),
|
|
307
|
+
autoDetect: z.object({
|
|
308
|
+
viaPortScan: z.boolean().default(false)
|
|
309
|
+
}).optional()
|
|
310
|
+
}).passthrough();
|
|
277
311
|
var RawConfigSchema = z.object({
|
|
278
312
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
279
313
|
project: z.object({
|
|
@@ -306,7 +340,13 @@ var RawConfigSchema = z.object({
|
|
|
306
340
|
verification: VerificationConfigSchema,
|
|
307
341
|
canonical_paths: CanonicalPathsSchema,
|
|
308
342
|
verification_types: VerificationTypesSchema,
|
|
309
|
-
detection: DetectionConfigSchema
|
|
343
|
+
detection: DetectionConfigSchema,
|
|
344
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
345
|
+
detected: DetectedConfigSchema,
|
|
346
|
+
// Plan 3a: file-watcher daemon tunables
|
|
347
|
+
watch: WatchConfigSchema,
|
|
348
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
349
|
+
lsp: LSPConfigSchema.optional()
|
|
310
350
|
}).passthrough();
|
|
311
351
|
var _config = null;
|
|
312
352
|
var _projectRoot = null;
|
|
@@ -413,7 +453,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
413
453
|
verification: parsed.verification,
|
|
414
454
|
canonical_paths: parsed.canonical_paths,
|
|
415
455
|
verification_types: parsed.verification_types,
|
|
416
|
-
detection: parsed.detection
|
|
456
|
+
detection: parsed.detection,
|
|
457
|
+
detected: parsed.detected,
|
|
458
|
+
watch: parsed.watch,
|
|
459
|
+
lsp: parsed.lsp
|
|
417
460
|
};
|
|
418
461
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
419
462
|
_config.cloud = {
|
|
@@ -252,6 +252,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
252
252
|
ui: z.string().default("none"),
|
|
253
253
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
254
254
|
}).passthrough();
|
|
255
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
255
256
|
var VerificationEntrySchema = z.object({
|
|
256
257
|
type: z.string().optional(),
|
|
257
258
|
test: z.string().optional(),
|
|
@@ -276,6 +277,39 @@ var DetectionConfigSchema = z.object({
|
|
|
276
277
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
277
278
|
disable_builtin: z.boolean().optional()
|
|
278
279
|
}).passthrough().optional();
|
|
280
|
+
var WatchConfigSchema = z.object({
|
|
281
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
282
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
283
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
284
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
285
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
286
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
287
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
288
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
289
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
290
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
291
|
+
// need root-level watching.
|
|
292
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
293
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
294
|
+
}).passthrough().optional();
|
|
295
|
+
var LSPConfigSchema = z.object({
|
|
296
|
+
enabled: z.boolean().default(false),
|
|
297
|
+
servers: z.array(z.object({
|
|
298
|
+
language: z.string(),
|
|
299
|
+
command: z.string(),
|
|
300
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
301
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
302
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
303
|
+
allow_setuid: z.boolean().default(false),
|
|
304
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
305
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
306
|
+
// Set to 0 to disable the watchdog for this server.
|
|
307
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
308
|
+
})).default([]),
|
|
309
|
+
autoDetect: z.object({
|
|
310
|
+
viaPortScan: z.boolean().default(false)
|
|
311
|
+
}).optional()
|
|
312
|
+
}).passthrough();
|
|
279
313
|
var RawConfigSchema = z.object({
|
|
280
314
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
281
315
|
project: z.object({
|
|
@@ -308,7 +342,13 @@ var RawConfigSchema = z.object({
|
|
|
308
342
|
verification: VerificationConfigSchema,
|
|
309
343
|
canonical_paths: CanonicalPathsSchema,
|
|
310
344
|
verification_types: VerificationTypesSchema,
|
|
311
|
-
detection: DetectionConfigSchema
|
|
345
|
+
detection: DetectionConfigSchema,
|
|
346
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
347
|
+
detected: DetectedConfigSchema,
|
|
348
|
+
// Plan 3a: file-watcher daemon tunables
|
|
349
|
+
watch: WatchConfigSchema,
|
|
350
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
351
|
+
lsp: LSPConfigSchema.optional()
|
|
312
352
|
}).passthrough();
|
|
313
353
|
var _config = null;
|
|
314
354
|
var _projectRoot = null;
|
|
@@ -415,7 +455,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
415
455
|
verification: parsed.verification,
|
|
416
456
|
canonical_paths: parsed.canonical_paths,
|
|
417
457
|
verification_types: parsed.verification_types,
|
|
418
|
-
detection: parsed.detection
|
|
458
|
+
detection: parsed.detection,
|
|
459
|
+
detected: parsed.detected,
|
|
460
|
+
watch: parsed.watch,
|
|
461
|
+
lsp: parsed.lsp
|
|
419
462
|
};
|
|
420
463
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
421
464
|
_config.cloud = {
|
|
@@ -252,6 +252,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
252
252
|
ui: z.string().default("none"),
|
|
253
253
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
254
254
|
}).passthrough();
|
|
255
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
255
256
|
var VerificationEntrySchema = z.object({
|
|
256
257
|
type: z.string().optional(),
|
|
257
258
|
test: z.string().optional(),
|
|
@@ -276,6 +277,39 @@ var DetectionConfigSchema = z.object({
|
|
|
276
277
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
277
278
|
disable_builtin: z.boolean().optional()
|
|
278
279
|
}).passthrough().optional();
|
|
280
|
+
var WatchConfigSchema = z.object({
|
|
281
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
282
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
283
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
284
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
285
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
286
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
287
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
288
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
289
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
290
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
291
|
+
// need root-level watching.
|
|
292
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
293
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
294
|
+
}).passthrough().optional();
|
|
295
|
+
var LSPConfigSchema = z.object({
|
|
296
|
+
enabled: z.boolean().default(false),
|
|
297
|
+
servers: z.array(z.object({
|
|
298
|
+
language: z.string(),
|
|
299
|
+
command: z.string(),
|
|
300
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
301
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
302
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
303
|
+
allow_setuid: z.boolean().default(false),
|
|
304
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
305
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
306
|
+
// Set to 0 to disable the watchdog for this server.
|
|
307
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
308
|
+
})).default([]),
|
|
309
|
+
autoDetect: z.object({
|
|
310
|
+
viaPortScan: z.boolean().default(false)
|
|
311
|
+
}).optional()
|
|
312
|
+
}).passthrough();
|
|
279
313
|
var RawConfigSchema = z.object({
|
|
280
314
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
281
315
|
project: z.object({
|
|
@@ -308,7 +342,13 @@ var RawConfigSchema = z.object({
|
|
|
308
342
|
verification: VerificationConfigSchema,
|
|
309
343
|
canonical_paths: CanonicalPathsSchema,
|
|
310
344
|
verification_types: VerificationTypesSchema,
|
|
311
|
-
detection: DetectionConfigSchema
|
|
345
|
+
detection: DetectionConfigSchema,
|
|
346
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
347
|
+
detected: DetectedConfigSchema,
|
|
348
|
+
// Plan 3a: file-watcher daemon tunables
|
|
349
|
+
watch: WatchConfigSchema,
|
|
350
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
351
|
+
lsp: LSPConfigSchema.optional()
|
|
312
352
|
}).passthrough();
|
|
313
353
|
var _config = null;
|
|
314
354
|
var _projectRoot = null;
|
|
@@ -415,7 +455,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
415
455
|
verification: parsed.verification,
|
|
416
456
|
canonical_paths: parsed.canonical_paths,
|
|
417
457
|
verification_types: parsed.verification_types,
|
|
418
|
-
detection: parsed.detection
|
|
458
|
+
detection: parsed.detection,
|
|
459
|
+
detected: parsed.detected,
|
|
460
|
+
watch: parsed.watch,
|
|
461
|
+
lsp: parsed.lsp
|
|
419
462
|
};
|
|
420
463
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
421
464
|
_config.cloud = {
|
|
@@ -251,6 +251,7 @@ var FrameworkConfigSchema = z.object({
|
|
|
251
251
|
ui: z.string().default("none"),
|
|
252
252
|
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional()
|
|
253
253
|
}).passthrough();
|
|
254
|
+
var DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
254
255
|
var VerificationEntrySchema = z.object({
|
|
255
256
|
type: z.string().optional(),
|
|
256
257
|
test: z.string().optional(),
|
|
@@ -275,6 +276,39 @@ var DetectionConfigSchema = z.object({
|
|
|
275
276
|
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
276
277
|
disable_builtin: z.boolean().optional()
|
|
277
278
|
}).passthrough().optional();
|
|
279
|
+
var WatchConfigSchema = z.object({
|
|
280
|
+
debounce_ms: z.number().int().positive().default(3e3),
|
|
281
|
+
storm_threshold: z.number().int().positive().default(50),
|
|
282
|
+
deep_storm_threshold: z.number().int().positive().default(500),
|
|
283
|
+
hard_timeout_ms: z.number().int().positive().default(3e5),
|
|
284
|
+
scope: z.enum(["paths", "full"]).default("paths"),
|
|
285
|
+
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
286
|
+
// exceeds this many files. Prevents the misconfig pattern where
|
|
287
|
+
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
288
|
+
// file tree, producing 30-100% steady CPU. Override via
|
|
289
|
+
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
290
|
+
// need root-level watching.
|
|
291
|
+
max_watched_files: z.number().int().positive().default(1e4),
|
|
292
|
+
paths_full_root_opt_in: z.boolean().default(false)
|
|
293
|
+
}).passthrough().optional();
|
|
294
|
+
var LSPConfigSchema = z.object({
|
|
295
|
+
enabled: z.boolean().default(false),
|
|
296
|
+
servers: z.array(z.object({
|
|
297
|
+
language: z.string(),
|
|
298
|
+
command: z.string(),
|
|
299
|
+
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
300
|
+
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
301
|
+
// unless this is true. Decision is auditable in the YAML.
|
|
302
|
+
allow_setuid: z.boolean().default(false),
|
|
303
|
+
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
304
|
+
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
305
|
+
// Set to 0 to disable the watchdog for this server.
|
|
306
|
+
max_rss_mb: z.number().int().nonnegative().default(1024)
|
|
307
|
+
})).default([]),
|
|
308
|
+
autoDetect: z.object({
|
|
309
|
+
viaPortScan: z.boolean().default(false)
|
|
310
|
+
}).optional()
|
|
311
|
+
}).passthrough();
|
|
278
312
|
var RawConfigSchema = z.object({
|
|
279
313
|
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
280
314
|
project: z.object({
|
|
@@ -307,7 +341,13 @@ var RawConfigSchema = z.object({
|
|
|
307
341
|
verification: VerificationConfigSchema,
|
|
308
342
|
canonical_paths: CanonicalPathsSchema,
|
|
309
343
|
verification_types: VerificationTypesSchema,
|
|
310
|
-
detection: DetectionConfigSchema
|
|
344
|
+
detection: DetectionConfigSchema,
|
|
345
|
+
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
346
|
+
detected: DetectedConfigSchema,
|
|
347
|
+
// Plan 3a: file-watcher daemon tunables
|
|
348
|
+
watch: WatchConfigSchema,
|
|
349
|
+
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
350
|
+
lsp: LSPConfigSchema.optional()
|
|
311
351
|
}).passthrough();
|
|
312
352
|
var _config = null;
|
|
313
353
|
var _projectRoot = null;
|
|
@@ -414,7 +454,10 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
414
454
|
verification: parsed.verification,
|
|
415
455
|
canonical_paths: parsed.canonical_paths,
|
|
416
456
|
verification_types: parsed.verification_types,
|
|
417
|
-
detection: parsed.detection
|
|
457
|
+
detection: parsed.detection,
|
|
458
|
+
detected: parsed.detected,
|
|
459
|
+
watch: parsed.watch,
|
|
460
|
+
lsp: parsed.lsp
|
|
418
461
|
};
|
|
419
462
|
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
420
463
|
_config.cloud = {
|