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