claude-setup 1.1.2 → 1.1.3

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/README.md CHANGED
@@ -52,30 +52,7 @@ npx claude-setup doctor --verbose # Include passing checks in output
52
52
  - `.claude/commands/` — project-specific slash commands
53
53
  - `.github/workflows/` — CI workflows (only if `.github/` exists)
54
54
 
55
- ## Token cost controls
56
-
57
- Every byte injected into command files costs tokens. The CLI enforces:
58
-
59
- | Control | Default |
60
- |---------|---------|
61
- | Init token budget | 12,000 |
62
- | Sync token budget | 6,000 |
63
- | Add token budget | 3,000 |
64
- | Remove token budget | 2,000 |
65
- | Max source files sampled | 15 |
66
- | Max file size | 80KB |
67
- | Max depth | 6 levels |
68
-
69
- ### File-specific truncation
70
-
71
- | File | Strategy |
72
- |------|----------|
73
- | `package-lock.json` | Extract `{ name, version, lockfileVersion }` only |
74
- | `Dockerfile` | First 50 lines |
75
- | `docker-compose.yml` | First 100 lines if > 8KB |
76
- | `pom.xml`, `build.gradle*` | First 80 lines |
77
- | `setup.py` | First 60 lines |
78
- | `*.config.{js,ts,mjs}` | First 100 lines |
55
+
79
56
 
80
57
  ## Configuration
81
58
 
package/dist/builder.js CHANGED
@@ -2,7 +2,7 @@ import { readFileSync } from "fs";
2
2
  import { join, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { loadConfig } from "./config.js";
5
- import { detectOS } from "./os.js";
5
+ import { detectOS, VERIFIED_MCP_PACKAGES } from "./os.js";
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
  const TEMPLATES_DIR = join(__dirname, "..", "templates");
8
8
  function estimateTokens(content) {
@@ -220,14 +220,44 @@ export function buildAtomicSteps(collected, state) {
220
220
  `- Env var name in .env.example matching a known service pattern\n` +
221
221
  `- Explicit dependency on an MCP-compatible package\n\n` +
222
222
  `No evidence = no server. Do not invent services.\n\n` +
223
+ `### Verified MCP package names — ONLY use these\n` +
224
+ `\`\`\`\n` +
225
+ Object.entries(VERIFIED_MCP_PACKAGES).map(([k, v]) => `${k.padEnd(12)} → ${v}`).join("\n") +
226
+ `\n\`\`\`\n` +
227
+ `If the service is not in this list, print:\n` +
228
+ `\`⚠️ UNKNOWN PACKAGE — [service] MCP server not added: package name unverified. Find it at https://github.com/modelcontextprotocol/servers\`\n` +
229
+ `Do not add a placeholder. Do not guess.\n\n` +
223
230
  `### OS-correct format (detected: ${os})\n` +
224
231
  (os === "Windows"
225
- ? `Use: \`{ "command": "cmd", "args": ["/c", "npx", "<package>"] }\`\n`
226
- : `Use: \`{ "command": "npx", "args": ["<package>"] }\`\n`) +
232
+ ? `Use: \`{ "command": "cmd", "args": ["/c", "npx", "-y", "<package>"] }\`\n`
233
+ : `Use: \`{ "command": "npx", "args": ["-y", "<package>"] }\`\n`) +
234
+ `Always include \`-y\` in npx args to prevent install hangs.\n` +
235
+ `\n### Connection strings — NEVER hardcode\n` +
236
+ `Wrong: \`"args": [..., "postgresql://localhost:5432/db"]\`\n` +
237
+ `Right: \`"env": { "DATABASE_URL": "\${DATABASE_URL}" }\`\n` +
238
+ `All credentials and connection strings must go in \`env\` using \`\${VARNAME}\` syntax.\n` +
239
+ `After adding any server with env vars, flag them:\n` +
240
+ `\`⚠️ Add DATABASE_URL to .env.example and populate before starting Claude Code\`\n\n` +
227
241
  `\n### Rules\n` +
228
- `- All env var refs use \`\${VARNAME}\` syntax\n` +
242
+ `- All env var refs use \`\${VARNAME}\` syntax — never literal values\n` +
229
243
  `- Produce valid JSON only\n` +
230
244
  `- If creating: document every new env var in .env.example\n\n` +
245
+ `### Channels (Telegram, Discord) — special MCP servers\n` +
246
+ `Channels are MCP servers that push events INTO a session. They require:\n` +
247
+ `- Claude Code v2.1.80+\n` +
248
+ `- claude.ai login (not API key / Console)\n` +
249
+ `- Bun runtime installed\n` +
250
+ `- \`--channels\` flag at EVERY session launch\n\n` +
251
+ `Verified channel plugins:\n` +
252
+ `\`\`\`\n` +
253
+ `Telegram → plugin:telegram@claude-plugins-official\n` +
254
+ `Discord → plugin:discord@claude-plugins-official\n` +
255
+ `\`\`\`\n\n` +
256
+ `If adding a channel-type server, bot tokens must NEVER be hardcoded:\n` +
257
+ (os === "Windows"
258
+ ? `\`{ "command": "cmd", "args": ["/c", "bun", "run", "\${CLAUDE_PLUGIN_ROOT}/servers/telegram"], "env": { "TELEGRAM_BOT_TOKEN": "\${TELEGRAM_BOT_TOKEN}" } }\`\n`
259
+ : `\`{ "command": "bun", "args": ["run", "\${CLAUDE_PLUGIN_ROOT}/servers/telegram"], "env": { "TELEGRAM_BOT_TOKEN": "\${TELEGRAM_BOT_TOKEN}" } }\`\n`) +
260
+ `After adding, flag: \`⚠️ CHANNEL ACTIVATION REQUIRED — launch with: claude --channels plugin:telegram@claude-plugins-official\`\n\n` +
231
261
  `### Output\n` +
232
262
  `Created/Updated: ✅ .mcp.json — [what server and evidence source]\n` +
233
263
  `Skipped: ⏭ .mcp.json — checked [files], found [nothing], no action\n`,
@@ -249,7 +279,12 @@ export function buildAtomicSteps(collected, state) {
249
279
  : `Use: \`{ "command": "bash", "args": ["-c", "<command>"] }\`\n` +
250
280
  `**Bash quoting rule**: never use bare \`"\` inside \`-c "..."\` — use \`\\x22\` instead.\n` +
251
281
  `Replace \`'\` with \`\\x27\`, \`$\` in character classes with \`\\x24\`.\n`) +
252
- `\n### Rules\n` +
282
+ `\n### Valid hook event names — use ONLY these\n` +
283
+ `\`PreToolUse\`, \`PostToolUse\`, \`PostToolUseFailure\`, \`Stop\`, \`SessionStart\`\n` +
284
+ `If unsure which event name to use: do not write the hook. Print:\n` +
285
+ `\`SKIPPED — hook event name uncertain. Valid names: PreToolUse, PostToolUse, PostToolUseFailure, Stop, SessionStart\`\n\n` +
286
+ `### Rules\n` +
287
+ `- **NEVER write a "model" key into settings.json** — it overrides the user's model selection silently\n` +
253
288
  `- If it exists above: audit quoting of existing hooks first, fix broken ones\n` +
254
289
  `- Only add hooks for patterns that genuinely recur for this project type\n` +
255
290
  `- Produce valid JSON only\n\n` +
@@ -283,19 +318,55 @@ export function buildAtomicSteps(collected, state) {
283
318
  `## Target: .claude/commands/ (excluding stack-*.md — those are setup artifacts)\n` +
284
319
  `Installed: ${vars.COMMANDS_LIST}\n\n` +
285
320
  `### When to create\n` +
286
- `Create a command ONLY for project-specific multi-step workflows a developer repeats:\n` +
287
- `- Deploy sequences\n` +
288
- `- Database migration + seed\n` +
289
- `- Release workflows\n` +
290
- `- Environment setup for a new contributor\n\n` +
291
- `Do NOT create commands for things expressible as a single shell alias.\n` +
292
- `Look at the scripts in /stack-0-context for evidence of multi-step workflows.\n\n` +
321
+ `Create a command ONLY for project-specific multi-step workflows a developer repeats.\n` +
322
+ `Do NOT create commands for things expressible as a single shell alias.\n\n` +
323
+ `### Smart environment detection\n` +
324
+ `Also scan for missing/incomplete environment setup:\n` +
325
+ `- \`.env.example\` exists but \`.env\` missing → suggest \`/setup-env\`\n` +
326
+ `- \`docker-compose.yml\` with \`depends_on\` suggest \`/up\` with correct startup order\n` +
327
+ `- Database migration files (\`migrations/\`, \`prisma/schema.prisma\`, \`alembic/\`) suggest \`/db:migrate\`, \`/db:rollback\`\n` +
328
+ `- \`package.json\` with \`"prepare"\` or \`"postinstall"\` hooks → suggest \`/install\`\n` +
329
+ `- \`Makefile\` with \`install\`, \`deps\`, \`bootstrap\` → fold into \`/init\`\n` +
330
+ `- README sections ("Environment Variables", "Database Setup") → each can become a command\n\n` +
331
+ `All suggestions must be built from actual project files — never assume fixed commands.\n` +
332
+ `Detect the real tooling (npm vs yarn vs pnpm, docker compose vs docker-compose) from project evidence.\n\n` +
333
+ `### REQUIRED: Scan for multi-step patterns before deciding\n` +
334
+ `You MUST actively scan these sources in /stack-0-context:\n` +
335
+ `- **Makefile targets**: multiple chained commands under one target\n` +
336
+ `- **package.json scripts**: chained commands with && or ;\n` +
337
+ `- **docker-compose.yml**: service dependencies implying a boot order\n` +
338
+ `- **Dockerfile**: multi-stage patterns implying a build sequence\n` +
339
+ `- **README.md / docs**: sections like "Getting Started", "How to run"\n` +
340
+ `- **Shell scripts** in /scripts or /bin\n` +
341
+ `- **.env.example**: many vars suggest a setup sequence\n\n` +
342
+ `### Pattern signatures to detect\n` +
343
+ `| Pattern found | Suggested command |\n` +
344
+ `|---------------|-------------------|\n` +
345
+ `| docker-compose down + volume removal + build + up | /clean-rebuild |\n` +
346
+ `| migrate + seed + start | /fresh-start |\n` +
347
+ `| build + test + deploy | /release |\n` +
348
+ `| lint + format + typecheck all separate | /check |\n` +
349
+ `| setup + install + configure in README or scripts | /init |\n` +
350
+ `| backup/restore scripts or pg_dump/mongodump | /db:backup, /db:restore |\n` +
351
+ `| test + test:watch + test:coverage | /test |\n` +
352
+ `| dev + start + debug in package.json | /dev |\n` +
353
+ `| >2 manual steps in README "how to run" | candidate for /start |\n\n` +
354
+ `For each pattern found, suggest to the user:\n` +
355
+ `\`\`\`\n` +
356
+ `## Suggested command: /[name]\n\n` +
357
+ `I found a multi-step pattern in [source]:\n` +
358
+ ` 1. [step]\n` +
359
+ ` 2. [step]\n\n` +
360
+ `Create .claude/commands/[name].md?\n` +
361
+ `\`\`\`\n\n` +
293
362
  `### Rules\n` +
294
363
  `- If existing commands cover the same workflow: skip\n` +
295
- `- Commands should be specific to this project, not generic\n\n` +
364
+ `- Commands should be specific to this project, not generic\n` +
365
+ `- Adapt exact commands from actual project files — never hardcode\n` +
366
+ `- Never skip with a blanket "no workflows found" without scanning all sources above\n\n` +
296
367
  `### Output\n` +
297
368
  `Created: ✅ .claude/commands/[name].md — [what workflow and why useful]\n` +
298
- `Skipped: ⏭ commands — [why no project-specific workflows found]\n`,
369
+ `Skipped: ⏭ commands — scanned [list each source checked and result]. Nothing warranted.\n`,
299
370
  },
300
371
  // --- Step 6: .github/workflows/ ---
301
372
  {
@@ -321,9 +392,33 @@ export function buildAtomicSteps(collected, state) {
321
392
  `- NEVER create or modify workflows without explicit developer confirmation\n` +
322
393
  `- If existing workflows exist: do not touch them\n` +
323
394
  `- Secrets must use \`\${{ secrets.VARNAME }}\` syntax only\n`)
324
- : (`### .github/ does not exist\n` +
325
- `Do not create workflows. Print:\n` +
326
- `Skipped: ⏭ .github/workflows/ .github/ directory does not exist\n`)),
395
+ : (`### .github/ does not exist — scan for CI/CD evidence before skipping\n\n` +
396
+ `The absence of .github/ is an opportunity to suggest, not a reason to stop.\n\n` +
397
+ `Scan /stack-0-context for CI/CD evidence:\n` +
398
+ `- \`tests/\` or \`__tests__/\` or \`spec/\` directory → test pipeline candidate\n` +
399
+ `- \`Dockerfile\` or \`docker-compose.yml\` → build + deploy pipeline candidate\n` +
400
+ `- \`package.json\` with build/test/lint scripts → Node CI candidate\n` +
401
+ `- \`Makefile\` with test/build/deploy targets → generic CI candidate\n` +
402
+ `- \`pyproject.toml\` with test config → Python CI candidate\n` +
403
+ `- README references to "deploy", "release", "staging", "production"\n\n` +
404
+ `If evidence found, print EXACTLY:\n` +
405
+ `\`\`\`\n` +
406
+ `⚙️ WORKFLOW SUGGESTION — .github/ does not exist\n\n` +
407
+ `Evidence that CI/CD would be useful:\n` +
408
+ ` [list each piece of evidence and its source]\n\n` +
409
+ `I can set up:\n` +
410
+ ` 1. CI pipeline — run tests + build on every push\n` +
411
+ ` 2. Deploy pipeline — build image + push to registry on merge to main\n` +
412
+ ` 3. Both\n\n` +
413
+ `Two questions before I create anything:\n` +
414
+ ` 1. Which of the above? (1 / 2 / 3 / none)\n` +
415
+ ` 2. Is this connected to a remote GitHub repository? (yes / no)\n` +
416
+ `\`\`\`\n\n` +
417
+ `If user confirms: create .github/workflows/ with workflows based on actual project commands.\n` +
418
+ `All secrets must use \`\${{ secrets.VARNAME }}\` syntax — never hardcoded.\n` +
419
+ `After writing, flag every secret: \`⚠️ Add [VARNAME] to GitHub Settings → Secrets\`\n\n` +
420
+ `If NO evidence found:\n` +
421
+ `Skipped: ⏭ .github/workflows/ — scanned: no tests dir, no Dockerfile, no build/deploy scripts, no deployment references. Nothing to automate.\n`)),
327
422
  },
328
423
  ];
329
424
  return steps;
@@ -52,7 +52,7 @@ export async function runStatus() {
52
52
  const settings = safeJsonParse(state.settings.content);
53
53
  let hookCount = 0;
54
54
  if (settings) {
55
- for (const key of ["PreToolUse", "PostToolUse", "PreCompact", "PostCompact", "Notification", "Stop", "SubagentStop"]) {
55
+ for (const key of ["PreToolUse", "PostToolUse", "PostToolUseFailure", "Stop", "SessionStart"]) {
56
56
  const hooks = settings[key];
57
57
  if (Array.isArray(hooks))
58
58
  hookCount += hooks.length;
@@ -1,4 +1,5 @@
1
- import { writeFileSync, mkdirSync, existsSync } from "fs";
1
+ import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
2
+ import { join } from "path";
2
3
  import { collectProjectFiles } from "../collect.js";
3
4
  import { readState } from "../state.js";
4
5
  import { readManifest, sha256, updateManifest } from "../manifest.js";
@@ -50,9 +51,35 @@ export async function runSync(opts = {}) {
50
51
  return;
51
52
  }
52
53
  const lastRun = manifest.runs.at(-1);
53
- const collected = await collectProjectFiles(process.cwd(), "normal");
54
+ const cwd = process.cwd();
55
+ // --- Out-of-band edit detection ---
56
+ // Check if CLI-managed files were modified outside the CLI (e.g. by Claude Code directly)
57
+ const managedFiles = [
58
+ { label: "CLAUDE.md", path: join(cwd, "CLAUDE.md"), snapshotKey: "CLAUDE.md" },
59
+ { label: ".mcp.json", path: join(cwd, ".mcp.json"), snapshotKey: ".mcp.json" },
60
+ { label: "settings.json", path: join(cwd, ".claude", "settings.json"), snapshotKey: ".claude/settings.json" },
61
+ ];
62
+ let oobDetected = false;
63
+ for (const mf of managedFiles) {
64
+ if (!existsSync(mf.path))
65
+ continue;
66
+ const currentContent = readFileSync(mf.path, "utf8");
67
+ const currentHash = sha256(currentContent);
68
+ const snapshotHash = lastRun.snapshot[mf.snapshotKey];
69
+ if (snapshotHash && currentHash !== snapshotHash) {
70
+ if (!oobDetected) {
71
+ oobDetected = true;
72
+ console.log("");
73
+ }
74
+ console.log(`${c.yellow("⚠️")} OUT-OF-BAND EDIT — ${mf.label} was modified outside the CLI`);
75
+ console.log(` Re-snapshotting. Run ${c.cyan("npx claude-setup doctor")} to validate the new state.`);
76
+ }
77
+ }
78
+ if (oobDetected)
79
+ console.log("");
80
+ const collected = await collectProjectFiles(cwd, "normal");
54
81
  const diff = computeDiff(lastRun.snapshot, collected);
55
- if (!diff.added.length && !diff.changed.length && !diff.deleted.length) {
82
+ if (!diff.added.length && !diff.changed.length && !diff.deleted.length && !oobDetected) {
56
83
  console.log(`${c.green("✅")} No changes since ${c.dim(lastRun.at)}. Setup is current.`);
57
84
  return;
58
85
  }
package/dist/doctor.js CHANGED
@@ -62,6 +62,26 @@ export async function runDoctor(verbose = false) {
62
62
  statusLine("⚠️ ", "Manifest", c.yellow("not found — run: npx claude-setup init"));
63
63
  counts.warnings++;
64
64
  }
65
+ // --- Check 2b: Out-of-band edit detection ---
66
+ if (lastRun) {
67
+ const { createHash } = await import("crypto");
68
+ const oobFiles = [
69
+ { label: "CLAUDE.md", path: join(process.cwd(), "CLAUDE.md"), snapshotKey: "CLAUDE.md" },
70
+ { label: ".mcp.json", path: join(process.cwd(), ".mcp.json"), snapshotKey: ".mcp.json" },
71
+ { label: "settings.json", path: join(process.cwd(), ".claude", "settings.json"), snapshotKey: ".claude/settings.json" },
72
+ ];
73
+ for (const mf of oobFiles) {
74
+ if (!existsSync(mf.path))
75
+ continue;
76
+ const content = readFileSync(mf.path, "utf8");
77
+ const currentHash = createHash("sha256").update(content).digest("hex");
78
+ const snapshotHash = lastRun.snapshot[mf.snapshotKey];
79
+ if (snapshotHash && currentHash !== snapshotHash) {
80
+ statusLine("⚠️ ", mf.label, c.yellow("modified outside the CLI since last run — run sync to re-snapshot"));
81
+ counts.warnings++;
82
+ }
83
+ }
84
+ }
65
85
  // --- Check 3: OS/MCP format mismatch ---
66
86
  if (state.mcpJson.content) {
67
87
  section("MCP servers");
@@ -87,6 +107,32 @@ export async function runDoctor(verbose = false) {
87
107
  statusLine("✅", name, `valid, OS-format correct (${cmd})`);
88
108
  counts.healthy++;
89
109
  }
110
+ // Check for missing -y flag in npx args
111
+ const args = config.args;
112
+ if (args) {
113
+ const npxIndex = args.indexOf("npx");
114
+ if (npxIndex >= 0 && args[npxIndex + 1] !== "-y") {
115
+ statusLine("⚠️ ", name, c.yellow(`npx without -y flag — installs may hang`));
116
+ counts.warnings++;
117
+ }
118
+ // Check for hardcoded connection strings in args
119
+ for (const arg of args) {
120
+ if (/^(postgresql|postgres|mysql|mongodb|redis|amqp):\/\//.test(arg)) {
121
+ statusLine("🔴", name, c.red(`HARDCODED connection string in args — move to env: { "DATABASE_URL": "\${DATABASE_URL}" }`));
122
+ counts.critical++;
123
+ break;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ // Check for channel-type servers
129
+ const channelNames = ["telegram", "discord", "fakechat"];
130
+ for (const [name] of Object.entries(servers)) {
131
+ if (channelNames.includes(name.toLowerCase())) {
132
+ statusLine("⚠️ ", `${name} (CHANNEL)`, c.yellow(`channel server detected — .mcp.json alone does not activate delivery. ` +
133
+ `Launch with: claude --channels plugin:${name.toLowerCase()}@claude-plugins-official`));
134
+ counts.warnings++;
135
+ }
90
136
  }
91
137
  }
92
138
  else if (mcp) {
@@ -105,9 +151,24 @@ export async function runDoctor(verbose = false) {
105
151
  const settings = safeJsonParse(state.settings.content);
106
152
  if (settings) {
107
153
  const hookCategories = [
108
- "PreToolUse", "PostToolUse", "PreCompact", "PostCompact",
109
- "Notification", "Stop", "SubagentStop"
154
+ "PreToolUse", "PostToolUse", "PostToolUseFailure",
155
+ "Stop", "SessionStart"
110
156
  ];
157
+ // Bug 6: Check for model override
158
+ if (settings["model"]) {
159
+ statusLine("⚠️ ", "MODEL OVERRIDE", c.yellow(`"model": "${settings["model"]}" in settings.json forces this model on every session. Remove it if not intentional.`));
160
+ counts.warnings++;
161
+ }
162
+ // Check for invalid hook event names
163
+ const validHookNames = new Set(hookCategories);
164
+ for (const key of Object.keys(settings)) {
165
+ if (key === "permissions" || key === "model" || key === "env" || key === "allowedTools")
166
+ continue;
167
+ if (Array.isArray(settings[key]) && !validHookNames.has(key)) {
168
+ statusLine("🔴", `"${key}"`, c.red(`INVALID hook event name. Valid names: ${hookCategories.join(", ")}`));
169
+ counts.critical++;
170
+ }
171
+ }
111
172
  let foundHooks = false;
112
173
  for (const category of hookCategories) {
113
174
  const hooks = settings[category];
package/dist/manifest.js CHANGED
@@ -47,6 +47,18 @@ export async function updateManifest(command, collected, opts = {}) {
47
47
  for (const { path, content } of collected.source) {
48
48
  snapshot[path] = sha256(content);
49
49
  }
50
+ // Also snapshot CLI-managed files for out-of-band edit detection
51
+ const managedFiles = [
52
+ { key: "CLAUDE.md", path: join(cwd, "CLAUDE.md") },
53
+ { key: ".mcp.json", path: join(cwd, ".mcp.json") },
54
+ { key: ".claude/settings.json", path: join(cwd, ".claude", "settings.json") },
55
+ ];
56
+ for (const mf of managedFiles) {
57
+ if (existsSync(mf.path)) {
58
+ const content = readFileSync(mf.path, "utf8");
59
+ snapshot[mf.key] = sha256(content);
60
+ }
61
+ }
50
62
  const filesRead = [
51
63
  ...Object.keys(collected.configs),
52
64
  ...collected.source.map(s => s.path),
package/dist/os.d.ts CHANGED
@@ -8,7 +8,12 @@ export type DetectedOS = "Windows" | "macOS" | "Linux";
8
8
  * 5. default → Linux
9
9
  */
10
10
  export declare function detectOS(): DetectedOS;
11
- /** MCP command format per OS */
11
+ /**
12
+ * Verified MCP package names — ONLY use these.
13
+ * If a service is not in this map, do not guess a package name.
14
+ */
15
+ export declare const VERIFIED_MCP_PACKAGES: Record<string, string>;
16
+ /** MCP command format per OS — always includes -y to prevent npx install hangs */
12
17
  export declare function mcpCommandFormat(os: DetectedOS, pkg: string): {
13
18
  command: string;
14
19
  args: string[];
package/dist/os.js CHANGED
@@ -22,12 +22,26 @@ export function detectOS() {
22
22
  catch { /* not unix — unlikely to reach here */ }
23
23
  return "Linux";
24
24
  }
25
- /** MCP command format per OS */
25
+ /**
26
+ * Verified MCP package names — ONLY use these.
27
+ * If a service is not in this map, do not guess a package name.
28
+ */
29
+ export const VERIFIED_MCP_PACKAGES = {
30
+ playwright: "@playwright/mcp@latest",
31
+ postgres: "@modelcontextprotocol/server-postgres",
32
+ filesystem: "@modelcontextprotocol/server-filesystem",
33
+ memory: "@modelcontextprotocol/server-memory",
34
+ github: "@modelcontextprotocol/server-github",
35
+ brave: "@modelcontextprotocol/server-brave-search",
36
+ puppeteer: "@modelcontextprotocol/server-puppeteer",
37
+ slack: "@modelcontextprotocol/server-slack",
38
+ };
39
+ /** MCP command format per OS — always includes -y to prevent npx install hangs */
26
40
  export function mcpCommandFormat(os, pkg) {
27
41
  if (os === "Windows") {
28
- return { command: "cmd", args: ["/c", "npx", pkg] };
42
+ return { command: "cmd", args: ["/c", "npx", "-y", pkg] };
29
43
  }
30
- return { command: "npx", args: [pkg] };
44
+ return { command: "npx", args: ["-y", pkg] };
31
45
  }
32
46
  /** Hook shell format per OS */
33
47
  export function hookShellFormat(os, cmd) {
package/dist/state.js CHANGED
@@ -9,9 +9,13 @@ export async function readState(cwd = process.cwd()) {
9
9
  const claudeMd = readIfExists(claudeMdPath);
10
10
  const mcpJson = readIfExists(mcpJsonPath);
11
11
  const settings = readIfExists(settingsPath);
12
+ // Scan all three skill patterns and deduplicate (Bug 4 fix)
12
13
  let skills = [];
13
14
  try {
14
- skills = await glob(".claude/skills/*/SKILL.md", { cwd, posix: true });
15
+ const structured = await glob(".claude/skills/*/SKILL.md", { cwd, posix: true });
16
+ const flat = await glob(".claude/skills/*.md", { cwd, posix: true });
17
+ const nested = await glob(".claude/skills/**/*.md", { cwd, posix: true });
18
+ skills = [...new Set([...structured, ...flat, ...nested])];
15
19
  }
16
20
  catch { /* no skills */ }
17
21
  let commands = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-setup",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Setup layer for Claude Code — reads your project, writes command files, Claude Code does the rest",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,8 +21,29 @@ Remove from setup: "{{USER_INPUT}}"
21
21
 
22
22
  Skills: {{SKILLS_LIST}} | Commands: {{COMMANDS_LIST}} | Workflows: {{WORKFLOWS_LIST}}
23
23
 
24
+ ## IMPORTANT: Scan the filesystem directly — do NOT rely on manifest history
25
+
26
+ The manifest is a historical record, not a reliable source of what exists now.
27
+ Claude Code can create, modify, or delete files outside the CLI at any time.
28
+
29
+ Before removing anything, scan these locations directly:
30
+ - Skills : `.claude/skills/*/SKILL.md`, `.claude/skills/*.md`, `.claude/skills/**/*.md`
31
+ - Commands : `.claude/commands/*.md` (exclude stack-*.md)
32
+ - MCP : read `.mcp.json` directly
33
+ - Hooks : read `.claude/settings.json` directly
34
+ - CLAUDE.md: read the file directly
35
+
36
+ Before deleting, list every reference found and confirm scope:
37
+ ```
38
+ Planning to remove:
39
+ [path/entry] — [what it is]
40
+
41
+ Dangling references that will break:
42
+ [path] — still references [thing being removed]
43
+ ```
44
+
24
45
  ## Rules
25
- - Find everything related to the removal request across ALL files above.
46
+ - Find everything related to the removal request across ALL files — scan the filesystem, not just the data shown above.
26
47
  - Remove surgically — section by section, key by key.
27
48
  - Never delete an entire file. Remove only the relevant section.
28
49
  - Never remove content unrelated to the request.