claude-setup 1.1.1 → 1.1.2
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 +119 -45
- package/dist/builder.js +131 -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 +35 -7
- package/dist/config.d.ts +14 -0
- package/dist/config.js +89 -3
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +259 -23
- package/dist/index.js +31 -9
- package/dist/os.d.ts +20 -0
- package/dist/os.js +38 -0
- package/dist/output.d.ts +18 -0
- package/dist/output.js +40 -0
- package/package.json +1 -1
- package/templates/add.md +8 -4
- package/templates/init.md +81 -17
- package/templates/remove.md +10 -5
- package/templates/sync.md +28 -13
package/README.md
CHANGED
|
@@ -1,45 +1,119 @@
|
|
|
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
|
+
## 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 |
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
Create `.claude-setup.json` in your project root to customize:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"maxSourceFiles": 15,
|
|
87
|
+
"maxDepth": 6,
|
|
88
|
+
"maxFileSizeKB": 80,
|
|
89
|
+
"tokenBudget": {
|
|
90
|
+
"init": 12000,
|
|
91
|
+
"sync": 6000,
|
|
92
|
+
"add": 3000,
|
|
93
|
+
"remove": 2000
|
|
94
|
+
},
|
|
95
|
+
"digestMode": true,
|
|
96
|
+
"extraBlockedDirs": ["my-custom-dir"],
|
|
97
|
+
"sourceDirs": ["src", "lib"]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Digest mode
|
|
102
|
+
|
|
103
|
+
When `digestMode` is enabled (default), the CLI extracts compact signal instead of dumping raw file content:
|
|
104
|
+
|
|
105
|
+
- **Config files found** — just names, not content
|
|
106
|
+
- **Dependencies** — extracted from any package manifest
|
|
107
|
+
- **Scripts** — available commands/tasks
|
|
108
|
+
- **Env vars** — names from `.env.example`
|
|
109
|
+
- **Directory tree** — compact structure (3 levels deep)
|
|
110
|
+
- **Source signatures** — imports, exports, declarations (not full content)
|
|
111
|
+
|
|
112
|
+
## OS detection
|
|
113
|
+
|
|
114
|
+
The CLI detects your OS and ensures command files tell Claude Code to use the correct format:
|
|
115
|
+
|
|
116
|
+
- **Windows**: `{ "command": "cmd", "args": ["/c", "npx", "<package>"] }`
|
|
117
|
+
- **macOS/Linux**: `{ "command": "npx", "args": ["<package>"] }`
|
|
118
|
+
|
|
119
|
+
`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 } 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,148 @@ 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
|
+
`### OS-correct format (detected: ${os})\n` +
|
|
224
|
+
(os === "Windows"
|
|
225
|
+
? `Use: \`{ "command": "cmd", "args": ["/c", "npx", "<package>"] }\`\n`
|
|
226
|
+
: `Use: \`{ "command": "npx", "args": ["<package>"] }\`\n`) +
|
|
227
|
+
`\n### Rules\n` +
|
|
228
|
+
`- All env var refs use \`\${VARNAME}\` syntax\n` +
|
|
229
|
+
`- Produce valid JSON only\n` +
|
|
230
|
+
`- If creating: document every new env var in .env.example\n\n` +
|
|
231
|
+
`### Output\n` +
|
|
232
|
+
`Created/Updated: ✅ .mcp.json — [what server and evidence source]\n` +
|
|
233
|
+
`Skipped: ⏭ .mcp.json — checked [files], found [nothing], no action\n`,
|
|
196
234
|
},
|
|
235
|
+
// --- Step 3: .claude/settings.json ---
|
|
197
236
|
{
|
|
198
237
|
filename: "stack-3-settings.md",
|
|
199
|
-
content: header +
|
|
200
|
-
|
|
201
|
-
`## Target: .claude/settings.json\n` +
|
|
238
|
+
content: header + preamble +
|
|
239
|
+
`## Target: .claude/settings.json\n\n` +
|
|
202
240
|
(state.settings.exists
|
|
203
|
-
?
|
|
204
|
-
: `Does not exist
|
|
205
|
-
|
|
241
|
+
? `### Current content — MERGE ONLY, never remove existing hooks:\n${vars.SETTINGS_CONTENT}\n\n`
|
|
242
|
+
: `Does not exist.\n\n`) +
|
|
243
|
+
`### When to create/update\n` +
|
|
244
|
+
`Add a hook ONLY if it runs on a pattern that repeats every session AND the cost is justified.\n` +
|
|
245
|
+
`Every hook adds overhead on every Claude Code action. Only add if clearly earned.\n\n` +
|
|
246
|
+
`### OS-correct hook format (detected: ${os})\n` +
|
|
247
|
+
(os === "Windows"
|
|
248
|
+
? `Use: \`{ "command": "cmd", "args": ["/c", "<command>"] }\`\n`
|
|
249
|
+
: `Use: \`{ "command": "bash", "args": ["-c", "<command>"] }\`\n` +
|
|
250
|
+
`**Bash quoting rule**: never use bare \`"\` inside \`-c "..."\` — use \`\\x22\` instead.\n` +
|
|
251
|
+
`Replace \`'\` with \`\\x27\`, \`$\` in character classes with \`\\x24\`.\n`) +
|
|
252
|
+
`\n### Rules\n` +
|
|
253
|
+
`- If it exists above: audit quoting of existing hooks first, fix broken ones\n` +
|
|
254
|
+
`- Only add hooks for patterns that genuinely recur for this project type\n` +
|
|
255
|
+
`- Produce valid JSON only\n\n` +
|
|
256
|
+
`### Output\n` +
|
|
257
|
+
`Created/Updated: ✅ settings.json — [hook name and justification]\n` +
|
|
258
|
+
`Skipped: ⏭ settings.json — [why no hooks warranted]\n`,
|
|
206
259
|
},
|
|
260
|
+
// --- Step 4: .claude/skills/ ---
|
|
207
261
|
{
|
|
208
262
|
filename: "stack-4-skills.md",
|
|
209
|
-
content: header +
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
263
|
+
content: header + preamble +
|
|
264
|
+
`## Target: .claude/skills/\n` +
|
|
265
|
+
`Installed: ${vars.SKILLS_LIST}\n\n` +
|
|
266
|
+
`### When to create\n` +
|
|
267
|
+
`Create a skill ONLY if:\n` +
|
|
268
|
+
`- A recurring multi-step project-specific pattern exists in /stack-0-context\n` +
|
|
269
|
+
`- It is NOT something Claude already knows (standard patterns don't need skills)\n` +
|
|
270
|
+
`- It will save time across multiple Claude Code sessions\n\n` +
|
|
271
|
+
`### Rules\n` +
|
|
272
|
+
`- Use \`applies-when:\` frontmatter so skills load only when relevant, not every message\n` +
|
|
273
|
+
`- If a similar skill already exists above: extend it, don't create a parallel one\n` +
|
|
274
|
+
`- Empty is valid — no skills is better than useless skills\n\n` +
|
|
275
|
+
`### Output\n` +
|
|
276
|
+
`Created: ✅ .claude/skills/[name] — [what pattern it captures]\n` +
|
|
277
|
+
`Skipped: ⏭ skills — checked [patterns], found [nothing project-specific]\n`,
|
|
213
278
|
},
|
|
279
|
+
// --- Step 5: .claude/commands/ ---
|
|
214
280
|
{
|
|
215
281
|
filename: "stack-5-commands.md",
|
|
216
|
-
content: header +
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
282
|
+
content: header + preamble +
|
|
283
|
+
`## Target: .claude/commands/ (excluding stack-*.md — those are setup artifacts)\n` +
|
|
284
|
+
`Installed: ${vars.COMMANDS_LIST}\n\n` +
|
|
285
|
+
`### 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` +
|
|
293
|
+
`### Rules\n` +
|
|
294
|
+
`- If existing commands cover the same workflow: skip\n` +
|
|
295
|
+
`- Commands should be specific to this project, not generic\n\n` +
|
|
296
|
+
`### Output\n` +
|
|
297
|
+
`Created: ✅ .claude/commands/[name].md — [what workflow and why useful]\n` +
|
|
298
|
+
`Skipped: ⏭ commands — [why no project-specific workflows found]\n`,
|
|
220
299
|
},
|
|
300
|
+
// --- Step 6: .github/workflows/ ---
|
|
221
301
|
{
|
|
222
302
|
filename: "stack-6-workflows.md",
|
|
223
|
-
content: header +
|
|
224
|
-
`## Target: .github/workflows/\n
|
|
303
|
+
content: header +
|
|
304
|
+
`## Target: .github/workflows/\n` +
|
|
305
|
+
`.github/ exists: ${vars.HAS_GITHUB_DIR}\n` +
|
|
306
|
+
`Installed: ${vars.WORKFLOWS_LIST}\n\n` +
|
|
225
307
|
(state.hasGithubDir
|
|
226
|
-
?
|
|
227
|
-
|
|
308
|
+
? (`### What to do\n` +
|
|
309
|
+
`Check /stack-0-context for CI evidence: tests dir, Dockerfile, CI-related scripts.\n` +
|
|
310
|
+
`If evidence found, print EXACTLY:\n\n` +
|
|
311
|
+
`\`\`\`\n` +
|
|
312
|
+
`⚙️ CI/CD GATE — action required\n\n` +
|
|
313
|
+
`Evidence found:\n` +
|
|
314
|
+
` [list each piece of evidence]\n\n` +
|
|
315
|
+
`Two questions before I proceed:\n` +
|
|
316
|
+
` 1. Set up CI/CD? (yes / no / later)\n` +
|
|
317
|
+
` 2. Connected to a remote GitHub repo? (yes / no)\n\n` +
|
|
318
|
+
`I will not write .github/workflows/ until you answer.\n` +
|
|
319
|
+
`\`\`\`\n\n` +
|
|
320
|
+
`### Rules\n` +
|
|
321
|
+
`- NEVER create or modify workflows without explicit developer confirmation\n` +
|
|
322
|
+
`- If existing workflows exist: do not touch them\n` +
|
|
323
|
+
`- 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`)),
|
|
228
327
|
},
|
|
229
328
|
];
|
|
230
329
|
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
package/dist/commands/init.js
CHANGED
|
@@ -4,39 +4,70 @@ import { collectProjectFiles, isEmptyProject } from "../collect.js";
|
|
|
4
4
|
import { readState } from "../state.js";
|
|
5
5
|
import { updateManifest } from "../manifest.js";
|
|
6
6
|
import { buildEmptyProjectCommand, buildAtomicSteps, buildOrchestratorCommand, } from "../builder.js";
|
|
7
|
+
import { c } from "../output.js";
|
|
8
|
+
import { ensureConfig } from "../config.js";
|
|
7
9
|
function ensureDir(dir) {
|
|
8
10
|
if (!existsSync(dir))
|
|
9
11
|
mkdirSync(dir, { recursive: true });
|
|
10
12
|
}
|
|
11
|
-
export async function runInit() {
|
|
13
|
+
export async function runInit(opts = {}) {
|
|
14
|
+
const dryRun = opts.dryRun ?? false;
|
|
15
|
+
// Auto-generate .claude-setup.json if it doesn't exist
|
|
16
|
+
// Developer can edit it anytime to tune token budgets, truncation rules, etc.
|
|
17
|
+
const configCreated = ensureConfig();
|
|
18
|
+
if (configCreated) {
|
|
19
|
+
console.log(`${c.dim("Created .claude-setup.json — edit to tune token budgets and truncation rules")}`);
|
|
20
|
+
}
|
|
12
21
|
const state = await readState();
|
|
13
22
|
const collected = await collectProjectFiles(process.cwd(), "deep");
|
|
14
|
-
ensureDir(".claude/commands");
|
|
15
23
|
if (isEmptyProject(collected)) {
|
|
16
24
|
const content = buildEmptyProjectCommand();
|
|
25
|
+
if (dryRun) {
|
|
26
|
+
console.log(c.bold("[DRY RUN] Would write:\n"));
|
|
27
|
+
console.log(` .claude/commands/stack-init.md (${content.length} chars, ~${Math.ceil(content.length / 4)} tokens)`);
|
|
28
|
+
console.log(`\n${c.dim("--- preview ---")}`);
|
|
29
|
+
console.log(content.slice(0, 500));
|
|
30
|
+
if (content.length > 500)
|
|
31
|
+
console.log(c.dim(`\n... +${content.length - 500} chars`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
ensureDir(".claude/commands");
|
|
17
35
|
writeFileSync(".claude/commands/stack-init.md", content, "utf8");
|
|
18
36
|
await updateManifest("init", collected);
|
|
19
37
|
console.log(`
|
|
20
|
-
✅ New project detected.
|
|
38
|
+
${c.green("✅")} New project detected.
|
|
21
39
|
|
|
22
40
|
Open Claude Code and run:
|
|
23
|
-
/stack-init
|
|
41
|
+
${c.cyan("/stack-init")}
|
|
24
42
|
|
|
25
43
|
Claude Code will ask 3 questions, then set up your environment.
|
|
26
44
|
`);
|
|
27
45
|
return;
|
|
28
46
|
}
|
|
29
|
-
// Standard init —
|
|
47
|
+
// Standard init — atomic steps + orchestrator
|
|
30
48
|
const steps = buildAtomicSteps(collected, state);
|
|
49
|
+
const orchestrator = buildOrchestratorCommand(steps);
|
|
50
|
+
if (dryRun) {
|
|
51
|
+
console.log(c.bold("[DRY RUN] Would write:\n"));
|
|
52
|
+
for (const step of steps) {
|
|
53
|
+
const tokens = Math.ceil(step.content.length / 4);
|
|
54
|
+
console.log(` .claude/commands/${step.filename} (${step.content.length} chars, ~${tokens} tokens)`);
|
|
55
|
+
}
|
|
56
|
+
console.log(` .claude/commands/stack-init.md (orchestrator)`);
|
|
57
|
+
const totalTokens = steps.reduce((sum, s) => sum + Math.ceil(s.content.length / 4), 0);
|
|
58
|
+
console.log(`\n${c.dim(`Total: ~${totalTokens} tokens across ${steps.length} files`)}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
ensureDir(".claude/commands");
|
|
31
62
|
for (const step of steps) {
|
|
32
63
|
writeFileSync(join(".claude/commands", step.filename), step.content, "utf8");
|
|
33
64
|
}
|
|
34
|
-
writeFileSync(".claude/commands/stack-init.md",
|
|
65
|
+
writeFileSync(".claude/commands/stack-init.md", orchestrator, "utf8");
|
|
35
66
|
await updateManifest("init", collected);
|
|
36
67
|
console.log(`
|
|
37
|
-
✅ Ready. Open Claude Code and run:
|
|
38
|
-
/stack-init
|
|
68
|
+
${c.green("✅")} Ready. Open Claude Code and run:
|
|
69
|
+
${c.cyan("/stack-init")}
|
|
39
70
|
|
|
40
|
-
Runs
|
|
71
|
+
Runs ${steps.length - 1} atomic steps. If one fails, re-run only that step.
|
|
41
72
|
`);
|
|
42
73
|
}
|
package/dist/commands/remove.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 { buildRemoveCommand } 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 });
|
|
@@ -29,5 +30,5 @@ export async function runRemove() {
|
|
|
29
30
|
ensureDir(".claude/commands");
|
|
30
31
|
writeFileSync(".claude/commands/stack-remove.md", content, "utf8");
|
|
31
32
|
await updateManifest("remove", collected, { input: userInput });
|
|
32
|
-
console.log(`\n✅ Ready. Open Claude Code and run:\n /stack-remove\n`);
|
|
33
|
+
console.log(`\n${c.green("✅")} Ready. Open Claude Code and run:\n ${c.cyan("/stack-remove")}\n`);
|
|
33
34
|
}
|