@massu/core 1.10.8 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +1334 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +581 -448
  4. package/dist/detect/adapters/.bundle-shasums.json +4 -7
  5. package/dist/detect/adapters/rails.js +6 -4
  6. package/dist/detect/adapters/spring.js +6 -4
  7. package/dist/detect/adapters/tree-sitter-loader.js +6 -4
  8. package/dist/hooks/auto-learning-pipeline.js +24 -1
  9. package/dist/hooks/classify-failure.js +32 -3
  10. package/dist/hooks/cost-tracker.js +24 -0
  11. package/dist/hooks/fix-detector.js +97 -1
  12. package/dist/hooks/incident-pipeline.js +30 -1
  13. package/dist/hooks/intent-suggester.js +6 -3
  14. package/dist/hooks/post-edit-context.js +24 -1
  15. package/dist/hooks/post-tool-use.js +67 -19
  16. package/dist/hooks/pre-compact.js +28 -3
  17. package/dist/hooks/pre-delete-check.js +53 -27
  18. package/dist/hooks/pre-tool-use-gate.js +962 -0
  19. package/dist/hooks/quality-event.js +24 -0
  20. package/dist/hooks/rule-enforcement-pipeline.js +24 -1
  21. package/dist/hooks/security-gate.js +29 -16
  22. package/dist/hooks/session-end.js +31 -6
  23. package/dist/hooks/session-start.js +8709 -8459
  24. package/dist/hooks/user-prompt.js +25 -1
  25. package/package.json +15 -18
  26. package/commands/README.md +0 -134
  27. package/src/adapter.ts +0 -121
  28. package/src/adr-generator.ts +0 -292
  29. package/src/analytics.ts +0 -373
  30. package/src/audit-trail.ts +0 -450
  31. package/src/backfill-sessions.ts +0 -182
  32. package/src/changelog-generator.ts +0 -178
  33. package/src/cli.ts +0 -246
  34. package/src/cloud-sync.ts +0 -234
  35. package/src/commands/adapters.ts +0 -824
  36. package/src/commands/changelog.ts +0 -165
  37. package/src/commands/config-check-drift.ts +0 -133
  38. package/src/commands/config-refresh.ts +0 -396
  39. package/src/commands/config-upgrade.ts +0 -127
  40. package/src/commands/doctor.ts +0 -485
  41. package/src/commands/hook-runner.ts +0 -145
  42. package/src/commands/init.ts +0 -1823
  43. package/src/commands/install-commands.ts +0 -692
  44. package/src/commands/install-hooks.ts +0 -27
  45. package/src/commands/permissions.ts +0 -150
  46. package/src/commands/refresh-log.ts +0 -37
  47. package/src/commands/show-template.ts +0 -65
  48. package/src/commands/template-engine.ts +0 -301
  49. package/src/commands/watch.ts +0 -430
  50. package/src/config.ts +0 -765
  51. package/src/cost-tracker.ts +0 -355
  52. package/src/db.ts +0 -370
  53. package/src/dependency-scorer.ts +0 -337
  54. package/src/detect/adapters/aspnet.ts +0 -4
  55. package/src/detect/adapters/discover.ts +0 -469
  56. package/src/detect/adapters/file-sampler.ts +0 -225
  57. package/src/detect/adapters/go-chi.ts +0 -4
  58. package/src/detect/adapters/index.ts +0 -51
  59. package/src/detect/adapters/nextjs-trpc.ts +0 -166
  60. package/src/detect/adapters/parse-guard.ts +0 -133
  61. package/src/detect/adapters/phoenix.ts +0 -4
  62. package/src/detect/adapters/python-django.ts +0 -208
  63. package/src/detect/adapters/python-fastapi.ts +0 -223
  64. package/src/detect/adapters/python-flask.ts +0 -235
  65. package/src/detect/adapters/query-helpers.ts +0 -170
  66. package/src/detect/adapters/rails.ts +0 -4
  67. package/src/detect/adapters/runner.ts +0 -284
  68. package/src/detect/adapters/spring.ts +0 -4
  69. package/src/detect/adapters/swift-swiftui.ts +0 -171
  70. package/src/detect/adapters/tree-sitter-loader.ts +0 -517
  71. package/src/detect/adapters/types.ts +0 -191
  72. package/src/detect/codebase-introspector.ts +0 -210
  73. package/src/detect/domain-inferrer.ts +0 -197
  74. package/src/detect/drift.ts +0 -199
  75. package/src/detect/framework-detector.ts +0 -307
  76. package/src/detect/index.ts +0 -209
  77. package/src/detect/manifest-registry.ts +0 -261
  78. package/src/detect/migrate.ts +0 -362
  79. package/src/detect/monorepo-detector.ts +0 -348
  80. package/src/detect/package-detector.ts +0 -828
  81. package/src/detect/passthrough.ts +0 -108
  82. package/src/detect/regex-fallback.ts +0 -449
  83. package/src/detect/source-dir-detector.ts +0 -297
  84. package/src/detect/vr-command-map.ts +0 -167
  85. package/src/docs-map.json +0 -100
  86. package/src/docs-tools.ts +0 -519
  87. package/src/domains.ts +0 -182
  88. package/src/hooks/auto-learning-pipeline.ts +0 -228
  89. package/src/hooks/classify-failure.ts +0 -259
  90. package/src/hooks/cost-tracker.ts +0 -66
  91. package/src/hooks/fix-detector.ts +0 -186
  92. package/src/hooks/incident-pipeline.ts +0 -190
  93. package/src/hooks/intent-suggester.ts +0 -131
  94. package/src/hooks/post-edit-context.ts +0 -91
  95. package/src/hooks/post-tool-use.ts +0 -413
  96. package/src/hooks/pre-compact.ts +0 -168
  97. package/src/hooks/pre-delete-check.ts +0 -243
  98. package/src/hooks/quality-event.ts +0 -127
  99. package/src/hooks/rule-enforcement-pipeline.ts +0 -159
  100. package/src/hooks/security-gate.ts +0 -153
  101. package/src/hooks/session-end.ts +0 -467
  102. package/src/hooks/session-start.ts +0 -436
  103. package/src/hooks/user-prompt.ts +0 -157
  104. package/src/import-resolver.ts +0 -226
  105. package/src/knowledge-db.ts +0 -176
  106. package/src/knowledge-indexer.ts +0 -708
  107. package/src/knowledge-tools.ts +0 -1414
  108. package/src/lib/fileLock.ts +0 -203
  109. package/src/lib/framework-supports.ts +0 -85
  110. package/src/lib/gitToplevel.ts +0 -22
  111. package/src/lib/hook-registry.ts +0 -43
  112. package/src/lib/installLock.ts +0 -66
  113. package/src/lib/memory-path.ts +0 -49
  114. package/src/lib/pidLiveness.ts +0 -67
  115. package/src/lib/settings-local.ts +0 -110
  116. package/src/lib/sql-table-names.ts +0 -71
  117. package/src/license.ts +0 -547
  118. package/src/lsp/auto-detect.ts +0 -98
  119. package/src/lsp/client.ts +0 -776
  120. package/src/lsp/enrich.ts +0 -127
  121. package/src/lsp/types.ts +0 -221
  122. package/src/memory-db.ts +0 -1614
  123. package/src/memory-file-ingest.ts +0 -128
  124. package/src/memory-tools.ts +0 -424
  125. package/src/middleware-tree.ts +0 -71
  126. package/src/observability-tools.ts +0 -343
  127. package/src/observation-extractor.ts +0 -418
  128. package/src/page-deps.ts +0 -285
  129. package/src/permissions.ts +0 -422
  130. package/src/prompt-analyzer.ts +0 -332
  131. package/src/python/coupling-detector.ts +0 -125
  132. package/src/python/domain-enforcer.ts +0 -84
  133. package/src/python/impact-analyzer.ts +0 -96
  134. package/src/python/import-parser.ts +0 -244
  135. package/src/python/import-resolver.ts +0 -136
  136. package/src/python/migration-indexer.ts +0 -116
  137. package/src/python/migration-parser.ts +0 -332
  138. package/src/python/model-indexer.ts +0 -71
  139. package/src/python/model-parser.ts +0 -279
  140. package/src/python/route-indexer.ts +0 -59
  141. package/src/python/route-parser.ts +0 -317
  142. package/src/python-tools.ts +0 -630
  143. package/src/regression-detector.ts +0 -319
  144. package/src/rules.ts +0 -57
  145. package/src/schema-mapper.ts +0 -232
  146. package/src/security/adapter-origin.ts +0 -130
  147. package/src/security/adapter-verifier.ts +0 -319
  148. package/src/security/atomic-write.ts +0 -164
  149. package/src/security/fetcher.ts +0 -200
  150. package/src/security/install-tracking.ts +0 -319
  151. package/src/security/license-pubkey.generated.ts +0 -30
  152. package/src/security/license-response-verifier.ts +0 -149
  153. package/src/security/local-fingerprint.ts +0 -225
  154. package/src/security/manifest-cache.ts +0 -333
  155. package/src/security/manifest-schema.ts +0 -129
  156. package/src/security/registry-pubkey.generated.ts +0 -35
  157. package/src/security/telemetry.ts +0 -320
  158. package/src/security-scorer.ts +0 -405
  159. package/src/security-utils.ts +0 -133
  160. package/src/sentinel-db.ts +0 -580
  161. package/src/sentinel-scanner.ts +0 -406
  162. package/src/sentinel-tools.ts +0 -513
  163. package/src/sentinel-types.ts +0 -140
  164. package/src/server-dispatch.ts +0 -225
  165. package/src/server.ts +0 -107
  166. package/src/session-archiver.ts +0 -111
  167. package/src/session-state-generator.ts +0 -174
  168. package/src/team-knowledge.ts +0 -407
  169. package/src/tool-db-needs.ts +0 -232
  170. package/src/tools.ts +0 -1206
  171. package/src/transcript-parser.ts +0 -458
  172. package/src/trpc-index.ts +0 -215
  173. package/src/validate-features-runner.ts +0 -107
  174. package/src/validation-engine.ts +0 -358
  175. package/src/watch/daemon.ts +0 -385
  176. package/src/watch/lockfile-detector.ts +0 -65
  177. package/src/watch/paths.ts +0 -279
  178. 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
- }