@massu/core 1.10.7 → 1.12.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/CHANGELOG.md +1303 -0
- package/dist/adapter.js +6 -4
- package/dist/cli.js +559 -400
- package/dist/detect/adapters/.bundle-shasums.json +4 -7
- package/dist/detect/adapters/rails.js +6 -4
- package/dist/detect/adapters/spring.js +6 -4
- package/dist/detect/adapters/tree-sitter-loader.js +6 -4
- package/dist/hooks/auto-learning-pipeline.js +24 -1
- package/dist/hooks/classify-failure.js +32 -3
- package/dist/hooks/cost-tracker.js +24 -0
- package/dist/hooks/fix-detector.js +97 -1
- package/dist/hooks/incident-pipeline.js +30 -1
- package/dist/hooks/intent-suggester.js +6 -3
- package/dist/hooks/post-edit-context.js +24 -1
- package/dist/hooks/post-tool-use.js +66 -18
- package/dist/hooks/pre-compact.js +27 -2
- package/dist/hooks/pre-delete-check.js +48 -22
- package/dist/hooks/pre-tool-use-gate.js +962 -0
- package/dist/hooks/quality-event.js +24 -0
- package/dist/hooks/rule-enforcement-pipeline.js +24 -1
- package/dist/hooks/security-gate.js +29 -16
- package/dist/hooks/session-end.js +24 -0
- package/dist/hooks/session-start.js +8710 -8460
- package/dist/hooks/user-prompt.js +24 -0
- package/package.json +15 -18
- package/commands/README.md +0 -134
- package/src/adapter.ts +0 -121
- package/src/adr-generator.ts +0 -292
- package/src/analytics.ts +0 -373
- package/src/audit-trail.ts +0 -450
- package/src/backfill-sessions.ts +0 -182
- package/src/changelog-generator.ts +0 -178
- package/src/cli.ts +0 -246
- package/src/cloud-sync.ts +0 -234
- package/src/commands/adapters.ts +0 -824
- package/src/commands/changelog.ts +0 -165
- package/src/commands/config-check-drift.ts +0 -133
- package/src/commands/config-refresh.ts +0 -396
- package/src/commands/config-upgrade.ts +0 -127
- package/src/commands/doctor.ts +0 -485
- package/src/commands/hook-runner.ts +0 -145
- package/src/commands/init.ts +0 -1823
- package/src/commands/install-commands.ts +0 -692
- package/src/commands/install-hooks.ts +0 -27
- package/src/commands/permissions.ts +0 -150
- package/src/commands/refresh-log.ts +0 -37
- package/src/commands/show-template.ts +0 -65
- package/src/commands/template-engine.ts +0 -301
- package/src/commands/watch.ts +0 -430
- package/src/config.ts +0 -765
- package/src/cost-tracker.ts +0 -355
- package/src/db.ts +0 -370
- package/src/dependency-scorer.ts +0 -337
- package/src/detect/adapters/aspnet.ts +0 -4
- package/src/detect/adapters/discover.ts +0 -469
- package/src/detect/adapters/file-sampler.ts +0 -225
- package/src/detect/adapters/go-chi.ts +0 -4
- package/src/detect/adapters/index.ts +0 -51
- package/src/detect/adapters/nextjs-trpc.ts +0 -166
- package/src/detect/adapters/parse-guard.ts +0 -133
- package/src/detect/adapters/phoenix.ts +0 -4
- package/src/detect/adapters/python-django.ts +0 -208
- package/src/detect/adapters/python-fastapi.ts +0 -223
- package/src/detect/adapters/python-flask.ts +0 -235
- package/src/detect/adapters/query-helpers.ts +0 -170
- package/src/detect/adapters/rails.ts +0 -4
- package/src/detect/adapters/runner.ts +0 -284
- package/src/detect/adapters/spring.ts +0 -4
- package/src/detect/adapters/swift-swiftui.ts +0 -171
- package/src/detect/adapters/tree-sitter-loader.ts +0 -517
- package/src/detect/adapters/types.ts +0 -191
- package/src/detect/codebase-introspector.ts +0 -210
- package/src/detect/domain-inferrer.ts +0 -197
- package/src/detect/drift.ts +0 -199
- package/src/detect/framework-detector.ts +0 -307
- package/src/detect/index.ts +0 -209
- package/src/detect/manifest-registry.ts +0 -261
- package/src/detect/migrate.ts +0 -362
- package/src/detect/monorepo-detector.ts +0 -348
- package/src/detect/package-detector.ts +0 -828
- package/src/detect/passthrough.ts +0 -108
- package/src/detect/regex-fallback.ts +0 -449
- package/src/detect/source-dir-detector.ts +0 -297
- package/src/detect/vr-command-map.ts +0 -167
- package/src/docs-map.json +0 -100
- package/src/docs-tools.ts +0 -519
- package/src/domains.ts +0 -182
- package/src/hooks/auto-learning-pipeline.ts +0 -228
- package/src/hooks/classify-failure.ts +0 -259
- package/src/hooks/cost-tracker.ts +0 -66
- package/src/hooks/fix-detector.ts +0 -186
- package/src/hooks/incident-pipeline.ts +0 -190
- package/src/hooks/intent-suggester.ts +0 -131
- package/src/hooks/post-edit-context.ts +0 -91
- package/src/hooks/post-tool-use.ts +0 -413
- package/src/hooks/pre-compact.ts +0 -168
- package/src/hooks/pre-delete-check.ts +0 -243
- package/src/hooks/quality-event.ts +0 -127
- package/src/hooks/rule-enforcement-pipeline.ts +0 -159
- package/src/hooks/security-gate.ts +0 -153
- package/src/hooks/session-end.ts +0 -467
- package/src/hooks/session-start.ts +0 -436
- package/src/hooks/user-prompt.ts +0 -157
- package/src/import-resolver.ts +0 -226
- package/src/knowledge-db.ts +0 -176
- package/src/knowledge-indexer.ts +0 -708
- package/src/knowledge-tools.ts +0 -1414
- package/src/lib/fileLock.ts +0 -203
- package/src/lib/gitToplevel.ts +0 -22
- package/src/lib/hook-registry.ts +0 -43
- package/src/lib/installLock.ts +0 -66
- package/src/lib/memory-path.ts +0 -49
- package/src/lib/pidLiveness.ts +0 -67
- package/src/lib/settings-local.ts +0 -110
- package/src/lib/sql-table-names.ts +0 -71
- package/src/license.ts +0 -547
- package/src/lsp/auto-detect.ts +0 -98
- package/src/lsp/client.ts +0 -776
- package/src/lsp/enrich.ts +0 -127
- package/src/lsp/types.ts +0 -221
- package/src/memory-db.ts +0 -1614
- package/src/memory-file-ingest.ts +0 -128
- package/src/memory-tools.ts +0 -424
- package/src/middleware-tree.ts +0 -71
- package/src/observability-tools.ts +0 -343
- package/src/observation-extractor.ts +0 -418
- package/src/page-deps.ts +0 -285
- package/src/permissions.ts +0 -422
- package/src/prompt-analyzer.ts +0 -332
- package/src/python/coupling-detector.ts +0 -125
- package/src/python/domain-enforcer.ts +0 -84
- package/src/python/impact-analyzer.ts +0 -96
- package/src/python/import-parser.ts +0 -244
- package/src/python/import-resolver.ts +0 -136
- package/src/python/migration-indexer.ts +0 -116
- package/src/python/migration-parser.ts +0 -332
- package/src/python/model-indexer.ts +0 -71
- package/src/python/model-parser.ts +0 -279
- package/src/python/route-indexer.ts +0 -59
- package/src/python/route-parser.ts +0 -317
- package/src/python-tools.ts +0 -630
- package/src/regression-detector.ts +0 -319
- package/src/rules.ts +0 -57
- package/src/schema-mapper.ts +0 -232
- package/src/security/adapter-origin.ts +0 -130
- package/src/security/adapter-verifier.ts +0 -319
- package/src/security/atomic-write.ts +0 -164
- package/src/security/fetcher.ts +0 -200
- package/src/security/install-tracking.ts +0 -319
- package/src/security/license-pubkey.generated.ts +0 -30
- package/src/security/license-response-verifier.ts +0 -149
- package/src/security/local-fingerprint.ts +0 -225
- package/src/security/manifest-cache.ts +0 -333
- package/src/security/manifest-schema.ts +0 -129
- package/src/security/registry-pubkey.generated.ts +0 -35
- package/src/security/telemetry.ts +0 -320
- package/src/security-scorer.ts +0 -405
- package/src/security-utils.ts +0 -133
- package/src/sentinel-db.ts +0 -580
- package/src/sentinel-scanner.ts +0 -406
- package/src/sentinel-tools.ts +0 -513
- package/src/sentinel-types.ts +0 -140
- package/src/server-dispatch.ts +0 -225
- package/src/server.ts +0 -107
- package/src/session-archiver.ts +0 -111
- package/src/session-state-generator.ts +0 -174
- package/src/team-knowledge.ts +0 -407
- package/src/tool-db-needs.ts +0 -232
- package/src/tools.ts +0 -1205
- package/src/transcript-parser.ts +0 -458
- package/src/trpc-index.ts +0 -215
- package/src/validate-features-runner.ts +0 -107
- package/src/validation-engine.ts +0 -358
- package/src/watch/daemon.ts +0 -385
- package/src/watch/lockfile-detector.ts +0 -65
- package/src/watch/paths.ts +0 -279
- package/src/watch/state.ts +0 -178
package/src/config.ts
DELETED
|
@@ -1,765 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
// ============================================================
|
|
5
|
-
// P2-000 (2026-04-19): Strategy A chosen — extend config.ts in-place.
|
|
6
|
-
// Per Phase 0 artifact (.massu/agent-results/phase0-discovery.md)
|
|
7
|
-
// the CR-10 blast radius is 52 non-test consumers, with only 1 CHANGE callsite
|
|
8
|
-
// (tools.ts) and zero INVESTIGATE items. No file split. All schema v2 extensions
|
|
9
|
-
// live below and preserve the v1 `framework.{type,router,orm,ui}` access paths
|
|
10
|
-
// so existing consumers (tools.ts:89,192,246) keep working unchanged.
|
|
11
|
-
// ============================================================
|
|
12
|
-
|
|
13
|
-
import { resolve, dirname } from 'path';
|
|
14
|
-
import { existsSync, readFileSync } from 'fs';
|
|
15
|
-
import { homedir } from 'os';
|
|
16
|
-
import { parse as parseYaml } from 'yaml';
|
|
17
|
-
import { z } from 'zod';
|
|
18
|
-
import { encodeMemoryDirName } from './lib/memory-path.ts';
|
|
19
|
-
|
|
20
|
-
// ============================================================
|
|
21
|
-
// Massu Configuration — Zod Schemas & Types
|
|
22
|
-
// ============================================================
|
|
23
|
-
|
|
24
|
-
// --- Domain Config ---
|
|
25
|
-
const DomainConfigSchema = z.object({
|
|
26
|
-
name: z.string().default('Unknown'),
|
|
27
|
-
routers: z.array(z.string()).default([]),
|
|
28
|
-
pages: z.array(z.string()).default([]),
|
|
29
|
-
tables: z.array(z.string()).default([]),
|
|
30
|
-
allowedImportsFrom: z.array(z.string()).default([]),
|
|
31
|
-
});
|
|
32
|
-
export type DomainConfig = z.infer<typeof DomainConfigSchema>;
|
|
33
|
-
|
|
34
|
-
// --- Pattern Rule Config ---
|
|
35
|
-
// P2-003: optional `language` discriminator so rules can be scoped to a specific
|
|
36
|
-
// runtime (e.g., only apply to Python files). Rules without `language` continue
|
|
37
|
-
// to apply to all files for v1 backcompat.
|
|
38
|
-
const PatternRuleConfigSchema = z.object({
|
|
39
|
-
pattern: z.string().default('**'),
|
|
40
|
-
rules: z.array(z.string()).default([]),
|
|
41
|
-
language: z.string().optional(),
|
|
42
|
-
});
|
|
43
|
-
export type PatternRuleConfig = z.infer<typeof PatternRuleConfigSchema>;
|
|
44
|
-
|
|
45
|
-
// --- Cost Model ---
|
|
46
|
-
const CostModelSchema = z.object({
|
|
47
|
-
input_per_million: z.number(),
|
|
48
|
-
output_per_million: z.number(),
|
|
49
|
-
cache_read_per_million: z.number().optional(),
|
|
50
|
-
cache_write_per_million: z.number().optional(),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// --- Analytics Config ---
|
|
54
|
-
const AnalyticsConfigSchema = z.object({
|
|
55
|
-
quality: z.object({
|
|
56
|
-
weights: z.record(z.string(), z.number()).default({
|
|
57
|
-
bug_found: -5, vr_failure: -10, incident: -20, cr_violation: -3,
|
|
58
|
-
vr_pass: 2, clean_commit: 5, successful_verification: 3,
|
|
59
|
-
}),
|
|
60
|
-
categories: z.array(z.string()).default(['security', 'architecture', 'coupling', 'tests', 'rule_compliance']),
|
|
61
|
-
}).optional(),
|
|
62
|
-
cost: z.object({
|
|
63
|
-
models: z.record(z.string(), CostModelSchema).default({}),
|
|
64
|
-
currency: z.string().default('USD'),
|
|
65
|
-
}).optional(),
|
|
66
|
-
prompts: z.object({
|
|
67
|
-
success_indicators: z.array(z.string()).default(['committed', 'approved', 'looks good', 'perfect', 'great', 'thanks']),
|
|
68
|
-
failure_indicators: z.array(z.string()).default(['revert', 'wrong', "that's not", 'undo', 'incorrect']),
|
|
69
|
-
max_turns_for_success: z.number().default(2),
|
|
70
|
-
}).optional(),
|
|
71
|
-
}).optional();
|
|
72
|
-
export type AnalyticsConfig = z.infer<typeof AnalyticsConfigSchema>;
|
|
73
|
-
|
|
74
|
-
// --- Custom Pattern (for validation) ---
|
|
75
|
-
const CustomPatternSchema = z.object({
|
|
76
|
-
pattern: z.string(),
|
|
77
|
-
severity: z.string(),
|
|
78
|
-
message: z.string(),
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// --- Governance Config ---
|
|
82
|
-
const GovernanceConfigSchema = z.object({
|
|
83
|
-
audit: z.object({
|
|
84
|
-
formats: z.array(z.string()).default(['summary', 'detailed', 'soc2']),
|
|
85
|
-
retention_days: z.number().default(365),
|
|
86
|
-
auto_log: z.record(z.string(), z.boolean()).default({
|
|
87
|
-
code_changes: true, rule_enforcement: true, approvals: true, commits: true,
|
|
88
|
-
}),
|
|
89
|
-
}).optional(),
|
|
90
|
-
validation: z.object({
|
|
91
|
-
realtime: z.boolean().default(true),
|
|
92
|
-
checks: z.record(z.string(), z.boolean()).default({
|
|
93
|
-
rule_compliance: true, import_existence: true, naming_conventions: true,
|
|
94
|
-
}),
|
|
95
|
-
custom_patterns: z.array(CustomPatternSchema).default([]),
|
|
96
|
-
}).optional(),
|
|
97
|
-
adr: z.object({
|
|
98
|
-
detection_phrases: z.array(z.string()).default(['chose', 'decided', 'switching to', 'moving from', 'going with']),
|
|
99
|
-
template: z.string().default('default'),
|
|
100
|
-
storage: z.string().default('database'),
|
|
101
|
-
output_dir: z.string().default('docs/adr'),
|
|
102
|
-
}).optional(),
|
|
103
|
-
}).optional();
|
|
104
|
-
export type GovernanceConfig = z.infer<typeof GovernanceConfigSchema>;
|
|
105
|
-
|
|
106
|
-
// --- Security Pattern ---
|
|
107
|
-
const SecurityPatternSchema = z.object({
|
|
108
|
-
pattern: z.string(),
|
|
109
|
-
severity: z.string(),
|
|
110
|
-
category: z.string(),
|
|
111
|
-
description: z.string(),
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// --- Security Config ---
|
|
115
|
-
const SecurityConfigSchema = z.object({
|
|
116
|
-
patterns: z.array(SecurityPatternSchema).default([]),
|
|
117
|
-
auto_score_on_edit: z.boolean().default(true),
|
|
118
|
-
score_threshold_alert: z.number().default(50),
|
|
119
|
-
severity_weights: z.record(z.string(), z.number()).optional(),
|
|
120
|
-
restrictive_licenses: z.array(z.string()).optional(),
|
|
121
|
-
dep_alternatives: z.record(z.string(), z.array(z.string())).optional(),
|
|
122
|
-
dependencies: z.object({
|
|
123
|
-
package_manager: z.string().default('npm'),
|
|
124
|
-
blocked_packages: z.array(z.string()).default([]),
|
|
125
|
-
preferred_packages: z.record(z.string(), z.string()).default({}),
|
|
126
|
-
max_bundle_size_kb: z.number().default(500),
|
|
127
|
-
}).optional(),
|
|
128
|
-
}).optional();
|
|
129
|
-
export type SecurityConfig = z.infer<typeof SecurityConfigSchema>;
|
|
130
|
-
|
|
131
|
-
// --- Team Config ---
|
|
132
|
-
const TeamConfigSchema = z.object({
|
|
133
|
-
enabled: z.boolean().default(false),
|
|
134
|
-
sync_backend: z.string().default('local'),
|
|
135
|
-
developer_id: z.string().default('auto'),
|
|
136
|
-
share_by_default: z.boolean().default(false),
|
|
137
|
-
expertise_weights: z.object({
|
|
138
|
-
session: z.number().default(20),
|
|
139
|
-
observation: z.number().default(10),
|
|
140
|
-
}).optional(),
|
|
141
|
-
privacy: z.object({
|
|
142
|
-
share_file_paths: z.boolean().default(true),
|
|
143
|
-
share_code_snippets: z.boolean().default(false),
|
|
144
|
-
share_observations: z.boolean().default(true),
|
|
145
|
-
}).optional(),
|
|
146
|
-
}).optional();
|
|
147
|
-
export type TeamConfig = z.infer<typeof TeamConfigSchema>;
|
|
148
|
-
|
|
149
|
-
// --- Regression Config ---
|
|
150
|
-
const RegressionConfigSchema = z.object({
|
|
151
|
-
test_patterns: z.array(z.string()).default([
|
|
152
|
-
'{dir}/__tests__/{name}.test.{ext}',
|
|
153
|
-
'{dir}/{name}.spec.{ext}',
|
|
154
|
-
'tests/{path}.test.{ext}',
|
|
155
|
-
]),
|
|
156
|
-
test_runner: z.string().default('npm test'),
|
|
157
|
-
health_thresholds: z.object({
|
|
158
|
-
healthy: z.number().default(80),
|
|
159
|
-
warning: z.number().default(50),
|
|
160
|
-
}).optional(),
|
|
161
|
-
}).optional();
|
|
162
|
-
export type RegressionConfig = z.infer<typeof RegressionConfigSchema>;
|
|
163
|
-
|
|
164
|
-
// --- Auto-Learning Config ---
|
|
165
|
-
const AutoLearningConfigSchema = z.object({
|
|
166
|
-
enabled: z.boolean().default(true),
|
|
167
|
-
incidentDir: z.string().default('docs/incidents'),
|
|
168
|
-
memoryDir: z.string().default('memory'),
|
|
169
|
-
memoryIndexFile: z.string().default('MEMORY.md'),
|
|
170
|
-
enforcementHooksDir: z.string().default('scripts/hooks'),
|
|
171
|
-
fixDetection: z.object({
|
|
172
|
-
enabled: z.boolean().default(true),
|
|
173
|
-
lookbackDays: z.number().default(7),
|
|
174
|
-
signals: z.array(z.string()).default([
|
|
175
|
-
'removed_broken_code',
|
|
176
|
-
'added_error_handling',
|
|
177
|
-
'method_name_correction',
|
|
178
|
-
'auth_fix',
|
|
179
|
-
'nil_handling_fix',
|
|
180
|
-
'concurrency_fix',
|
|
181
|
-
'async_pattern_fix',
|
|
182
|
-
'added_missing_import',
|
|
183
|
-
]),
|
|
184
|
-
}).default({}),
|
|
185
|
-
failureClassification: z.object({
|
|
186
|
-
enabled: z.boolean().default(true),
|
|
187
|
-
thresholds: z.object({
|
|
188
|
-
known: z.number().default(5),
|
|
189
|
-
similar: z.number().default(3),
|
|
190
|
-
}).default({}),
|
|
191
|
-
scoring: z.object({
|
|
192
|
-
diffPatternWeight: z.number().default(3),
|
|
193
|
-
filePatternWeight: z.number().default(2),
|
|
194
|
-
promptKeywordWeight: z.number().default(2),
|
|
195
|
-
}).default({}),
|
|
196
|
-
}).default({}),
|
|
197
|
-
pipeline: z.object({
|
|
198
|
-
requireIncidentReport: z.boolean().default(true),
|
|
199
|
-
requirePreventionRule: z.boolean().default(true),
|
|
200
|
-
requireEnforcement: z.boolean().default(true),
|
|
201
|
-
}).default({}),
|
|
202
|
-
}).optional();
|
|
203
|
-
export type AutoLearningConfig = z.infer<typeof AutoLearningConfigSchema>;
|
|
204
|
-
|
|
205
|
-
// --- Cloud Config ---
|
|
206
|
-
const CloudConfigSchema = z.object({
|
|
207
|
-
enabled: z.boolean().default(false),
|
|
208
|
-
apiKey: z.string().optional(),
|
|
209
|
-
endpoint: z.string().optional(),
|
|
210
|
-
sync: z.object({
|
|
211
|
-
memory: z.boolean().default(true),
|
|
212
|
-
analytics: z.boolean().default(true),
|
|
213
|
-
audit: z.boolean().default(true),
|
|
214
|
-
}).default({ memory: true, analytics: true, audit: true }),
|
|
215
|
-
}).optional();
|
|
216
|
-
export type CloudConfig = z.infer<typeof CloudConfigSchema>;
|
|
217
|
-
|
|
218
|
-
// --- Conventions Config ---
|
|
219
|
-
const ConventionsConfigSchema = z.object({
|
|
220
|
-
claudeDirName: z.string().default('.claude').refine(
|
|
221
|
-
s => !s.includes('..') && !s.startsWith('/'),
|
|
222
|
-
{ message: 'claudeDirName must not contain ".." or start with "/"' }
|
|
223
|
-
),
|
|
224
|
-
sessionStatePath: z.string().default('.claude/session-state/CURRENT.md').refine(
|
|
225
|
-
s => !s.includes('..') && !s.startsWith('/'),
|
|
226
|
-
{ message: 'sessionStatePath must not contain ".." or start with "/"' }
|
|
227
|
-
),
|
|
228
|
-
sessionArchivePath: z.string().default('.claude/session-state/archive').refine(
|
|
229
|
-
s => !s.includes('..') && !s.startsWith('/'),
|
|
230
|
-
{ message: 'sessionArchivePath must not contain ".." or start with "/"' }
|
|
231
|
-
),
|
|
232
|
-
knowledgeCategories: z.array(z.string()).default([
|
|
233
|
-
'patterns', 'commands', 'incidents', 'reference', 'protocols',
|
|
234
|
-
'checklists', 'playbooks', 'critical', 'scripts', 'status',
|
|
235
|
-
'templates', 'loop-state', 'session-state', 'agents',
|
|
236
|
-
]),
|
|
237
|
-
knowledgeSourceFiles: z.array(z.string()).default(['CLAUDE.md', 'MEMORY.md', 'corrections.md']),
|
|
238
|
-
excludePatterns: z.array(z.string()).default(['/ARCHIVE/', '/SESSION-HISTORY/']),
|
|
239
|
-
}).optional();
|
|
240
|
-
export type ConventionsConfig = z.infer<typeof ConventionsConfigSchema>;
|
|
241
|
-
|
|
242
|
-
// --- Python Config ---
|
|
243
|
-
const PythonDomainConfigSchema = z.object({
|
|
244
|
-
name: z.string(),
|
|
245
|
-
packages: z.array(z.string()),
|
|
246
|
-
allowed_imports_from: z.array(z.string()).default([]),
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const PythonConfigSchema = z.object({
|
|
250
|
-
root: z.string(),
|
|
251
|
-
alembic_dir: z.string().optional(),
|
|
252
|
-
domains: z.array(PythonDomainConfigSchema).default([]),
|
|
253
|
-
exclude_dirs: z.array(z.string()).default(['__pycache__', '.venv', 'venv', '.mypy_cache', '.pytest_cache']),
|
|
254
|
-
}).optional();
|
|
255
|
-
export type PythonConfig = z.infer<typeof PythonConfigSchema>;
|
|
256
|
-
|
|
257
|
-
// --- Paths Config ---
|
|
258
|
-
// `monorepo_roots` (P1-005): optional, additive. Emitted by `init --ci` when
|
|
259
|
-
// `monorepo.type !== 'single'` and at least one workspace package exists.
|
|
260
|
-
// Downstream tools may consume it for monorepo-aware scanning. Existing v1
|
|
261
|
-
// configs omit it — `.optional()` preserves full back-compat.
|
|
262
|
-
const PathsConfigSchema = z.object({
|
|
263
|
-
source: z.string().default('src'),
|
|
264
|
-
aliases: z.record(z.string(), z.string()).default({ '@': 'src' }),
|
|
265
|
-
monorepo_roots: z.array(z.string()).optional(),
|
|
266
|
-
routers: z.string().optional(),
|
|
267
|
-
routerRoot: z.string().optional(),
|
|
268
|
-
pages: z.string().optional(),
|
|
269
|
-
middleware: z.string().optional(),
|
|
270
|
-
schema: z.string().optional(),
|
|
271
|
-
components: z.string().optional(),
|
|
272
|
-
hooks: z.string().optional(),
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// --- P2-002: Multi-runtime per-language entry ---
|
|
276
|
-
// Each entry declares the framework + test framework + optional router/orm/ui
|
|
277
|
-
// used for that language slot. Used when `framework.type === 'multi'` via
|
|
278
|
-
// `framework.languages`.
|
|
279
|
-
const LanguageFrameworkEntrySchema = z.object({
|
|
280
|
-
framework: z.string().optional(),
|
|
281
|
-
test_framework: z.string().optional(),
|
|
282
|
-
test: z.string().optional(),
|
|
283
|
-
runtime: z.string().optional(),
|
|
284
|
-
orm: z.string().optional(),
|
|
285
|
-
router: z.string().optional(),
|
|
286
|
-
ui: z.string().optional(),
|
|
287
|
-
}).passthrough();
|
|
288
|
-
export type LanguageFrameworkEntry = z.infer<typeof LanguageFrameworkEntrySchema>;
|
|
289
|
-
|
|
290
|
-
// --- P2-002: Framework schema (supports v1 single-language AND v2 multi-runtime) ---
|
|
291
|
-
// v1: { type: 'typescript', router: 'none', orm: 'none', ui: 'none' }
|
|
292
|
-
// v2: { type: 'multi', primary: 'python', languages: { python: {...}, typescript: {...} } }
|
|
293
|
-
// Legacy top-level router/orm/ui keys are PRESERVED in both modes so that
|
|
294
|
-
// existing consumers (tools.ts:89,192,246) keep working without refactor.
|
|
295
|
-
const FrameworkConfigSchema = z.object({
|
|
296
|
-
type: z.string().default('typescript'),
|
|
297
|
-
primary: z.string().optional(),
|
|
298
|
-
router: z.string().default('none'),
|
|
299
|
-
orm: z.string().default('none'),
|
|
300
|
-
ui: z.string().default('none'),
|
|
301
|
-
languages: z.record(z.string(), LanguageFrameworkEntrySchema).optional(),
|
|
302
|
-
}).passthrough();
|
|
303
|
-
export type FrameworkConfig = z.infer<typeof FrameworkConfigSchema>;
|
|
304
|
-
|
|
305
|
-
// --- Codebase-aware templates (Plan #2): `detected:` block ---
|
|
306
|
-
// Detector-owned per-language conventions extracted from existing source files
|
|
307
|
-
// (auth dep names, common imports, biometric policies, etc.). Refreshed on
|
|
308
|
-
// every `init`/`config refresh` and consumed by the templating engine when
|
|
309
|
-
// installing slash commands. Free-form via `.passthrough()` so future detector
|
|
310
|
-
// fields don't break parsing of older configs.
|
|
311
|
-
const DetectedConfigSchema = z.object({}).passthrough().optional();
|
|
312
|
-
export type DetectedConfig = z.infer<typeof DetectedConfigSchema>;
|
|
313
|
-
|
|
314
|
-
// --- P2-004: Verification command map ---
|
|
315
|
-
// Map of language name -> command strings for each verification type.
|
|
316
|
-
// User entries take precedence over any Phase 1 auto-defaults.
|
|
317
|
-
const VerificationEntrySchema = z.object({
|
|
318
|
-
type: z.string().optional(),
|
|
319
|
-
test: z.string().optional(),
|
|
320
|
-
syntax: z.string().optional(),
|
|
321
|
-
lint: z.string().optional(),
|
|
322
|
-
build: z.string().optional(),
|
|
323
|
-
}).passthrough();
|
|
324
|
-
export type VerificationEntry = z.infer<typeof VerificationEntrySchema>;
|
|
325
|
-
|
|
326
|
-
const VerificationConfigSchema = z.record(z.string(), VerificationEntrySchema).optional();
|
|
327
|
-
export type VerificationConfig = z.infer<typeof VerificationConfigSchema>;
|
|
328
|
-
|
|
329
|
-
// --- P2-005: Canonical paths extension (free-form string map) ---
|
|
330
|
-
const CanonicalPathsSchema = z.record(z.string(), z.string()).optional();
|
|
331
|
-
export type CanonicalPaths = z.infer<typeof CanonicalPathsSchema>;
|
|
332
|
-
|
|
333
|
-
// --- P2-006: VR-* types extension (user-declared verification types) ---
|
|
334
|
-
const VerificationTypesSchema = z.record(z.string(), z.string()).optional();
|
|
335
|
-
export type VerificationTypes = z.infer<typeof VerificationTypesSchema>;
|
|
336
|
-
|
|
337
|
-
// --- P2-008: Detection rules extension ---
|
|
338
|
-
// Users may add or override built-in detection patterns. Phase 1 detectors
|
|
339
|
-
// merge this with the inline DETECTION_RULES table.
|
|
340
|
-
const DetectionRuleEntrySchema = z.object({
|
|
341
|
-
signals: z.array(z.string()).default([]),
|
|
342
|
-
priority: z.number().optional(),
|
|
343
|
-
}).passthrough();
|
|
344
|
-
|
|
345
|
-
const DetectionConfigSchema = z.object({
|
|
346
|
-
rules: z.record(
|
|
347
|
-
z.string(), // language
|
|
348
|
-
z.record(z.string(), DetectionRuleEntrySchema) // framework -> rule entry
|
|
349
|
-
).optional(),
|
|
350
|
-
signal_weights: z.record(z.string(), z.number()).optional(),
|
|
351
|
-
disable_builtin: z.boolean().optional(),
|
|
352
|
-
}).passthrough().optional();
|
|
353
|
-
export type DetectionConfig = z.infer<typeof DetectionConfigSchema>;
|
|
354
|
-
|
|
355
|
-
// --- Watch Config (Plan 3a — `massu watch` daemon) ---
|
|
356
|
-
// Tunable knobs for the file-watcher daemon. All optional with sensible
|
|
357
|
-
// defaults; users override only when their repo has unusual quiescence
|
|
358
|
-
// patterns (e.g. monorepos with continuous codegen, NFS-mounted volumes).
|
|
359
|
-
const WatchConfigSchema = z.object({
|
|
360
|
-
debounce_ms: z.number().int().positive().default(3000),
|
|
361
|
-
storm_threshold: z.number().int().positive().default(50),
|
|
362
|
-
deep_storm_threshold: z.number().int().positive().default(500),
|
|
363
|
-
hard_timeout_ms: z.number().int().positive().default(300_000),
|
|
364
|
-
scope: z.enum(['paths', 'full']).default('paths'),
|
|
365
|
-
// Plan 3a hotfix 2026-05-02: refuse to start if the watch surface
|
|
366
|
-
// exceeds this many files. Prevents the misconfig pattern where
|
|
367
|
-
// `paths.source_dirs` includes `.` or otherwise expands to a 60K+
|
|
368
|
-
// file tree, producing 30-100% steady CPU. Override via
|
|
369
|
-
// `paths_full_root_opt_in: true` for users on small repos who genuinely
|
|
370
|
-
// need root-level watching.
|
|
371
|
-
max_watched_files: z.number().int().positive().default(10_000),
|
|
372
|
-
paths_full_root_opt_in: z.boolean().default(false),
|
|
373
|
-
}).passthrough().optional();
|
|
374
|
-
export type WatchConfig = z.infer<typeof WatchConfigSchema>;
|
|
375
|
-
|
|
376
|
-
// --- Adapters Config (Plan 3c — third-party adapter registry) ---
|
|
377
|
-
// Tunable knobs for the third-party adapter loading subsystem.
|
|
378
|
-
// `enabled` is the kill switch (default false for first 2 minor releases per
|
|
379
|
-
// Plan 3c gap-1 + audit-iteration-1 deliverable). When false, only the 35
|
|
380
|
-
// CORE-BUNDLED adapters in @massu/core itself load; @massu/adapter-* npm
|
|
381
|
-
// packages and adapters.local entries are ignored.
|
|
382
|
-
// `local` is the explicit per-path opt-in for project-local TS/JS adapters.
|
|
383
|
-
//
|
|
384
|
-
// Note: `allow_unsigned` was REMOVED 2026-05-07 (CR-9 audit C2 fix). The
|
|
385
|
-
// field had been parsed by the schema but never consulted at any callsite,
|
|
386
|
-
// creating a tripwire — a future contributor wiring it would silently
|
|
387
|
-
// disable signature verification. Per Rule 0 / drift-prevention, dead
|
|
388
|
-
// fields are removed, not documented. No v1 use case exists for unsigned
|
|
389
|
-
// adapter loading; if a future v2 adds one, it ships with documented
|
|
390
|
-
// callsite enforcement and a different field name to avoid the tripwire
|
|
391
|
-
// pattern.
|
|
392
|
-
//
|
|
393
|
-
// Path validation (gap-58): adapters.local entries are normalized to POSIX form
|
|
394
|
-
// for cross-platform fingerprint stability. Absolute paths and `..` traversal
|
|
395
|
-
// are rejected at schema validation (attack-surface defense — a malicious
|
|
396
|
-
// postinstall could add "/etc/passwd" or "../../../home/user/.ssh/" entries
|
|
397
|
-
// that bypass the project tree).
|
|
398
|
-
export const AdapterLocalPathSchema = z.string()
|
|
399
|
-
.refine((s) => !/^([A-Za-z]:[\\/]|[\\/])/.test(s), {
|
|
400
|
-
message: 'absolute paths are rejected; adapters.local entries must be relative to the massu.config.yaml directory',
|
|
401
|
-
})
|
|
402
|
-
.refine((s) => !s.split(/[\\/]/).includes('..'), {
|
|
403
|
-
message: 'parent-directory traversal (`..`) is rejected; adapters.local entries must stay inside the project tree',
|
|
404
|
-
})
|
|
405
|
-
// Normalize to POSIX form for stable cross-platform sha256 fingerprinting.
|
|
406
|
-
// Windows backslash inputs are converted to forward slashes; consecutive
|
|
407
|
-
// separators are collapsed; trailing `./` segments are removed.
|
|
408
|
-
.transform((s) => s.split(/[\\/]/).filter((part) => part !== '' && part !== '.').join('/'));
|
|
409
|
-
|
|
410
|
-
const AdaptersConfigSchema = z.object({
|
|
411
|
-
enabled: z.boolean().default(false),
|
|
412
|
-
local: z.array(AdapterLocalPathSchema).default([]),
|
|
413
|
-
}).passthrough().optional();
|
|
414
|
-
export type AdaptersConfig = z.infer<typeof AdaptersConfigSchema>;
|
|
415
|
-
|
|
416
|
-
// --- Telemetry Config (Plan 3c — adapter-discovery telemetry) ---
|
|
417
|
-
// Optional anonymous telemetry on adapter-discovery counts (NOT file paths,
|
|
418
|
-
// NOT symbol names, NOT source content). Strictly off by default; never
|
|
419
|
-
// enabled silently. When `adapters: true`, the telemetry writer at
|
|
420
|
-
// packages/core/src/security/telemetry.ts emits one JSONL line per discovery
|
|
421
|
-
// event matching AdapterDiscoveryPayloadSchema (defined inline in that
|
|
422
|
-
// module). PII guardrail: schema is `.strict()` so unknown keys are
|
|
423
|
-
// rejected at write time AND at replay time.
|
|
424
|
-
const TelemetryConfigSchema = z.object({
|
|
425
|
-
adapters: z.boolean().default(false),
|
|
426
|
-
}).passthrough().optional();
|
|
427
|
-
export type TelemetryConfig = z.infer<typeof TelemetryConfigSchema>;
|
|
428
|
-
|
|
429
|
-
// --- LSP Config (Plan 3b — Phase 4: LSP integration) ---
|
|
430
|
-
// Top-level optional `lsp:` block configuring optional LSP enrichment of AST
|
|
431
|
-
// adapter results. Per VR-LSP-AUTODETECT-OFF-BY-DEFAULT (audit-iter-1 fix G4):
|
|
432
|
-
// `autoDetect.viaPortScan` defaults to false — port-scanning is opt-in only.
|
|
433
|
-
// Empty-servers + enabled is a valid runtime state (see auto-detect.ts);
|
|
434
|
-
// schema does not reject it.
|
|
435
|
-
export const LSPConfigSchema = z.object({
|
|
436
|
-
enabled: z.boolean().default(false),
|
|
437
|
-
servers: z.array(z.object({
|
|
438
|
-
language: z.string(),
|
|
439
|
-
command: z.string(),
|
|
440
|
-
// F-014 (closed 2026-05-06): explicit opt-in to spawn SUID/SGID
|
|
441
|
-
// binaries. Default false — argv[0] with the SUID bit is rejected
|
|
442
|
-
// unless this is true. Decision is auditable in the YAML.
|
|
443
|
-
allow_setuid: z.boolean().default(false),
|
|
444
|
-
// F-015 (closed 2026-05-06): per-server RSS budget (MB). Watchdog
|
|
445
|
-
// SIGKILLs the server after sustained breach. Default 1024 MB.
|
|
446
|
-
// Set to 0 to disable the watchdog for this server.
|
|
447
|
-
max_rss_mb: z.number().int().nonnegative().default(1024),
|
|
448
|
-
})).default([]),
|
|
449
|
-
autoDetect: z.object({
|
|
450
|
-
viaPortScan: z.boolean().default(false),
|
|
451
|
-
}).optional(),
|
|
452
|
-
}).passthrough();
|
|
453
|
-
export type LSPConfig = z.infer<typeof LSPConfigSchema>;
|
|
454
|
-
|
|
455
|
-
// --- Top-level Raw Config Schema ---
|
|
456
|
-
// This validates the raw YAML output, coercing types and providing defaults.
|
|
457
|
-
// P2-001: schema_version tracks the config shape version. Defaults to 1 so
|
|
458
|
-
// existing v1 configs (without the field) keep loading unchanged. New v2
|
|
459
|
-
// configs set `schema_version: 2` explicitly.
|
|
460
|
-
const RawConfigSchema = z.object({
|
|
461
|
-
schema_version: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
462
|
-
project: z.object({
|
|
463
|
-
name: z.string().default('my-project'),
|
|
464
|
-
root: z.string().default('auto'),
|
|
465
|
-
}).default({ name: 'my-project', root: 'auto' }),
|
|
466
|
-
framework: FrameworkConfigSchema.default({
|
|
467
|
-
type: 'typescript',
|
|
468
|
-
router: 'none',
|
|
469
|
-
orm: 'none',
|
|
470
|
-
ui: 'none',
|
|
471
|
-
}),
|
|
472
|
-
paths: PathsConfigSchema.default({ source: 'src', aliases: { '@': 'src' } }),
|
|
473
|
-
toolPrefix: z.string().default('massu'),
|
|
474
|
-
dbAccessPattern: z.string().optional(),
|
|
475
|
-
knownMismatches: z.record(z.string(), z.record(z.string(), z.string())).optional(),
|
|
476
|
-
accessScopes: z.array(z.string()).optional(),
|
|
477
|
-
domains: z.array(DomainConfigSchema).default([]),
|
|
478
|
-
rules: z.array(PatternRuleConfigSchema).default([]),
|
|
479
|
-
analytics: AnalyticsConfigSchema,
|
|
480
|
-
governance: GovernanceConfigSchema,
|
|
481
|
-
security: SecurityConfigSchema,
|
|
482
|
-
team: TeamConfigSchema,
|
|
483
|
-
regression: RegressionConfigSchema,
|
|
484
|
-
cloud: CloudConfigSchema,
|
|
485
|
-
conventions: ConventionsConfigSchema,
|
|
486
|
-
autoLearning: AutoLearningConfigSchema,
|
|
487
|
-
python: PythonConfigSchema,
|
|
488
|
-
// P2-004 / P2-005 / P2-006 / P2-008: v2 extensions (all optional)
|
|
489
|
-
verification: VerificationConfigSchema,
|
|
490
|
-
canonical_paths: CanonicalPathsSchema,
|
|
491
|
-
verification_types: VerificationTypesSchema,
|
|
492
|
-
detection: DetectionConfigSchema,
|
|
493
|
-
// Plan #2: detector-owned per-language conventions (free-form passthrough)
|
|
494
|
-
detected: DetectedConfigSchema,
|
|
495
|
-
// Plan 3a: file-watcher daemon tunables
|
|
496
|
-
watch: WatchConfigSchema,
|
|
497
|
-
// Plan 3c: third-party adapter registry kill-switch + signing override + local-path opt-in.
|
|
498
|
-
adapters: AdaptersConfigSchema,
|
|
499
|
-
// Plan 3c: anonymous adapter-discovery telemetry opt-in (default off).
|
|
500
|
-
telemetry: TelemetryConfigSchema,
|
|
501
|
-
// Plan 3b Phase 4: optional LSP enrichment of AST adapter results.
|
|
502
|
-
lsp: LSPConfigSchema.optional(),
|
|
503
|
-
}).passthrough();
|
|
504
|
-
|
|
505
|
-
// --- Final Config interface (derived from Zod) ---
|
|
506
|
-
// Legacy v1 access paths (framework.type/router/orm/ui) are preserved here so
|
|
507
|
-
// that existing consumers (tools.ts:89,192,246 etc.) keep working regardless
|
|
508
|
-
// of whether the config is v1 or v2 multi-runtime shape.
|
|
509
|
-
export interface Config {
|
|
510
|
-
schema_version: 1 | 2;
|
|
511
|
-
project: { name: string; root: string };
|
|
512
|
-
framework: {
|
|
513
|
-
type: string;
|
|
514
|
-
router: string;
|
|
515
|
-
orm: string;
|
|
516
|
-
ui: string;
|
|
517
|
-
primary?: string;
|
|
518
|
-
languages?: Record<string, LanguageFrameworkEntry>;
|
|
519
|
-
};
|
|
520
|
-
paths: z.infer<typeof PathsConfigSchema>;
|
|
521
|
-
toolPrefix: string;
|
|
522
|
-
dbAccessPattern?: string;
|
|
523
|
-
knownMismatches?: Record<string, Record<string, string>>;
|
|
524
|
-
accessScopes?: string[];
|
|
525
|
-
domains: DomainConfig[];
|
|
526
|
-
rules: PatternRuleConfig[];
|
|
527
|
-
analytics?: AnalyticsConfig;
|
|
528
|
-
governance?: GovernanceConfig;
|
|
529
|
-
security?: SecurityConfig;
|
|
530
|
-
team?: TeamConfig;
|
|
531
|
-
regression?: RegressionConfig;
|
|
532
|
-
cloud?: CloudConfig;
|
|
533
|
-
conventions?: ConventionsConfig;
|
|
534
|
-
autoLearning?: AutoLearningConfig;
|
|
535
|
-
python?: PythonConfig;
|
|
536
|
-
// P2-004/005/006/008: optional v2 extensions
|
|
537
|
-
verification?: VerificationConfig;
|
|
538
|
-
canonical_paths?: CanonicalPaths;
|
|
539
|
-
verification_types?: VerificationTypes;
|
|
540
|
-
detection?: DetectionConfig;
|
|
541
|
-
// Plan #2: detector-owned per-language conventions
|
|
542
|
-
detected?: DetectedConfig;
|
|
543
|
-
// Plan 3a: file-watcher daemon tunables
|
|
544
|
-
watch?: WatchConfig;
|
|
545
|
-
// Plan 3c: third-party adapter registry config block.
|
|
546
|
-
adapters?: AdaptersConfig;
|
|
547
|
-
// Plan 3c: telemetry opt-in block.
|
|
548
|
-
telemetry?: TelemetryConfig;
|
|
549
|
-
// Plan 3b Phase 4: optional LSP enrichment block (default disabled).
|
|
550
|
-
lsp?: LSPConfig;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
let _config: Config | null = null;
|
|
554
|
-
let _projectRoot: string | null = null;
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Find the project root by walking up from cwd.
|
|
558
|
-
* Prioritizes massu.config.yaml (searched all the way up),
|
|
559
|
-
* then falls back to the nearest package.json or .git directory.
|
|
560
|
-
*/
|
|
561
|
-
function findProjectRoot(): string {
|
|
562
|
-
const cwd = process.cwd();
|
|
563
|
-
|
|
564
|
-
// First pass: look for massu.config.yaml all the way up
|
|
565
|
-
let dir = cwd;
|
|
566
|
-
while (true) {
|
|
567
|
-
if (existsSync(resolve(dir, 'massu.config.yaml'))) {
|
|
568
|
-
return dir;
|
|
569
|
-
}
|
|
570
|
-
const parent = dirname(dir);
|
|
571
|
-
if (parent === dir) break;
|
|
572
|
-
dir = parent;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Second pass: fall back to nearest package.json or .git
|
|
576
|
-
dir = cwd;
|
|
577
|
-
while (true) {
|
|
578
|
-
if (existsSync(resolve(dir, 'package.json'))) {
|
|
579
|
-
return dir;
|
|
580
|
-
}
|
|
581
|
-
if (existsSync(resolve(dir, '.git'))) {
|
|
582
|
-
return dir;
|
|
583
|
-
}
|
|
584
|
-
const parent = dirname(dir);
|
|
585
|
-
if (parent === dir) break;
|
|
586
|
-
dir = parent;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
return cwd;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* Get the project root directory.
|
|
594
|
-
*/
|
|
595
|
-
export function getProjectRoot(): string {
|
|
596
|
-
if (!_projectRoot) {
|
|
597
|
-
_projectRoot = findProjectRoot();
|
|
598
|
-
}
|
|
599
|
-
return _projectRoot;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Load and return the Massu configuration.
|
|
604
|
-
* Searches for massu.config.yaml in the project root.
|
|
605
|
-
* Uses Zod for runtime validation with sensible defaults.
|
|
606
|
-
*/
|
|
607
|
-
export function getConfig(): Config {
|
|
608
|
-
if (_config) return _config;
|
|
609
|
-
|
|
610
|
-
const root = getProjectRoot();
|
|
611
|
-
const configPath = resolve(root, 'massu.config.yaml');
|
|
612
|
-
|
|
613
|
-
let rawYaml: unknown = {};
|
|
614
|
-
if (existsSync(configPath)) {
|
|
615
|
-
const content = readFileSync(configPath, 'utf-8');
|
|
616
|
-
rawYaml = parseYaml(content) ?? {};
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// P2-007: Validate with Zod and surface actionable errors on failure.
|
|
620
|
-
// Every Zod issue is translated into "<field path>: <message> (received <type>)"
|
|
621
|
-
// lines, grouped under a single Error message that names massu.config.yaml.
|
|
622
|
-
const result = RawConfigSchema.safeParse(rawYaml);
|
|
623
|
-
if (!result.success) {
|
|
624
|
-
const issues = result.error.issues.map(i => {
|
|
625
|
-
const path = i.path.length > 0 ? i.path.join('.') : '(root)';
|
|
626
|
-
const received = 'received' in i && i.received !== undefined
|
|
627
|
-
? ` (received ${JSON.stringify(i.received)})`
|
|
628
|
-
: '';
|
|
629
|
-
return ` - ${path}: ${i.message}${received}`;
|
|
630
|
-
}).join('\n');
|
|
631
|
-
throw new Error(
|
|
632
|
-
`Invalid massu.config.yaml at ${configPath}:\n${issues}\n` +
|
|
633
|
-
`Hint: run \`massu config refresh\` to regenerate a valid config or fix the listed fields manually.`
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
const parsed = result.data;
|
|
637
|
-
|
|
638
|
-
// Resolve project root path
|
|
639
|
-
const projectRoot = parsed.project.root === 'auto' || !parsed.project.root
|
|
640
|
-
? root
|
|
641
|
-
: resolve(root, parsed.project.root);
|
|
642
|
-
|
|
643
|
-
// P2-002 backcompat: when `framework.type === 'multi'`, mirror router/orm/ui
|
|
644
|
-
// from the primary language entry so that tools.ts:89,192,246 keep resolving.
|
|
645
|
-
// User-provided top-level keys always win — only fall back to the primary
|
|
646
|
-
// language when the top-level value is missing OR still the schema default
|
|
647
|
-
// 'none' (meaning the user didn't set it explicitly).
|
|
648
|
-
const fw = parsed.framework;
|
|
649
|
-
let router = fw.router;
|
|
650
|
-
let orm = fw.orm;
|
|
651
|
-
let ui = fw.ui;
|
|
652
|
-
if (fw.type === 'multi' && fw.primary && fw.languages) {
|
|
653
|
-
const primaryEntry = fw.languages[fw.primary];
|
|
654
|
-
if (primaryEntry) {
|
|
655
|
-
if (router === 'none' && primaryEntry.router) router = primaryEntry.router;
|
|
656
|
-
if (orm === 'none' && primaryEntry.orm) orm = primaryEntry.orm;
|
|
657
|
-
if (ui === 'none' && primaryEntry.ui) ui = primaryEntry.ui;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
_config = {
|
|
662
|
-
schema_version: parsed.schema_version,
|
|
663
|
-
project: {
|
|
664
|
-
name: parsed.project.name,
|
|
665
|
-
root: projectRoot,
|
|
666
|
-
},
|
|
667
|
-
// Spread `fw` first so zod-`.passthrough()` extras (e.g., `framework.swift`,
|
|
668
|
-
// `framework.python`) survive into the consumer-visible Config. Then override
|
|
669
|
-
// the v2-backcompat-mirrored router/orm/ui values. Without the spread, the
|
|
670
|
-
// variant-resolution `pickVariant` (install-commands.ts) cannot see the
|
|
671
|
-
// top-level passthrough language blocks.
|
|
672
|
-
framework: {
|
|
673
|
-
...fw,
|
|
674
|
-
router,
|
|
675
|
-
orm,
|
|
676
|
-
ui,
|
|
677
|
-
},
|
|
678
|
-
paths: parsed.paths,
|
|
679
|
-
toolPrefix: parsed.toolPrefix,
|
|
680
|
-
dbAccessPattern: parsed.dbAccessPattern,
|
|
681
|
-
knownMismatches: parsed.knownMismatches,
|
|
682
|
-
accessScopes: parsed.accessScopes,
|
|
683
|
-
domains: parsed.domains,
|
|
684
|
-
rules: parsed.rules,
|
|
685
|
-
analytics: parsed.analytics,
|
|
686
|
-
governance: parsed.governance,
|
|
687
|
-
security: parsed.security,
|
|
688
|
-
team: parsed.team,
|
|
689
|
-
regression: parsed.regression,
|
|
690
|
-
cloud: parsed.cloud,
|
|
691
|
-
conventions: parsed.conventions,
|
|
692
|
-
autoLearning: parsed.autoLearning,
|
|
693
|
-
python: parsed.python,
|
|
694
|
-
verification: parsed.verification,
|
|
695
|
-
canonical_paths: parsed.canonical_paths,
|
|
696
|
-
verification_types: parsed.verification_types,
|
|
697
|
-
detection: parsed.detection,
|
|
698
|
-
detected: parsed.detected,
|
|
699
|
-
watch: parsed.watch,
|
|
700
|
-
adapters: parsed.adapters,
|
|
701
|
-
telemetry: parsed.telemetry,
|
|
702
|
-
lsp: parsed.lsp,
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
// Allow environment variable override for API key (security best practice)
|
|
706
|
-
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
707
|
-
_config.cloud = {
|
|
708
|
-
enabled: true,
|
|
709
|
-
sync: { memory: true, analytics: true, audit: true },
|
|
710
|
-
..._config.cloud,
|
|
711
|
-
apiKey: process.env.MASSU_API_KEY,
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
return _config;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* Get resolved paths for common project locations.
|
|
720
|
-
* Computed from the YAML config with sensible defaults.
|
|
721
|
-
*/
|
|
722
|
-
export function getResolvedPaths() {
|
|
723
|
-
const config = getConfig();
|
|
724
|
-
const root = getProjectRoot();
|
|
725
|
-
const claudeDirName = config.conventions?.claudeDirName ?? '.claude';
|
|
726
|
-
|
|
727
|
-
return {
|
|
728
|
-
codegraphDbPath: resolve(root, '.codegraph/codegraph.db'),
|
|
729
|
-
dataDbPath: resolve(root, '.massu/data.db'),
|
|
730
|
-
prismaSchemaPath: resolve(root, config.paths.schema ?? 'prisma/schema.prisma'),
|
|
731
|
-
rootRouterPath: resolve(root, config.paths.routerRoot ?? 'src/server/api/root.ts'),
|
|
732
|
-
routersDir: resolve(root, config.paths.routers ?? 'src/server/api/routers'),
|
|
733
|
-
srcDir: resolve(root, config.paths.source),
|
|
734
|
-
pathAlias: Object.fromEntries(
|
|
735
|
-
Object.entries(config.paths.aliases).map(([alias, target]) => [
|
|
736
|
-
alias,
|
|
737
|
-
resolve(root, target),
|
|
738
|
-
])
|
|
739
|
-
) as Record<string, string>,
|
|
740
|
-
extensions: ['.ts', '.tsx', '.js', '.jsx'] as const,
|
|
741
|
-
indexFiles: ['index.ts', 'index.tsx', 'index.js', 'index.jsx'] as const,
|
|
742
|
-
patternsDir: resolve(root, claudeDirName, 'patterns'),
|
|
743
|
-
claudeMdPath: resolve(root, claudeDirName, 'CLAUDE.md'),
|
|
744
|
-
docsMapPath: resolve(root, '.massu/docs-map.json'),
|
|
745
|
-
helpSitePath: resolve(root, '../' + config.project.name + '-help'),
|
|
746
|
-
memoryDbPath: resolve(root, '.massu/memory.db'),
|
|
747
|
-
knowledgeDbPath: resolve(root, '.massu/knowledge.db'),
|
|
748
|
-
plansDir: resolve(root, 'docs/plans'),
|
|
749
|
-
docsDir: resolve(root, 'docs'),
|
|
750
|
-
claudeDir: resolve(root, claudeDirName),
|
|
751
|
-
memoryDir: resolve(homedir(), claudeDirName, 'projects', encodeMemoryDirName(root), 'memory'),
|
|
752
|
-
sessionStatePath: resolve(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
|
|
753
|
-
sessionArchivePath: resolve(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
|
|
754
|
-
mcpJsonPath: resolve(root, '.mcp.json'),
|
|
755
|
-
settingsLocalPath: resolve(root, claudeDirName, 'settings.local.json'),
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/**
|
|
760
|
-
* Reset the cached config (useful for testing).
|
|
761
|
-
*/
|
|
762
|
-
export function resetConfig(): void {
|
|
763
|
-
_config = null;
|
|
764
|
-
_projectRoot = null;
|
|
765
|
-
}
|