create-harness-vibe-coding 0.6.2 → 0.6.4
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/package.json +1 -1
- package/src/generator.js +466 -466
- package/src/index.js +355 -355
- package/templates/common/.claude/agents/architect.md +31 -35
- package/templates/common/.claude/agents/context-master.md +0 -1
- package/templates/common/.claude/agents/debugger.md +0 -1
- package/templates/common/.claude/agents/docs-researcher.md +41 -43
- package/templates/common/.claude/agents/implementer.md +0 -1
- package/templates/common/.claude/agents/memory-master.md +0 -1
- package/templates/common/.claude/agents/planner.md +0 -1
- package/templates/common/.claude/agents/researcher.md +0 -1
- package/templates/common/.claude/agents/reviewer.md +34 -35
- package/templates/common/.claude/agents/test-writer.md +0 -1
- package/templates/common/.claude/agents/verifier.md +0 -1
- package/templates/common/.claude/commands/wf-max.md +7 -0
- package/templates/common/.claude/commands/{update.md → wf-update.md} +4 -0
- package/templates/common/.claude/commands/wf.md +10 -3
- package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +2 -1
- package/templates/common/.claude/skills/wf-max/SKILL.md +29 -70
- package/templates/common/.claude/skills/{readme-optimizer → wf-readme}/SKILL.md +1 -1
- package/templates/common/.claude/skills/wf-review/SKILL.md +50 -50
- package/templates/common/.claude/skills/wf-update/SKILL.md +58 -58
- package/templates/common/CLAUDE.md +77 -76
- package/templates/common/MEMORY.md +73 -76
- package/templates/common/README.md +1 -1
- package/templates/common/SETUP.md +273 -341
- package/templates/common/docs/README.md +131 -145
- package/templates/common/docs/harness/WF.md +13 -1
- package/templates/common/docs/harness/agent-workflow.md +94 -94
- package/templates/common/docs/harness/architecture.md +1 -1
- package/templates/common/docs/harness/context-loading.md +104 -108
- package/templates/common/docs/harness/extension.md +70 -79
- package/templates/common/docs/harness/lifecycle.md +33 -33
- package/templates/common/docs/harness/subagents.md +1 -1
- package/templates/common/docs/research/PRD.md +65 -65
- package/templates/common/docs/research/README.md +169 -169
- package/templates/common/scripts/validate-harness.mjs +439 -460
- package/templates/optional/skills/browser-e2e/.claude/commands/wf-browser.md +25 -0
- package/templates/optional/skills/browser-e2e/.claude/skills/browser-e2e/SKILL.md +42 -42
- package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +194 -0
- package/templates/optional/skills/browser-e2e/docs/workflows/browser-e2e.md +55 -34
- package/templates/optional/skills/github-pr-review/.claude/skills/github-pr-review/SKILL.md +40 -40
- package/templates/optional/skills/python-backend/.claude/skills/python-backend/SKILL.md +40 -40
- package/templates/optional/skills/ts-react-frontend/.claude/skills/ts-react-frontend/SKILL.md +43 -43
- package/templates/optional/skills/ui-ux-review/.claude/skills/ui-ux-review/SKILL.md +40 -40
- package/templates/common/.claude/skills/harness-build-loop/SKILL.md +0 -23
- package/templates/common/.claude/skills/harness-context/SKILL.md +0 -26
- package/templates/common/.claude/skills/harness-lifecycle/SKILL.md +0 -20
- package/templates/common/.claude/skills/harness-research/SKILL.md +0 -30
- package/templates/common/.claude/skills/harness-router/SKILL.md +0 -16
- package/templates/common/.claude/skills/wf-mode/SKILL.md +0 -55
- package/templates/common/docs/domain/ports.md +0 -76
- package/templates/common/docs/features/_template.md +0 -177
- package/templates/common/docs/harness/PLAN.md +0 -52
- package/templates/common/docs/harness/data-flow.md +0 -59
- package/templates/common/docs/harness/state-machines.md +0 -58
- /package/templates/common/.claude/commands/{learn.md → wf-learn.md} +0 -0
package/src/index.js
CHANGED
|
@@ -1,355 +1,355 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import * as p from '@clack/prompts';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
import { askProjectName, askTargetDir } from './prompts.js';
|
|
5
|
-
import { generate, getOptionalCatalog } from './generator.js';
|
|
6
|
-
|
|
7
|
-
// ── CLI flags ──────────────────────────────────────────────
|
|
8
|
-
const raw = process.argv.slice(2);
|
|
9
|
-
const parsed = parseArgs(raw);
|
|
10
|
-
const showHelp = parsed.flags.help || parsed.flags.h;
|
|
11
|
-
const skipPrompts = parsed.flags.yes || parsed.flags.y;
|
|
12
|
-
|
|
13
|
-
if (parsed.errors.length > 0) {
|
|
14
|
-
for (const err of parsed.errors) {
|
|
15
|
-
console.error(pc.red(`Error: ${err}`));
|
|
16
|
-
}
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (showHelp) {
|
|
21
|
-
console.log('');
|
|
22
|
-
console.log(' create-harness-vibe-coding');
|
|
23
|
-
console.log('');
|
|
24
|
-
console.log(' Usage:');
|
|
25
|
-
console.log(' npx create-harness-vibe-coding@latest [project-name] [target-dir] [flags]');
|
|
26
|
-
console.log('');
|
|
27
|
-
console.log(' Arguments:');
|
|
28
|
-
console.log(' project-name Name for the new project (default: my-vibe-project)');
|
|
29
|
-
console.log(' target-dir Directory to create the project in (default: ./<project-name>)');
|
|
30
|
-
console.log('');
|
|
31
|
-
console.log(' Flags:');
|
|
32
|
-
console.log(' -y, --yes Skip all prompts, use defaults or provided args');
|
|
33
|
-
console.log(' -h, --help Show this help');
|
|
34
|
-
console.log(' --dry-run Print the planned writes without creating files');
|
|
35
|
-
console.log(' --on-conflict <policy> fail, skip, backup, or overwrite (default: fail)');
|
|
36
|
-
console.log(' --with <id,id> Add optional local workflow skills');
|
|
37
|
-
console.log(' --without <id,id> Remove optional workflow skills selected by --preset or --with');
|
|
38
|
-
console.log(' --preset <name> Add a built-in optional workflow preset');
|
|
39
|
-
console.log(' --list-options Print optional workflow skills and presets');
|
|
40
|
-
console.log(' --json Output machine-readable JSON (use with --dry-run for planning)');
|
|
41
|
-
console.log('');
|
|
42
|
-
console.log(' Examples:');
|
|
43
|
-
console.log(' npx create-harness-vibe-coding@latest');
|
|
44
|
-
console.log(' npx create-harness-vibe-coding@latest -y');
|
|
45
|
-
console.log(' npx create-harness-vibe-coding@latest my-project');
|
|
46
|
-
console.log(' npx create-harness-vibe-coding@latest my-project ./dist/my-project -y');
|
|
47
|
-
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --dry-run');
|
|
48
|
-
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --on-conflict skip');
|
|
49
|
-
console.log(' npx create-harness-vibe-coding@latest web ./web -y --with ts-react-frontend,browser-e2e');
|
|
50
|
-
console.log(' npx create-harness-vibe-coding@latest web ./web -y --preset web-app');
|
|
51
|
-
console.log(' npx create-harness-vibe-coding@latest api ./api -y --preset fullstack --without github-pr-review');
|
|
52
|
-
console.log('');
|
|
53
|
-
process.exit(0);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (parsed.flags.listOptions) {
|
|
57
|
-
printOptions();
|
|
58
|
-
process.exit(0);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Positional args (non-flag)
|
|
62
|
-
const positional = parsed.positionals;
|
|
63
|
-
const argName = positional[0];
|
|
64
|
-
const argDir = positional[1];
|
|
65
|
-
const generationOptions = {
|
|
66
|
-
dryRun: Boolean(parsed.flags.dryRun),
|
|
67
|
-
onConflict: parsed.flags.onConflict || 'fail',
|
|
68
|
-
withOptions: parsed.flags.with || [],
|
|
69
|
-
withoutOptions: parsed.flags.without || [],
|
|
70
|
-
preset: parsed.flags.preset,
|
|
71
|
-
json: Boolean(parsed.flags.json),
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const DEFAULT_NAME = 'my-vibe-project';
|
|
75
|
-
|
|
76
|
-
// --json: machine-readable output, no prompts, no decorative output
|
|
77
|
-
if (generationOptions.json) {
|
|
78
|
-
const projectName = argName || DEFAULT_NAME;
|
|
79
|
-
const targetDir = argDir || `./${projectName}`;
|
|
80
|
-
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
81
|
-
printJsonResult(result);
|
|
82
|
-
// printJsonResult exits with 1 on failure; we only reach here on success
|
|
83
|
-
process.exit(0);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
console.log('');
|
|
87
|
-
console.log(pc.magenta('╔══════════════════════════════════════════╗'));
|
|
88
|
-
console.log(pc.magenta('║ create-harness-vibe-coding ║'));
|
|
89
|
-
console.log(pc.magenta('║ 0-1 Product Harness Scaffold ║'));
|
|
90
|
-
console.log(pc.magenta('╚══════════════════════════════════════════╝'));
|
|
91
|
-
console.log('');
|
|
92
|
-
|
|
93
|
-
let projectName, targetDir;
|
|
94
|
-
|
|
95
|
-
// Non-interactive: positionals provided OR -y/--yes flag set
|
|
96
|
-
if (argName || skipPrompts) {
|
|
97
|
-
projectName = argName || DEFAULT_NAME;
|
|
98
|
-
targetDir = argDir || `./${projectName}`;
|
|
99
|
-
|
|
100
|
-
console.log(pc.dim('────────────────────────────────────────────'));
|
|
101
|
-
console.log(` Project ${pc.green(projectName)}`);
|
|
102
|
-
console.log(` Directory ${pc.green(targetDir)}`);
|
|
103
|
-
console.log(` Creates ${pc.cyan('CLAUDE.md, README.md, Harness/
|
|
104
|
-
if (generationOptions.dryRun) {
|
|
105
|
-
console.log(` Mode ${pc.yellow('dry-run')}`);
|
|
106
|
-
}
|
|
107
|
-
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
108
|
-
if (generationOptions.withOptions.length > 0) {
|
|
109
|
-
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
110
|
-
}
|
|
111
|
-
if (generationOptions.withoutOptions.length > 0) {
|
|
112
|
-
console.log(` Without ${pc.cyan(generationOptions.withoutOptions.join(','))}`);
|
|
113
|
-
}
|
|
114
|
-
if (generationOptions.preset) {
|
|
115
|
-
console.log(` Preset ${pc.cyan(generationOptions.preset)}`);
|
|
116
|
-
}
|
|
117
|
-
if (skipPrompts) {
|
|
118
|
-
console.log(` Mode ${pc.dim('non-interactive (-y)')}`);
|
|
119
|
-
}
|
|
120
|
-
console.log(pc.dim('────────────────────────────────────────────'));
|
|
121
|
-
console.log('');
|
|
122
|
-
|
|
123
|
-
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
124
|
-
printResult(result, targetDir);
|
|
125
|
-
} else {
|
|
126
|
-
// Interactive mode
|
|
127
|
-
try {
|
|
128
|
-
projectName = await askProjectName();
|
|
129
|
-
} catch {
|
|
130
|
-
projectName = 'my-vibe-project';
|
|
131
|
-
console.log(pc.dim(` Project: ${projectName} (default)`));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
targetDir = await askTargetDir(projectName);
|
|
136
|
-
} catch {
|
|
137
|
-
targetDir = `./${projectName}`;
|
|
138
|
-
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
console.log('');
|
|
142
|
-
console.log(pc.dim('────────────────────────────────────────────'));
|
|
143
|
-
console.log(` Project ${pc.green(projectName)}`);
|
|
144
|
-
console.log(` Directory ${pc.green(targetDir)}`);
|
|
145
|
-
console.log(` Creates ${pc.cyan('CLAUDE.md, README.md, Harness/
|
|
146
|
-
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
147
|
-
console.log(pc.dim('────────────────────────────────────────────'));
|
|
148
|
-
console.log('');
|
|
149
|
-
|
|
150
|
-
const preview = generate({ projectName, targetDir, ...generationOptions, dryRun: true });
|
|
151
|
-
if (!preview.success) {
|
|
152
|
-
printResult(preview, targetDir);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
console.log(pc.yellow('Planned changes: no files have been written yet.'));
|
|
156
|
-
printSummary(preview.summary);
|
|
157
|
-
printPlan(preview.plan);
|
|
158
|
-
printWarnings(preview);
|
|
159
|
-
console.log('');
|
|
160
|
-
|
|
161
|
-
if (generationOptions.dryRun) {
|
|
162
|
-
process.exit(0);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let proceed = true;
|
|
166
|
-
try {
|
|
167
|
-
proceed = await p.confirm({
|
|
168
|
-
message: 'Confirm generation with this plan?',
|
|
169
|
-
initialValue: true,
|
|
170
|
-
});
|
|
171
|
-
if (p.isCancel(proceed)) proceed = false;
|
|
172
|
-
} catch {
|
|
173
|
-
proceed = true;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (!proceed) {
|
|
177
|
-
p.cancel('Cancelled');
|
|
178
|
-
process.exit(0);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
console.log('');
|
|
182
|
-
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
183
|
-
printResult(result, targetDir);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function printResult(result, targetDir) {
|
|
187
|
-
if (result.success) {
|
|
188
|
-
if (result.dryRun) {
|
|
189
|
-
console.log(pc.yellow('\nDry run: no files or directories were written.'));
|
|
190
|
-
printSummary(result.summary);
|
|
191
|
-
printPlan(result.plan);
|
|
192
|
-
if (result.warnings.length > 0) {
|
|
193
|
-
console.log(pc.yellow('\nWarning(s):'));
|
|
194
|
-
for (const warning of result.warnings) {
|
|
195
|
-
console.log(pc.yellow(` - ${warning}`));
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
console.log('');
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
console.log(pc.green('\nGeneration complete.\n'));
|
|
203
|
-
printSummary(result.summary);
|
|
204
|
-
|
|
205
|
-
printWarnings(result);
|
|
206
|
-
|
|
207
|
-
console.log(pc.bold('Next steps:'));
|
|
208
|
-
console.log(` ${pc.cyan(`cd ${targetDir}`)}`);
|
|
209
|
-
console.log(` ${pc.cyan('claude')} # Start Claude Code`);
|
|
210
|
-
console.log(` Tell Claude: "${pc.yellow('Read Harness/SETUP.md. Bootstrap this project from idea to first vertical slice.')}"`);
|
|
211
|
-
console.log('');
|
|
212
|
-
console.log(pc.dim(' Harness/SETUP.md is temporary. Delete it after initialization.'));
|
|
213
|
-
console.log('');
|
|
214
|
-
|
|
215
|
-
} else {
|
|
216
|
-
console.log(pc.red('\nGeneration failed:'));
|
|
217
|
-
for (const err of result.errors) {
|
|
218
|
-
console.log(pc.red(` - ${err}`));
|
|
219
|
-
}
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function parseArgs(args) {
|
|
225
|
-
const flags = {
|
|
226
|
-
with: [],
|
|
227
|
-
without: [],
|
|
228
|
-
};
|
|
229
|
-
const positionals = [];
|
|
230
|
-
const errors = [];
|
|
231
|
-
|
|
232
|
-
function readValue(flagName, index) {
|
|
233
|
-
const value = args[index + 1];
|
|
234
|
-
if (!value || value.startsWith('-')) {
|
|
235
|
-
errors.push(`${flagName} requires a value`);
|
|
236
|
-
return { value: undefined, nextIndex: index };
|
|
237
|
-
}
|
|
238
|
-
return { value, nextIndex: index + 1 };
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function readEqualsValue(flagName, value) {
|
|
242
|
-
if (!value || value.startsWith('-')) {
|
|
243
|
-
errors.push(`${flagName} requires a value`);
|
|
244
|
-
return undefined;
|
|
245
|
-
}
|
|
246
|
-
return value;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
250
|
-
const arg = args[i];
|
|
251
|
-
|
|
252
|
-
if (arg === '-h') {
|
|
253
|
-
flags.h = true;
|
|
254
|
-
} else if (arg === '--help') {
|
|
255
|
-
flags.help = true;
|
|
256
|
-
} else if (arg === '-y') {
|
|
257
|
-
flags.y = true;
|
|
258
|
-
} else if (arg === '--yes') {
|
|
259
|
-
flags.yes = true;
|
|
260
|
-
} else if (arg === '--dry-run') {
|
|
261
|
-
flags.dryRun = true;
|
|
262
|
-
} else if (arg === '--list-options') {
|
|
263
|
-
flags.listOptions = true;
|
|
264
|
-
} else if (arg === '--json') {
|
|
265
|
-
flags.json = true;
|
|
266
|
-
} else if (arg === '--on-conflict') {
|
|
267
|
-
const parsedValue = readValue('--on-conflict', i);
|
|
268
|
-
flags.onConflict = parsedValue.value;
|
|
269
|
-
i = parsedValue.nextIndex;
|
|
270
|
-
} else if (arg.startsWith('--on-conflict=')) {
|
|
271
|
-
flags.onConflict = readEqualsValue('--on-conflict', arg.slice('--on-conflict='.length));
|
|
272
|
-
} else if (arg === '--with') {
|
|
273
|
-
const parsedValue = readValue('--with', i);
|
|
274
|
-
if (parsedValue.value !== undefined) flags.with.push(parsedValue.value);
|
|
275
|
-
i = parsedValue.nextIndex;
|
|
276
|
-
} else if (arg.startsWith('--with=')) {
|
|
277
|
-
const value = readEqualsValue('--with', arg.slice('--with='.length));
|
|
278
|
-
if (value !== undefined) flags.with.push(value);
|
|
279
|
-
} else if (arg === '--without') {
|
|
280
|
-
const parsedValue = readValue('--without', i);
|
|
281
|
-
if (parsedValue.value !== undefined) flags.without.push(parsedValue.value);
|
|
282
|
-
i = parsedValue.nextIndex;
|
|
283
|
-
} else if (arg.startsWith('--without=')) {
|
|
284
|
-
const value = readEqualsValue('--without', arg.slice('--without='.length));
|
|
285
|
-
if (value !== undefined) flags.without.push(value);
|
|
286
|
-
} else if (arg === '--preset') {
|
|
287
|
-
const parsedValue = readValue('--preset', i);
|
|
288
|
-
flags.preset = parsedValue.value;
|
|
289
|
-
i = parsedValue.nextIndex;
|
|
290
|
-
} else if (arg.startsWith('--preset=')) {
|
|
291
|
-
flags.preset = readEqualsValue('--preset', arg.slice('--preset='.length));
|
|
292
|
-
} else if (arg.startsWith('-')) {
|
|
293
|
-
errors.push(`Unknown flag "${arg}"`);
|
|
294
|
-
} else {
|
|
295
|
-
positionals.push(arg);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return { flags, positionals, errors };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function printOptions() {
|
|
303
|
-
const catalog = getOptionalCatalog();
|
|
304
|
-
|
|
305
|
-
console.log('');
|
|
306
|
-
console.log(pc.bold('Optional workflow skills:'));
|
|
307
|
-
for (const skill of catalog.skills) {
|
|
308
|
-
console.log(` ${pc.cyan(skill.id)} - ${skill.description}`);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
console.log('');
|
|
312
|
-
console.log(pc.bold('Presets:'));
|
|
313
|
-
for (const [name, skills] of Object.entries(catalog.presets)) {
|
|
314
|
-
console.log(` ${pc.cyan(name)} - ${skills.join(', ')}`);
|
|
315
|
-
}
|
|
316
|
-
console.log('');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function printSummary(summary) {
|
|
320
|
-
console.log(` created ${pc.green(summary.created)}`);
|
|
321
|
-
console.log(` skipped ${pc.yellow(summary.skipped)}`);
|
|
322
|
-
console.log(` backed up ${pc.cyan(summary.backedUp)}`);
|
|
323
|
-
console.log(` overwritten ${pc.cyan(summary.overwritten)}`);
|
|
324
|
-
console.log(` conflicts ${summary.conflicts > 0 ? pc.red(summary.conflicts) : pc.dim(summary.conflicts)}`);
|
|
325
|
-
console.log(` directories ${pc.dim(summary.mkdir)}`);
|
|
326
|
-
console.log('');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function printPlan(plan) {
|
|
330
|
-
for (const [label, files] of Object.entries(plan)) {
|
|
331
|
-
if (!files.length) continue;
|
|
332
|
-
console.log(` ${label}:`);
|
|
333
|
-
for (const file of files) {
|
|
334
|
-
console.log(` - ${file}`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function printWarnings(result) {
|
|
340
|
-
if (!result.warnings.length) return;
|
|
341
|
-
|
|
342
|
-
console.log(pc.yellow('\nWarning(s):'));
|
|
343
|
-
for (const warning of result.warnings) {
|
|
344
|
-
console.log(pc.yellow(` - ${warning}`));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function printJsonResult(result) {
|
|
349
|
-
// Remove `created` array from output — it is already in the plan, avoid duplication
|
|
350
|
-
const { created, ...rest } = result;
|
|
351
|
-
console.log(JSON.stringify(rest, null, 2));
|
|
352
|
-
if (!result.success) {
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { askProjectName, askTargetDir } from './prompts.js';
|
|
5
|
+
import { generate, getOptionalCatalog } from './generator.js';
|
|
6
|
+
|
|
7
|
+
// ── CLI flags ──────────────────────────────────────────────
|
|
8
|
+
const raw = process.argv.slice(2);
|
|
9
|
+
const parsed = parseArgs(raw);
|
|
10
|
+
const showHelp = parsed.flags.help || parsed.flags.h;
|
|
11
|
+
const skipPrompts = parsed.flags.yes || parsed.flags.y;
|
|
12
|
+
|
|
13
|
+
if (parsed.errors.length > 0) {
|
|
14
|
+
for (const err of parsed.errors) {
|
|
15
|
+
console.error(pc.red(`Error: ${err}`));
|
|
16
|
+
}
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (showHelp) {
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log(' create-harness-vibe-coding');
|
|
23
|
+
console.log('');
|
|
24
|
+
console.log(' Usage:');
|
|
25
|
+
console.log(' npx create-harness-vibe-coding@latest [project-name] [target-dir] [flags]');
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log(' Arguments:');
|
|
28
|
+
console.log(' project-name Name for the new project (default: my-vibe-project)');
|
|
29
|
+
console.log(' target-dir Directory to create the project in (default: ./<project-name>)');
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(' Flags:');
|
|
32
|
+
console.log(' -y, --yes Skip all prompts, use defaults or provided args');
|
|
33
|
+
console.log(' -h, --help Show this help');
|
|
34
|
+
console.log(' --dry-run Print the planned writes without creating files');
|
|
35
|
+
console.log(' --on-conflict <policy> fail, skip, backup, or overwrite (default: fail)');
|
|
36
|
+
console.log(' --with <id,id> Add optional local workflow skills');
|
|
37
|
+
console.log(' --without <id,id> Remove optional workflow skills selected by --preset or --with');
|
|
38
|
+
console.log(' --preset <name> Add a built-in optional workflow preset');
|
|
39
|
+
console.log(' --list-options Print optional workflow skills and presets');
|
|
40
|
+
console.log(' --json Output machine-readable JSON (use with --dry-run for planning)');
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log(' Examples:');
|
|
43
|
+
console.log(' npx create-harness-vibe-coding@latest');
|
|
44
|
+
console.log(' npx create-harness-vibe-coding@latest -y');
|
|
45
|
+
console.log(' npx create-harness-vibe-coding@latest my-project');
|
|
46
|
+
console.log(' npx create-harness-vibe-coding@latest my-project ./dist/my-project -y');
|
|
47
|
+
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --dry-run');
|
|
48
|
+
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --on-conflict skip');
|
|
49
|
+
console.log(' npx create-harness-vibe-coding@latest web ./web -y --with ts-react-frontend,browser-e2e');
|
|
50
|
+
console.log(' npx create-harness-vibe-coding@latest web ./web -y --preset web-app');
|
|
51
|
+
console.log(' npx create-harness-vibe-coding@latest api ./api -y --preset fullstack --without github-pr-review');
|
|
52
|
+
console.log('');
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (parsed.flags.listOptions) {
|
|
57
|
+
printOptions();
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Positional args (non-flag)
|
|
62
|
+
const positional = parsed.positionals;
|
|
63
|
+
const argName = positional[0];
|
|
64
|
+
const argDir = positional[1];
|
|
65
|
+
const generationOptions = {
|
|
66
|
+
dryRun: Boolean(parsed.flags.dryRun),
|
|
67
|
+
onConflict: parsed.flags.onConflict || 'fail',
|
|
68
|
+
withOptions: parsed.flags.with || [],
|
|
69
|
+
withoutOptions: parsed.flags.without || [],
|
|
70
|
+
preset: parsed.flags.preset,
|
|
71
|
+
json: Boolean(parsed.flags.json),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const DEFAULT_NAME = 'my-vibe-project';
|
|
75
|
+
|
|
76
|
+
// --json: machine-readable output, no prompts, no decorative output
|
|
77
|
+
if (generationOptions.json) {
|
|
78
|
+
const projectName = argName || DEFAULT_NAME;
|
|
79
|
+
const targetDir = argDir || `./${projectName}`;
|
|
80
|
+
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
81
|
+
printJsonResult(result);
|
|
82
|
+
// printJsonResult exits with 1 on failure; we only reach here on success
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(pc.magenta('╔══════════════════════════════════════════╗'));
|
|
88
|
+
console.log(pc.magenta('║ create-harness-vibe-coding ║'));
|
|
89
|
+
console.log(pc.magenta('║ 0-1 Product Harness Scaffold ║'));
|
|
90
|
+
console.log(pc.magenta('╚══════════════════════════════════════════╝'));
|
|
91
|
+
console.log('');
|
|
92
|
+
|
|
93
|
+
let projectName, targetDir;
|
|
94
|
+
|
|
95
|
+
// Non-interactive: positionals provided OR -y/--yes flag set
|
|
96
|
+
if (argName || skipPrompts) {
|
|
97
|
+
projectName = argName || DEFAULT_NAME;
|
|
98
|
+
targetDir = argDir || `./${projectName}`;
|
|
99
|
+
|
|
100
|
+
console.log(pc.dim('────────────────────────────────────────────'));
|
|
101
|
+
console.log(` Project ${pc.green(projectName)}`);
|
|
102
|
+
console.log(` Directory ${pc.green(targetDir)}`);
|
|
103
|
+
console.log(` Creates ${pc.cyan('CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, tests/')}`);
|
|
104
|
+
if (generationOptions.dryRun) {
|
|
105
|
+
console.log(` Mode ${pc.yellow('dry-run')}`);
|
|
106
|
+
}
|
|
107
|
+
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
108
|
+
if (generationOptions.withOptions.length > 0) {
|
|
109
|
+
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
110
|
+
}
|
|
111
|
+
if (generationOptions.withoutOptions.length > 0) {
|
|
112
|
+
console.log(` Without ${pc.cyan(generationOptions.withoutOptions.join(','))}`);
|
|
113
|
+
}
|
|
114
|
+
if (generationOptions.preset) {
|
|
115
|
+
console.log(` Preset ${pc.cyan(generationOptions.preset)}`);
|
|
116
|
+
}
|
|
117
|
+
if (skipPrompts) {
|
|
118
|
+
console.log(` Mode ${pc.dim('non-interactive (-y)')}`);
|
|
119
|
+
}
|
|
120
|
+
console.log(pc.dim('────────────────────────────────────────────'));
|
|
121
|
+
console.log('');
|
|
122
|
+
|
|
123
|
+
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
124
|
+
printResult(result, targetDir);
|
|
125
|
+
} else {
|
|
126
|
+
// Interactive mode
|
|
127
|
+
try {
|
|
128
|
+
projectName = await askProjectName();
|
|
129
|
+
} catch {
|
|
130
|
+
projectName = 'my-vibe-project';
|
|
131
|
+
console.log(pc.dim(` Project: ${projectName} (default)`));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
targetDir = await askTargetDir(projectName);
|
|
136
|
+
} catch {
|
|
137
|
+
targetDir = `./${projectName}`;
|
|
138
|
+
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log(pc.dim('────────────────────────────────────────────'));
|
|
143
|
+
console.log(` Project ${pc.green(projectName)}`);
|
|
144
|
+
console.log(` Directory ${pc.green(targetDir)}`);
|
|
145
|
+
console.log(` Creates ${pc.cyan('CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, tests/')}`);
|
|
146
|
+
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
147
|
+
console.log(pc.dim('────────────────────────────────────────────'));
|
|
148
|
+
console.log('');
|
|
149
|
+
|
|
150
|
+
const preview = generate({ projectName, targetDir, ...generationOptions, dryRun: true });
|
|
151
|
+
if (!preview.success) {
|
|
152
|
+
printResult(preview, targetDir);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(pc.yellow('Planned changes: no files have been written yet.'));
|
|
156
|
+
printSummary(preview.summary);
|
|
157
|
+
printPlan(preview.plan);
|
|
158
|
+
printWarnings(preview);
|
|
159
|
+
console.log('');
|
|
160
|
+
|
|
161
|
+
if (generationOptions.dryRun) {
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let proceed = true;
|
|
166
|
+
try {
|
|
167
|
+
proceed = await p.confirm({
|
|
168
|
+
message: 'Confirm generation with this plan?',
|
|
169
|
+
initialValue: true,
|
|
170
|
+
});
|
|
171
|
+
if (p.isCancel(proceed)) proceed = false;
|
|
172
|
+
} catch {
|
|
173
|
+
proceed = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!proceed) {
|
|
177
|
+
p.cancel('Cancelled');
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log('');
|
|
182
|
+
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
183
|
+
printResult(result, targetDir);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function printResult(result, targetDir) {
|
|
187
|
+
if (result.success) {
|
|
188
|
+
if (result.dryRun) {
|
|
189
|
+
console.log(pc.yellow('\nDry run: no files or directories were written.'));
|
|
190
|
+
printSummary(result.summary);
|
|
191
|
+
printPlan(result.plan);
|
|
192
|
+
if (result.warnings.length > 0) {
|
|
193
|
+
console.log(pc.yellow('\nWarning(s):'));
|
|
194
|
+
for (const warning of result.warnings) {
|
|
195
|
+
console.log(pc.yellow(` - ${warning}`));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
console.log('');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(pc.green('\nGeneration complete.\n'));
|
|
203
|
+
printSummary(result.summary);
|
|
204
|
+
|
|
205
|
+
printWarnings(result);
|
|
206
|
+
|
|
207
|
+
console.log(pc.bold('Next steps:'));
|
|
208
|
+
console.log(` ${pc.cyan(`cd ${targetDir}`)}`);
|
|
209
|
+
console.log(` ${pc.cyan('claude')} # Start Claude Code`);
|
|
210
|
+
console.log(` Tell Claude: "${pc.yellow('Read Harness/SETUP.md. Bootstrap this project from idea to first vertical slice.')}"`);
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(pc.dim(' Harness/SETUP.md is temporary. Delete it after initialization.'));
|
|
213
|
+
console.log('');
|
|
214
|
+
|
|
215
|
+
} else {
|
|
216
|
+
console.log(pc.red('\nGeneration failed:'));
|
|
217
|
+
for (const err of result.errors) {
|
|
218
|
+
console.log(pc.red(` - ${err}`));
|
|
219
|
+
}
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function parseArgs(args) {
|
|
225
|
+
const flags = {
|
|
226
|
+
with: [],
|
|
227
|
+
without: [],
|
|
228
|
+
};
|
|
229
|
+
const positionals = [];
|
|
230
|
+
const errors = [];
|
|
231
|
+
|
|
232
|
+
function readValue(flagName, index) {
|
|
233
|
+
const value = args[index + 1];
|
|
234
|
+
if (!value || value.startsWith('-')) {
|
|
235
|
+
errors.push(`${flagName} requires a value`);
|
|
236
|
+
return { value: undefined, nextIndex: index };
|
|
237
|
+
}
|
|
238
|
+
return { value, nextIndex: index + 1 };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function readEqualsValue(flagName, value) {
|
|
242
|
+
if (!value || value.startsWith('-')) {
|
|
243
|
+
errors.push(`${flagName} requires a value`);
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
250
|
+
const arg = args[i];
|
|
251
|
+
|
|
252
|
+
if (arg === '-h') {
|
|
253
|
+
flags.h = true;
|
|
254
|
+
} else if (arg === '--help') {
|
|
255
|
+
flags.help = true;
|
|
256
|
+
} else if (arg === '-y') {
|
|
257
|
+
flags.y = true;
|
|
258
|
+
} else if (arg === '--yes') {
|
|
259
|
+
flags.yes = true;
|
|
260
|
+
} else if (arg === '--dry-run') {
|
|
261
|
+
flags.dryRun = true;
|
|
262
|
+
} else if (arg === '--list-options') {
|
|
263
|
+
flags.listOptions = true;
|
|
264
|
+
} else if (arg === '--json') {
|
|
265
|
+
flags.json = true;
|
|
266
|
+
} else if (arg === '--on-conflict') {
|
|
267
|
+
const parsedValue = readValue('--on-conflict', i);
|
|
268
|
+
flags.onConflict = parsedValue.value;
|
|
269
|
+
i = parsedValue.nextIndex;
|
|
270
|
+
} else if (arg.startsWith('--on-conflict=')) {
|
|
271
|
+
flags.onConflict = readEqualsValue('--on-conflict', arg.slice('--on-conflict='.length));
|
|
272
|
+
} else if (arg === '--with') {
|
|
273
|
+
const parsedValue = readValue('--with', i);
|
|
274
|
+
if (parsedValue.value !== undefined) flags.with.push(parsedValue.value);
|
|
275
|
+
i = parsedValue.nextIndex;
|
|
276
|
+
} else if (arg.startsWith('--with=')) {
|
|
277
|
+
const value = readEqualsValue('--with', arg.slice('--with='.length));
|
|
278
|
+
if (value !== undefined) flags.with.push(value);
|
|
279
|
+
} else if (arg === '--without') {
|
|
280
|
+
const parsedValue = readValue('--without', i);
|
|
281
|
+
if (parsedValue.value !== undefined) flags.without.push(parsedValue.value);
|
|
282
|
+
i = parsedValue.nextIndex;
|
|
283
|
+
} else if (arg.startsWith('--without=')) {
|
|
284
|
+
const value = readEqualsValue('--without', arg.slice('--without='.length));
|
|
285
|
+
if (value !== undefined) flags.without.push(value);
|
|
286
|
+
} else if (arg === '--preset') {
|
|
287
|
+
const parsedValue = readValue('--preset', i);
|
|
288
|
+
flags.preset = parsedValue.value;
|
|
289
|
+
i = parsedValue.nextIndex;
|
|
290
|
+
} else if (arg.startsWith('--preset=')) {
|
|
291
|
+
flags.preset = readEqualsValue('--preset', arg.slice('--preset='.length));
|
|
292
|
+
} else if (arg.startsWith('-')) {
|
|
293
|
+
errors.push(`Unknown flag "${arg}"`);
|
|
294
|
+
} else {
|
|
295
|
+
positionals.push(arg);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { flags, positionals, errors };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function printOptions() {
|
|
303
|
+
const catalog = getOptionalCatalog();
|
|
304
|
+
|
|
305
|
+
console.log('');
|
|
306
|
+
console.log(pc.bold('Optional workflow skills:'));
|
|
307
|
+
for (const skill of catalog.skills) {
|
|
308
|
+
console.log(` ${pc.cyan(skill.id)} - ${skill.description}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log('');
|
|
312
|
+
console.log(pc.bold('Presets:'));
|
|
313
|
+
for (const [name, skills] of Object.entries(catalog.presets)) {
|
|
314
|
+
console.log(` ${pc.cyan(name)} - ${skills.join(', ')}`);
|
|
315
|
+
}
|
|
316
|
+
console.log('');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function printSummary(summary) {
|
|
320
|
+
console.log(` created ${pc.green(summary.created)}`);
|
|
321
|
+
console.log(` skipped ${pc.yellow(summary.skipped)}`);
|
|
322
|
+
console.log(` backed up ${pc.cyan(summary.backedUp)}`);
|
|
323
|
+
console.log(` overwritten ${pc.cyan(summary.overwritten)}`);
|
|
324
|
+
console.log(` conflicts ${summary.conflicts > 0 ? pc.red(summary.conflicts) : pc.dim(summary.conflicts)}`);
|
|
325
|
+
console.log(` directories ${pc.dim(summary.mkdir)}`);
|
|
326
|
+
console.log('');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function printPlan(plan) {
|
|
330
|
+
for (const [label, files] of Object.entries(plan)) {
|
|
331
|
+
if (!files.length) continue;
|
|
332
|
+
console.log(` ${label}:`);
|
|
333
|
+
for (const file of files) {
|
|
334
|
+
console.log(` - ${file}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function printWarnings(result) {
|
|
340
|
+
if (!result.warnings.length) return;
|
|
341
|
+
|
|
342
|
+
console.log(pc.yellow('\nWarning(s):'));
|
|
343
|
+
for (const warning of result.warnings) {
|
|
344
|
+
console.log(pc.yellow(` - ${warning}`));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function printJsonResult(result) {
|
|
349
|
+
// Remove `created` array from output — it is already in the plan, avoid duplication
|
|
350
|
+
const { created, ...rest } = result;
|
|
351
|
+
console.log(JSON.stringify(rest, null, 2));
|
|
352
|
+
if (!result.success) {
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
}
|