@massu/core 0.1.0 → 0.1.2

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 (67) hide show
  1. package/LICENSE +71 -0
  2. package/README.md +2 -2
  3. package/dist/hooks/cost-tracker.js +149 -11527
  4. package/dist/hooks/post-edit-context.js +127 -11493
  5. package/dist/hooks/post-tool-use.js +169 -11550
  6. package/dist/hooks/pre-compact.js +149 -11530
  7. package/dist/hooks/pre-delete-check.js +144 -11523
  8. package/dist/hooks/quality-event.js +149 -11527
  9. package/dist/hooks/session-end.js +188 -11570
  10. package/dist/hooks/session-start.js +159 -11534
  11. package/dist/hooks/user-prompt.js +149 -11530
  12. package/package.json +14 -19
  13. package/src/adr-generator.ts +292 -0
  14. package/src/analytics.ts +373 -0
  15. package/src/audit-trail.ts +450 -0
  16. package/src/backfill-sessions.ts +180 -0
  17. package/src/cli.ts +105 -0
  18. package/src/cloud-sync.ts +190 -0
  19. package/src/commands/doctor.ts +300 -0
  20. package/src/commands/init.ts +395 -0
  21. package/src/commands/install-hooks.ts +26 -0
  22. package/src/config.ts +357 -0
  23. package/src/cost-tracker.ts +355 -0
  24. package/src/db.ts +233 -0
  25. package/src/dependency-scorer.ts +337 -0
  26. package/src/docs-map.json +100 -0
  27. package/src/docs-tools.ts +517 -0
  28. package/src/domains.ts +181 -0
  29. package/src/hooks/cost-tracker.ts +66 -0
  30. package/src/hooks/intent-suggester.ts +131 -0
  31. package/src/hooks/post-edit-context.ts +91 -0
  32. package/src/hooks/post-tool-use.ts +175 -0
  33. package/src/hooks/pre-compact.ts +146 -0
  34. package/src/hooks/pre-delete-check.ts +153 -0
  35. package/src/hooks/quality-event.ts +127 -0
  36. package/src/hooks/security-gate.ts +121 -0
  37. package/src/hooks/session-end.ts +467 -0
  38. package/src/hooks/session-start.ts +210 -0
  39. package/src/hooks/user-prompt.ts +91 -0
  40. package/src/import-resolver.ts +224 -0
  41. package/src/memory-db.ts +1376 -0
  42. package/src/memory-tools.ts +391 -0
  43. package/src/middleware-tree.ts +70 -0
  44. package/src/observability-tools.ts +343 -0
  45. package/src/observation-extractor.ts +411 -0
  46. package/src/page-deps.ts +283 -0
  47. package/src/prompt-analyzer.ts +332 -0
  48. package/src/regression-detector.ts +319 -0
  49. package/src/rules.ts +57 -0
  50. package/src/schema-mapper.ts +232 -0
  51. package/src/security-scorer.ts +405 -0
  52. package/src/security-utils.ts +133 -0
  53. package/src/sentinel-db.ts +578 -0
  54. package/src/sentinel-scanner.ts +405 -0
  55. package/src/sentinel-tools.ts +512 -0
  56. package/src/sentinel-types.ts +140 -0
  57. package/src/server.ts +189 -0
  58. package/src/session-archiver.ts +112 -0
  59. package/src/session-state-generator.ts +174 -0
  60. package/src/team-knowledge.ts +407 -0
  61. package/src/tools.ts +847 -0
  62. package/src/transcript-parser.ts +458 -0
  63. package/src/trpc-index.ts +214 -0
  64. package/src/validate-features-runner.ts +106 -0
  65. package/src/validation-engine.ts +358 -0
  66. package/dist/cli.js +0 -7890
  67. package/dist/server.js +0 -7008
package/src/config.ts ADDED
@@ -0,0 +1,357 @@
1
+ // Copyright (c) 2026 Massu. All rights reserved.
2
+ // Licensed under BSL 1.1 - see LICENSE file for details.
3
+
4
+ import { resolve, dirname } from 'path';
5
+ import { existsSync, readFileSync } from 'fs';
6
+ import { parse as parseYaml } from 'yaml';
7
+ import { z } from 'zod';
8
+
9
+ // ============================================================
10
+ // Massu Configuration — Zod Schemas & Types
11
+ // ============================================================
12
+
13
+ // --- Domain Config ---
14
+ const DomainConfigSchema = z.object({
15
+ name: z.string().default('Unknown'),
16
+ routers: z.array(z.string()).default([]),
17
+ pages: z.array(z.string()).default([]),
18
+ tables: z.array(z.string()).default([]),
19
+ allowedImportsFrom: z.array(z.string()).default([]),
20
+ });
21
+ export type DomainConfig = z.infer<typeof DomainConfigSchema>;
22
+
23
+ // --- Pattern Rule Config ---
24
+ const PatternRuleConfigSchema = z.object({
25
+ pattern: z.string().default('**'),
26
+ rules: z.array(z.string()).default([]),
27
+ });
28
+ export type PatternRuleConfig = z.infer<typeof PatternRuleConfigSchema>;
29
+
30
+ // --- Cost Model ---
31
+ const CostModelSchema = z.object({
32
+ input_per_million: z.number(),
33
+ output_per_million: z.number(),
34
+ cache_read_per_million: z.number().optional(),
35
+ cache_write_per_million: z.number().optional(),
36
+ });
37
+
38
+ // --- Analytics Config ---
39
+ const AnalyticsConfigSchema = z.object({
40
+ quality: z.object({
41
+ weights: z.record(z.string(), z.number()).default({
42
+ bug_found: -5, vr_failure: -10, incident: -20, cr_violation: -3,
43
+ vr_pass: 2, clean_commit: 5, successful_verification: 3,
44
+ }),
45
+ categories: z.array(z.string()).default(['security', 'architecture', 'coupling', 'tests', 'rule_compliance']),
46
+ }).optional(),
47
+ cost: z.object({
48
+ models: z.record(z.string(), CostModelSchema).default({}),
49
+ currency: z.string().default('USD'),
50
+ }).optional(),
51
+ prompts: z.object({
52
+ success_indicators: z.array(z.string()).default(['committed', 'approved', 'looks good', 'perfect', 'great', 'thanks']),
53
+ failure_indicators: z.array(z.string()).default(['revert', 'wrong', "that's not", 'undo', 'incorrect']),
54
+ max_turns_for_success: z.number().default(2),
55
+ }).optional(),
56
+ }).optional();
57
+ export type AnalyticsConfig = z.infer<typeof AnalyticsConfigSchema>;
58
+
59
+ // --- Custom Pattern (for validation) ---
60
+ const CustomPatternSchema = z.object({
61
+ pattern: z.string(),
62
+ severity: z.string(),
63
+ message: z.string(),
64
+ });
65
+
66
+ // --- Governance Config ---
67
+ const GovernanceConfigSchema = z.object({
68
+ audit: z.object({
69
+ formats: z.array(z.string()).default(['summary', 'detailed', 'soc2']),
70
+ retention_days: z.number().default(365),
71
+ auto_log: z.record(z.string(), z.boolean()).default({
72
+ code_changes: true, rule_enforcement: true, approvals: true, commits: true,
73
+ }),
74
+ }).optional(),
75
+ validation: z.object({
76
+ realtime: z.boolean().default(true),
77
+ checks: z.record(z.string(), z.boolean()).default({
78
+ rule_compliance: true, import_existence: true, naming_conventions: true,
79
+ }),
80
+ custom_patterns: z.array(CustomPatternSchema).default([]),
81
+ }).optional(),
82
+ adr: z.object({
83
+ detection_phrases: z.array(z.string()).default(['chose', 'decided', 'switching to', 'moving from', 'going with']),
84
+ template: z.string().default('default'),
85
+ storage: z.string().default('database'),
86
+ output_dir: z.string().default('docs/adr'),
87
+ }).optional(),
88
+ }).optional();
89
+ export type GovernanceConfig = z.infer<typeof GovernanceConfigSchema>;
90
+
91
+ // --- Security Pattern ---
92
+ const SecurityPatternSchema = z.object({
93
+ pattern: z.string(),
94
+ severity: z.string(),
95
+ category: z.string(),
96
+ description: z.string(),
97
+ });
98
+
99
+ // --- Security Config ---
100
+ const SecurityConfigSchema = z.object({
101
+ patterns: z.array(SecurityPatternSchema).default([]),
102
+ auto_score_on_edit: z.boolean().default(true),
103
+ score_threshold_alert: z.number().default(50),
104
+ severity_weights: z.record(z.string(), z.number()).optional(),
105
+ restrictive_licenses: z.array(z.string()).optional(),
106
+ dep_alternatives: z.record(z.string(), z.array(z.string())).optional(),
107
+ dependencies: z.object({
108
+ package_manager: z.string().default('npm'),
109
+ blocked_packages: z.array(z.string()).default([]),
110
+ preferred_packages: z.record(z.string(), z.string()).default({}),
111
+ max_bundle_size_kb: z.number().default(500),
112
+ }).optional(),
113
+ }).optional();
114
+ export type SecurityConfig = z.infer<typeof SecurityConfigSchema>;
115
+
116
+ // --- Team Config ---
117
+ const TeamConfigSchema = z.object({
118
+ enabled: z.boolean().default(false),
119
+ sync_backend: z.string().default('local'),
120
+ developer_id: z.string().default('auto'),
121
+ share_by_default: z.boolean().default(false),
122
+ expertise_weights: z.object({
123
+ session: z.number().default(20),
124
+ observation: z.number().default(10),
125
+ }).optional(),
126
+ privacy: z.object({
127
+ share_file_paths: z.boolean().default(true),
128
+ share_code_snippets: z.boolean().default(false),
129
+ share_observations: z.boolean().default(true),
130
+ }).optional(),
131
+ }).optional();
132
+ export type TeamConfig = z.infer<typeof TeamConfigSchema>;
133
+
134
+ // --- Regression Config ---
135
+ const RegressionConfigSchema = z.object({
136
+ test_patterns: z.array(z.string()).default([
137
+ '{dir}/__tests__/{name}.test.{ext}',
138
+ '{dir}/{name}.spec.{ext}',
139
+ 'tests/{path}.test.{ext}',
140
+ ]),
141
+ test_runner: z.string().default('npm test'),
142
+ health_thresholds: z.object({
143
+ healthy: z.number().default(80),
144
+ warning: z.number().default(50),
145
+ }).optional(),
146
+ }).optional();
147
+ export type RegressionConfig = z.infer<typeof RegressionConfigSchema>;
148
+
149
+ // --- Cloud Config ---
150
+ const CloudConfigSchema = z.object({
151
+ enabled: z.boolean().default(false),
152
+ apiKey: z.string().optional(),
153
+ endpoint: z.string().optional(),
154
+ sync: z.object({
155
+ memory: z.boolean().default(true),
156
+ analytics: z.boolean().default(true),
157
+ audit: z.boolean().default(true),
158
+ }).default({ memory: true, analytics: true, audit: true }),
159
+ }).optional();
160
+ export type CloudConfig = z.infer<typeof CloudConfigSchema>;
161
+
162
+ // --- Paths Config ---
163
+ const PathsConfigSchema = z.object({
164
+ source: z.string().default('src'),
165
+ aliases: z.record(z.string(), z.string()).default({ '@': 'src' }),
166
+ routers: z.string().optional(),
167
+ routerRoot: z.string().optional(),
168
+ pages: z.string().optional(),
169
+ middleware: z.string().optional(),
170
+ schema: z.string().optional(),
171
+ components: z.string().optional(),
172
+ hooks: z.string().optional(),
173
+ });
174
+
175
+ // --- Top-level Raw Config Schema ---
176
+ // This validates the raw YAML output, coercing types and providing defaults.
177
+ const RawConfigSchema = z.object({
178
+ project: z.object({
179
+ name: z.string().default('my-project'),
180
+ root: z.string().default('auto'),
181
+ }).default({ name: 'my-project', root: 'auto' }),
182
+ framework: z.object({
183
+ type: z.string().default('typescript'),
184
+ router: z.string().default('none'),
185
+ orm: z.string().default('none'),
186
+ ui: z.string().default('none'),
187
+ }).default({ type: 'typescript', router: 'none', orm: 'none', ui: 'none' }),
188
+ paths: PathsConfigSchema.default({ source: 'src', aliases: { '@': 'src' } }),
189
+ toolPrefix: z.string().default('massu'),
190
+ dbAccessPattern: z.string().optional(),
191
+ knownMismatches: z.record(z.string(), z.record(z.string(), z.string())).optional(),
192
+ accessScopes: z.array(z.string()).optional(),
193
+ domains: z.array(DomainConfigSchema).default([]),
194
+ rules: z.array(PatternRuleConfigSchema).default([]),
195
+ analytics: AnalyticsConfigSchema,
196
+ governance: GovernanceConfigSchema,
197
+ security: SecurityConfigSchema,
198
+ team: TeamConfigSchema,
199
+ regression: RegressionConfigSchema,
200
+ cloud: CloudConfigSchema,
201
+ }).passthrough();
202
+
203
+ // --- Final Config interface (derived from Zod) ---
204
+ export interface Config {
205
+ project: { name: string; root: string };
206
+ framework: { type: string; router: string; orm: string; ui: string };
207
+ paths: z.infer<typeof PathsConfigSchema>;
208
+ toolPrefix: string;
209
+ dbAccessPattern?: string;
210
+ knownMismatches?: Record<string, Record<string, string>>;
211
+ accessScopes?: string[];
212
+ domains: DomainConfig[];
213
+ rules: PatternRuleConfig[];
214
+ analytics?: AnalyticsConfig;
215
+ governance?: GovernanceConfig;
216
+ security?: SecurityConfig;
217
+ team?: TeamConfig;
218
+ regression?: RegressionConfig;
219
+ cloud?: CloudConfig;
220
+ }
221
+
222
+ let _config: Config | null = null;
223
+ let _projectRoot: string | null = null;
224
+
225
+ /**
226
+ * Find the project root by walking up from cwd.
227
+ * Prioritizes massu.config.yaml (searched all the way up),
228
+ * then falls back to the nearest package.json or .git directory.
229
+ */
230
+ function findProjectRoot(): string {
231
+ const cwd = process.cwd();
232
+
233
+ // First pass: look for massu.config.yaml all the way up
234
+ let dir = cwd;
235
+ while (true) {
236
+ if (existsSync(resolve(dir, 'massu.config.yaml'))) {
237
+ return dir;
238
+ }
239
+ const parent = dirname(dir);
240
+ if (parent === dir) break;
241
+ dir = parent;
242
+ }
243
+
244
+ // Second pass: fall back to nearest package.json or .git
245
+ dir = cwd;
246
+ while (true) {
247
+ if (existsSync(resolve(dir, 'package.json'))) {
248
+ return dir;
249
+ }
250
+ if (existsSync(resolve(dir, '.git'))) {
251
+ return dir;
252
+ }
253
+ const parent = dirname(dir);
254
+ if (parent === dir) break;
255
+ dir = parent;
256
+ }
257
+
258
+ return cwd;
259
+ }
260
+
261
+ /**
262
+ * Get the project root directory.
263
+ */
264
+ export function getProjectRoot(): string {
265
+ if (!_projectRoot) {
266
+ _projectRoot = findProjectRoot();
267
+ }
268
+ return _projectRoot;
269
+ }
270
+
271
+ /**
272
+ * Load and return the Massu configuration.
273
+ * Searches for massu.config.yaml in the project root.
274
+ * Uses Zod for runtime validation with sensible defaults.
275
+ */
276
+ export function getConfig(): Config {
277
+ if (_config) return _config;
278
+
279
+ const root = getProjectRoot();
280
+ const configPath = resolve(root, 'massu.config.yaml');
281
+
282
+ let rawYaml: unknown = {};
283
+ if (existsSync(configPath)) {
284
+ const content = readFileSync(configPath, 'utf-8');
285
+ rawYaml = parseYaml(content) ?? {};
286
+ }
287
+
288
+ // Validate with Zod — provides defaults and type coercion
289
+ const parsed = RawConfigSchema.parse(rawYaml);
290
+
291
+ // Resolve project root path
292
+ const projectRoot = parsed.project.root === 'auto' || !parsed.project.root
293
+ ? root
294
+ : resolve(root, parsed.project.root);
295
+
296
+ _config = {
297
+ project: {
298
+ name: parsed.project.name,
299
+ root: projectRoot,
300
+ },
301
+ framework: parsed.framework,
302
+ paths: parsed.paths,
303
+ toolPrefix: parsed.toolPrefix,
304
+ dbAccessPattern: parsed.dbAccessPattern,
305
+ knownMismatches: parsed.knownMismatches,
306
+ accessScopes: parsed.accessScopes,
307
+ domains: parsed.domains,
308
+ rules: parsed.rules,
309
+ analytics: parsed.analytics,
310
+ governance: parsed.governance,
311
+ security: parsed.security,
312
+ team: parsed.team,
313
+ regression: parsed.regression,
314
+ cloud: parsed.cloud,
315
+ };
316
+
317
+ return _config;
318
+ }
319
+
320
+ /**
321
+ * Get resolved paths for common project locations.
322
+ * Computed from the YAML config with sensible defaults.
323
+ */
324
+ export function getResolvedPaths() {
325
+ const config = getConfig();
326
+ const root = getProjectRoot();
327
+
328
+ return {
329
+ codegraphDbPath: resolve(root, '.codegraph/codegraph.db'),
330
+ dataDbPath: resolve(root, '.massu/data.db'),
331
+ prismaSchemaPath: resolve(root, config.paths.schema ?? 'prisma/schema.prisma'),
332
+ rootRouterPath: resolve(root, config.paths.routerRoot ?? 'src/server/api/root.ts'),
333
+ routersDir: resolve(root, config.paths.routers ?? 'src/server/api/routers'),
334
+ srcDir: resolve(root, config.paths.source),
335
+ pathAlias: Object.fromEntries(
336
+ Object.entries(config.paths.aliases).map(([alias, target]) => [
337
+ alias,
338
+ resolve(root, target),
339
+ ])
340
+ ) as Record<string, string>,
341
+ extensions: ['.ts', '.tsx', '.js', '.jsx'] as const,
342
+ indexFiles: ['index.ts', 'index.tsx', 'index.js', 'index.jsx'] as const,
343
+ patternsDir: resolve(root, '.claude/patterns'),
344
+ claudeMdPath: resolve(root, '.claude/CLAUDE.md'),
345
+ docsMapPath: resolve(root, '.massu/docs-map.json'),
346
+ helpSitePath: resolve(root, '../' + config.project.name + '-help'),
347
+ memoryDbPath: resolve(root, '.massu/memory.db'),
348
+ };
349
+ }
350
+
351
+ /**
352
+ * Reset the cached config (useful for testing).
353
+ */
354
+ export function resetConfig(): void {
355
+ _config = null;
356
+ _projectRoot = null;
357
+ }