@goobits/sherpa 1.0.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 (55) hide show
  1. package/dist/chunk-3CILH2TO.js +387 -0
  2. package/dist/chunk-3CILH2TO.js.map +7 -0
  3. package/dist/chunk-5NF3BSD6.js +512 -0
  4. package/dist/chunk-5NF3BSD6.js.map +7 -0
  5. package/dist/chunk-IIU6U7TE.js +307 -0
  6. package/dist/chunk-IIU6U7TE.js.map +7 -0
  7. package/dist/chunk-LQZTKH3U.js +307 -0
  8. package/dist/chunk-LQZTKH3U.js.map +7 -0
  9. package/dist/cli.d.ts +11 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +84 -0
  12. package/dist/cli.js.map +7 -0
  13. package/dist/commands/init.d.ts +7 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +333 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/post.d.ts +20 -0
  18. package/dist/commands/post.d.ts.map +1 -0
  19. package/dist/commands/post.js +183 -0
  20. package/dist/commands/post.js.map +1 -0
  21. package/dist/commands/pre.d.ts +18 -0
  22. package/dist/commands/pre.d.ts.map +1 -0
  23. package/dist/commands/pre.js +102 -0
  24. package/dist/commands/pre.js.map +1 -0
  25. package/dist/commands/status.d.ts +5 -0
  26. package/dist/commands/status.d.ts.map +1 -0
  27. package/dist/commands/status.js +48 -0
  28. package/dist/commands/status.js.map +1 -0
  29. package/dist/daemon-V2QDZTUB.js +89 -0
  30. package/dist/daemon-V2QDZTUB.js.map +7 -0
  31. package/dist/daemon.d.ts +9 -0
  32. package/dist/daemon.d.ts.map +1 -0
  33. package/dist/daemon.js +112 -0
  34. package/dist/daemon.js.map +1 -0
  35. package/dist/index.d.ts +15 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +36 -0
  38. package/dist/index.js.map +7 -0
  39. package/dist/parser.d.ts +21 -0
  40. package/dist/parser.d.ts.map +1 -0
  41. package/dist/parser.js +152 -0
  42. package/dist/parser.js.map +1 -0
  43. package/dist/reviewer/index.js +544 -0
  44. package/dist/reviewer/index.js.map +7 -0
  45. package/dist/rules.d.ts +21 -0
  46. package/dist/rules.d.ts.map +1 -0
  47. package/dist/rules.js +165 -0
  48. package/dist/rules.js.map +1 -0
  49. package/dist/status-Q6Z4TFJZ.js +52 -0
  50. package/dist/status-Q6Z4TFJZ.js.map +7 -0
  51. package/dist/types.d.ts +69 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +13 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +52 -0
@@ -0,0 +1,307 @@
1
+ // src/commands/init.ts
2
+ import { execSync } from "child_process";
3
+ import { createRequire } from "module";
4
+ import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { dirname, join, resolve } from "path";
6
+ import { fileURLToPath } from "url";
7
+ var __dirname = dirname(fileURLToPath(import.meta.url));
8
+ var CLAUDE_HOOK_CONFIG = {
9
+ PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: "sherpa pre" }] }],
10
+ PostToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: "sherpa post" }] }]
11
+ };
12
+ var GUARD_CONFIG = {
13
+ maxTokens: 2e3,
14
+ previewTokens: 500,
15
+ scratchDir: ".claude/scratch",
16
+ maxAgeMinutes: 60,
17
+ maxScratchSizeMB: 50
18
+ };
19
+ var REVIEW_COMMAND = `# review
20
+
21
+ Use the MCP tool \`review\` with the arguments provided by the user.
22
+ If no arguments are given, ask for the paths or mode.
23
+ `;
24
+ var TREE_COMMAND = `# tree
25
+
26
+ Use the MCP tool \`tree\` with the arguments provided by the user.
27
+ If no arguments are given, show the default repo tree.
28
+ `;
29
+ var LINT_STAGED_CONFIG = {
30
+ "*.{js,jsx,ts,tsx,json,md,yml,yaml}": ["prettier --write"]
31
+ };
32
+ var HUSKY_PRE_COMMIT = `npx lint-staged
33
+ if command -v gitleaks >/dev/null 2>&1; then
34
+ gitleaks protect --staged --verbose
35
+ else
36
+ echo "gitleaks not found - skipping"
37
+ fi
38
+ `;
39
+ function getNodePath() {
40
+ try {
41
+ return execSync("which node", { encoding: "utf-8" }).trim();
42
+ } catch {
43
+ return "node";
44
+ }
45
+ }
46
+ function getReviewerPath() {
47
+ const require2 = createRequire(import.meta.url);
48
+ const bundledPath = resolve(__dirname, "..", "reviewer", "index.js");
49
+ if (existsSync(bundledPath)) {
50
+ return bundledPath;
51
+ }
52
+ try {
53
+ return require2.resolve("@goobits/sherpa-reviewer/dist/index.js");
54
+ } catch {
55
+ }
56
+ const reviewerPath = resolve(__dirname, "../../../reviewer/dist/index.js");
57
+ if (existsSync(reviewerPath)) {
58
+ return reviewerPath;
59
+ }
60
+ return reviewerPath;
61
+ }
62
+ function runInit() {
63
+ const isInitCommand = process.argv[2] === "init";
64
+ const initArgs = isInitCommand ? process.argv.slice(3) : [];
65
+ const force = initArgs.includes("--force");
66
+ const cwd = process.cwd();
67
+ console.log("Setting up sherpa...\n");
68
+ setupClaudeHooks(cwd, force);
69
+ setupClaudeCommands(cwd, force);
70
+ setupMcpConfig(cwd, force);
71
+ setupHusky(cwd, force);
72
+ setupLintStaged(cwd, force);
73
+ checkGitleaks();
74
+ console.log(`
75
+ ${"=".repeat(50)}`);
76
+ console.log("Sherpa setup complete!\n");
77
+ console.log("What was configured:");
78
+ console.log(" [x] .claude/settings.local.json - Hooks");
79
+ console.log(" [x] .claude/guard.json - Guard config");
80
+ console.log(" [x] .claude/commands/review.md - Slash command");
81
+ console.log(" [x] .claude/commands/tree.md - Slash command");
82
+ console.log(" [x] .mcp.json - MCP servers");
83
+ console.log(" [x] .husky/pre-commit - Git pre-commit hook");
84
+ console.log(" [x] .lintstagedrc.json - Lint staged files");
85
+ console.log("");
86
+ console.log("Pre-commit will run:");
87
+ console.log(" 1. lint-staged (lint/format changed files)");
88
+ console.log(" 2. gitleaks (scan for secrets)");
89
+ console.log("");
90
+ console.log("Claude Code:");
91
+ console.log(" - sherpa pre: Block dangerous bash commands");
92
+ console.log(" - sherpa post: Offload large outputs");
93
+ console.log(" - reviewer: AI code review (MCP)");
94
+ console.log("");
95
+ console.log("IMPORTANT: Restart Claude Code to load the MCP server.");
96
+ console.log("=".repeat(50));
97
+ }
98
+ function setupClaudeHooks(cwd, force) {
99
+ const claudeDir = join(cwd, ".claude");
100
+ const configPath = join(claudeDir, "guard.json");
101
+ const settingsPath = join(claudeDir, "settings.local.json");
102
+ if (!existsSync(claudeDir)) {
103
+ mkdirSync(claudeDir, { recursive: true });
104
+ console.log("Created .claude/ directory");
105
+ }
106
+ if (!existsSync(configPath) || force) {
107
+ writeFileSync(configPath, `${JSON.stringify(GUARD_CONFIG, null, 2)}
108
+ `);
109
+ console.log("Created .claude/guard.json");
110
+ } else {
111
+ console.log(".claude/guard.json already exists (use --force to overwrite)");
112
+ }
113
+ let settings = {};
114
+ if (existsSync(settingsPath)) {
115
+ try {
116
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
117
+ } catch {
118
+ console.warn("Warning: Could not parse existing settings.local.json");
119
+ }
120
+ }
121
+ settings.hooks = settings.hooks || {};
122
+ let hooksUpdated = false;
123
+ for (const [hookType, hooks] of Object.entries(CLAUDE_HOOK_CONFIG)) {
124
+ const existing = settings.hooks[hookType] || [];
125
+ const hasSherpa = existing.some(
126
+ (h) => h.hooks?.some((hook) => hook.command?.startsWith("sherpa "))
127
+ );
128
+ if (!hasSherpa) {
129
+ settings.hooks[hookType] = [...existing, ...hooks];
130
+ hooksUpdated = true;
131
+ }
132
+ }
133
+ if (hooksUpdated) {
134
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
135
+ `);
136
+ console.log("Updated .claude/settings.local.json with hooks");
137
+ } else {
138
+ console.log("Claude hooks already configured");
139
+ }
140
+ }
141
+ function setupClaudeCommands(cwd, force) {
142
+ const claudeDir = join(cwd, ".claude");
143
+ const commandsDir = join(claudeDir, "commands");
144
+ const reviewCommandPath = join(commandsDir, "review.md");
145
+ const treeCommandPath = join(commandsDir, "tree.md");
146
+ if (!existsSync(commandsDir)) {
147
+ mkdirSync(commandsDir, { recursive: true });
148
+ }
149
+ if (!existsSync(reviewCommandPath) || force) {
150
+ writeFileSync(reviewCommandPath, REVIEW_COMMAND);
151
+ console.log("Created .claude/commands/review.md");
152
+ } else {
153
+ console.log(".claude/commands/review.md already exists");
154
+ }
155
+ if (!existsSync(treeCommandPath) || force) {
156
+ writeFileSync(treeCommandPath, TREE_COMMAND);
157
+ console.log("Created .claude/commands/tree.md");
158
+ } else {
159
+ console.log(".claude/commands/tree.md already exists");
160
+ }
161
+ }
162
+ function setupMcpConfig(cwd, force) {
163
+ const mcpPath = join(cwd, ".mcp.json");
164
+ const settingsPath = join(cwd, ".claude/settings.local.json");
165
+ const nodePath = getNodePath();
166
+ const reviewerPath = getReviewerPath();
167
+ if (existsSync(settingsPath)) {
168
+ try {
169
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
170
+ if (settings.mcpServers) {
171
+ delete settings.mcpServers;
172
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
173
+ `);
174
+ console.log("Cleaned up stale MCP config from settings.local.json");
175
+ }
176
+ } catch {
177
+ }
178
+ }
179
+ const mcpConfig = {
180
+ type: "stdio",
181
+ command: nodePath,
182
+ args: [reviewerPath],
183
+ env: {}
184
+ };
185
+ try {
186
+ execSync("claude mcp remove reviewer -s project 2>/dev/null || true", {
187
+ cwd,
188
+ stdio: "pipe"
189
+ });
190
+ execSync("claude mcp remove cerebras-reviewer -s project 2>/dev/null || true", {
191
+ cwd,
192
+ stdio: "pipe"
193
+ });
194
+ execSync(`claude mcp add reviewer -s project ${nodePath} ${reviewerPath}`, {
195
+ cwd,
196
+ stdio: "pipe"
197
+ });
198
+ console.log("Configured MCP server via claude CLI");
199
+ return;
200
+ } catch {
201
+ }
202
+ let mcpJson = { mcpServers: {} };
203
+ if (existsSync(mcpPath)) {
204
+ try {
205
+ mcpJson = JSON.parse(readFileSync(mcpPath, "utf-8"));
206
+ } catch {
207
+ }
208
+ }
209
+ delete mcpJson.mcpServers["cerebras-reviewer"];
210
+ mcpJson.mcpServers["reviewer"] = mcpConfig;
211
+ writeFileSync(mcpPath, `${JSON.stringify(mcpJson, null, 2)}
212
+ `);
213
+ console.log("Configured .mcp.json with reviewer");
214
+ try {
215
+ const testMsg = '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}';
216
+ execSync(`echo '${testMsg}' | "${nodePath}" "${reviewerPath}"`, {
217
+ stdio: "pipe",
218
+ timeout: 5e3
219
+ });
220
+ console.log("Verified MCP server responds correctly");
221
+ } catch {
222
+ console.warn("Warning: MCP server test failed - check paths and try restarting Claude Code");
223
+ }
224
+ }
225
+ function setupHusky(cwd, force) {
226
+ const huskyDir = join(cwd, ".husky");
227
+ const preCommitPath = join(huskyDir, "pre-commit");
228
+ const pkgPath = join(cwd, "package.json");
229
+ if (!existsSync(pkgPath)) {
230
+ console.log("No package.json found - skipping husky setup");
231
+ return;
232
+ }
233
+ try {
234
+ JSON.parse(readFileSync(pkgPath, "utf-8"));
235
+ } catch {
236
+ console.warn("Warning: Could not parse package.json");
237
+ return;
238
+ }
239
+ const hasHusky = existsSync(huskyDir);
240
+ if (!hasHusky) {
241
+ try {
242
+ console.log("Initializing husky...");
243
+ execSync("npx husky init", { cwd, stdio: "pipe" });
244
+ console.log("Initialized husky");
245
+ } catch {
246
+ console.warn("Could not initialize husky automatically");
247
+ console.warn("Run: npx husky init");
248
+ return;
249
+ }
250
+ }
251
+ if (!existsSync(preCommitPath) || force) {
252
+ writeFileSync(preCommitPath, HUSKY_PRE_COMMIT);
253
+ chmodSync(preCommitPath, "755");
254
+ console.log("Created .husky/pre-commit");
255
+ } else {
256
+ const existing = readFileSync(preCommitPath, "utf-8");
257
+ if (existing.includes("npm test")) {
258
+ writeFileSync(preCommitPath, HUSKY_PRE_COMMIT);
259
+ chmodSync(preCommitPath, "755");
260
+ console.log("Replaced default pre-commit with lint-staged + gitleaks");
261
+ return;
262
+ }
263
+ let updated = false;
264
+ if (!existing.includes("lint-staged")) {
265
+ appendFileSync(preCommitPath, "\nnpx lint-staged\n");
266
+ updated = true;
267
+ }
268
+ if (!existing.includes("gitleaks")) {
269
+ appendFileSync(preCommitPath, "\ngitleaks protect --staged --verbose\n");
270
+ updated = true;
271
+ }
272
+ if (updated) {
273
+ console.log("Updated .husky/pre-commit with lint-staged + gitleaks");
274
+ } else {
275
+ console.log(".husky/pre-commit already configured");
276
+ }
277
+ }
278
+ }
279
+ function setupLintStaged(cwd, force) {
280
+ const configPath = join(cwd, ".lintstagedrc.json");
281
+ if (!existsSync(configPath) || force) {
282
+ writeFileSync(configPath, `${JSON.stringify(LINT_STAGED_CONFIG, null, 2)}
283
+ `);
284
+ console.log("Created .lintstagedrc.json");
285
+ } else {
286
+ console.log(".lintstagedrc.json already exists");
287
+ }
288
+ }
289
+ function checkGitleaks() {
290
+ try {
291
+ const checkCmd = process.platform === "win32" ? "where gitleaks" : "command -v gitleaks";
292
+ execSync(checkCmd, { stdio: "pipe" });
293
+ console.log("gitleaks found");
294
+ } catch {
295
+ console.log("");
296
+ console.log("NOTE: gitleaks not found. Install it:");
297
+ console.log(" brew install gitleaks # macOS");
298
+ console.log(" apt install gitleaks # Debian/Ubuntu");
299
+ console.log(" choco install gitleaks # Windows");
300
+ console.log(" https://github.com/gitleaks/gitleaks#installing");
301
+ }
302
+ }
303
+
304
+ export {
305
+ runInit
306
+ };
307
+ //# sourceMappingURL=chunk-IIU6U7TE.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/commands/init.ts"],
4
+ "sourcesContent": ["/**\n * sherpa init - Set up repo with husky, lint-staged, gitleaks, and claude hooks\n *\n * Usage: sherpa init [--force]\n */\n\nimport { execSync } from 'child_process'\nimport { createRequire } from 'module'\nimport { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'\nimport { dirname, join, resolve } from 'path'\nimport { fileURLToPath } from 'url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\ninterface HookEntry {\n matcher: string\n hooks: Array<{ type: string; command: string }>\n}\n\ninterface McpServer {\n type: 'stdio'\n command: string\n args: string[]\n env?: Record<string, string>\n}\n\ninterface McpJson {\n mcpServers: Record<string, McpServer>\n}\n\ninterface ClaudeSettings {\n hooks?: {\n PreToolUse?: HookEntry[]\n PostToolUse?: HookEntry[]\n }\n [key: string]: unknown\n}\n\nconst CLAUDE_HOOK_CONFIG = {\n PreToolUse: [{ matcher: 'Bash', hooks: [{ type: 'command', command: 'sherpa pre' }] }],\n PostToolUse: [{ matcher: 'Bash', hooks: [{ type: 'command', command: 'sherpa post' }] }]\n}\n\nconst GUARD_CONFIG = {\n maxTokens: 2000,\n previewTokens: 500,\n scratchDir: '.claude/scratch',\n maxAgeMinutes: 60,\n maxScratchSizeMB: 50\n}\n\nconst REVIEW_COMMAND = `# review\n\nUse the MCP tool \\`review\\` with the arguments provided by the user.\nIf no arguments are given, ask for the paths or mode.\n`\n\nconst TREE_COMMAND = `# tree\n\nUse the MCP tool \\`tree\\` with the arguments provided by the user.\nIf no arguments are given, show the default repo tree.\n`\n\nconst LINT_STAGED_CONFIG = {\n '*.{js,jsx,ts,tsx,json,md,yml,yaml}': ['prettier --write']\n}\n\nconst HUSKY_PRE_COMMIT = `npx lint-staged\nif command -v gitleaks >/dev/null 2>&1; then\n\tgitleaks protect --staged --verbose\nelse\n\techo \"gitleaks not found - skipping\"\nfi\n`\n\n/**\n * Get absolute path to node binary\n */\nfunction getNodePath(): string {\n try {\n return execSync('which node', { encoding: 'utf-8' }).trim()\n } catch {\n return 'node' // fallback\n }\n}\n\n/**\n * Get absolute path to reviewer dist\n */\nfunction getReviewerPath(): string {\n const require = createRequire(import.meta.url)\n const bundledPath = resolve(__dirname, '..', 'reviewer', 'index.js')\n if (existsSync(bundledPath)) {\n return bundledPath\n }\n try {\n return require.resolve('@goobits/sherpa-reviewer/dist/index.js')\n } catch {\n // Fall through to monorepo path\n }\n\n // __dirname is packages/sherpa/dist/commands in compiled code\n // Reviewer is at packages/reviewer/dist/index.js\n const reviewerPath = resolve(__dirname, '../../../reviewer/dist/index.js')\n if (existsSync(reviewerPath)) {\n return reviewerPath\n }\n\n return reviewerPath\n}\n\nexport function runInit(): void {\n const isInitCommand = process.argv[2] === 'init'\n const initArgs = isInitCommand ? process.argv.slice(3) : []\n const force = initArgs.includes('--force')\n const cwd = process.cwd()\n\n console.log('Setting up sherpa...\\n')\n\n // 1. Create .claude directory and hooks config\n setupClaudeHooks(cwd, force)\n\n // 2. Create .claude commands\n setupClaudeCommands(cwd, force)\n\n // 3. Set up MCP server in .mcp.json\n setupMcpConfig(cwd, force)\n\n // 4. Set up husky\n setupHusky(cwd, force)\n\n // 5. Set up lint-staged\n setupLintStaged(cwd, force)\n\n // 6. Check for gitleaks\n checkGitleaks()\n\n // Print success\n console.log(`\\n${'='.repeat(50)}`)\n console.log('Sherpa setup complete!\\n')\n console.log('What was configured:')\n console.log(' [x] .claude/settings.local.json - Hooks')\n console.log(' [x] .claude/guard.json - Guard config')\n console.log(' [x] .claude/commands/review.md - Slash command')\n console.log(' [x] .claude/commands/tree.md - Slash command')\n console.log(' [x] .mcp.json - MCP servers')\n console.log(' [x] .husky/pre-commit - Git pre-commit hook')\n console.log(' [x] .lintstagedrc.json - Lint staged files')\n console.log('')\n console.log('Pre-commit will run:')\n console.log(' 1. lint-staged (lint/format changed files)')\n console.log(' 2. gitleaks (scan for secrets)')\n console.log('')\n console.log('Claude Code:')\n console.log(' - sherpa pre: Block dangerous bash commands')\n console.log(' - sherpa post: Offload large outputs')\n console.log(' - reviewer: AI code review (MCP)')\n console.log('')\n console.log('IMPORTANT: Restart Claude Code to load the MCP server.')\n console.log('='.repeat(50))\n}\n\nfunction setupClaudeHooks(cwd: string, force: boolean): void {\n const claudeDir = join(cwd, '.claude')\n const configPath = join(claudeDir, 'guard.json')\n const settingsPath = join(claudeDir, 'settings.local.json')\n\n // Create .claude directory\n if (!existsSync(claudeDir)) {\n mkdirSync(claudeDir, { recursive: true })\n console.log('Created .claude/ directory')\n }\n\n // Create guard.json\n if (!existsSync(configPath) || force) {\n writeFileSync(configPath, `${JSON.stringify(GUARD_CONFIG, null, 2)}\\n`)\n console.log('Created .claude/guard.json')\n } else {\n console.log('.claude/guard.json already exists (use --force to overwrite)')\n }\n\n // Update settings.local.json with hooks only (not MCP)\n let settings: ClaudeSettings = {}\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'))\n } catch {\n console.warn('Warning: Could not parse existing settings.local.json')\n }\n }\n\n // Merge hook config\n settings.hooks = settings.hooks || {}\n let hooksUpdated = false\n\n for (const [hookType, hooks] of Object.entries(CLAUDE_HOOK_CONFIG)) {\n const existing = settings.hooks[hookType as keyof typeof CLAUDE_HOOK_CONFIG] || []\n const hasSherpa = existing.some((h) =>\n h.hooks?.some((hook: { command?: string }) => hook.command?.startsWith('sherpa '))\n )\n\n if (!hasSherpa) {\n settings.hooks[hookType as keyof typeof CLAUDE_HOOK_CONFIG] = [...existing, ...hooks]\n hooksUpdated = true\n }\n }\n\n if (hooksUpdated) {\n writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`)\n console.log('Updated .claude/settings.local.json with hooks')\n } else {\n console.log('Claude hooks already configured')\n }\n}\n\nfunction setupClaudeCommands(cwd: string, force: boolean): void {\n const claudeDir = join(cwd, '.claude')\n const commandsDir = join(claudeDir, 'commands')\n const reviewCommandPath = join(commandsDir, 'review.md')\n const treeCommandPath = join(commandsDir, 'tree.md')\n\n if (!existsSync(commandsDir)) {\n mkdirSync(commandsDir, { recursive: true })\n }\n\n if (!existsSync(reviewCommandPath) || force) {\n writeFileSync(reviewCommandPath, REVIEW_COMMAND)\n console.log('Created .claude/commands/review.md')\n } else {\n console.log('.claude/commands/review.md already exists')\n }\n\n if (!existsSync(treeCommandPath) || force) {\n writeFileSync(treeCommandPath, TREE_COMMAND)\n console.log('Created .claude/commands/tree.md')\n } else {\n console.log('.claude/commands/tree.md already exists')\n }\n}\n\nfunction setupMcpConfig(cwd: string, force: boolean): void {\n const mcpPath = join(cwd, '.mcp.json')\n const settingsPath = join(cwd, '.claude/settings.local.json')\n const nodePath = getNodePath()\n const reviewerPath = getReviewerPath()\n\n // 1. Clean up stale MCP config from settings.local.json (wrong location)\n if (existsSync(settingsPath)) {\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'))\n if (settings.mcpServers) {\n delete settings.mcpServers\n writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`)\n console.log('Cleaned up stale MCP config from settings.local.json')\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n const mcpConfig: McpServer = {\n type: 'stdio',\n command: nodePath,\n args: [reviewerPath],\n env: {}\n }\n\n // 2. Try using claude CLI first (most reliable)\n try {\n // Remove existing and add fresh (always, to ensure correct config)\n execSync('claude mcp remove reviewer -s project 2>/dev/null || true', {\n cwd,\n stdio: 'pipe'\n })\n execSync('claude mcp remove cerebras-reviewer -s project 2>/dev/null || true', {\n cwd,\n stdio: 'pipe'\n })\n execSync(`claude mcp add reviewer -s project ${nodePath} ${reviewerPath}`, {\n cwd,\n stdio: 'pipe'\n })\n console.log('Configured MCP server via claude CLI')\n return\n } catch {\n // Claude CLI not available, fall back to manual config\n }\n\n // 3. Manual .mcp.json creation (always overwrite reviewer to fix any issues)\n let mcpJson: McpJson = { mcpServers: {} }\n if (existsSync(mcpPath)) {\n try {\n mcpJson = JSON.parse(readFileSync(mcpPath, 'utf-8'))\n } catch {\n // Start fresh if parse fails\n }\n }\n\n delete mcpJson.mcpServers['cerebras-reviewer']\n mcpJson.mcpServers['reviewer'] = mcpConfig\n writeFileSync(mcpPath, `${JSON.stringify(mcpJson, null, 2)}\\n`)\n console.log('Configured .mcp.json with reviewer')\n\n // 4. Verify it works\n try {\n const testMsg =\n '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0.0\"}}}'\n execSync(`echo '${testMsg}' | \"${nodePath}\" \"${reviewerPath}\"`, {\n stdio: 'pipe',\n timeout: 5000\n })\n console.log('Verified MCP server responds correctly')\n } catch {\n console.warn('Warning: MCP server test failed - check paths and try restarting Claude Code')\n }\n}\n\nfunction setupHusky(cwd: string, force: boolean): void {\n const huskyDir = join(cwd, '.husky')\n const preCommitPath = join(huskyDir, 'pre-commit')\n const pkgPath = join(cwd, 'package.json')\n\n if (!existsSync(pkgPath)) {\n console.log('No package.json found - skipping husky setup')\n return\n }\n\n try {\n JSON.parse(readFileSync(pkgPath, 'utf-8'))\n } catch {\n console.warn('Warning: Could not parse package.json')\n return\n }\n\n const hasHusky = existsSync(huskyDir)\n\n if (!hasHusky) {\n try {\n console.log('Initializing husky...')\n execSync('npx husky init', { cwd, stdio: 'pipe' })\n console.log('Initialized husky')\n } catch {\n console.warn('Could not initialize husky automatically')\n console.warn('Run: npx husky init')\n return\n }\n }\n\n if (!existsSync(preCommitPath) || force) {\n writeFileSync(preCommitPath, HUSKY_PRE_COMMIT)\n chmodSync(preCommitPath, '755')\n console.log('Created .husky/pre-commit')\n } else {\n const existing = readFileSync(preCommitPath, 'utf-8')\n if (existing.includes('npm test')) {\n writeFileSync(preCommitPath, HUSKY_PRE_COMMIT)\n chmodSync(preCommitPath, '755')\n console.log('Replaced default pre-commit with lint-staged + gitleaks')\n return\n }\n\n let updated = false\n\n if (!existing.includes('lint-staged')) {\n appendFileSync(preCommitPath, '\\nnpx lint-staged\\n')\n updated = true\n }\n\n if (!existing.includes('gitleaks')) {\n appendFileSync(preCommitPath, '\\ngitleaks protect --staged --verbose\\n')\n updated = true\n }\n\n if (updated) {\n console.log('Updated .husky/pre-commit with lint-staged + gitleaks')\n } else {\n console.log('.husky/pre-commit already configured')\n }\n }\n}\n\nfunction setupLintStaged(cwd: string, force: boolean): void {\n const configPath = join(cwd, '.lintstagedrc.json')\n\n if (!existsSync(configPath) || force) {\n writeFileSync(configPath, `${JSON.stringify(LINT_STAGED_CONFIG, null, 2)}\\n`)\n console.log('Created .lintstagedrc.json')\n } else {\n console.log('.lintstagedrc.json already exists')\n }\n}\n\nfunction checkGitleaks(): void {\n try {\n const checkCmd = process.platform === 'win32' ? 'where gitleaks' : 'command -v gitleaks'\n execSync(checkCmd, { stdio: 'pipe' })\n console.log('gitleaks found')\n } catch {\n console.log('')\n console.log('NOTE: gitleaks not found. Install it:')\n console.log(' brew install gitleaks # macOS')\n console.log(' apt install gitleaks # Debian/Ubuntu')\n console.log(' choco install gitleaks # Windows')\n console.log(' https://github.com/gitleaks/gitleaks#installing')\n }\n}\n"],
5
+ "mappings": ";AAMA,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB,WAAW,YAAY,WAAW,cAAc,qBAAqB;AAC9F,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,qBAAqB;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AA0BxD,IAAM,qBAAqB;AAAA,EACzB,YAAY,CAAC,EAAE,SAAS,QAAQ,OAAO,CAAC,EAAE,MAAM,WAAW,SAAS,aAAa,CAAC,EAAE,CAAC;AAAA,EACrF,aAAa,CAAC,EAAE,SAAS,QAAQ,OAAO,CAAC,EAAE,MAAM,WAAW,SAAS,cAAc,CAAC,EAAE,CAAC;AACzF;AAEA,IAAM,eAAe;AAAA,EACnB,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,kBAAkB;AACpB;AAEA,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAMvB,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAMrB,IAAM,qBAAqB;AAAA,EACzB,sCAAsC,CAAC,kBAAkB;AAC3D;AAEA,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWzB,SAAS,cAAsB;AAC7B,MAAI;AACF,WAAO,SAAS,cAAc,EAAE,UAAU,QAAQ,CAAC,EAAE,KAAK;AAAA,EAC5D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,kBAA0B;AACjC,QAAMA,WAAU,cAAc,YAAY,GAAG;AAC7C,QAAM,cAAc,QAAQ,WAAW,MAAM,YAAY,UAAU;AACnE,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAOA,SAAQ,QAAQ,wCAAwC;AAAA,EACjE,QAAQ;AAAA,EAER;AAIA,QAAM,eAAe,QAAQ,WAAW,iCAAiC;AACzE,MAAI,WAAW,YAAY,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,UAAgB;AAC9B,QAAM,gBAAgB,QAAQ,KAAK,CAAC,MAAM;AAC1C,QAAM,WAAW,gBAAgB,QAAQ,KAAK,MAAM,CAAC,IAAI,CAAC;AAC1D,QAAM,QAAQ,SAAS,SAAS,SAAS;AACzC,QAAM,MAAM,QAAQ,IAAI;AAExB,UAAQ,IAAI,wBAAwB;AAGpC,mBAAiB,KAAK,KAAK;AAG3B,sBAAoB,KAAK,KAAK;AAG9B,iBAAe,KAAK,KAAK;AAGzB,aAAW,KAAK,KAAK;AAGrB,kBAAgB,KAAK,KAAK;AAG1B,gBAAc;AAGd,UAAQ,IAAI;AAAA,EAAK,IAAI,OAAO,EAAE,CAAC,EAAE;AACjC,UAAQ,IAAI,0BAA0B;AACtC,UAAQ,IAAI,sBAAsB;AAClC,UAAQ,IAAI,2CAA2C;AACvD,UAAQ,IAAI,yCAAyC;AACrD,UAAQ,IAAI,kDAAkD;AAC9D,UAAQ,IAAI,gDAAgD;AAC5D,UAAQ,IAAI,+BAA+B;AAC3C,UAAQ,IAAI,+CAA+C;AAC3D,UAAQ,IAAI,8CAA8C;AAC1D,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,sBAAsB;AAClC,UAAQ,IAAI,8CAA8C;AAC1D,UAAQ,IAAI,kCAAkC;AAC9C,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,cAAc;AAC1B,UAAQ,IAAI,+CAA+C;AAC3D,UAAQ,IAAI,wCAAwC;AACpD,UAAQ,IAAI,oCAAoC;AAChD,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,wDAAwD;AACpE,UAAQ,IAAI,IAAI,OAAO,EAAE,CAAC;AAC5B;AAEA,SAAS,iBAAiB,KAAa,OAAsB;AAC3D,QAAM,YAAY,KAAK,KAAK,SAAS;AACrC,QAAM,aAAa,KAAK,WAAW,YAAY;AAC/C,QAAM,eAAe,KAAK,WAAW,qBAAqB;AAG1D,MAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,cAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACxC,YAAQ,IAAI,4BAA4B;AAAA,EAC1C;AAGA,MAAI,CAAC,WAAW,UAAU,KAAK,OAAO;AACpC,kBAAc,YAAY,GAAG,KAAK,UAAU,cAAc,MAAM,CAAC,CAAC;AAAA,CAAI;AACtE,YAAQ,IAAI,4BAA4B;AAAA,EAC1C,OAAO;AACL,YAAQ,IAAI,8DAA8D;AAAA,EAC5E;AAGA,MAAI,WAA2B,CAAC;AAChC,MAAI,WAAW,YAAY,GAAG;AAC5B,QAAI;AACF,iBAAW,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAAA,IAC3D,QAAQ;AACN,cAAQ,KAAK,uDAAuD;AAAA,IACtE;AAAA,EACF;AAGA,WAAS,QAAQ,SAAS,SAAS,CAAC;AACpC,MAAI,eAAe;AAEnB,aAAW,CAAC,UAAU,KAAK,KAAK,OAAO,QAAQ,kBAAkB,GAAG;AAClE,UAAM,WAAW,SAAS,MAAM,QAA2C,KAAK,CAAC;AACjF,UAAM,YAAY,SAAS;AAAA,MAAK,CAAC,MAC/B,EAAE,OAAO,KAAK,CAAC,SAA+B,KAAK,SAAS,WAAW,SAAS,CAAC;AAAA,IACnF;AAEA,QAAI,CAAC,WAAW;AACd,eAAS,MAAM,QAA2C,IAAI,CAAC,GAAG,UAAU,GAAG,KAAK;AACpF,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,MAAI,cAAc;AAChB,kBAAc,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,CAAI;AACpE,YAAQ,IAAI,gDAAgD;AAAA,EAC9D,OAAO;AACL,YAAQ,IAAI,iCAAiC;AAAA,EAC/C;AACF;AAEA,SAAS,oBAAoB,KAAa,OAAsB;AAC9D,QAAM,YAAY,KAAK,KAAK,SAAS;AACrC,QAAM,cAAc,KAAK,WAAW,UAAU;AAC9C,QAAM,oBAAoB,KAAK,aAAa,WAAW;AACvD,QAAM,kBAAkB,KAAK,aAAa,SAAS;AAEnD,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,cAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAAA,EAC5C;AAEA,MAAI,CAAC,WAAW,iBAAiB,KAAK,OAAO;AAC3C,kBAAc,mBAAmB,cAAc;AAC/C,YAAQ,IAAI,oCAAoC;AAAA,EAClD,OAAO;AACL,YAAQ,IAAI,2CAA2C;AAAA,EACzD;AAEA,MAAI,CAAC,WAAW,eAAe,KAAK,OAAO;AACzC,kBAAc,iBAAiB,YAAY;AAC3C,YAAQ,IAAI,kCAAkC;AAAA,EAChD,OAAO;AACL,YAAQ,IAAI,yCAAyC;AAAA,EACvD;AACF;AAEA,SAAS,eAAe,KAAa,OAAsB;AACzD,QAAM,UAAU,KAAK,KAAK,WAAW;AACrC,QAAM,eAAe,KAAK,KAAK,6BAA6B;AAC5D,QAAM,WAAW,YAAY;AAC7B,QAAM,eAAe,gBAAgB;AAGrC,MAAI,WAAW,YAAY,GAAG;AAC5B,QAAI;AACF,YAAM,WAAW,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAC/D,UAAI,SAAS,YAAY;AACvB,eAAO,SAAS;AAChB,sBAAc,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,CAAI;AACpE,gBAAQ,IAAI,sDAAsD;AAAA,MACpE;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,YAAuB;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM,CAAC,YAAY;AAAA,IACnB,KAAK,CAAC;AAAA,EACR;AAGA,MAAI;AAEF,aAAS,6DAA6D;AAAA,MACpE;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AACD,aAAS,sEAAsE;AAAA,MAC7E;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AACD,aAAS,sCAAsC,QAAQ,IAAI,YAAY,IAAI;AAAA,MACzE;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AACD,YAAQ,IAAI,sCAAsC;AAClD;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI,UAAmB,EAAE,YAAY,CAAC,EAAE;AACxC,MAAI,WAAW,OAAO,GAAG;AACvB,QAAI;AACF,gBAAU,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AAAA,IACrD,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,QAAQ,WAAW,mBAAmB;AAC7C,UAAQ,WAAW,UAAU,IAAI;AACjC,gBAAc,SAAS,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,CAAI;AAC9D,UAAQ,IAAI,oCAAoC;AAGhD,MAAI;AACF,UAAM,UACJ;AACF,aAAS,SAAS,OAAO,QAAQ,QAAQ,MAAM,YAAY,KAAK;AAAA,MAC9D,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,YAAQ,IAAI,wCAAwC;AAAA,EACtD,QAAQ;AACN,YAAQ,KAAK,8EAA8E;AAAA,EAC7F;AACF;AAEA,SAAS,WAAW,KAAa,OAAsB;AACrD,QAAM,WAAW,KAAK,KAAK,QAAQ;AACnC,QAAM,gBAAgB,KAAK,UAAU,YAAY;AACjD,QAAM,UAAU,KAAK,KAAK,cAAc;AAExC,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,YAAQ,IAAI,8CAA8C;AAC1D;AAAA,EACF;AAEA,MAAI;AACF,SAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AAAA,EAC3C,QAAQ;AACN,YAAQ,KAAK,uCAAuC;AACpD;AAAA,EACF;AAEA,QAAM,WAAW,WAAW,QAAQ;AAEpC,MAAI,CAAC,UAAU;AACb,QAAI;AACF,cAAQ,IAAI,uBAAuB;AACnC,eAAS,kBAAkB,EAAE,KAAK,OAAO,OAAO,CAAC;AACjD,cAAQ,IAAI,mBAAmB;AAAA,IACjC,QAAQ;AACN,cAAQ,KAAK,0CAA0C;AACvD,cAAQ,KAAK,qBAAqB;AAClC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,WAAW,aAAa,KAAK,OAAO;AACvC,kBAAc,eAAe,gBAAgB;AAC7C,cAAU,eAAe,KAAK;AAC9B,YAAQ,IAAI,2BAA2B;AAAA,EACzC,OAAO;AACL,UAAM,WAAW,aAAa,eAAe,OAAO;AACpD,QAAI,SAAS,SAAS,UAAU,GAAG;AACjC,oBAAc,eAAe,gBAAgB;AAC7C,gBAAU,eAAe,KAAK;AAC9B,cAAQ,IAAI,yDAAyD;AACrE;AAAA,IACF;AAEA,QAAI,UAAU;AAEd,QAAI,CAAC,SAAS,SAAS,aAAa,GAAG;AACrC,qBAAe,eAAe,qBAAqB;AACnD,gBAAU;AAAA,IACZ;AAEA,QAAI,CAAC,SAAS,SAAS,UAAU,GAAG;AAClC,qBAAe,eAAe,yCAAyC;AACvE,gBAAU;AAAA,IACZ;AAEA,QAAI,SAAS;AACX,cAAQ,IAAI,uDAAuD;AAAA,IACrE,OAAO;AACL,cAAQ,IAAI,sCAAsC;AAAA,IACpD;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,KAAa,OAAsB;AAC1D,QAAM,aAAa,KAAK,KAAK,oBAAoB;AAEjD,MAAI,CAAC,WAAW,UAAU,KAAK,OAAO;AACpC,kBAAc,YAAY,GAAG,KAAK,UAAU,oBAAoB,MAAM,CAAC,CAAC;AAAA,CAAI;AAC5E,YAAQ,IAAI,4BAA4B;AAAA,EAC1C,OAAO;AACL,YAAQ,IAAI,mCAAmC;AAAA,EACjD;AACF;AAEA,SAAS,gBAAsB;AAC7B,MAAI;AACF,UAAM,WAAW,QAAQ,aAAa,UAAU,mBAAmB;AACnE,aAAS,UAAU,EAAE,OAAO,OAAO,CAAC;AACpC,YAAQ,IAAI,gBAAgB;AAAA,EAC9B,QAAQ;AACN,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,uCAAuC;AACnD,YAAQ,IAAI,uCAAuC;AACnD,YAAQ,IAAI,+CAA+C;AAC3D,YAAQ,IAAI,yCAAyC;AACrD,YAAQ,IAAI,mDAAmD;AAAA,EACjE;AACF;",
6
+ "names": ["require"]
7
+ }
@@ -0,0 +1,307 @@
1
+ // src/commands/init.ts
2
+ import { execSync } from "child_process";
3
+ import { createRequire } from "module";
4
+ import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { dirname, join, resolve } from "path";
6
+ import { fileURLToPath } from "url";
7
+ var __dirname = dirname(fileURLToPath(import.meta.url));
8
+ var CLAUDE_HOOK_CONFIG = {
9
+ PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: "sherpa pre" }] }],
10
+ PostToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: "sherpa post" }] }]
11
+ };
12
+ var GUARD_CONFIG = {
13
+ maxTokens: 2e3,
14
+ previewTokens: 500,
15
+ scratchDir: ".claude/scratch",
16
+ maxAgeMinutes: 60,
17
+ maxScratchSizeMB: 50
18
+ };
19
+ var REVIEW_COMMAND = `# review
20
+
21
+ Use the MCP tool \`review\` with the arguments provided by the user.
22
+ If no arguments are given, ask for the paths or mode.
23
+ `;
24
+ var TREE_COMMAND = `# tree
25
+
26
+ Use the MCP tool \`tree\` with the arguments provided by the user.
27
+ If no arguments are given, show the default repo tree.
28
+ `;
29
+ var LINT_STAGED_CONFIG = {
30
+ "*.{js,jsx,ts,tsx,json,md,yml,yaml}": ["prettier --write"]
31
+ };
32
+ var HUSKY_PRE_COMMIT = `npx lint-staged
33
+ if command -v gitleaks >/dev/null 2>&1; then
34
+ gitleaks protect --staged --verbose
35
+ else
36
+ echo "gitleaks not found - skipping"
37
+ fi
38
+ `;
39
+ function getNodePath() {
40
+ try {
41
+ return execSync("which node", { encoding: "utf-8" }).trim();
42
+ } catch {
43
+ return "node";
44
+ }
45
+ }
46
+ function getReviewerPath() {
47
+ const require2 = createRequire(import.meta.url);
48
+ const bundledPath = resolve(__dirname, "..", "reviewer", "index.js");
49
+ if (existsSync(bundledPath)) {
50
+ return bundledPath;
51
+ }
52
+ try {
53
+ return require2.resolve("@goobits/sherpa-reviewer/dist/index.js");
54
+ } catch {
55
+ }
56
+ const reviewerPath = resolve(__dirname, "../../../reviewer/dist/index.js");
57
+ if (existsSync(reviewerPath)) {
58
+ return reviewerPath;
59
+ }
60
+ return reviewerPath;
61
+ }
62
+ function runInit() {
63
+ const isInitCommand = process.argv[2] === "init";
64
+ const initArgs = isInitCommand ? process.argv.slice(3) : [];
65
+ const force = initArgs.includes("--force");
66
+ const cwd = process.cwd();
67
+ console.log("Setting up sherpa...\n");
68
+ setupClaudeHooks(cwd, force);
69
+ setupClaudeCommands(cwd, force);
70
+ setupMcpConfig(cwd, force);
71
+ setupHusky(cwd, force);
72
+ setupLintStaged(cwd, force);
73
+ checkGitleaks();
74
+ console.log(`
75
+ ${"=".repeat(50)}`);
76
+ console.log("Sherpa setup complete!\n");
77
+ console.log("What was configured:");
78
+ console.log(" [x] .claude/settings.local.json - Hooks");
79
+ console.log(" [x] .claude/guard.json - Guard config");
80
+ console.log(" [x] .claude/commands/review.md - Slash command");
81
+ console.log(" [x] .claude/commands/tree.md - Slash command");
82
+ console.log(" [x] .mcp.json - MCP servers");
83
+ console.log(" [x] .husky/pre-commit - Git pre-commit hook");
84
+ console.log(" [x] .lintstagedrc.json - Lint staged files");
85
+ console.log("");
86
+ console.log("Pre-commit will run:");
87
+ console.log(" 1. lint-staged (lint/format changed files)");
88
+ console.log(" 2. gitleaks (scan for secrets)");
89
+ console.log("");
90
+ console.log("Claude Code:");
91
+ console.log(" - sherpa pre: Block dangerous bash commands");
92
+ console.log(" - sherpa post: Offload large outputs");
93
+ console.log(" - reviewer: AI code review (MCP)");
94
+ console.log("");
95
+ console.log("IMPORTANT: Restart Claude Code to load the MCP server.");
96
+ console.log("=".repeat(50));
97
+ }
98
+ function setupClaudeHooks(cwd, force) {
99
+ const claudeDir = join(cwd, ".claude");
100
+ const configPath = join(claudeDir, "guard.json");
101
+ const settingsPath = join(claudeDir, "settings.local.json");
102
+ if (!existsSync(claudeDir)) {
103
+ mkdirSync(claudeDir, { recursive: true });
104
+ console.log("Created .claude/ directory");
105
+ }
106
+ if (!existsSync(configPath) || force) {
107
+ writeFileSync(configPath, `${JSON.stringify(GUARD_CONFIG, null, 2)}
108
+ `);
109
+ console.log("Created .claude/guard.json");
110
+ } else {
111
+ console.log(".claude/guard.json already exists (use --force to overwrite)");
112
+ }
113
+ let settings = {};
114
+ if (existsSync(settingsPath)) {
115
+ try {
116
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
117
+ } catch {
118
+ console.warn("Warning: Could not parse existing settings.local.json");
119
+ }
120
+ }
121
+ settings.hooks = settings.hooks || {};
122
+ let hooksUpdated = false;
123
+ for (const [hookType, hooks] of Object.entries(CLAUDE_HOOK_CONFIG)) {
124
+ const existing = settings.hooks[hookType] || [];
125
+ const hasSherpa = existing.some(
126
+ (h) => h.hooks?.some((hook) => hook.command?.startsWith("sherpa "))
127
+ );
128
+ if (!hasSherpa) {
129
+ settings.hooks[hookType] = [...existing, ...hooks];
130
+ hooksUpdated = true;
131
+ }
132
+ }
133
+ if (hooksUpdated) {
134
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
135
+ `);
136
+ console.log("Updated .claude/settings.local.json with hooks");
137
+ } else {
138
+ console.log("Claude hooks already configured");
139
+ }
140
+ }
141
+ function setupClaudeCommands(cwd, force) {
142
+ const claudeDir = join(cwd, ".claude");
143
+ const commandsDir = join(claudeDir, "commands");
144
+ const reviewCommandPath = join(commandsDir, "review.md");
145
+ const treeCommandPath = join(commandsDir, "tree.md");
146
+ if (!existsSync(commandsDir)) {
147
+ mkdirSync(commandsDir, { recursive: true });
148
+ }
149
+ if (!existsSync(reviewCommandPath) || force) {
150
+ writeFileSync(reviewCommandPath, REVIEW_COMMAND);
151
+ console.log("Created .claude/commands/review.md");
152
+ } else {
153
+ console.log(".claude/commands/review.md already exists");
154
+ }
155
+ if (!existsSync(treeCommandPath) || force) {
156
+ writeFileSync(treeCommandPath, TREE_COMMAND);
157
+ console.log("Created .claude/commands/tree.md");
158
+ } else {
159
+ console.log(".claude/commands/tree.md already exists");
160
+ }
161
+ }
162
+ function setupMcpConfig(cwd, force) {
163
+ const mcpPath = join(cwd, ".mcp.json");
164
+ const settingsPath = join(cwd, ".claude/settings.local.json");
165
+ const nodePath = getNodePath();
166
+ const reviewerPath = getReviewerPath();
167
+ if (existsSync(settingsPath)) {
168
+ try {
169
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
170
+ if (settings.mcpServers) {
171
+ delete settings.mcpServers;
172
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
173
+ `);
174
+ console.log("Cleaned up stale MCP config from settings.local.json");
175
+ }
176
+ } catch {
177
+ }
178
+ }
179
+ const mcpConfig = {
180
+ type: "stdio",
181
+ command: nodePath,
182
+ args: [reviewerPath],
183
+ env: {}
184
+ };
185
+ try {
186
+ execSync("claude mcp remove reviewer -s project 2>/dev/null || true", {
187
+ cwd,
188
+ stdio: "pipe"
189
+ });
190
+ execSync("claude mcp remove cerebras-reviewer -s project 2>/dev/null || true", {
191
+ cwd,
192
+ stdio: "pipe"
193
+ });
194
+ execSync(`claude mcp add reviewer -s project ${nodePath} ${reviewerPath}`, {
195
+ cwd,
196
+ stdio: "pipe"
197
+ });
198
+ console.log("Configured MCP server via claude CLI");
199
+ return;
200
+ } catch {
201
+ }
202
+ let mcpJson = { mcpServers: {} };
203
+ if (existsSync(mcpPath)) {
204
+ try {
205
+ mcpJson = JSON.parse(readFileSync(mcpPath, "utf-8"));
206
+ } catch {
207
+ }
208
+ }
209
+ delete mcpJson.mcpServers["cerebras-reviewer"];
210
+ mcpJson.mcpServers["reviewer"] = mcpConfig;
211
+ writeFileSync(mcpPath, `${JSON.stringify(mcpJson, null, 2)}
212
+ `);
213
+ console.log("Configured .mcp.json with reviewer");
214
+ try {
215
+ const testMsg = '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}';
216
+ execSync(`echo '${testMsg}' | "${nodePath}" "${reviewerPath}"`, {
217
+ stdio: "pipe",
218
+ timeout: 5e3
219
+ });
220
+ console.log("Verified MCP server responds correctly");
221
+ } catch {
222
+ console.warn("Warning: MCP server test failed - check paths and try restarting Claude Code");
223
+ }
224
+ }
225
+ function setupHusky(cwd, force) {
226
+ const huskyDir = join(cwd, ".husky");
227
+ const preCommitPath = join(huskyDir, "pre-commit");
228
+ const pkgPath = join(cwd, "package.json");
229
+ if (!existsSync(pkgPath)) {
230
+ console.log("No package.json found - skipping husky setup");
231
+ return;
232
+ }
233
+ try {
234
+ JSON.parse(readFileSync(pkgPath, "utf-8"));
235
+ } catch {
236
+ console.warn("Warning: Could not parse package.json");
237
+ return;
238
+ }
239
+ const hasHusky = existsSync(huskyDir);
240
+ if (!hasHusky) {
241
+ try {
242
+ console.log("Initializing husky...");
243
+ execSync("npx husky init", { cwd, stdio: "pipe" });
244
+ console.log("Initialized husky");
245
+ } catch {
246
+ console.warn("Could not initialize husky automatically");
247
+ console.warn("Run: npx husky init");
248
+ return;
249
+ }
250
+ }
251
+ if (!existsSync(preCommitPath) || force) {
252
+ writeFileSync(preCommitPath, HUSKY_PRE_COMMIT);
253
+ chmodSync(preCommitPath, "755");
254
+ console.log("Created .husky/pre-commit");
255
+ } else {
256
+ const existing = readFileSync(preCommitPath, "utf-8");
257
+ if (existing.includes("npm test")) {
258
+ writeFileSync(preCommitPath, HUSKY_PRE_COMMIT);
259
+ chmodSync(preCommitPath, "755");
260
+ console.log("Replaced default pre-commit with lint-staged + gitleaks");
261
+ return;
262
+ }
263
+ let updated = false;
264
+ if (!existing.includes("lint-staged")) {
265
+ appendFileSync(preCommitPath, "\nnpx lint-staged\n");
266
+ updated = true;
267
+ }
268
+ if (!existing.includes("gitleaks")) {
269
+ appendFileSync(preCommitPath, "\ngitleaks protect --staged --verbose\n");
270
+ updated = true;
271
+ }
272
+ if (updated) {
273
+ console.log("Updated .husky/pre-commit with lint-staged + gitleaks");
274
+ } else {
275
+ console.log(".husky/pre-commit already configured");
276
+ }
277
+ }
278
+ }
279
+ function setupLintStaged(cwd, force) {
280
+ const configPath = join(cwd, ".lintstagedrc.json");
281
+ if (!existsSync(configPath) || force) {
282
+ writeFileSync(configPath, `${JSON.stringify(LINT_STAGED_CONFIG, null, 2)}
283
+ `);
284
+ console.log("Created .lintstagedrc.json");
285
+ } else {
286
+ console.log(".lintstagedrc.json already exists");
287
+ }
288
+ }
289
+ function checkGitleaks() {
290
+ try {
291
+ const checkCmd = process.platform === "win32" ? "where gitleaks" : "command -v gitleaks";
292
+ execSync(checkCmd, { stdio: "pipe" });
293
+ console.log("gitleaks found");
294
+ } catch {
295
+ console.log("");
296
+ console.log("NOTE: gitleaks not found. Install it:");
297
+ console.log(" brew install gitleaks # macOS");
298
+ console.log(" apt install gitleaks # Debian/Ubuntu");
299
+ console.log(" choco install gitleaks # Windows");
300
+ console.log(" https://github.com/gitleaks/gitleaks#installing");
301
+ }
302
+ }
303
+
304
+ export {
305
+ runInit
306
+ };
307
+ //# sourceMappingURL=chunk-LQZTKH3U.js.map