claude-setup 1.1.1 → 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 +96 -45
- package/dist/builder.js +226 -32
- package/dist/collect.js +40 -5
- package/dist/commands/add.js +10 -7
- package/dist/commands/doctor.d.ts +5 -1
- package/dist/commands/doctor.js +5 -1
- package/dist/commands/init.d.ts +3 -1
- package/dist/commands/init.js +40 -9
- package/dist/commands/remove.js +2 -1
- package/dist/commands/status.js +123 -11
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +65 -10
- package/dist/config.d.ts +14 -0
- package/dist/config.js +89 -3
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +320 -23
- package/dist/index.js +31 -9
- package/dist/manifest.js +12 -0
- package/dist/os.d.ts +25 -0
- package/dist/os.js +52 -0
- package/dist/output.d.ts +18 -0
- package/dist/output.js +40 -0
- package/dist/state.js +5 -1
- package/package.json +1 -1
- package/templates/add.md +8 -4
- package/templates/init.md +81 -17
- package/templates/remove.md +31 -5
- package/templates/sync.md +28 -13
package/README.md
CHANGED
|
@@ -1,45 +1,96 @@
|
|
|
1
|
-
# claude-setup
|
|
2
|
-
|
|
3
|
-
Setup layer for Claude Code. Reads your project, writes command files, Claude Code does the rest.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
|
18
|
-
|
|
19
|
-
| `npx claude-setup
|
|
20
|
-
| `npx claude-setup
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
# claude-setup
|
|
2
|
+
|
|
3
|
+
Setup layer for Claude Code. Reads your project, writes command files, Claude Code does the rest.
|
|
4
|
+
|
|
5
|
+
**The CLI has zero intelligence.** All reasoning is delegated to Claude Code via the command files. The CLI reads files. Claude Code decides.
|
|
6
|
+
|
|
7
|
+
## Install & Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx claude-setup init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then open Claude Code and run `/stack-init`.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
| Command | What it does |
|
|
18
|
+
|---------|-------------|
|
|
19
|
+
| `npx claude-setup init` | Full project setup — new or existing. Detects empty projects automatically. |
|
|
20
|
+
| `npx claude-setup add` | Add a multi-file capability (MCP + hooks + skills together) |
|
|
21
|
+
| `npx claude-setup sync` | Update setup after project changes (uses diff, not full re-scan) |
|
|
22
|
+
| `npx claude-setup status` | Show current setup state — OS, servers, hooks, staleness |
|
|
23
|
+
| `npx claude-setup doctor` | Validate environment — OS/MCP format, hook quoting, env vars, stale skills |
|
|
24
|
+
| `npx claude-setup remove` | Remove a capability cleanly with dangling reference detection |
|
|
25
|
+
|
|
26
|
+
### Flags
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx claude-setup init --dry-run # Preview without writing
|
|
30
|
+
npx claude-setup sync --dry-run # Show changes without writing
|
|
31
|
+
npx claude-setup doctor --verbose # Include passing checks in output
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
1. **CLI collects** — reads project files (configs, source samples) with strict token cost controls
|
|
37
|
+
2. **CLI writes command files** — assembles markdown instructions into `.claude/commands/`
|
|
38
|
+
3. **Claude Code executes** — you run `/stack-init` (or `/stack-sync`, etc.) in Claude Code
|
|
39
|
+
|
|
40
|
+
## Three project states
|
|
41
|
+
|
|
42
|
+
- **Empty project** — Claude Code asks 3 discovery questions, then sets up a tailored environment
|
|
43
|
+
- **In development** — reads existing files, writes setup that references actual code patterns
|
|
44
|
+
- **Production** — same as development; merge rules protect existing Claude config (append only, never rewrite)
|
|
45
|
+
|
|
46
|
+
## What it creates
|
|
47
|
+
|
|
48
|
+
- `CLAUDE.md` — project-specific context for Claude Code
|
|
49
|
+
- `.mcp.json` — MCP server connections (only if evidenced by project files, OS-correct format)
|
|
50
|
+
- `.claude/settings.json` — hooks (only if warranted, OS-correct shell format)
|
|
51
|
+
- `.claude/skills/` — reusable patterns (only if recurring, with `applies-when` frontmatter)
|
|
52
|
+
- `.claude/commands/` — project-specific slash commands
|
|
53
|
+
- `.github/workflows/` — CI workflows (only if `.github/` exists)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Create `.claude-setup.json` in your project root to customize:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"maxSourceFiles": 15,
|
|
64
|
+
"maxDepth": 6,
|
|
65
|
+
"maxFileSizeKB": 80,
|
|
66
|
+
"tokenBudget": {
|
|
67
|
+
"init": 12000,
|
|
68
|
+
"sync": 6000,
|
|
69
|
+
"add": 3000,
|
|
70
|
+
"remove": 2000
|
|
71
|
+
},
|
|
72
|
+
"digestMode": true,
|
|
73
|
+
"extraBlockedDirs": ["my-custom-dir"],
|
|
74
|
+
"sourceDirs": ["src", "lib"]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Digest mode
|
|
79
|
+
|
|
80
|
+
When `digestMode` is enabled (default), the CLI extracts compact signal instead of dumping raw file content:
|
|
81
|
+
|
|
82
|
+
- **Config files found** — just names, not content
|
|
83
|
+
- **Dependencies** — extracted from any package manifest
|
|
84
|
+
- **Scripts** — available commands/tasks
|
|
85
|
+
- **Env vars** — names from `.env.example`
|
|
86
|
+
- **Directory tree** — compact structure (3 levels deep)
|
|
87
|
+
- **Source signatures** — imports, exports, declarations (not full content)
|
|
88
|
+
|
|
89
|
+
## OS detection
|
|
90
|
+
|
|
91
|
+
The CLI detects your OS and ensures command files tell Claude Code to use the correct format:
|
|
92
|
+
|
|
93
|
+
- **Windows**: `{ "command": "cmd", "args": ["/c", "npx", "<package>"] }`
|
|
94
|
+
- **macOS/Linux**: `{ "command": "npx", "args": ["<package>"] }`
|
|
95
|
+
|
|
96
|
+
`doctor` checks for mismatches and reports them as critical issues.
|
package/dist/builder.js
CHANGED
|
@@ -2,6 +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, VERIFIED_MCP_PACKAGES } from "./os.js";
|
|
5
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
7
|
const TEMPLATES_DIR = join(__dirname, "..", "templates");
|
|
7
8
|
function estimateTokens(content) {
|
|
@@ -63,6 +64,9 @@ function formatSourceContext(source) {
|
|
|
63
64
|
}
|
|
64
65
|
// --- Template variable building ---
|
|
65
66
|
function buildVars(collected, state) {
|
|
67
|
+
const skippedList = collected.skipped.length > 0
|
|
68
|
+
? collected.skipped.map(s => `- ${s.path} — ${s.reason}`).join("\n")
|
|
69
|
+
: "";
|
|
66
70
|
return {
|
|
67
71
|
VERSION: getVersion(),
|
|
68
72
|
DATE: new Date().toISOString().split("T")[0],
|
|
@@ -81,11 +85,14 @@ function buildVars(collected, state) {
|
|
|
81
85
|
COMMANDS_LIST: formatList(state.commands),
|
|
82
86
|
WORKFLOWS_LIST: formatList(state.workflows),
|
|
83
87
|
HAS_GITHUB_DIR: state.hasGithubDir ? "yes" : "no",
|
|
88
|
+
SKIPPED_LIST: skippedList,
|
|
89
|
+
DETECTED_OS: detectOS(),
|
|
84
90
|
};
|
|
85
91
|
}
|
|
86
92
|
function buildFlags(_collected, state) {
|
|
87
93
|
return {
|
|
88
94
|
HAS_SOURCE: _collected.source.length > 0,
|
|
95
|
+
HAS_SKIPPED: _collected.skipped.length > 0,
|
|
89
96
|
HAS_CLAUDE_MD: state.claudeMd.exists,
|
|
90
97
|
HAS_MCP_JSON: state.mcpJson.exists,
|
|
91
98
|
HAS_SETTINGS: state.settings.exists,
|
|
@@ -162,9 +169,10 @@ export function buildAtomicSteps(collected, state) {
|
|
|
162
169
|
const version = getVersion();
|
|
163
170
|
const date = new Date().toISOString().split("T")[0];
|
|
164
171
|
const vars = buildVars(collected, state);
|
|
172
|
+
const os = detectOS();
|
|
165
173
|
const header = `<!-- claude-setup ${version} ${date} -->\n`;
|
|
166
|
-
const
|
|
167
|
-
// Shared context block — written once
|
|
174
|
+
const preamble = `Before writing: check if what you are about to write already exists in the target file (current content provided below if it exists). If already up to date: print "SKIPPED — already up to date" and stop. Write only what is genuinely missing.\n\nRead /stack-0-context for full project info.\n\n`;
|
|
175
|
+
// Shared context block — written once, referenced by all steps
|
|
168
176
|
const sharedContext = header +
|
|
169
177
|
`## Project\n\n${vars.PROJECT_CONTEXT}\n\n` +
|
|
170
178
|
`{{#if HAS_SOURCE}}## Source samples\n\n${vars.SOURCE_CONTEXT}\n{{/if}}`;
|
|
@@ -174,57 +182,243 @@ export function buildAtomicSteps(collected, state) {
|
|
|
174
182
|
filename: "stack-0-context.md",
|
|
175
183
|
content: sharedContextProcessed,
|
|
176
184
|
},
|
|
185
|
+
// --- Step 1: CLAUDE.md ---
|
|
177
186
|
{
|
|
178
187
|
filename: "stack-1-claude-md.md",
|
|
179
|
-
content: header +
|
|
180
|
-
|
|
181
|
-
`## Target: CLAUDE.md\n` +
|
|
188
|
+
content: header + preamble +
|
|
189
|
+
`## Target: CLAUDE.md\n\n` +
|
|
182
190
|
(state.claudeMd.exists
|
|
183
|
-
?
|
|
184
|
-
: `Does not exist
|
|
185
|
-
|
|
191
|
+
? `### Current content — APPEND ONLY, never rewrite, never remove:\n${vars.CLAUDE_MD_CONTENT}\n\n`
|
|
192
|
+
: `Does not exist — create it.\n\n`) +
|
|
193
|
+
`### What to write\n` +
|
|
194
|
+
`CLAUDE.md is the most valuable artifact. Make it specific to THIS project:\n` +
|
|
195
|
+
`- **Purpose**: one sentence describing what this project does\n` +
|
|
196
|
+
`- **Runtime**: language, framework, key dependencies from /stack-0-context\n` +
|
|
197
|
+
`- **Key dirs**: reference actual directory paths from the project tree\n` +
|
|
198
|
+
`- **Run/test/build commands**: extract from scripts in /stack-0-context\n` +
|
|
199
|
+
`- **Non-obvious conventions**: patterns you see in the source samples\n\n` +
|
|
200
|
+
`### Rules\n` +
|
|
201
|
+
`- Every line must reference something you actually saw in /stack-0-context\n` +
|
|
202
|
+
`- No generic boilerplate. Two different projects must produce two different CLAUDE.md files\n` +
|
|
203
|
+
`- If it exists above: read it fully, add only what is genuinely missing\n\n` +
|
|
204
|
+
`### Output\n` +
|
|
205
|
+
`Created/Updated: ✅ CLAUDE.md — [one clause: what you wrote and why]\n` +
|
|
206
|
+
`Skipped: ⏭ CLAUDE.md — [why not needed]\n`,
|
|
186
207
|
},
|
|
208
|
+
// --- Step 2: .mcp.json ---
|
|
187
209
|
{
|
|
188
210
|
filename: "stack-2-mcp.md",
|
|
189
|
-
content: header +
|
|
190
|
-
|
|
191
|
-
`## Target: .mcp.json\n` +
|
|
211
|
+
content: header + preamble +
|
|
212
|
+
`## Target: .mcp.json\n\n` +
|
|
192
213
|
(state.mcpJson.exists
|
|
193
|
-
?
|
|
194
|
-
: `Does not exist
|
|
195
|
-
|
|
214
|
+
? `### Current content — MERGE ONLY, never remove existing entries:\n${vars.MCP_JSON_CONTENT}\n\n`
|
|
215
|
+
: `Does not exist.\n\n`) +
|
|
216
|
+
`### When to create/update\n` +
|
|
217
|
+
`Add an MCP server ONLY if you find evidence in /stack-0-context:\n` +
|
|
218
|
+
`- Import statement referencing an external service\n` +
|
|
219
|
+
`- docker-compose service (database, cache, queue)\n` +
|
|
220
|
+
`- Env var name in .env.example matching a known service pattern\n` +
|
|
221
|
+
`- Explicit dependency on an MCP-compatible package\n\n` +
|
|
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` +
|
|
230
|
+
`### OS-correct format (detected: ${os})\n` +
|
|
231
|
+
(os === "Windows"
|
|
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` +
|
|
241
|
+
`\n### Rules\n` +
|
|
242
|
+
`- All env var refs use \`\${VARNAME}\` syntax — never literal values\n` +
|
|
243
|
+
`- Produce valid JSON only\n` +
|
|
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` +
|
|
261
|
+
`### Output\n` +
|
|
262
|
+
`Created/Updated: ✅ .mcp.json — [what server and evidence source]\n` +
|
|
263
|
+
`Skipped: ⏭ .mcp.json — checked [files], found [nothing], no action\n`,
|
|
196
264
|
},
|
|
265
|
+
// --- Step 3: .claude/settings.json ---
|
|
197
266
|
{
|
|
198
267
|
filename: "stack-3-settings.md",
|
|
199
|
-
content: header +
|
|
200
|
-
|
|
201
|
-
`## Target: .claude/settings.json\n` +
|
|
268
|
+
content: header + preamble +
|
|
269
|
+
`## Target: .claude/settings.json\n\n` +
|
|
202
270
|
(state.settings.exists
|
|
203
|
-
?
|
|
204
|
-
: `Does not exist
|
|
205
|
-
|
|
271
|
+
? `### Current content — MERGE ONLY, never remove existing hooks:\n${vars.SETTINGS_CONTENT}\n\n`
|
|
272
|
+
: `Does not exist.\n\n`) +
|
|
273
|
+
`### When to create/update\n` +
|
|
274
|
+
`Add a hook ONLY if it runs on a pattern that repeats every session AND the cost is justified.\n` +
|
|
275
|
+
`Every hook adds overhead on every Claude Code action. Only add if clearly earned.\n\n` +
|
|
276
|
+
`### OS-correct hook format (detected: ${os})\n` +
|
|
277
|
+
(os === "Windows"
|
|
278
|
+
? `Use: \`{ "command": "cmd", "args": ["/c", "<command>"] }\`\n`
|
|
279
|
+
: `Use: \`{ "command": "bash", "args": ["-c", "<command>"] }\`\n` +
|
|
280
|
+
`**Bash quoting rule**: never use bare \`"\` inside \`-c "..."\` — use \`\\x22\` instead.\n` +
|
|
281
|
+
`Replace \`'\` with \`\\x27\`, \`$\` in character classes with \`\\x24\`.\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` +
|
|
288
|
+
`- If it exists above: audit quoting of existing hooks first, fix broken ones\n` +
|
|
289
|
+
`- Only add hooks for patterns that genuinely recur for this project type\n` +
|
|
290
|
+
`- Produce valid JSON only\n\n` +
|
|
291
|
+
`### Output\n` +
|
|
292
|
+
`Created/Updated: ✅ settings.json — [hook name and justification]\n` +
|
|
293
|
+
`Skipped: ⏭ settings.json — [why no hooks warranted]\n`,
|
|
206
294
|
},
|
|
295
|
+
// --- Step 4: .claude/skills/ ---
|
|
207
296
|
{
|
|
208
297
|
filename: "stack-4-skills.md",
|
|
209
|
-
content: header +
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
298
|
+
content: header + preamble +
|
|
299
|
+
`## Target: .claude/skills/\n` +
|
|
300
|
+
`Installed: ${vars.SKILLS_LIST}\n\n` +
|
|
301
|
+
`### When to create\n` +
|
|
302
|
+
`Create a skill ONLY if:\n` +
|
|
303
|
+
`- A recurring multi-step project-specific pattern exists in /stack-0-context\n` +
|
|
304
|
+
`- It is NOT something Claude already knows (standard patterns don't need skills)\n` +
|
|
305
|
+
`- It will save time across multiple Claude Code sessions\n\n` +
|
|
306
|
+
`### Rules\n` +
|
|
307
|
+
`- Use \`applies-when:\` frontmatter so skills load only when relevant, not every message\n` +
|
|
308
|
+
`- If a similar skill already exists above: extend it, don't create a parallel one\n` +
|
|
309
|
+
`- Empty is valid — no skills is better than useless skills\n\n` +
|
|
310
|
+
`### Output\n` +
|
|
311
|
+
`Created: ✅ .claude/skills/[name] — [what pattern it captures]\n` +
|
|
312
|
+
`Skipped: ⏭ skills — checked [patterns], found [nothing project-specific]\n`,
|
|
213
313
|
},
|
|
314
|
+
// --- Step 5: .claude/commands/ ---
|
|
214
315
|
{
|
|
215
316
|
filename: "stack-5-commands.md",
|
|
216
|
-
content: header +
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
317
|
+
content: header + preamble +
|
|
318
|
+
`## Target: .claude/commands/ (excluding stack-*.md — those are setup artifacts)\n` +
|
|
319
|
+
`Installed: ${vars.COMMANDS_LIST}\n\n` +
|
|
320
|
+
`### When to create\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` +
|
|
362
|
+
`### Rules\n` +
|
|
363
|
+
`- If existing commands cover the same workflow: skip\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` +
|
|
367
|
+
`### Output\n` +
|
|
368
|
+
`Created: ✅ .claude/commands/[name].md — [what workflow and why useful]\n` +
|
|
369
|
+
`Skipped: ⏭ commands — scanned [list each source checked and result]. Nothing warranted.\n`,
|
|
220
370
|
},
|
|
371
|
+
// --- Step 6: .github/workflows/ ---
|
|
221
372
|
{
|
|
222
373
|
filename: "stack-6-workflows.md",
|
|
223
|
-
content: header +
|
|
224
|
-
`## Target: .github/workflows/\n
|
|
374
|
+
content: header +
|
|
375
|
+
`## Target: .github/workflows/\n` +
|
|
376
|
+
`.github/ exists: ${vars.HAS_GITHUB_DIR}\n` +
|
|
377
|
+
`Installed: ${vars.WORKFLOWS_LIST}\n\n` +
|
|
225
378
|
(state.hasGithubDir
|
|
226
|
-
?
|
|
227
|
-
|
|
379
|
+
? (`### What to do\n` +
|
|
380
|
+
`Check /stack-0-context for CI evidence: tests dir, Dockerfile, CI-related scripts.\n` +
|
|
381
|
+
`If evidence found, print EXACTLY:\n\n` +
|
|
382
|
+
`\`\`\`\n` +
|
|
383
|
+
`⚙️ CI/CD GATE — action required\n\n` +
|
|
384
|
+
`Evidence found:\n` +
|
|
385
|
+
` [list each piece of evidence]\n\n` +
|
|
386
|
+
`Two questions before I proceed:\n` +
|
|
387
|
+
` 1. Set up CI/CD? (yes / no / later)\n` +
|
|
388
|
+
` 2. Connected to a remote GitHub repo? (yes / no)\n\n` +
|
|
389
|
+
`I will not write .github/workflows/ until you answer.\n` +
|
|
390
|
+
`\`\`\`\n\n` +
|
|
391
|
+
`### Rules\n` +
|
|
392
|
+
`- NEVER create or modify workflows without explicit developer confirmation\n` +
|
|
393
|
+
`- If existing workflows exist: do not touch them\n` +
|
|
394
|
+
`- Secrets must use \`\${{ secrets.VARNAME }}\` syntax only\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`)),
|
|
228
422
|
},
|
|
229
423
|
];
|
|
230
424
|
return steps;
|
package/dist/collect.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync, statSync, readdirSync } from "fs";
|
|
2
2
|
import { glob } from "glob";
|
|
3
3
|
import { join, extname, basename, dirname } from "path";
|
|
4
|
-
import { loadConfig } from "./config.js";
|
|
4
|
+
import { loadConfig, applyTruncation } from "./config.js";
|
|
5
5
|
// --- Blocklists ---
|
|
6
6
|
const BLOCKED_DIRS = new Set([
|
|
7
7
|
"node_modules", "vendor", ".venv", "venv", "env", "__pypackages__",
|
|
@@ -22,7 +22,7 @@ const BLOCKED_EXTENSIONS = new Set([
|
|
|
22
22
|
]);
|
|
23
23
|
const BLOCKED_FILES = new Set([
|
|
24
24
|
"go.sum", "poetry.lock", "Pipfile.lock", "composer.lock",
|
|
25
|
-
".DS_Store", "Thumbs.db",
|
|
25
|
+
".DS_Store", "Thumbs.db",
|
|
26
26
|
]);
|
|
27
27
|
const SOURCE_EXTENSIONS = new Set([
|
|
28
28
|
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
|
|
@@ -35,7 +35,8 @@ const ENTRY_DIRS = [".", "src", "app", "cmd", "bin"];
|
|
|
35
35
|
const PRIMARY_SOURCE_DIRS = ["src", "app", "lib", "core", "pkg", "internal", "api", "cmd"];
|
|
36
36
|
// Config files the CLI knows how to find — but NOT what they mean
|
|
37
37
|
const KNOWN_CONFIG_FILES = [
|
|
38
|
-
"package.json", "
|
|
38
|
+
"package.json", "package-lock.json", "pyproject.toml", "setup.py",
|
|
39
|
+
"requirements.txt", "Pipfile",
|
|
39
40
|
"go.mod", "Cargo.toml", "pom.xml", "build.gradle", "build.gradle.kts",
|
|
40
41
|
"composer.json", "Gemfile", "turbo.json", "nx.json", "pnpm-workspace.yaml",
|
|
41
42
|
"lerna.json", ".env.example", ".env.sample", ".env.template",
|
|
@@ -441,18 +442,52 @@ function truncateSource(content) {
|
|
|
441
442
|
}
|
|
442
443
|
// --- Legacy raw config collection ---
|
|
443
444
|
async function collectRawConfigs(cwd, configs, skipped) {
|
|
445
|
+
const config = loadConfig(cwd);
|
|
444
446
|
for (const name of KNOWN_CONFIG_FILES) {
|
|
445
447
|
const p = join(cwd, name);
|
|
446
448
|
if (existsSync(p)) {
|
|
447
449
|
try {
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
+
const raw = readFileSync(p, "utf8");
|
|
451
|
+
// Dynamic truncation — driven by config, not hardcoded
|
|
452
|
+
configs[name] = applyTruncation(name, raw, config);
|
|
450
453
|
}
|
|
451
454
|
catch {
|
|
452
455
|
skipped.push({ path: name, reason: "could not read" });
|
|
453
456
|
}
|
|
454
457
|
}
|
|
455
458
|
}
|
|
459
|
+
// Scan for *.config.{js,ts,mjs} at root level — truncation from config
|
|
460
|
+
try {
|
|
461
|
+
for (const ext of ["js", "ts", "mjs"]) {
|
|
462
|
+
const matches = await glob(`*.config.${ext}`, { cwd, nodir: true });
|
|
463
|
+
for (const match of matches) {
|
|
464
|
+
if (configs[match])
|
|
465
|
+
continue;
|
|
466
|
+
const p = join(cwd, match);
|
|
467
|
+
try {
|
|
468
|
+
const content = readFileSync(p, "utf8");
|
|
469
|
+
configs[match] = applyTruncation(match, content, config);
|
|
470
|
+
}
|
|
471
|
+
catch { /* skip */ }
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch { /* skip */ }
|
|
476
|
+
// Scan for *.csproj at root level
|
|
477
|
+
try {
|
|
478
|
+
const csprojFiles = await glob("*.csproj", { cwd, nodir: true });
|
|
479
|
+
for (const match of csprojFiles) {
|
|
480
|
+
if (configs[match])
|
|
481
|
+
continue;
|
|
482
|
+
const p = join(cwd, match);
|
|
483
|
+
try {
|
|
484
|
+
const content = readFileSync(p, "utf8");
|
|
485
|
+
configs[match] = applyTruncation(match, content, config);
|
|
486
|
+
}
|
|
487
|
+
catch { /* skip */ }
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
catch { /* skip */ }
|
|
456
491
|
}
|
|
457
492
|
// --- Source file discovery ---
|
|
458
493
|
async function findSourceFiles(cwd, maxDepth, blocked) {
|
package/dist/commands/add.js
CHANGED
|
@@ -4,6 +4,7 @@ import { collectProjectFiles } from "../collect.js";
|
|
|
4
4
|
import { readState } from "../state.js";
|
|
5
5
|
import { updateManifest } from "../manifest.js";
|
|
6
6
|
import { buildAddCommand } from "../builder.js";
|
|
7
|
+
import { c } from "../output.js";
|
|
7
8
|
function ensureDir(dir) {
|
|
8
9
|
if (!existsSync(dir))
|
|
9
10
|
mkdirSync(dir, { recursive: true });
|
|
@@ -18,6 +19,8 @@ async function promptFreeText(question) {
|
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
// Conservative — only redirect when unambiguously single-file
|
|
22
|
+
// False negatives (multi-step for single-file request) are fine
|
|
23
|
+
// False positives (redirecting a genuinely multi-file request) are bad
|
|
21
24
|
function isSingleFileOperation(input) {
|
|
22
25
|
return (/to \.mcp\.json\s*$/i.test(input) ||
|
|
23
26
|
/to settings\.json\s*$/i.test(input) ||
|
|
@@ -30,12 +33,12 @@ export async function runAdd() {
|
|
|
30
33
|
return;
|
|
31
34
|
}
|
|
32
35
|
if (isSingleFileOperation(userInput)) {
|
|
33
|
-
console.log(`
|
|
34
|
-
For single changes, Claude Code is faster:
|
|
35
|
-
Just tell it: "${userInput}"
|
|
36
|
-
|
|
37
|
-
Use claude-setup add when the change spans multiple files —
|
|
38
|
-
capabilities that need documentation, MCP servers, skills, and hooks together.
|
|
36
|
+
console.log(`
|
|
37
|
+
For single changes, Claude Code is faster:
|
|
38
|
+
Just tell it: "${userInput}"
|
|
39
|
+
|
|
40
|
+
Use ${c.cyan("claude-setup add")} when the change spans multiple files —
|
|
41
|
+
capabilities that need documentation, MCP servers, skills, and hooks together.
|
|
39
42
|
`);
|
|
40
43
|
return;
|
|
41
44
|
}
|
|
@@ -46,5 +49,5 @@ capabilities that need documentation, MCP servers, skills, and hooks together.
|
|
|
46
49
|
ensureDir(".claude/commands");
|
|
47
50
|
writeFileSync(".claude/commands/stack-add.md", content, "utf8");
|
|
48
51
|
await updateManifest("add", collected, { input: userInput });
|
|
49
|
-
console.log(`\n✅ Ready. Open Claude Code and run:\n /stack-add\n`);
|
|
52
|
+
console.log(`\n${c.green("✅")} Ready. Open Claude Code and run:\n ${c.cyan("/stack-add")}\n`);
|
|
50
53
|
}
|
package/dist/commands/doctor.js
CHANGED
package/dist/commands/init.d.ts
CHANGED