claude-setup 1.0.0 → 1.1.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.
- package/README.md +45 -51
- package/dist/builder.js +117 -128
- package/dist/collect.d.ts +10 -1
- package/dist/collect.js +448 -179
- package/dist/commands/add.js +8 -7
- package/dist/commands/init.js +1 -1
- package/dist/commands/remove.js +1 -1
- package/dist/commands/sync.js +1 -1
- package/dist/config.d.ts +15 -0
- package/dist/config.js +42 -0
- package/package.json +1 -1
- package/templates/add.md +12 -43
- package/templates/init.md +24 -107
- package/templates/remove.md +12 -34
- package/templates/sync.md +16 -47
package/README.md
CHANGED
|
@@ -1,51 +1,45 @@
|
|
|
1
|
-
# claude-setup
|
|
2
|
-
|
|
3
|
-
Setup layer for Claude Code. Reads your project, writes command files, Claude Code does the rest.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx claude-setup init
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Commands
|
|
12
|
-
|
|
13
|
-
| Command | What it does |
|
|
14
|
-
|---------|-------------|
|
|
15
|
-
| `npx claude-setup init` | Full project setup — new or existing |
|
|
16
|
-
| `npx claude-setup add` | Add a multi-file capability |
|
|
17
|
-
| `npx claude-setup sync` | Update setup after project changes |
|
|
18
|
-
| `npx claude-setup status` | Show current setup |
|
|
19
|
-
| `npx claude-setup doctor` | Validate environment |
|
|
20
|
-
| `npx claude-setup remove` | Remove a capability cleanly |
|
|
21
|
-
|
|
22
|
-
## How it works
|
|
23
|
-
|
|
24
|
-
1. **CLI collects** — reads project files (configs, source samples) with strict cost controls
|
|
25
|
-
2. **CLI writes command files** — assembles markdown instructions into `.claude/commands/`
|
|
26
|
-
3. **Claude Code executes** — you run `/stack-init` (or `/stack-sync`, etc.) in Claude Code
|
|
27
|
-
|
|
28
|
-
The CLI has zero intelligence. All reasoning is delegated to Claude Code via the command files.
|
|
29
|
-
|
|
30
|
-
## Three project states
|
|
31
|
-
|
|
32
|
-
- **Empty project** — Claude Code asks 3 discovery questions, then sets up a tailored environment
|
|
33
|
-
- **In development** — reads existing files, writes setup that references actual code patterns
|
|
34
|
-
- **Production** — same as development; merge rules protect existing Claude config (append only, never rewrite)
|
|
35
|
-
|
|
36
|
-
## What it creates
|
|
37
|
-
|
|
38
|
-
- `CLAUDE.md` — project-specific context for Claude Code
|
|
39
|
-
- `.mcp.json` — MCP server connections (only if evidenced by project files)
|
|
40
|
-
- `.claude/settings.json` — hooks (only if warranted)
|
|
41
|
-
- `.claude/skills/` — reusable patterns (only if recurring)
|
|
42
|
-
- `.claude/commands/` — project-specific slash commands
|
|
43
|
-
- `.github/workflows/` — CI workflows (only if `.github/` exists)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Every byte in a command file costs tokens. The CLI enforces:
|
|
48
|
-
- Source file sampling (max 10 files, smallest first)
|
|
49
|
-
- Hard truncation per file (150/400 line thresholds)
|
|
50
|
-
- Token budget cap (20,000 tokens max per command file)
|
|
51
|
-
- Universal blocklist (node_modules, dist, binaries, etc.)
|
|
1
|
+
# claude-setup
|
|
2
|
+
|
|
3
|
+
Setup layer for Claude Code. Reads your project, writes command files, Claude Code does the rest.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx claude-setup init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
| Command | What it does |
|
|
14
|
+
|---------|-------------|
|
|
15
|
+
| `npx claude-setup init` | Full project setup — new or existing |
|
|
16
|
+
| `npx claude-setup add` | Add a multi-file capability |
|
|
17
|
+
| `npx claude-setup sync` | Update setup after project changes |
|
|
18
|
+
| `npx claude-setup status` | Show current setup |
|
|
19
|
+
| `npx claude-setup doctor` | Validate environment |
|
|
20
|
+
| `npx claude-setup remove` | Remove a capability cleanly |
|
|
21
|
+
|
|
22
|
+
## How it works
|
|
23
|
+
|
|
24
|
+
1. **CLI collects** — reads project files (configs, source samples) with strict cost controls
|
|
25
|
+
2. **CLI writes command files** — assembles markdown instructions into `.claude/commands/`
|
|
26
|
+
3. **Claude Code executes** — you run `/stack-init` (or `/stack-sync`, etc.) in Claude Code
|
|
27
|
+
|
|
28
|
+
The CLI has zero intelligence. All reasoning is delegated to Claude Code via the command files.
|
|
29
|
+
|
|
30
|
+
## Three project states
|
|
31
|
+
|
|
32
|
+
- **Empty project** — Claude Code asks 3 discovery questions, then sets up a tailored environment
|
|
33
|
+
- **In development** — reads existing files, writes setup that references actual code patterns
|
|
34
|
+
- **Production** — same as development; merge rules protect existing Claude config (append only, never rewrite)
|
|
35
|
+
|
|
36
|
+
## What it creates
|
|
37
|
+
|
|
38
|
+
- `CLAUDE.md` — project-specific context for Claude Code
|
|
39
|
+
- `.mcp.json` — MCP server connections (only if evidenced by project files)
|
|
40
|
+
- `.claude/settings.json` — hooks (only if warranted)
|
|
41
|
+
- `.claude/skills/` — reusable patterns (only if recurring)
|
|
42
|
+
- `.claude/commands/` — project-specific slash commands
|
|
43
|
+
- `.github/workflows/` — CI workflows (only if `.github/` exists)
|
|
44
|
+
|
|
45
|
+
|
package/dist/builder.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
4
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
6
|
const TEMPLATES_DIR = join(__dirname, "..", "templates");
|
|
6
|
-
const TOKEN_SOFT_WARN = 8_000;
|
|
7
|
-
const TOKEN_HARD_CAP = 20_000;
|
|
8
7
|
function estimateTokens(content) {
|
|
9
8
|
return Math.ceil(content.length / 4);
|
|
10
9
|
}
|
|
11
10
|
function loadTemplate(name) {
|
|
12
11
|
return readFileSync(join(TEMPLATES_DIR, name), "utf8");
|
|
13
12
|
}
|
|
14
|
-
// Simple {{VARIABLE}} replacement
|
|
15
13
|
function replaceVars(template, vars) {
|
|
16
14
|
let result = template;
|
|
17
15
|
for (const [key, value] of Object.entries(vars)) {
|
|
@@ -19,56 +17,57 @@ function replaceVars(template, vars) {
|
|
|
19
17
|
}
|
|
20
18
|
return result;
|
|
21
19
|
}
|
|
22
|
-
// Conditional blocks: {{#if VAR}}...{{else}}...{{/if}} and {{#if VAR}}...{{/if}}
|
|
23
20
|
function processConditionals(template, flags) {
|
|
24
|
-
// Handle {{#if VAR}}...{{else}}...{{/if}}
|
|
25
21
|
let result = template;
|
|
26
|
-
|
|
27
|
-
result = result.replace(
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
// Handle {{#if VAR}}...{{/if}} (no else)
|
|
31
|
-
const ifRegex = /\{\{#if\s+(\w+)\}\}\n?([\s\S]*?)\{\{\/if\}\}/g;
|
|
32
|
-
result = result.replace(ifRegex, (_match, key, block) => {
|
|
33
|
-
return flags[key] ? block : "";
|
|
34
|
-
});
|
|
22
|
+
// {{#if VAR}}...{{else}}...{{/if}}
|
|
23
|
+
result = result.replace(/\{\{#if\s+(\w+)\}\}\n?([\s\S]*?)\{\{else\}\}\n?([\s\S]*?)\{\{\/if\}\}/g, (_m, key, ifBlock, elseBlock) => flags[key] ? ifBlock : elseBlock);
|
|
24
|
+
// {{#if VAR}}...{{/if}}
|
|
25
|
+
result = result.replace(/\{\{#if\s+(\w+)\}\}\n?([\s\S]*?)\{\{\/if\}\}/g, (_m, key, block) => flags[key] ? block : "");
|
|
35
26
|
return result;
|
|
36
27
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
28
|
+
function formatList(items) {
|
|
29
|
+
return items.length === 0 ? "none" : items.join(", ");
|
|
30
|
+
}
|
|
31
|
+
function getVersion() {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8")).version ?? "0.0.0";
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return "0.0.0";
|
|
37
|
+
}
|
|
45
38
|
}
|
|
46
|
-
|
|
39
|
+
// --- Project context formatter ---
|
|
40
|
+
// This is the key optimization: instead of dumping raw files, we format
|
|
41
|
+
// the digest compactly. Full file content only when truly needed.
|
|
42
|
+
function formatProjectContext(collected) {
|
|
43
|
+
const lines = [];
|
|
44
|
+
// Digest (compact signal extraction)
|
|
45
|
+
const digest = collected.configs["__digest__"];
|
|
46
|
+
if (digest) {
|
|
47
|
+
lines.push(digest);
|
|
48
|
+
}
|
|
49
|
+
// Additional config files that kept full content (docker-compose, etc.)
|
|
50
|
+
for (const [name, content] of Object.entries(collected.configs)) {
|
|
51
|
+
if (name === "__digest__" || name === ".env")
|
|
52
|
+
continue;
|
|
53
|
+
lines.push(`\n### ${name}\n\`\`\`\n${content}\n\`\`\``);
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n") || "(no config files found)";
|
|
56
|
+
}
|
|
57
|
+
function formatSourceContext(source) {
|
|
47
58
|
if (source.length === 0)
|
|
48
|
-
return "
|
|
59
|
+
return "";
|
|
49
60
|
return source
|
|
50
|
-
.map(({ path, content }) => {
|
|
51
|
-
return `#### ${path}\n\`\`\`\n${content}\n\`\`\``;
|
|
52
|
-
})
|
|
61
|
+
.map(({ path, content }) => `### ${path}\n\`\`\`\n${content}\n\`\`\``)
|
|
53
62
|
.join("\n\n");
|
|
54
63
|
}
|
|
55
|
-
|
|
56
|
-
return skipped.map(({ path, reason }) => `- ${path} — ${reason}`).join("\n");
|
|
57
|
-
}
|
|
58
|
-
function formatList(items) {
|
|
59
|
-
if (items.length === 0)
|
|
60
|
-
return "none";
|
|
61
|
-
return items.join(", ");
|
|
62
|
-
}
|
|
64
|
+
// --- Template variable building ---
|
|
63
65
|
function buildVars(collected, state) {
|
|
64
|
-
const version = getVersion();
|
|
65
|
-
const date = new Date().toISOString().split("T")[0];
|
|
66
66
|
return {
|
|
67
|
-
VERSION:
|
|
68
|
-
DATE:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
SKIPPED_LIST: formatSkippedFiles(collected.skipped),
|
|
67
|
+
VERSION: getVersion(),
|
|
68
|
+
DATE: new Date().toISOString().split("T")[0],
|
|
69
|
+
PROJECT_CONTEXT: formatProjectContext(collected),
|
|
70
|
+
SOURCE_CONTEXT: formatSourceContext(collected.source),
|
|
72
71
|
CLAUDE_MD_CONTENT: state.claudeMd.content
|
|
73
72
|
? `\`\`\`\n${state.claudeMd.content}\n\`\`\``
|
|
74
73
|
: "",
|
|
@@ -84,158 +83,148 @@ function buildVars(collected, state) {
|
|
|
84
83
|
HAS_GITHUB_DIR: state.hasGithubDir ? "yes" : "no",
|
|
85
84
|
};
|
|
86
85
|
}
|
|
87
|
-
function buildFlags(
|
|
86
|
+
function buildFlags(_collected, state) {
|
|
88
87
|
return {
|
|
89
|
-
|
|
88
|
+
HAS_SOURCE: _collected.source.length > 0,
|
|
90
89
|
HAS_CLAUDE_MD: state.claudeMd.exists,
|
|
91
90
|
HAS_MCP_JSON: state.mcpJson.exists,
|
|
92
91
|
HAS_SETTINGS: state.settings.exists,
|
|
93
92
|
HAS_GITHUB_DIR: state.hasGithubDir,
|
|
94
93
|
};
|
|
95
94
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
100
|
-
return pkg.version ?? "0.0.0";
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return "0.0.0";
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function fitToTokenBudget(content, sources) {
|
|
107
|
-
if (estimateTokens(content) <= TOKEN_HARD_CAP)
|
|
95
|
+
// --- Token budget enforcement ---
|
|
96
|
+
function fitToTokenBudget(content, sources, hardCap) {
|
|
97
|
+
if (estimateTokens(content) <= hardCap)
|
|
108
98
|
return content;
|
|
109
|
-
//
|
|
99
|
+
// Remove source files largest-first until under budget
|
|
110
100
|
const sorted = [...sources].sort((a, b) => b.content.length - a.content.length);
|
|
111
101
|
for (const remove of sorted) {
|
|
112
|
-
const
|
|
113
|
-
content = content.replace(
|
|
114
|
-
if (estimateTokens(content) <=
|
|
102
|
+
const block = `### ${remove.path}\n\`\`\`\n${remove.content}\n\`\`\``;
|
|
103
|
+
content = content.replace(block, `[${remove.path} — trimmed]`);
|
|
104
|
+
if (estimateTokens(content) <= hardCap)
|
|
115
105
|
break;
|
|
116
106
|
}
|
|
117
107
|
return content;
|
|
118
108
|
}
|
|
119
|
-
function applyTemplate(templateName, collected, state, extraVars = {}) {
|
|
109
|
+
function applyTemplate(templateName, collected, state, extraVars = {}, budgetKey = "init") {
|
|
110
|
+
const config = loadConfig();
|
|
111
|
+
const budget = config.tokenBudget[budgetKey];
|
|
120
112
|
const template = loadTemplate(templateName);
|
|
121
113
|
const vars = { ...buildVars(collected, state), ...extraVars };
|
|
122
114
|
const flags = buildFlags(collected, state);
|
|
123
115
|
let content = replaceVars(template, vars);
|
|
124
116
|
content = processConditionals(content, flags);
|
|
125
117
|
const tokens = estimateTokens(content);
|
|
126
|
-
if (tokens >
|
|
127
|
-
|
|
118
|
+
if (tokens > budget) {
|
|
119
|
+
content = fitToTokenBudget(content, collected.source, budget);
|
|
120
|
+
const finalTokens = estimateTokens(content);
|
|
121
|
+
if (finalTokens > budget) {
|
|
122
|
+
console.warn(`⚠️ ${templateName}: ${finalTokens} tokens (budget: ${budget})`);
|
|
123
|
+
}
|
|
128
124
|
}
|
|
129
|
-
content = fitToTokenBudget(content, collected.source);
|
|
130
125
|
return content;
|
|
131
126
|
}
|
|
127
|
+
// --- Public API ---
|
|
132
128
|
export function buildInitCommand(collected, state) {
|
|
133
|
-
return applyTemplate("init.md", collected, state);
|
|
129
|
+
return applyTemplate("init.md", collected, state, {}, "init");
|
|
134
130
|
}
|
|
135
131
|
export function buildEmptyProjectCommand() {
|
|
136
132
|
const template = loadTemplate("init-empty.md");
|
|
137
|
-
|
|
138
|
-
const date = new Date().toISOString().split("T")[0];
|
|
139
|
-
return replaceVars(template, { VERSION: version, DATE: date });
|
|
133
|
+
return replaceVars(template, { VERSION: getVersion(), DATE: new Date().toISOString().split("T")[0] });
|
|
140
134
|
}
|
|
141
135
|
export function buildAddCommand(input, collected, state) {
|
|
142
|
-
return applyTemplate("add.md", collected, state, { USER_INPUT: input });
|
|
136
|
+
return applyTemplate("add.md", collected, state, { USER_INPUT: input }, "add");
|
|
143
137
|
}
|
|
144
138
|
export function buildSyncCommand(diff, collected, state) {
|
|
145
|
-
|
|
139
|
+
// Compact diff format — paths + one-line summary, not full content
|
|
146
140
|
const addedStr = diff.added.length > 0
|
|
147
|
-
? diff.added.map(f =>
|
|
141
|
+
? diff.added.map(f => `- **${f.path}** (new) — ${f.content.split("\n").length} lines`).join("\n")
|
|
148
142
|
: "(none)";
|
|
149
143
|
const modifiedStr = diff.changed.length > 0
|
|
150
|
-
? diff.changed.map(f =>
|
|
144
|
+
? diff.changed.map(f => `- **${f.path}** (modified)`).join("\n")
|
|
151
145
|
: "(none)";
|
|
152
146
|
const deletedStr = diff.deleted.length > 0
|
|
153
147
|
? diff.deleted.map(f => `- ${f}`).join("\n")
|
|
154
148
|
: "(none)";
|
|
149
|
+
const lastRun = state.manifest?.runs.at(-1);
|
|
155
150
|
return applyTemplate("sync.md", collected, state, {
|
|
156
151
|
LAST_RUN_DATE: lastRun?.at ?? "unknown",
|
|
157
152
|
ADDED_FILES: addedStr,
|
|
158
153
|
MODIFIED_FILES: modifiedStr,
|
|
159
154
|
DELETED_FILES: deletedStr,
|
|
160
|
-
});
|
|
155
|
+
}, "sync");
|
|
161
156
|
}
|
|
162
157
|
export function buildRemoveCommand(input, state) {
|
|
163
|
-
// Remove uses a minimal collected set — no project files needed
|
|
164
158
|
const emptyCollected = { configs: {}, source: [], skipped: [] };
|
|
165
|
-
return applyTemplate("remove.md", emptyCollected, state, { USER_INPUT: input });
|
|
159
|
+
return applyTemplate("remove.md", emptyCollected, state, { USER_INPUT: input }, "remove");
|
|
166
160
|
}
|
|
167
161
|
export function buildAtomicSteps(collected, state) {
|
|
168
|
-
const fullContent = buildInitCommand(collected, state);
|
|
169
|
-
const vars = buildVars(collected, state);
|
|
170
|
-
const flags = buildFlags(collected, state);
|
|
171
162
|
const version = getVersion();
|
|
172
163
|
const date = new Date().toISOString().split("T")[0];
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
164
|
+
const vars = buildVars(collected, state);
|
|
165
|
+
const header = `<!-- claude-setup ${version} ${date} -->\n`;
|
|
166
|
+
const check = `Check if target already has this content. If yes: print "SKIPPED" and stop. Write only what's missing.\n\n`;
|
|
167
|
+
// Shared context block — written once to a reference file, not duplicated
|
|
168
|
+
const sharedContext = header +
|
|
169
|
+
`## Project\n\n${vars.PROJECT_CONTEXT}\n\n` +
|
|
170
|
+
`{{#if HAS_SOURCE}}## Source samples\n\n${vars.SOURCE_CONTEXT}\n{{/if}}`;
|
|
171
|
+
const sharedContextProcessed = processConditionals(sharedContext, buildFlags(collected, state));
|
|
176
172
|
const steps = [
|
|
173
|
+
{
|
|
174
|
+
filename: "stack-0-context.md",
|
|
175
|
+
content: sharedContextProcessed,
|
|
176
|
+
},
|
|
177
177
|
{
|
|
178
178
|
filename: "stack-1-claude-md.md",
|
|
179
|
-
content:
|
|
180
|
-
|
|
181
|
-
`## Target: CLAUDE.md\n
|
|
179
|
+
content: header + check +
|
|
180
|
+
`Read /stack-0-context for project info.\n\n` +
|
|
181
|
+
`## Target: CLAUDE.md\n` +
|
|
182
182
|
(state.claudeMd.exists
|
|
183
|
-
?
|
|
184
|
-
: `
|
|
185
|
-
`Write
|
|
186
|
-
(state.claudeMd.exists ? `\nAppend only — never rewrite or remove existing content.` : ""),
|
|
183
|
+
? `Current content (append only, never rewrite):\n${vars.CLAUDE_MD_CONTENT}\n\n`
|
|
184
|
+
: `Does not exist. Create it.\n\n`) +
|
|
185
|
+
`Write CLAUDE.md specific to this project. Reference actual paths and patterns. No generic boilerplate.`,
|
|
187
186
|
},
|
|
188
187
|
{
|
|
189
188
|
filename: "stack-2-mcp.md",
|
|
190
|
-
content:
|
|
191
|
-
|
|
192
|
-
`## Target: .mcp.json\n
|
|
189
|
+
content: header + check +
|
|
190
|
+
`Read /stack-0-context for project info.\n\n` +
|
|
191
|
+
`## Target: .mcp.json\n` +
|
|
193
192
|
(state.mcpJson.exists
|
|
194
|
-
?
|
|
195
|
-
:
|
|
196
|
-
`
|
|
197
|
-
(state.mcpJson.exists ? `Merge only — never remove existing entries. Produce valid JSON.` : ""),
|
|
193
|
+
? `Current (merge only, never remove):\n${vars.MCP_JSON_CONTENT}\n\n`
|
|
194
|
+
: `Does not exist. Create only if services are evidenced.\n\n`) +
|
|
195
|
+
`No evidence = no server. Valid JSON only.`,
|
|
198
196
|
},
|
|
199
197
|
{
|
|
200
198
|
filename: "stack-3-settings.md",
|
|
201
|
-
content:
|
|
202
|
-
|
|
203
|
-
`## Target: .claude/settings.json\n
|
|
199
|
+
content: header + check +
|
|
200
|
+
`Read /stack-0-context for project info.\n\n` +
|
|
201
|
+
`## Target: .claude/settings.json\n` +
|
|
204
202
|
(state.settings.exists
|
|
205
|
-
?
|
|
206
|
-
:
|
|
207
|
-
`Every hook
|
|
208
|
-
(state.settings.exists ? `Merge only — never remove existing hooks. Never modify existing values.` : ""),
|
|
203
|
+
? `Current (merge only, never remove hooks):\n${vars.SETTINGS_CONTENT}\n\n`
|
|
204
|
+
: `Does not exist. Create only if hooks earn their cost.\n\n`) +
|
|
205
|
+
`Every hook runs on every action. Only add if clearly justified.`,
|
|
209
206
|
},
|
|
210
207
|
{
|
|
211
208
|
filename: "stack-4-skills.md",
|
|
212
|
-
content:
|
|
213
|
-
|
|
214
|
-
`## Target: .claude/skills/\n\n` +
|
|
215
|
-
`
|
|
216
|
-
`Only create skills for patterns that recur across this codebase and benefit from automatic loading.\n` +
|
|
217
|
-
`Use applies-when frontmatter so skills load only when relevant.\n` +
|
|
218
|
-
`If a similar skill already exists: extend it. Do not create a parallel one.\n` +
|
|
219
|
-
`Empty is fine — not every project needs skills.`,
|
|
209
|
+
content: header + check +
|
|
210
|
+
`Read /stack-0-context for project info.\n\n` +
|
|
211
|
+
`## Target: .claude/skills/\nInstalled: ${vars.SKILLS_LIST}\n\n` +
|
|
212
|
+
`Only for recurring patterns. Use applies-when frontmatter. Empty is fine.`,
|
|
220
213
|
},
|
|
221
214
|
{
|
|
222
215
|
filename: "stack-5-commands.md",
|
|
223
|
-
content:
|
|
224
|
-
|
|
225
|
-
`## Target: .claude/commands/ (not stack-*.md
|
|
226
|
-
`
|
|
227
|
-
`Only create commands that will actually be useful for this kind of project.\n` +
|
|
228
|
-
`Do not duplicate existing commands. Do not create stack-*.md files.`,
|
|
216
|
+
content: header + check +
|
|
217
|
+
`Read /stack-0-context for project info.\n\n` +
|
|
218
|
+
`## Target: .claude/commands/ (not stack-*.md)\nInstalled: ${vars.COMMANDS_LIST}\n\n` +
|
|
219
|
+
`Only useful commands for this project type. No duplicates.`,
|
|
229
220
|
},
|
|
230
221
|
{
|
|
231
222
|
filename: "stack-6-workflows.md",
|
|
232
|
-
content:
|
|
233
|
-
`## Target: .github/workflows/\n\n` +
|
|
234
|
-
`.github/ exists: ${vars.HAS_GITHUB_DIR}\n` +
|
|
235
|
-
`Workflows installed: ${vars.WORKFLOWS_LIST}\n\n` +
|
|
223
|
+
content: header + check +
|
|
224
|
+
`## Target: .github/workflows/\n.github/ exists: ${vars.HAS_GITHUB_DIR}\nInstalled: ${vars.WORKFLOWS_LIST}\n\n` +
|
|
236
225
|
(state.hasGithubDir
|
|
237
|
-
? `Only
|
|
238
|
-
:
|
|
226
|
+
? `Only warranted workflows. Don't touch existing ones.`
|
|
227
|
+
: `Only create if clearly warranted.`),
|
|
239
228
|
},
|
|
240
229
|
];
|
|
241
230
|
return steps;
|
|
@@ -243,17 +232,17 @@ export function buildAtomicSteps(collected, state) {
|
|
|
243
232
|
export function buildOrchestratorCommand(steps) {
|
|
244
233
|
const version = getVersion();
|
|
245
234
|
const date = new Date().toISOString().split("T")[0];
|
|
246
|
-
|
|
235
|
+
// Skip step 0 (context) in the run list — it's referenced by other steps
|
|
236
|
+
const runSteps = steps.filter(s => s.filename !== "stack-0-context.md");
|
|
237
|
+
const stepList = runSteps
|
|
247
238
|
.map((s, i) => `${i + 1}. /${s.filename.replace(".md", "")}`)
|
|
248
239
|
.join("\n");
|
|
249
|
-
return `<!--
|
|
250
|
-
<!-- Run /stack-init in Claude Code -->
|
|
240
|
+
return `<!-- claude-setup ${version} ${date} -->
|
|
251
241
|
|
|
252
|
-
Run these in order. If one fails, fix
|
|
253
|
-
Do not re-run steps that already completed.
|
|
242
|
+
Run these in order. If one fails, fix and continue from that step.
|
|
254
243
|
|
|
255
244
|
${stepList}
|
|
256
245
|
|
|
257
|
-
After all
|
|
246
|
+
After all complete: one-line summary of what was created.
|
|
258
247
|
`;
|
|
259
248
|
}
|
package/dist/collect.d.ts
CHANGED
|
@@ -9,5 +9,14 @@ export interface CollectedFiles {
|
|
|
9
9
|
reason: string;
|
|
10
10
|
}>;
|
|
11
11
|
}
|
|
12
|
-
export
|
|
12
|
+
export interface ProjectDigest {
|
|
13
|
+
configFilesFound: string[];
|
|
14
|
+
deps: string[];
|
|
15
|
+
scripts: string[];
|
|
16
|
+
tree: string;
|
|
17
|
+
envVars: string[];
|
|
18
|
+
configs: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
export type CollectMode = "deep" | "normal" | "configOnly";
|
|
21
|
+
export declare function collectProjectFiles(cwd?: string, mode?: CollectMode): Promise<CollectedFiles>;
|
|
13
22
|
export declare function isEmptyProject(collected: CollectedFiles): boolean;
|