@massu/core 0.1.2 → 0.4.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 (84) hide show
  1. package/commands/_shared-preamble.md +76 -0
  2. package/commands/massu-audit-deps.md +211 -0
  3. package/commands/massu-changelog.md +174 -0
  4. package/commands/massu-cleanup.md +315 -0
  5. package/commands/massu-commit.md +481 -0
  6. package/commands/massu-create-plan.md +752 -0
  7. package/commands/massu-dead-code.md +131 -0
  8. package/commands/massu-debug.md +484 -0
  9. package/commands/massu-deploy.md +91 -0
  10. package/commands/massu-deps.md +374 -0
  11. package/commands/massu-doc-gen.md +279 -0
  12. package/commands/massu-docs.md +364 -0
  13. package/commands/massu-estimate.md +313 -0
  14. package/commands/massu-golden-path.md +973 -0
  15. package/commands/massu-guide.md +167 -0
  16. package/commands/massu-hotfix.md +480 -0
  17. package/commands/massu-loop-playwright.md +837 -0
  18. package/commands/massu-loop.md +775 -0
  19. package/commands/massu-new-feature.md +511 -0
  20. package/commands/massu-parity.md +214 -0
  21. package/commands/massu-plan.md +456 -0
  22. package/commands/massu-push-light.md +207 -0
  23. package/commands/massu-push.md +434 -0
  24. package/commands/massu-refactor.md +410 -0
  25. package/commands/massu-release.md +363 -0
  26. package/commands/massu-review.md +238 -0
  27. package/commands/massu-simplify.md +281 -0
  28. package/commands/massu-status.md +278 -0
  29. package/commands/massu-tdd.md +201 -0
  30. package/commands/massu-test.md +516 -0
  31. package/commands/massu-verify-playwright.md +281 -0
  32. package/commands/massu-verify.md +667 -0
  33. package/dist/cli.js +12521 -0
  34. package/dist/hooks/cost-tracker.js +80 -5
  35. package/dist/hooks/post-edit-context.js +72 -6
  36. package/dist/hooks/post-tool-use.js +234 -57
  37. package/dist/hooks/pre-compact.js +144 -5
  38. package/dist/hooks/pre-delete-check.js +141 -11
  39. package/dist/hooks/quality-event.js +80 -5
  40. package/dist/hooks/security-gate.js +29 -0
  41. package/dist/hooks/session-end.js +83 -8
  42. package/dist/hooks/session-start.js +153 -7
  43. package/dist/hooks/user-prompt.js +166 -5
  44. package/package.json +6 -5
  45. package/src/backfill-sessions.ts +5 -4
  46. package/src/cli.ts +6 -1
  47. package/src/commands/doctor.ts +193 -6
  48. package/src/commands/init.ts +235 -6
  49. package/src/commands/install-commands.ts +137 -0
  50. package/src/config.ts +68 -2
  51. package/src/db.ts +115 -2
  52. package/src/docs-tools.ts +8 -6
  53. package/src/hooks/post-edit-context.ts +1 -1
  54. package/src/hooks/post-tool-use.ts +130 -0
  55. package/src/hooks/pre-compact.ts +23 -1
  56. package/src/hooks/pre-delete-check.ts +92 -4
  57. package/src/hooks/security-gate.ts +32 -0
  58. package/src/hooks/session-start.ts +97 -4
  59. package/src/hooks/user-prompt.ts +46 -1
  60. package/src/import-resolver.ts +2 -1
  61. package/src/knowledge-db.ts +169 -0
  62. package/src/knowledge-indexer.ts +704 -0
  63. package/src/knowledge-tools.ts +1413 -0
  64. package/src/license.ts +482 -0
  65. package/src/memory-db.ts +14 -1
  66. package/src/observation-extractor.ts +11 -4
  67. package/src/page-deps.ts +3 -2
  68. package/src/python/coupling-detector.ts +124 -0
  69. package/src/python/domain-enforcer.ts +83 -0
  70. package/src/python/impact-analyzer.ts +95 -0
  71. package/src/python/import-parser.ts +244 -0
  72. package/src/python/import-resolver.ts +135 -0
  73. package/src/python/migration-indexer.ts +115 -0
  74. package/src/python/migration-parser.ts +332 -0
  75. package/src/python/model-indexer.ts +70 -0
  76. package/src/python/model-parser.ts +279 -0
  77. package/src/python/route-indexer.ts +58 -0
  78. package/src/python/route-parser.ts +317 -0
  79. package/src/python-tools.ts +629 -0
  80. package/src/sentinel-db.ts +2 -1
  81. package/src/server.ts +29 -6
  82. package/src/session-archiver.ts +4 -5
  83. package/src/tools.ts +283 -31
  84. package/README.md +0 -40
@@ -13,7 +13,7 @@ import { resolve, basename } from 'path';
13
13
  import { getMemoryDb, createSession, addObservation, addSummary, addUserPrompt, deduplicateFailedAttempt } from './memory-db.ts';
14
14
  import { parseTranscript, extractUserMessages, getLastAssistantMessage } from './transcript-parser.ts';
15
15
  import { extractObservationsFromEntries } from './observation-extractor.ts';
16
- import { getProjectRoot } from './config.ts';
16
+ import { getProjectRoot, getConfig } from './config.ts';
17
17
 
18
18
  /**
19
19
  * Auto-detect the Claude Code project transcript directory.
@@ -22,12 +22,13 @@ import { getProjectRoot } from './config.ts';
22
22
  function findTranscriptDir(): string {
23
23
  const home = process.env.HOME ?? '~';
24
24
  const projectRoot = getProjectRoot();
25
+ const claudeDirName = getConfig().conventions?.claudeDirName ?? '.claude';
25
26
  // Claude Code escapes the path by replacing / with -
26
27
  const escapedPath = projectRoot.replace(/\//g, '-');
27
- const candidate = resolve(home, '.claude/projects', escapedPath);
28
+ const candidate = resolve(home, `${claudeDirName}/projects`, escapedPath);
28
29
  if (existsSync(candidate)) return candidate;
29
- // Fallback: scan .claude/projects/ for directories matching the project name
30
- const projectsDir = resolve(home, '.claude/projects');
30
+ // Fallback: scan projects dir for directories matching the project name
31
+ const projectsDir = resolve(home, `${claudeDirName}/projects`);
31
32
  if (existsSync(projectsDir)) {
32
33
  try {
33
34
  const entries = readdirSync(projectsDir);
package/src/cli.ts CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  // Copyright (c) 2026 Massu. All rights reserved.
3
2
  // Licensed under BSL 1.1 - see LICENSE file for details.
4
3
 
@@ -43,6 +42,11 @@ async function main(): Promise<void> {
43
42
  await runInstallHooks();
44
43
  break;
45
44
  }
45
+ case 'install-commands': {
46
+ const { runInstallCommands } = await import('./commands/install-commands.ts');
47
+ await runInstallCommands();
48
+ break;
49
+ }
46
50
  case 'validate-config': {
47
51
  const { runValidateConfig } = await import('./commands/doctor.ts');
48
52
  await runValidateConfig();
@@ -77,6 +81,7 @@ Commands:
77
81
  init Set up Massu AI in your project (one command, full setup)
78
82
  doctor Check installation health
79
83
  install-hooks Install/update Claude Code hooks
84
+ install-commands Install/update slash commands
80
85
  validate-config Validate massu.config.yaml
81
86
 
82
87
  Options:
@@ -9,15 +9,20 @@
9
9
  * 2. .mcp.json has massu entry
10
10
  * 3. .claude/settings.local.json has hooks config
11
11
  * 4. All 11 compiled hook files exist
12
- * 5. better-sqlite3 native module loads
13
- * 6. Node.js version >= 18
14
- * 7. Git repository detected
12
+ * 5. Knowledge DB exists (.massu/memory.db)
13
+ * 6. Memory directory exists (~/.claude/projects/.../memory/)
14
+ * 7. Shell hooks wired in settings.local.json
15
+ * 8. better-sqlite3 native module loads
16
+ * 9. Node.js version >= 18
17
+ * 10. Git repository detected
15
18
  */
16
19
 
17
- import { existsSync, readFileSync } from 'fs';
20
+ import { existsSync, readFileSync, readdirSync } from 'fs';
18
21
  import { resolve, dirname } from 'path';
19
22
  import { fileURLToPath } from 'url';
20
23
  import { parse as parseYaml } from 'yaml';
24
+ import { getConfig, getResolvedPaths } from '../config.ts';
25
+ import { getCurrentTier, getLicenseInfo, daysUntilExpiry } from '../license.ts';
21
26
 
22
27
  const __filename = fileURLToPath(import.meta.url);
23
28
  const __dirname = dirname(__filename);
@@ -73,7 +78,7 @@ function checkConfig(projectRoot: string): CheckResult {
73
78
  }
74
79
 
75
80
  function checkMcpServer(projectRoot: string): CheckResult {
76
- const mcpPath = resolve(projectRoot, '.mcp.json');
81
+ const mcpPath = getResolvedPaths().mcpJsonPath;
77
82
  if (!existsSync(mcpPath)) {
78
83
  return { name: 'MCP Server', status: 'fail', detail: '.mcp.json not found. Run: npx massu init' };
79
84
  }
@@ -91,7 +96,7 @@ function checkMcpServer(projectRoot: string): CheckResult {
91
96
  }
92
97
 
93
98
  function checkHooksConfig(projectRoot: string): CheckResult {
94
- const settingsPath = resolve(projectRoot, '.claude/settings.local.json');
99
+ const settingsPath = getResolvedPaths().settingsLocalPath;
95
100
  if (!existsSync(settingsPath)) {
96
101
  return { name: 'Hooks Config', status: 'fail', detail: '.claude/settings.local.json not found. Run: npx massu init' };
97
102
  }
@@ -194,6 +199,180 @@ async function checkGitRepo(projectRoot: string): Promise<CheckResult> {
194
199
  }
195
200
  }
196
201
 
202
+ function checkKnowledgeDb(projectRoot: string): CheckResult {
203
+ // Knowledge DB is the memory DB
204
+ const knowledgeDbPath = getResolvedPaths().memoryDbPath;
205
+ if (!existsSync(knowledgeDbPath)) {
206
+ return {
207
+ name: 'Knowledge DB',
208
+ status: 'warn',
209
+ detail: '.massu/memory.db not found (will auto-create on first session)',
210
+ };
211
+ }
212
+ return { name: 'Knowledge DB', status: 'pass', detail: '.massu/memory.db exists' };
213
+ }
214
+
215
+ function checkMemoryDir(_projectRoot: string): CheckResult {
216
+ // Memory dir: ~/.claude/projects/-<encoded-root>/memory/ (resolved via config)
217
+ const memoryDir = getResolvedPaths().memoryDir;
218
+ if (!existsSync(memoryDir)) {
219
+ return {
220
+ name: 'Memory Directory',
221
+ status: 'warn',
222
+ detail: 'Memory directory not found. Run: npx massu init',
223
+ };
224
+ }
225
+ return { name: 'Memory Directory', status: 'pass', detail: `Memory directory exists` };
226
+ }
227
+
228
+ function checkShellHooksWired(_projectRoot: string): CheckResult {
229
+ // Verify that .claude/settings.local.json has hooks configured (shell hooks are wired)
230
+ const settingsPath = getResolvedPaths().settingsLocalPath;
231
+ if (!existsSync(settingsPath)) {
232
+ return {
233
+ name: 'Shell Hooks',
234
+ status: 'fail',
235
+ detail: 'settings.local.json not found. Run: npx massu install-hooks',
236
+ };
237
+ }
238
+
239
+ try {
240
+ const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
241
+ const hooks = content.hooks ?? {};
242
+ const hasSessionStart = Array.isArray(hooks.SessionStart) && hooks.SessionStart.length > 0;
243
+ const hasPreToolUse = Array.isArray(hooks.PreToolUse) && hooks.PreToolUse.length > 0;
244
+ if (!hasSessionStart && !hasPreToolUse) {
245
+ return {
246
+ name: 'Shell Hooks',
247
+ status: 'fail',
248
+ detail: 'No lifecycle hooks wired. Run: npx massu install-hooks',
249
+ };
250
+ }
251
+ return { name: 'Shell Hooks', status: 'pass', detail: 'Lifecycle hooks wired in settings.local.json' };
252
+ } catch (err) {
253
+ return {
254
+ name: 'Shell Hooks',
255
+ status: 'fail',
256
+ detail: `settings.local.json parse error: ${err instanceof Error ? err.message : String(err)}`,
257
+ };
258
+ }
259
+ }
260
+
261
+ async function checkLicenseStatus(): Promise<CheckResult> {
262
+ try {
263
+ const tier = await getCurrentTier();
264
+ const info = await getLicenseInfo();
265
+
266
+ if (tier === 'free' && !info.validUntil) {
267
+ return { name: 'License', status: 'pass', detail: 'Free (no API key configured)' };
268
+ }
269
+
270
+ const days = await daysUntilExpiry();
271
+ if (days >= 0 && info.validUntil) {
272
+ return {
273
+ name: 'License',
274
+ status: 'pass',
275
+ detail: `${tier.charAt(0).toUpperCase() + tier.slice(1)} (valid until ${info.validUntil})`,
276
+ };
277
+ }
278
+
279
+ return {
280
+ name: 'License',
281
+ status: 'pass',
282
+ detail: `${tier.charAt(0).toUpperCase() + tier.slice(1)} (valid)`,
283
+ };
284
+ } catch (err) {
285
+ return {
286
+ name: 'License',
287
+ status: 'warn',
288
+ detail: `Could not check license: ${err instanceof Error ? err.message : String(err)}`,
289
+ };
290
+ }
291
+ }
292
+
293
+ function checkPythonHealth(projectRoot: string): CheckResult | null {
294
+ const config = getConfig();
295
+ if (!config.python?.root) return null;
296
+
297
+ const pythonRoot = resolve(projectRoot, config.python.root);
298
+ if (!existsSync(pythonRoot)) {
299
+ return {
300
+ name: 'Python',
301
+ status: 'fail',
302
+ detail: `Python root directory not found: ${config.python.root}`,
303
+ };
304
+ }
305
+
306
+ // Count .py files recursively (shallow scan for performance)
307
+ let pyFileCount = 0;
308
+ let routeCount = 0;
309
+ let modelCount = 0;
310
+ const initPyMissing: string[] = [];
311
+
312
+ function scanDir(dir: string, depth: number): void {
313
+ if (depth > 5) return;
314
+ try {
315
+ const entries = readdirSync(dir, { withFileTypes: true });
316
+ for (const entry of entries) {
317
+ if (entry.isDirectory()) {
318
+ const excludeDirs = config.python?.exclude_dirs || ['__pycache__', '.venv', 'venv', '.mypy_cache', '.pytest_cache'];
319
+ if (!excludeDirs.includes(entry.name)) {
320
+ const subdir = resolve(dir, entry.name);
321
+ // Check for __init__.py in package directories
322
+ if (depth <= 2 && !existsSync(resolve(subdir, '__init__.py'))) {
323
+ // Only flag directories that contain .py files
324
+ try {
325
+ const subEntries = readdirSync(subdir);
326
+ if (subEntries.some(f => f.endsWith('.py') && f !== '__init__.py')) {
327
+ initPyMissing.push(entry.name);
328
+ }
329
+ } catch { /* skip */ }
330
+ }
331
+ scanDir(subdir, depth + 1);
332
+ }
333
+ } else if (entry.name.endsWith('.py')) {
334
+ pyFileCount++;
335
+ // Rough heuristic for routes and models
336
+ if (entry.name === 'routes.py' || entry.name === 'router.py' || entry.name === 'endpoints.py') {
337
+ routeCount++;
338
+ }
339
+ if (entry.name === 'models.py' || entry.name === 'model.py' || entry.name === 'schemas.py') {
340
+ modelCount++;
341
+ }
342
+ }
343
+ }
344
+ } catch { /* skip unreadable dirs */ }
345
+ }
346
+
347
+ scanDir(pythonRoot, 0);
348
+
349
+ if (pyFileCount === 0) {
350
+ return {
351
+ name: 'Python',
352
+ status: 'warn',
353
+ detail: `Python root ${config.python.root} exists but no .py files found`,
354
+ };
355
+ }
356
+
357
+ const parts: string[] = [`${pyFileCount} .py files`];
358
+ if (routeCount > 0) parts.push(`${routeCount} route files`);
359
+ if (modelCount > 0) parts.push(`${modelCount} model files`);
360
+ if (initPyMissing.length > 0) {
361
+ parts.push(`missing __init__.py: ${initPyMissing.slice(0, 3).join(', ')}${initPyMissing.length > 3 ? '...' : ''}`);
362
+ return {
363
+ name: 'Python',
364
+ status: 'warn',
365
+ detail: parts.join(', '),
366
+ };
367
+ }
368
+
369
+ return {
370
+ name: 'Python',
371
+ status: 'pass',
372
+ detail: parts.join(', '),
373
+ };
374
+ }
375
+
197
376
  // ============================================================
198
377
  // Main Doctor Flow
199
378
  // ============================================================
@@ -211,11 +390,19 @@ export async function runDoctor(): Promise<void> {
211
390
  checkMcpServer(projectRoot),
212
391
  checkHooksConfig(projectRoot),
213
392
  checkHookFiles(projectRoot),
393
+ checkKnowledgeDb(projectRoot),
394
+ checkMemoryDir(projectRoot),
395
+ checkShellHooksWired(projectRoot),
214
396
  await checkNativeModules(),
215
397
  checkNodeVersion(),
216
398
  await checkGitRepo(projectRoot),
399
+ await checkLicenseStatus(),
217
400
  ];
218
401
 
402
+ // Add Python health check if configured
403
+ const pythonCheck = checkPythonHealth(projectRoot);
404
+ if (pythonCheck) checks.push(pythonCheck);
405
+
219
406
  let passed = 0;
220
407
  let failed = 0;
221
408
  let warned = 0;
@@ -8,12 +8,21 @@
8
8
  * 2. Generates massu.config.yaml (or preserves existing)
9
9
  * 3. Registers MCP server in .mcp.json (creates or merges)
10
10
  * 4. Installs all 11 hooks in .claude/settings.local.json
11
- * 5. Prints success summary
11
+ * 5. Installs slash commands into .claude/commands/
12
+ * 6. Initializes memory directory
13
+ * 7. Prints success summary
12
14
  */
13
15
 
14
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
15
- import { resolve, basename } from 'path';
16
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
17
+ import { resolve, basename, dirname } from 'path';
18
+ import { fileURLToPath } from 'url';
19
+ import { homedir } from 'os';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = dirname(__filename);
16
23
  import { stringify as yamlStringify } from 'yaml';
24
+ import { getConfig } from '../config.ts';
25
+ import { installCommands } from './install-commands.ts';
17
26
 
18
27
  // ============================================================
19
28
  // Types
@@ -87,6 +96,135 @@ export function detectFramework(projectRoot: string): FrameworkDetection {
87
96
  return result;
88
97
  }
89
98
 
99
+ // ============================================================
100
+ // Python Project Detection
101
+ // ============================================================
102
+
103
+ interface PythonDetection {
104
+ detected: boolean;
105
+ root: string;
106
+ hasFastapi: boolean;
107
+ hasSqlalchemy: boolean;
108
+ hasAlembic: boolean;
109
+ alembicDir: string | null;
110
+ }
111
+
112
+ export function detectPython(projectRoot: string): PythonDetection {
113
+ const result: PythonDetection = {
114
+ detected: false,
115
+ root: '',
116
+ hasFastapi: false,
117
+ hasSqlalchemy: false,
118
+ hasAlembic: false,
119
+ alembicDir: null,
120
+ };
121
+
122
+ // Check for Python project markers
123
+ const markers = ['pyproject.toml', 'setup.py', 'requirements.txt', 'Pipfile'];
124
+ const hasMarker = markers.some(m => existsSync(resolve(projectRoot, m)));
125
+ if (!hasMarker) return result;
126
+
127
+ result.detected = true;
128
+
129
+ // Scan dependencies for FastAPI and SQLAlchemy
130
+ const depFiles = [
131
+ { file: 'pyproject.toml', parser: parsePyprojectDeps },
132
+ { file: 'requirements.txt', parser: parseRequirementsDeps },
133
+ { file: 'setup.py', parser: parseSetupPyDeps },
134
+ { file: 'Pipfile', parser: parsePipfileDeps },
135
+ ];
136
+
137
+ for (const { file, parser } of depFiles) {
138
+ const filePath = resolve(projectRoot, file);
139
+ if (existsSync(filePath)) {
140
+ try {
141
+ const content = readFileSync(filePath, 'utf-8');
142
+ const deps = parser(content);
143
+ if (deps.includes('fastapi')) result.hasFastapi = true;
144
+ if (deps.includes('sqlalchemy')) result.hasSqlalchemy = true;
145
+ } catch {
146
+ // Best effort
147
+ }
148
+ }
149
+ }
150
+
151
+ // Check for Alembic
152
+ if (existsSync(resolve(projectRoot, 'alembic.ini'))) {
153
+ result.hasAlembic = true;
154
+ // Try to find the alembic versions directory
155
+ if (existsSync(resolve(projectRoot, 'alembic'))) {
156
+ result.alembicDir = 'alembic';
157
+ }
158
+ } else if (existsSync(resolve(projectRoot, 'alembic'))) {
159
+ result.hasAlembic = true;
160
+ result.alembicDir = 'alembic';
161
+ }
162
+
163
+ // Auto-detect Python source root
164
+ const candidateRoots = ['app', 'src', 'backend', 'api'];
165
+ for (const candidate of candidateRoots) {
166
+ const candidatePath = resolve(projectRoot, candidate);
167
+ if (existsSync(candidatePath) && existsSync(resolve(candidatePath, '__init__.py'))) {
168
+ result.root = candidate;
169
+ break;
170
+ }
171
+ // Also check for .py files directly (some projects use app/ without __init__.py)
172
+ if (existsSync(candidatePath)) {
173
+ try {
174
+ const files = readdirSync(candidatePath);
175
+ if (files.some(f => f.endsWith('.py'))) {
176
+ result.root = candidate;
177
+ break;
178
+ }
179
+ } catch {
180
+ // Best effort
181
+ }
182
+ }
183
+ }
184
+
185
+ // Fallback: use '.' if no candidate root found
186
+ if (!result.root) {
187
+ result.root = '.';
188
+ }
189
+
190
+ return result;
191
+ }
192
+
193
+ function parsePyprojectDeps(content: string): string[] {
194
+ const deps: string[] = [];
195
+ const lower = content.toLowerCase();
196
+ if (lower.includes('fastapi')) deps.push('fastapi');
197
+ if (lower.includes('sqlalchemy')) deps.push('sqlalchemy');
198
+ return deps;
199
+ }
200
+
201
+ function parseRequirementsDeps(content: string): string[] {
202
+ const deps: string[] = [];
203
+ const lower = content.toLowerCase();
204
+ for (const line of lower.split('\n')) {
205
+ const trimmed = line.trim();
206
+ if (trimmed.startsWith('fastapi')) deps.push('fastapi');
207
+ if (trimmed.startsWith('sqlalchemy')) deps.push('sqlalchemy');
208
+ }
209
+ return deps;
210
+ }
211
+
212
+ function parseSetupPyDeps(content: string): string[] {
213
+ const deps: string[] = [];
214
+ const lower = content.toLowerCase();
215
+ if (lower.includes('fastapi')) deps.push('fastapi');
216
+ if (lower.includes('sqlalchemy')) deps.push('sqlalchemy');
217
+ return deps;
218
+ }
219
+
220
+ function parsePipfileDeps(content: string): string[] {
221
+ const deps: string[] = [];
222
+ const lower = content.toLowerCase();
223
+ if (lower.includes('fastapi')) deps.push('fastapi');
224
+ if (lower.includes('sqlalchemy')) deps.push('sqlalchemy');
225
+ return deps;
226
+ }
227
+
90
228
  // ============================================================
91
229
  // Config File Generation
92
230
  // ============================================================
@@ -100,7 +238,7 @@ export function generateConfig(projectRoot: string, framework: FrameworkDetectio
100
238
 
101
239
  const projectName = basename(projectRoot);
102
240
 
103
- const config = {
241
+ const config: Record<string, unknown> = {
104
242
  project: {
105
243
  name: projectName,
106
244
  root: 'auto',
@@ -125,6 +263,21 @@ export function generateConfig(projectRoot: string, framework: FrameworkDetectio
125
263
  ],
126
264
  };
127
265
 
266
+ // Detect and add Python configuration
267
+ const python = detectPython(projectRoot);
268
+ if (python.detected) {
269
+ const pythonConfig: Record<string, unknown> = {
270
+ root: python.root,
271
+ exclude_dirs: ['__pycache__', '.venv', 'venv', '.mypy_cache', '.pytest_cache'],
272
+ };
273
+ if (python.hasFastapi) pythonConfig.framework = 'fastapi';
274
+ if (python.hasSqlalchemy) pythonConfig.orm = 'sqlalchemy';
275
+ if (python.hasAlembic && python.alembicDir) {
276
+ pythonConfig.alembic_dir = python.alembicDir;
277
+ }
278
+ config.python = pythonConfig;
279
+ }
280
+
128
281
  const yamlContent = `# Massu AI Configuration
129
282
  # Generated by: npx massu init
130
283
  # Documentation: https://massu.ai/docs/getting-started/configuration
@@ -277,7 +430,8 @@ export function buildHooksConfig(hooksDir: string): HooksConfig {
277
430
  }
278
431
 
279
432
  export function installHooks(projectRoot: string): { installed: boolean; count: number } {
280
- const claudeDir = resolve(projectRoot, '.claude');
433
+ const claudeDirName = getConfig().conventions?.claudeDirName ?? '.claude';
434
+ const claudeDir = resolve(projectRoot, claudeDirName);
281
435
  const settingsPath = resolve(claudeDir, 'settings.local.json');
282
436
 
283
437
  // Ensure .claude directory exists
@@ -317,6 +471,51 @@ export function installHooks(projectRoot: string): { installed: boolean; count:
317
471
  return { installed: true, count: hookCount };
318
472
  }
319
473
 
474
+ // ============================================================
475
+ // Memory Directory Initialization
476
+ // ============================================================
477
+
478
+ /**
479
+ * Initialize the memory directory and create an initial MEMORY.md if absent.
480
+ * The memory directory lives in ~/.claude/projects/<encoded-root>/memory/
481
+ * matching the path used by memory-db.ts / knowledge-tools.ts.
482
+ */
483
+ export function initMemoryDir(projectRoot: string): { created: boolean; memoryMdCreated: boolean } {
484
+ // Encode the project root the same way as getResolvedPaths() in config.ts
485
+ const encodedRoot = '-' + projectRoot.replace(/\//g, '-');
486
+ const memoryDir = resolve(homedir(), `.claude/projects/${encodedRoot}/memory`);
487
+
488
+ let created = false;
489
+ if (!existsSync(memoryDir)) {
490
+ mkdirSync(memoryDir, { recursive: true });
491
+ created = true;
492
+ }
493
+
494
+ const memoryMdPath = resolve(memoryDir, 'MEMORY.md');
495
+ let memoryMdCreated = false;
496
+ if (!existsSync(memoryMdPath)) {
497
+ const projectName = basename(projectRoot);
498
+ const memoryContent = `# ${projectName} - Massu Memory
499
+
500
+ ## Key Learnings
501
+ <!-- Important patterns and conventions discovered during development -->
502
+
503
+ ## Common Gotchas
504
+ <!-- Non-obvious issues and how to avoid them -->
505
+
506
+ ## Corrections
507
+ <!-- Wrong behaviors that were corrected and how to prevent them -->
508
+
509
+ ## File Index
510
+ <!-- Significant files and directories -->
511
+ `;
512
+ writeFileSync(memoryMdPath, memoryContent, 'utf-8');
513
+ memoryMdCreated = true;
514
+ }
515
+
516
+ return { created, memoryMdCreated };
517
+ }
518
+
320
519
  // ============================================================
321
520
  // Main Init Flow
322
521
  // ============================================================
@@ -339,6 +538,16 @@ export async function runInit(): Promise<void> {
339
538
  const detected = frameworkParts.length > 0 ? frameworkParts.join(', ') : 'JavaScript';
340
539
  console.log(` Detected: ${detected}`);
341
540
 
541
+ // Step 1b: Detect Python
542
+ const python = detectPython(projectRoot);
543
+ if (python.detected) {
544
+ const pyParts: string[] = ['Python'];
545
+ if (python.hasFastapi) pyParts.push('FastAPI');
546
+ if (python.hasSqlalchemy) pyParts.push('SQLAlchemy');
547
+ if (python.hasAlembic) pyParts.push('Alembic');
548
+ console.log(` Detected: ${pyParts.join(', ')} (root: ${python.root})`);
549
+ }
550
+
342
551
  // Step 2: Create config
343
552
  const configCreated = generateConfig(projectRoot, framework);
344
553
  if (configCreated) {
@@ -359,7 +568,27 @@ export async function runInit(): Promise<void> {
359
568
  const { count: hooksCount } = installHooks(projectRoot);
360
569
  console.log(` Installed ${hooksCount} hooks in .claude/settings.local.json`);
361
570
 
362
- // Step 5: Databases info
571
+ // Step 5: Install slash commands
572
+ const cmdResult = installCommands(projectRoot);
573
+ const cmdTotal = cmdResult.installed + cmdResult.updated + cmdResult.skipped;
574
+ if (cmdResult.installed > 0 || cmdResult.updated > 0) {
575
+ console.log(` Installed ${cmdTotal} slash commands (${cmdResult.installed} new, ${cmdResult.updated} updated)`);
576
+ } else {
577
+ console.log(` ${cmdTotal} slash commands already up to date`);
578
+ }
579
+
580
+ // Step 6: Initialize memory directory
581
+ const { created: memDirCreated, memoryMdCreated } = initMemoryDir(projectRoot);
582
+ if (memDirCreated) {
583
+ console.log(' Created memory directory (~/.claude/projects/.../memory/)');
584
+ } else {
585
+ console.log(' Memory directory already exists');
586
+ }
587
+ if (memoryMdCreated) {
588
+ console.log(' Created initial MEMORY.md');
589
+ }
590
+
591
+ // Step 7: Databases info
363
592
  console.log(' Databases will auto-create on first session');
364
593
 
365
594
  // Summary