@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
@@ -0,0 +1,395 @@
1
+ // Copyright (c) 2026 Massu. All rights reserved.
2
+ // Licensed under BSL 1.1 - see LICENSE file for details.
3
+
4
+ /**
5
+ * `massu init` — One-command full project setup.
6
+ *
7
+ * 1. Detects project framework (scans package.json)
8
+ * 2. Generates massu.config.yaml (or preserves existing)
9
+ * 3. Registers MCP server in .mcp.json (creates or merges)
10
+ * 4. Installs all 11 hooks in .claude/settings.local.json
11
+ * 5. Prints success summary
12
+ */
13
+
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
15
+ import { resolve, basename } from 'path';
16
+ import { stringify as yamlStringify } from 'yaml';
17
+
18
+ // ============================================================
19
+ // Types
20
+ // ============================================================
21
+
22
+ interface FrameworkDetection {
23
+ type: string;
24
+ router: string;
25
+ orm: string;
26
+ ui: string;
27
+ }
28
+
29
+ interface InitResult {
30
+ configCreated: boolean;
31
+ configSkipped: boolean;
32
+ mcpRegistered: boolean;
33
+ mcpSkipped: boolean;
34
+ hooksInstalled: boolean;
35
+ hooksCount: number;
36
+ framework: FrameworkDetection;
37
+ }
38
+
39
+ // ============================================================
40
+ // Framework Auto-Detection
41
+ // ============================================================
42
+
43
+ export function detectFramework(projectRoot: string): FrameworkDetection {
44
+ const result: FrameworkDetection = {
45
+ type: 'javascript',
46
+ router: 'none',
47
+ orm: 'none',
48
+ ui: 'none',
49
+ };
50
+
51
+ const pkgPath = resolve(projectRoot, 'package.json');
52
+ if (!existsSync(pkgPath)) return result;
53
+
54
+ try {
55
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
56
+ const allDeps = {
57
+ ...pkg.dependencies,
58
+ ...pkg.devDependencies,
59
+ };
60
+
61
+ // Language detection
62
+ if (allDeps['typescript']) result.type = 'typescript';
63
+
64
+ // UI framework detection
65
+ if (allDeps['next']) result.ui = 'nextjs';
66
+ else if (allDeps['@sveltejs/kit']) result.ui = 'sveltekit';
67
+ else if (allDeps['nuxt']) result.ui = 'nuxt';
68
+ else if (allDeps['@angular/core']) result.ui = 'angular';
69
+ else if (allDeps['vue']) result.ui = 'vue';
70
+ else if (allDeps['react']) result.ui = 'react';
71
+
72
+ // Router detection
73
+ if (allDeps['@trpc/server']) result.router = 'trpc';
74
+ else if (allDeps['graphql'] || allDeps['@apollo/server']) result.router = 'graphql';
75
+ else if (allDeps['express'] || allDeps['fastify'] || allDeps['hono']) result.router = 'rest';
76
+
77
+ // ORM detection
78
+ if (allDeps['@prisma/client'] || allDeps['prisma']) result.orm = 'prisma';
79
+ else if (allDeps['drizzle-orm']) result.orm = 'drizzle';
80
+ else if (allDeps['typeorm']) result.orm = 'typeorm';
81
+ else if (allDeps['sequelize']) result.orm = 'sequelize';
82
+ else if (allDeps['mongoose']) result.orm = 'mongoose';
83
+ } catch {
84
+ // Best effort
85
+ }
86
+
87
+ return result;
88
+ }
89
+
90
+ // ============================================================
91
+ // Config File Generation
92
+ // ============================================================
93
+
94
+ export function generateConfig(projectRoot: string, framework: FrameworkDetection): boolean {
95
+ const configPath = resolve(projectRoot, 'massu.config.yaml');
96
+
97
+ if (existsSync(configPath)) {
98
+ return false; // Config already exists
99
+ }
100
+
101
+ const projectName = basename(projectRoot);
102
+
103
+ const config = {
104
+ project: {
105
+ name: projectName,
106
+ root: 'auto',
107
+ },
108
+ framework: {
109
+ type: framework.type,
110
+ router: framework.router,
111
+ orm: framework.orm,
112
+ ui: framework.ui,
113
+ },
114
+ paths: {
115
+ source: 'src',
116
+ aliases: { '@': 'src' },
117
+ },
118
+ toolPrefix: 'massu',
119
+ domains: [],
120
+ rules: [
121
+ {
122
+ pattern: 'src/**/*.ts',
123
+ rules: ['Use ESM imports, not CommonJS'],
124
+ },
125
+ ],
126
+ };
127
+
128
+ const yamlContent = `# Massu AI Configuration
129
+ # Generated by: npx massu init
130
+ # Documentation: https://massu.ai/docs/getting-started/configuration
131
+
132
+ ${yamlStringify(config)}`;
133
+
134
+ writeFileSync(configPath, yamlContent, 'utf-8');
135
+ return true;
136
+ }
137
+
138
+ // ============================================================
139
+ // MCP Server Registration
140
+ // ============================================================
141
+
142
+ export function registerMcpServer(projectRoot: string): boolean {
143
+ const mcpPath = resolve(projectRoot, '.mcp.json');
144
+
145
+ let existing: Record<string, unknown> = {};
146
+ if (existsSync(mcpPath)) {
147
+ try {
148
+ existing = JSON.parse(readFileSync(mcpPath, 'utf-8'));
149
+ } catch {
150
+ existing = {};
151
+ }
152
+ }
153
+
154
+ // Check if already registered
155
+ const servers = (existing.mcpServers ?? {}) as Record<string, unknown>;
156
+ if (servers.massu) {
157
+ return false; // Already registered
158
+ }
159
+
160
+ // Add massu server
161
+ servers.massu = {
162
+ type: 'stdio',
163
+ command: 'npx',
164
+ args: ['-y', '@massu/core'],
165
+ };
166
+
167
+ existing.mcpServers = servers;
168
+
169
+ writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
170
+ return true;
171
+ }
172
+
173
+ // ============================================================
174
+ // Hook Installation
175
+ // ============================================================
176
+
177
+ interface HookEntry {
178
+ type: 'command';
179
+ command: string;
180
+ timeout: number;
181
+ }
182
+
183
+ interface HookGroup {
184
+ matcher?: string;
185
+ hooks: HookEntry[];
186
+ }
187
+
188
+ type HooksConfig = Record<string, HookGroup[]>;
189
+
190
+ /**
191
+ * Resolve the path to compiled hook files.
192
+ * Handles both local development and npm-installed scenarios.
193
+ */
194
+ export function resolveHooksDir(): string {
195
+ // Try to find the hooks in node_modules first (installed via npm)
196
+ const cwd = process.cwd();
197
+ const nodeModulesPath = resolve(cwd, 'node_modules/@massu/core/dist/hooks');
198
+ if (existsSync(nodeModulesPath)) {
199
+ return 'node_modules/@massu/core/dist/hooks';
200
+ }
201
+
202
+ // Fall back to finding relative to this source file
203
+ const localPath = resolve(__dirname, '../dist/hooks');
204
+ if (existsSync(localPath)) {
205
+ return localPath;
206
+ }
207
+
208
+ // Default to node_modules path (will be created on npm install)
209
+ return 'node_modules/@massu/core/dist/hooks';
210
+ }
211
+
212
+ function hookCmd(hooksDir: string, hookFile: string): string {
213
+ return `node ${hooksDir}/${hookFile}`;
214
+ }
215
+
216
+ export function buildHooksConfig(hooksDir: string): HooksConfig {
217
+ return {
218
+ SessionStart: [
219
+ {
220
+ hooks: [
221
+ { type: 'command', command: hookCmd(hooksDir, 'session-start.js'), timeout: 10 },
222
+ ],
223
+ },
224
+ ],
225
+ PreToolUse: [
226
+ {
227
+ matcher: 'Bash',
228
+ hooks: [
229
+ { type: 'command', command: hookCmd(hooksDir, 'security-gate.js'), timeout: 5 },
230
+ ],
231
+ },
232
+ {
233
+ matcher: 'Bash|Write',
234
+ hooks: [
235
+ { type: 'command', command: hookCmd(hooksDir, 'pre-delete-check.js'), timeout: 5 },
236
+ ],
237
+ },
238
+ ],
239
+ PostToolUse: [
240
+ {
241
+ hooks: [
242
+ { type: 'command', command: hookCmd(hooksDir, 'post-tool-use.js'), timeout: 10 },
243
+ { type: 'command', command: hookCmd(hooksDir, 'quality-event.js'), timeout: 5 },
244
+ { type: 'command', command: hookCmd(hooksDir, 'cost-tracker.js'), timeout: 5 },
245
+ ],
246
+ },
247
+ {
248
+ matcher: 'Edit|Write',
249
+ hooks: [
250
+ { type: 'command', command: hookCmd(hooksDir, 'post-edit-context.js'), timeout: 5 },
251
+ ],
252
+ },
253
+ ],
254
+ Stop: [
255
+ {
256
+ hooks: [
257
+ { type: 'command', command: hookCmd(hooksDir, 'session-end.js'), timeout: 15 },
258
+ ],
259
+ },
260
+ ],
261
+ PreCompact: [
262
+ {
263
+ hooks: [
264
+ { type: 'command', command: hookCmd(hooksDir, 'pre-compact.js'), timeout: 10 },
265
+ ],
266
+ },
267
+ ],
268
+ UserPromptSubmit: [
269
+ {
270
+ hooks: [
271
+ { type: 'command', command: hookCmd(hooksDir, 'user-prompt.js'), timeout: 5 },
272
+ { type: 'command', command: hookCmd(hooksDir, 'intent-suggester.js'), timeout: 5 },
273
+ ],
274
+ },
275
+ ],
276
+ };
277
+ }
278
+
279
+ export function installHooks(projectRoot: string): { installed: boolean; count: number } {
280
+ const claudeDir = resolve(projectRoot, '.claude');
281
+ const settingsPath = resolve(claudeDir, 'settings.local.json');
282
+
283
+ // Ensure .claude directory exists
284
+ if (!existsSync(claudeDir)) {
285
+ mkdirSync(claudeDir, { recursive: true });
286
+ }
287
+
288
+ // Read existing settings
289
+ let settings: Record<string, unknown> = {};
290
+ if (existsSync(settingsPath)) {
291
+ try {
292
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
293
+ } catch {
294
+ settings = {};
295
+ }
296
+ }
297
+
298
+ // Resolve hook paths
299
+ const hooksDir = resolveHooksDir();
300
+
301
+ // Build hooks config
302
+ const hooksConfig = buildHooksConfig(hooksDir);
303
+
304
+ // Count total hooks
305
+ let hookCount = 0;
306
+ for (const groups of Object.values(hooksConfig)) {
307
+ for (const group of groups) {
308
+ hookCount += group.hooks.length;
309
+ }
310
+ }
311
+
312
+ // Merge hooks into settings (replace hooks section, preserve everything else)
313
+ settings.hooks = hooksConfig;
314
+
315
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
316
+
317
+ return { installed: true, count: hookCount };
318
+ }
319
+
320
+ // ============================================================
321
+ // Main Init Flow
322
+ // ============================================================
323
+
324
+ export async function runInit(): Promise<void> {
325
+ const projectRoot = process.cwd();
326
+
327
+ console.log('');
328
+ console.log('Massu AI - Project Setup');
329
+ console.log('========================');
330
+ console.log('');
331
+
332
+ // Step 1: Detect framework
333
+ const framework = detectFramework(projectRoot);
334
+ const frameworkParts: string[] = [];
335
+ if (framework.type !== 'javascript') frameworkParts.push(capitalize(framework.type));
336
+ if (framework.ui !== 'none') frameworkParts.push(formatName(framework.ui));
337
+ if (framework.orm !== 'none') frameworkParts.push(capitalize(framework.orm));
338
+ if (framework.router !== 'none') frameworkParts.push(framework.router.toUpperCase());
339
+ const detected = frameworkParts.length > 0 ? frameworkParts.join(', ') : 'JavaScript';
340
+ console.log(` Detected: ${detected}`);
341
+
342
+ // Step 2: Create config
343
+ const configCreated = generateConfig(projectRoot, framework);
344
+ if (configCreated) {
345
+ console.log(' Created massu.config.yaml');
346
+ } else {
347
+ console.log(' massu.config.yaml already exists (preserved)');
348
+ }
349
+
350
+ // Step 3: Register MCP server
351
+ const mcpRegistered = registerMcpServer(projectRoot);
352
+ if (mcpRegistered) {
353
+ console.log(' Registered MCP server in .mcp.json');
354
+ } else {
355
+ console.log(' MCP server already registered in .mcp.json');
356
+ }
357
+
358
+ // Step 4: Install hooks
359
+ const { count: hooksCount } = installHooks(projectRoot);
360
+ console.log(` Installed ${hooksCount} hooks in .claude/settings.local.json`);
361
+
362
+ // Step 5: Databases info
363
+ console.log(' Databases will auto-create on first session');
364
+
365
+ // Summary
366
+ console.log('');
367
+ console.log('Massu AI is ready. Start a Claude Code session to begin.');
368
+ console.log('');
369
+ console.log('Next steps:');
370
+ console.log(' claude # Start a session (hooks activate automatically)');
371
+ console.log(' npx massu doctor # Verify installation health');
372
+ console.log('');
373
+ console.log('Documentation: https://massu.ai/docs');
374
+ console.log('');
375
+ }
376
+
377
+ // ============================================================
378
+ // Helpers
379
+ // ============================================================
380
+
381
+ function capitalize(str: string): string {
382
+ return str.charAt(0).toUpperCase() + str.slice(1);
383
+ }
384
+
385
+ function formatName(name: string): string {
386
+ const names: Record<string, string> = {
387
+ nextjs: 'Next.js',
388
+ sveltekit: 'SvelteKit',
389
+ nuxt: 'Nuxt',
390
+ angular: 'Angular',
391
+ vue: 'Vue',
392
+ react: 'React',
393
+ };
394
+ return names[name] ?? capitalize(name);
395
+ }
@@ -0,0 +1,26 @@
1
+ // Copyright (c) 2026 Massu. All rights reserved.
2
+ // Licensed under BSL 1.1 - see LICENSE file for details.
3
+
4
+ /**
5
+ * `massu install-hooks` — Standalone hook installation.
6
+ *
7
+ * Installs or updates all 11 Claude Code hooks in .claude/settings.local.json.
8
+ * Uses the same logic as `massu init` but only handles hooks.
9
+ */
10
+
11
+ import { installHooks } from './init.ts';
12
+
13
+ export async function runInstallHooks(): Promise<void> {
14
+ const projectRoot = process.cwd();
15
+
16
+ console.log('');
17
+ console.log('Massu AI - Hook Installation');
18
+ console.log('============================');
19
+ console.log('');
20
+
21
+ const { count } = installHooks(projectRoot);
22
+ console.log(` Installed ${count} hooks in .claude/settings.local.json`);
23
+ console.log('');
24
+ console.log('Hooks will activate on your next Claude Code session.');
25
+ console.log('');
26
+ }