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 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
- ## Cost controls
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
- const ifElseRegex = /\{\{#if\s+(\w+)\}\}\n?([\s\S]*?)\{\{else\}\}\n?([\s\S]*?)\{\{\/if\}\}/g;
27
- result = result.replace(ifElseRegex, (_match, key, ifBlock, elseBlock) => {
28
- return flags[key] ? ifBlock : elseBlock;
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 formatConfigFiles(configs) {
38
- if (Object.keys(configs).length === 0)
39
- return "(no config files found)";
40
- return Object.entries(configs)
41
- .map(([path, content]) => {
42
- return `#### ${path}\n\`\`\`\n${content}\n\`\`\``;
43
- })
44
- .join("\n\n");
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
- function formatSourceFiles(source) {
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 "(no source files sampled)";
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
- function formatSkippedFiles(skipped) {
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: version,
68
- DATE: date,
69
- CONFIG_FILES: formatConfigFiles(collected.configs),
70
- SOURCE_FILES: formatSourceFiles(collected.source),
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(collected, state) {
86
+ function buildFlags(_collected, state) {
88
87
  return {
89
- HAS_SKIPPED: collected.skipped.length > 0,
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
- function getVersion() {
97
- try {
98
- const pkgPath = join(__dirname, "..", "package.json");
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
- // Progressively remove source files, largest first
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 sourceBlock = `#### ${remove.path}\n\`\`\`\n${remove.content}\n\`\`\``;
113
- content = content.replace(sourceBlock, `[${remove.path} — removed to fit token budget]`);
114
- if (estimateTokens(content) <= TOKEN_HARD_CAP)
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 > TOKEN_SOFT_WARN) {
127
- console.warn(`⚠️ Command file is ${tokens} tokens (soft limit: ${TOKEN_SOFT_WARN})`);
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
- const version = getVersion();
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
- const lastRun = state.manifest?.runs.at(-1);
139
+ // Compact diff format — paths + one-line summary, not full content
146
140
  const addedStr = diff.added.length > 0
147
- ? diff.added.map(f => `#### ${f.path}\n\`\`\`\n${f.content}\n\`\`\``).join("\n\n")
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 => `#### ${f.path}\n\`\`\`\n${f.current}\n\`\`\``).join("\n\n")
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 preamble = `<!-- Generated by claude-setup ${version} on ${date} — DO NOT hand-edit -->\n`;
174
- const idempotentCheck = `\nBefore writing: check if what you are about to write already exists in the target file\n(current content provided below). If yes: print "SKIPPED — already up to date" and stop.\nWrite only what is genuinely missing.\n\n`;
175
- // Each step gets project context + specific instructions for its target
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: preamble + idempotentCheck +
180
- `## Project context\n\n${vars.CONFIG_FILES}\n\n${vars.SOURCE_FILES}\n\n` +
181
- `## Target: CLAUDE.md\n\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
- ? `### Current CLAUDE.md — EXISTS — append only, never rewrite, never remove\n${vars.CLAUDE_MD_CONTENT}\n\n`
184
- : `CLAUDE.md does not exist. Create it.\n\n`) +
185
- `Write or update CLAUDE.md for THIS specific project.\nMake it specific: reference actual file paths, actual patterns, actual conventions from the source above.\nNo generic boilerplate. Every line must trace back to something in the project files.\n` +
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: preamble + idempotentCheck +
191
- `## Project context\n\n${vars.CONFIG_FILES}\n\n` +
192
- `## Target: .mcp.json\n\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
- ? `### Current .mcp.json — EXISTS — merge only, never remove existing entries\n${vars.MCP_JSON_CONTENT}\n\n`
195
- : `.mcp.json does not exist. Create only if you find evidence of external services in the config files above.\n\n`) +
196
- `Only add MCP servers for services evidenced in the project files. No evidence = no server.\n` +
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: preamble + idempotentCheck +
202
- `## Project context\n\n${vars.CONFIG_FILES}\n\n` +
203
- `## Target: .claude/settings.json\n\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
- ? `### Current settings.json — EXISTS — merge only, never remove existing hooks\n${vars.SETTINGS_CONTENT}\n\n`
206
- : `.claude/settings.json does not exist. Create only if hooks are genuinely warranted for this project.\n\n`) +
207
- `Every hook adds overhead on every Claude Code action. Only add if clearly earned for THIS project.\n` +
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: preamble + idempotentCheck +
213
- `## Project context\n\n${vars.CONFIG_FILES}\n\n${vars.SOURCE_FILES}\n\n` +
214
- `## Target: .claude/skills/\n\n` +
215
- `Skills installed: ${vars.SKILLS_LIST}\n\n` +
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: preamble + idempotentCheck +
224
- `## Project context\n\n${vars.CONFIG_FILES}\n\n` +
225
- `## Target: .claude/commands/ (not stack-*.md files)\n\n` +
226
- `Commands installed: ${vars.COMMANDS_LIST}\n\n` +
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: preamble + idempotentCheck +
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 create workflows warranted by the project. If workflows already exist: do not touch them.`
238
- : `.github/ does not exist. Only create workflows if the project clearly warrants them.`),
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
- const stepList = steps
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 `<!-- Generated by claude-setup ${version} on ${date} — DO NOT hand-edit -->
250
- <!-- Run /stack-init in Claude Code -->
240
+ return `<!-- claude-setup ${version} ${date} -->
251
241
 
252
- Run these in order. If one fails, fix it and continue from that step only.
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 steps complete: one-line summary of what was created.
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 declare function collectProjectFiles(cwd?: string): Promise<CollectedFiles>;
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;