@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.
- package/LICENSE +71 -0
- package/README.md +2 -2
- package/dist/hooks/cost-tracker.js +149 -11527
- package/dist/hooks/post-edit-context.js +127 -11493
- package/dist/hooks/post-tool-use.js +169 -11550
- package/dist/hooks/pre-compact.js +149 -11530
- package/dist/hooks/pre-delete-check.js +144 -11523
- package/dist/hooks/quality-event.js +149 -11527
- package/dist/hooks/session-end.js +188 -11570
- package/dist/hooks/session-start.js +159 -11534
- package/dist/hooks/user-prompt.js +149 -11530
- package/package.json +14 -19
- package/src/adr-generator.ts +292 -0
- package/src/analytics.ts +373 -0
- package/src/audit-trail.ts +450 -0
- package/src/backfill-sessions.ts +180 -0
- package/src/cli.ts +105 -0
- package/src/cloud-sync.ts +190 -0
- package/src/commands/doctor.ts +300 -0
- package/src/commands/init.ts +395 -0
- package/src/commands/install-hooks.ts +26 -0
- package/src/config.ts +357 -0
- package/src/cost-tracker.ts +355 -0
- package/src/db.ts +233 -0
- package/src/dependency-scorer.ts +337 -0
- package/src/docs-map.json +100 -0
- package/src/docs-tools.ts +517 -0
- package/src/domains.ts +181 -0
- package/src/hooks/cost-tracker.ts +66 -0
- package/src/hooks/intent-suggester.ts +131 -0
- package/src/hooks/post-edit-context.ts +91 -0
- package/src/hooks/post-tool-use.ts +175 -0
- package/src/hooks/pre-compact.ts +146 -0
- package/src/hooks/pre-delete-check.ts +153 -0
- package/src/hooks/quality-event.ts +127 -0
- package/src/hooks/security-gate.ts +121 -0
- package/src/hooks/session-end.ts +467 -0
- package/src/hooks/session-start.ts +210 -0
- package/src/hooks/user-prompt.ts +91 -0
- package/src/import-resolver.ts +224 -0
- package/src/memory-db.ts +1376 -0
- package/src/memory-tools.ts +391 -0
- package/src/middleware-tree.ts +70 -0
- package/src/observability-tools.ts +343 -0
- package/src/observation-extractor.ts +411 -0
- package/src/page-deps.ts +283 -0
- package/src/prompt-analyzer.ts +332 -0
- package/src/regression-detector.ts +319 -0
- package/src/rules.ts +57 -0
- package/src/schema-mapper.ts +232 -0
- package/src/security-scorer.ts +405 -0
- package/src/security-utils.ts +133 -0
- package/src/sentinel-db.ts +578 -0
- package/src/sentinel-scanner.ts +405 -0
- package/src/sentinel-tools.ts +512 -0
- package/src/sentinel-types.ts +140 -0
- package/src/server.ts +189 -0
- package/src/session-archiver.ts +112 -0
- package/src/session-state-generator.ts +174 -0
- package/src/team-knowledge.ts +407 -0
- package/src/tools.ts +847 -0
- package/src/transcript-parser.ts +458 -0
- package/src/trpc-index.ts +214 -0
- package/src/validate-features-runner.ts +106 -0
- package/src/validation-engine.ts +358 -0
- package/dist/cli.js +0 -7890
- 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
|
+
}
|