create-harness-vibe-coding 0.7.2 → 0.8.0
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-CN.md +91 -155
- package/README.md +200 -177
- package/bin/create-harness-vibe-coding.js +2 -2
- package/package.json +45 -43
- package/src/generator.js +115 -77
- package/src/index.js +159 -49
- package/src/prompts.js +95 -27
- package/templates/common/.claude/agents/architect-manager.md +45 -45
- package/templates/common/.claude/agents/context-master.md +77 -77
- package/templates/common/.claude/agents/debugger.md +41 -41
- package/templates/common/.claude/agents/explore-manager.md +41 -41
- package/templates/common/.claude/agents/implement-manager.md +49 -49
- package/templates/common/.claude/agents/implementer.md +40 -40
- package/templates/common/.claude/agents/memory-master.md +66 -66
- package/templates/common/.claude/agents/planner.md +34 -34
- package/templates/common/.claude/agents/researcher.md +41 -41
- package/templates/common/.claude/agents/review-manager.md +56 -56
- package/templates/common/.claude/agents/test-writer.md +38 -38
- package/templates/common/.claude/agents/verifier.md +32 -32
- package/templates/common/.claude/rules/ecc/common.md +45 -45
- package/templates/common/.claude/settings.json +68 -68
- package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +36 -31
- package/templates/common/.claude/skills/wf/SKILL.md +36 -0
- package/templates/common/.claude/skills/wf-learn/SKILL.md +13 -28
- package/templates/common/.claude/skills/wf-max/SKILL.md +24 -88
- package/templates/common/.claude/skills/wf-readme/SKILL.md +49 -49
- package/templates/common/.claude/skills/wf-remove/SKILL.md +17 -38
- package/templates/common/.claude/skills/wf-review/SKILL.md +19 -69
- package/templates/common/.claude/skills/wf-update/SKILL.md +19 -61
- package/templates/common/.codex/config.toml +2 -2
- package/templates/common/.codex/hooks.json +37 -37
- package/templates/common/.harness-version +53 -51
- package/templates/common/AGENTS.md +30 -30
- package/templates/common/CLAUDE.md +94 -94
- package/templates/common/{docs/harness → Harness}/PROGRESS.md +17 -17
- package/templates/common/{docs → Harness}/README.md +18 -17
- package/templates/common/{docs/harness → Harness}/WF-MAX.md +223 -223
- package/templates/common/Harness/WF.md +217 -0
- package/templates/common/{docs/harness → Harness}/architecture.md +126 -126
- package/templates/common/{docs/harness → Harness}/dispatch.md +99 -99
- package/templates/common/{docs → Harness}/research/research-results.md +66 -66
- package/templates/common/{docs/harness → Harness}/subagents.md +194 -194
- package/templates/common/{docs → Harness}/tasks/_template/ARTIFACTS.md +3 -3
- package/templates/common/{docs → Harness}/tasks/_template/NOTES.md +3 -3
- package/templates/common/{docs → Harness}/tasks/_template/PLAN.md +40 -40
- package/templates/common/{docs → Harness}/tasks/_template/PROGRESS.md +29 -29
- package/templates/common/MEMORY.md +68 -66
- package/templates/common/README.md +44 -37
- package/templates/common/SETUP.md +41 -34
- package/templates/common/memory/agent-lessons-patterns.md +21 -21
- package/templates/common/memory/tool-usage-reflections.md +21 -21
- package/templates/common/memory/user-corrections-preferences.md +21 -21
- package/templates/common/scripts/scan-clean.mjs +450 -449
- package/templates/common/scripts/validate-harness.mjs +56 -34
- package/templates/common/scripts/wf-mode-hook.mjs +318 -318
- package/templates/common/scripts/wf-remove.mjs +400 -384
- package/templates/common/scripts/wf-statusline.ps1 +38 -38
- package/templates/common/scripts/wf-statusline.sh +48 -48
- package/templates/common/scripts/wf-update-check.mjs +389 -389
- package/templates/optional/catalog.json +63 -29
- package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +194 -194
- package/templates/optional/skills/browser-e2e/{docs → Harness}/workflows/browser-e2e.md +75 -75
- package/templates/optional/skills/github-pr-review/{docs → Harness}/workflows/github-pr-review.md +28 -28
- package/templates/optional/skills/python-backend/{docs → Harness}/workflows/python-backend.md +34 -34
- package/templates/optional/skills/ts-react-frontend/{docs → Harness}/workflows/ts-react-frontend.md +35 -35
- package/templates/optional/skills/ui-ux-review/{docs → Harness}/workflows/ui-ux-review.md +26 -26
- package/templates/common/.claude/commands/wf-learn.md +0 -17
- package/templates/common/.claude/commands/wf-max.md +0 -49
- package/templates/common/.claude/commands/wf-remove.md +0 -23
- package/templates/common/.claude/commands/wf-review.md +0 -25
- package/templates/common/.claude/commands/wf-update.md +0 -17
- package/templates/common/.claude/commands/wf.md +0 -31
- package/templates/common/commands/wf-max.toml +0 -18
- package/templates/common/commands/wf-review.toml +0 -15
- package/templates/common/docs/harness/WF.md +0 -174
- package/templates/optional/skills/browser-e2e/.claude/commands/wf-browser.md +0 -25
- /package/templates/common/{docs/harness → Harness}/agent-workflow.md +0 -0
- /package/templates/common/{docs/harness → Harness}/context-loading.md +0 -0
- /package/templates/common/{docs/harness → Harness}/extension.md +0 -0
- /package/templates/common/{docs/harness → Harness}/lifecycle.md +0 -0
- /package/templates/common/{docs → Harness}/research/PRD.md +0 -0
- /package/templates/common/{docs → Harness}/research/README.md +0 -0
package/src/generator.js
CHANGED
|
@@ -76,18 +76,12 @@ const EMPTY_DIRS = [
|
|
|
76
76
|
export function harnessDest(file) {
|
|
77
77
|
if (file === '.harness-version') return 'Harness/.harness-version';
|
|
78
78
|
if (file === 'SETUP.md') return 'Harness/SETUP.md';
|
|
79
|
-
if (file === 'MEMORY.md') return 'Harness/MEMORY.md';
|
|
80
|
-
if (file.startsWith('scripts/')) return `Harness/${file}`;
|
|
81
|
-
if (file.startsWith('memory/')) return `Harness/${file}`;
|
|
82
|
-
if (file
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (file.startsWith('docs/domain/')) return file.replace(/^docs\/domain\//, 'Harness/domain/');
|
|
86
|
-
if (file.startsWith('docs/features/')) return file.replace(/^docs\/features\//, 'Harness/features/');
|
|
87
|
-
if (file.startsWith('docs/workflows/')) return file.replace(/^docs\/workflows\//, 'Harness/workflows/');
|
|
88
|
-
if (file.startsWith('docs/tasks/')) return file.replace(/^docs\/tasks\//, 'Harness/tasks/');
|
|
89
|
-
return file;
|
|
90
|
-
}
|
|
79
|
+
if (file === 'MEMORY.md') return 'Harness/MEMORY.md';
|
|
80
|
+
if (file.startsWith('scripts/')) return `Harness/${file}`;
|
|
81
|
+
if (file.startsWith('memory/')) return `Harness/${file}`;
|
|
82
|
+
if (file.startsWith('Harness/')) return file;
|
|
83
|
+
return file;
|
|
84
|
+
}
|
|
91
85
|
|
|
92
86
|
/**
|
|
93
87
|
* Replace {{vars}} in file contents.
|
|
@@ -126,27 +120,39 @@ function normalizePath(filePath) {
|
|
|
126
120
|
}
|
|
127
121
|
|
|
128
122
|
export function getOptionalCatalog() {
|
|
129
|
-
if (!fs.existsSync(OPTIONAL_CATALOG)) {
|
|
130
|
-
return { skills: [], presets: {} };
|
|
131
|
-
}
|
|
123
|
+
if (!fs.existsSync(OPTIONAL_CATALOG)) {
|
|
124
|
+
return { skills: [], presets: {}, externalRecommendations: [] };
|
|
125
|
+
}
|
|
132
126
|
|
|
133
127
|
return JSON.parse(fs.readFileSync(OPTIONAL_CATALOG, 'utf-8'));
|
|
134
128
|
}
|
|
135
129
|
|
|
136
|
-
function normalizeOptionList(value) {
|
|
130
|
+
function normalizeOptionList(value) {
|
|
137
131
|
const values = Array.isArray(value) ? value : [value];
|
|
138
132
|
return values
|
|
139
133
|
.filter(Boolean)
|
|
140
134
|
.flatMap(item => String(item).split(','))
|
|
141
135
|
.map(item => item.trim())
|
|
142
136
|
.filter(Boolean);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function externalRecommendations(catalog) {
|
|
140
|
+
return Array.isArray(catalog.externalRecommendations)
|
|
141
|
+
? catalog.externalRecommendations
|
|
142
|
+
: [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveOptionalSelection({
|
|
146
|
+
withOptions = [],
|
|
147
|
+
withoutOptions = [],
|
|
148
|
+
preset = undefined,
|
|
149
|
+
externalOptions = [],
|
|
150
|
+
}) {
|
|
151
|
+
const catalog = getOptionalCatalog();
|
|
152
|
+
const skillsById = new Map(catalog.skills.map(skill => [skill.id, skill]));
|
|
153
|
+
const recommendationsById = new Map(externalRecommendations(catalog).map(item => [item.id, item]));
|
|
154
|
+
const selected = [];
|
|
155
|
+
const errors = [];
|
|
150
156
|
|
|
151
157
|
if (preset) {
|
|
152
158
|
if (!catalog.presets[preset]) {
|
|
@@ -165,37 +171,46 @@ function resolveOptionalSelection({ withOptions = [], withoutOptions = [], prese
|
|
|
165
171
|
errors.push(`Unknown optional skill "${id}". Run --list-options to see available options.`);
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
|
-
for (const id of withoutIds) {
|
|
169
|
-
if (!skillsById.has(id)) {
|
|
170
|
-
errors.push(`Unknown optional skill in --without "${id}". Run --list-options to see available options.`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.map(
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
174
|
+
for (const id of withoutIds) {
|
|
175
|
+
if (!skillsById.has(id)) {
|
|
176
|
+
errors.push(`Unknown optional skill in --without "${id}". Run --list-options to see available options.`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const recommendationIds = [...new Set(normalizeOptionList(externalOptions))];
|
|
181
|
+
for (const id of recommendationIds) {
|
|
182
|
+
if (!recommendationsById.has(id)) {
|
|
183
|
+
errors.push(`Unknown external recommendation "${id}". Run --list-options to see available options.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const withoutSet = new Set(withoutIds);
|
|
188
|
+
const selectedIds = ids.filter(id => !withoutSet.has(id));
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
catalog,
|
|
192
|
+
selectedSkills: errors.length ? [] : selectedIds.map(id => skillsById.get(id)),
|
|
193
|
+
selectedRecommendations: errors.length ? [] : recommendationIds.map(id => recommendationsById.get(id)),
|
|
194
|
+
errors,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function createCoreFileSpecs() {
|
|
199
|
+
const specs = walkFiles(TEMPLATES_DIR)
|
|
200
|
+
.map(file => normalizePath(file))
|
|
201
|
+
.sort()
|
|
202
|
+
.map(file => ({
|
|
203
|
+
dest: harnessDest(file),
|
|
204
|
+
src: path.join(TEMPLATES_DIR, ...file.split('/')),
|
|
205
|
+
type: 'common',
|
|
206
|
+
}));
|
|
207
|
+
|
|
208
|
+
specs.push(...createCodexSkillMirrors(specs));
|
|
209
|
+
specs.push({ dest: 'tests/.gitkeep', src: undefined, type: 'empty' });
|
|
210
|
+
return specs;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function createOptionalFileSpecs(selectedSkills) {
|
|
199
214
|
const specs = [];
|
|
200
215
|
|
|
201
216
|
for (const skill of selectedSkills) {
|
|
@@ -208,12 +223,22 @@ function createOptionalFileSpecs(selectedSkills) {
|
|
|
208
223
|
type: 'optional',
|
|
209
224
|
skillId: skill.id,
|
|
210
225
|
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
specs.push(...createCodexSkillMirrors(specs));
|
|
231
|
+
return specs;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function createCodexSkillMirrors(fileSpecs) {
|
|
235
|
+
return fileSpecs
|
|
236
|
+
.filter(spec => spec.dest.startsWith('.claude/skills/'))
|
|
237
|
+
.map(spec => ({
|
|
238
|
+
...spec,
|
|
239
|
+
dest: spec.dest.replace(/^\.claude\/skills\//, '.agents/skills/'),
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
217
242
|
|
|
218
243
|
function duplicateDests(fileSpecs) {
|
|
219
244
|
const seen = new Set();
|
|
@@ -325,13 +350,13 @@ function nextBackupPath(destPath) {
|
|
|
325
350
|
}
|
|
326
351
|
}
|
|
327
352
|
|
|
328
|
-
function registerOptionalContent(file, content, selectedSkills) {
|
|
353
|
+
function registerOptionalContent(file, content, selectedSkills) {
|
|
329
354
|
if (!selectedSkills.length) return content;
|
|
330
355
|
|
|
331
|
-
if (file === 'Harness/MEMORY.md') {
|
|
332
|
-
const lines = selectedSkills.map(skill => (
|
|
333
|
-
`- [${skill.id}](../.claude/skills/${skill.id}/SKILL.md) - ${skill.description} Workflow: [workflows/${skill.id}.md](workflows/${skill.id}.md)`
|
|
334
|
-
));
|
|
356
|
+
if (file === 'Harness/MEMORY.md') {
|
|
357
|
+
const lines = selectedSkills.map(skill => (
|
|
358
|
+
`- [${skill.id}](../.claude/skills/${skill.id}/SKILL.md) - ${skill.description} Codex mirror: [${skill.id}](../.agents/skills/${skill.id}/SKILL.md). Workflow: [workflows/${skill.id}.md](workflows/${skill.id}.md)`
|
|
359
|
+
));
|
|
335
360
|
return content.replace(
|
|
336
361
|
'Stack-specific skills can be added after the product shape is known.',
|
|
337
362
|
`Installed optional skills:\n\n${lines.join('\n')}\n\nStack-specific skills can be added after the product shape is known.`,
|
|
@@ -345,8 +370,18 @@ function registerOptionalContent(file, content, selectedSkills) {
|
|
|
345
370
|
return `${content.trimEnd()}\n\n## Installed Optional Workflows\n\n${lines.join('\n')}\n`;
|
|
346
371
|
}
|
|
347
372
|
|
|
348
|
-
return content;
|
|
349
|
-
}
|
|
373
|
+
return content;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function registerExternalRecommendations(file, content, selectedRecommendations) {
|
|
377
|
+
if (!selectedRecommendations.length || file !== 'Harness/SETUP.md') return content;
|
|
378
|
+
|
|
379
|
+
const lines = selectedRecommendations.map(item => (
|
|
380
|
+
`- \`${item.id}\` - ${item.description} Recommendation only; not installed by this scaffold. Use the discovery/install hint: ${item.installHint}`
|
|
381
|
+
));
|
|
382
|
+
|
|
383
|
+
return `${content.trimEnd()}\n\n## Selected External Recommendations\n\n${lines.join('\n')}\n`;
|
|
384
|
+
}
|
|
350
385
|
|
|
351
386
|
function registrationWarnings(plan, selectedSkills) {
|
|
352
387
|
const warnings = [];
|
|
@@ -378,10 +413,11 @@ export function generate({
|
|
|
378
413
|
targetDir,
|
|
379
414
|
dryRun = false,
|
|
380
415
|
onConflict = 'fail',
|
|
381
|
-
withOptions = [],
|
|
382
|
-
withoutOptions = [],
|
|
383
|
-
preset = undefined,
|
|
384
|
-
|
|
416
|
+
withOptions = [],
|
|
417
|
+
withoutOptions = [],
|
|
418
|
+
preset = undefined,
|
|
419
|
+
externalOptions = [],
|
|
420
|
+
}) {
|
|
385
421
|
const created = [];
|
|
386
422
|
const errors = [];
|
|
387
423
|
const warnings = [];
|
|
@@ -394,7 +430,7 @@ export function generate({
|
|
|
394
430
|
generatedTimestamp: new Date().toISOString(),
|
|
395
431
|
};
|
|
396
432
|
|
|
397
|
-
const optional = resolveOptionalSelection({ withOptions, withoutOptions, preset });
|
|
433
|
+
const optional = resolveOptionalSelection({ withOptions, withoutOptions, preset, externalOptions });
|
|
398
434
|
const fileSpecs = [
|
|
399
435
|
...createCoreFileSpecs(),
|
|
400
436
|
...createOptionalFileSpecs(optional.selectedSkills),
|
|
@@ -500,10 +536,11 @@ export function generate({
|
|
|
500
536
|
}
|
|
501
537
|
const spec = specsByDest.get(file);
|
|
502
538
|
const destPath = path.join(resolvedDir, ...file.split('/'));
|
|
503
|
-
let content = spec.type === 'empty'
|
|
504
|
-
? ''
|
|
505
|
-
: renderTemplate(spec.src, vars);
|
|
506
|
-
content = registerOptionalContent(file, content, optional.selectedSkills);
|
|
539
|
+
let content = spec.type === 'empty'
|
|
540
|
+
? ''
|
|
541
|
+
: renderTemplate(spec.src, vars);
|
|
542
|
+
content = registerOptionalContent(file, content, optional.selectedSkills);
|
|
543
|
+
content = registerExternalRecommendations(file, content, optional.selectedRecommendations);
|
|
507
544
|
|
|
508
545
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
509
546
|
fs.writeFileSync(destPath, content, 'utf-8');
|
|
@@ -521,7 +558,8 @@ export function generate({
|
|
|
521
558
|
}
|
|
522
559
|
parsed.generator = vars.generatorVersion;
|
|
523
560
|
parsed.generated = vars.generatedTimestamp;
|
|
524
|
-
parsed.options = optional.selectedSkills.map(s => s.id);
|
|
561
|
+
parsed.options = optional.selectedSkills.map(s => s.id);
|
|
562
|
+
parsed.externalRecommendations = optional.selectedRecommendations.map(item => item.id);
|
|
525
563
|
parsed.autoCheck = true;
|
|
526
564
|
parsed.checksums = computeChecksums(generatedFiles);
|
|
527
565
|
// Build sources map from fileSpecs (all writable files, not just generatedFiles)
|
package/src/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import * as p from '@clack/prompts';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import pc from 'picocolors';
|
|
6
|
+
import { askConflictPolicy, askOptionalSelections, askProjectName, askTargetDir } from './prompts.js';
|
|
7
|
+
import { generate, getOptionalCatalog } from './generator.js';
|
|
6
8
|
|
|
7
9
|
// ── CLI flags ──────────────────────────────────────────────
|
|
8
10
|
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;
|
|
11
|
+
const parsed = parseArgs(raw);
|
|
12
|
+
const showHelp = parsed.flags.help || parsed.flags.h;
|
|
13
|
+
const skipPrompts = parsed.flags.yes || parsed.flags.y;
|
|
14
|
+
const conflictPolicyProvided = parsed.flags.onConflict !== undefined;
|
|
12
15
|
|
|
13
16
|
if (parsed.errors.length > 0) {
|
|
14
17
|
for (const err of parsed.errors) {
|
|
@@ -33,11 +36,14 @@ if (showHelp) {
|
|
|
33
36
|
console.log(' -h, --help Show this help');
|
|
34
37
|
console.log(' --dry-run Print the planned writes without creating files');
|
|
35
38
|
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(' --
|
|
39
|
-
console.log(' --
|
|
40
|
-
console.log(' --
|
|
39
|
+
console.log(' --with <id,id> Add optional local workflow skills');
|
|
40
|
+
console.log(' --without <id,id> Remove optional workflow skills selected by --preset or --with');
|
|
41
|
+
console.log(' --recommend <id,id> Record recommendation-only external capabilities');
|
|
42
|
+
console.log(' --preset <name> Add a built-in optional workflow preset');
|
|
43
|
+
console.log(' --list-options Print optional workflow skills and presets');
|
|
44
|
+
console.log(' --json Output machine-readable JSON (use with --dry-run for planning)');
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log(' Interactive mode offers checkbox selection for optional workflows and external recommendations.');
|
|
41
47
|
console.log('');
|
|
42
48
|
console.log(' Examples:');
|
|
43
49
|
console.log(' npx create-harness-vibe-coding@latest');
|
|
@@ -66,10 +72,11 @@ const generationOptions = {
|
|
|
66
72
|
dryRun: Boolean(parsed.flags.dryRun),
|
|
67
73
|
onConflict: parsed.flags.onConflict || 'fail',
|
|
68
74
|
withOptions: parsed.flags.with || [],
|
|
69
|
-
withoutOptions: parsed.flags.without || [],
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
withoutOptions: parsed.flags.without || [],
|
|
76
|
+
externalOptions: parsed.flags.recommend || [],
|
|
77
|
+
preset: parsed.flags.preset,
|
|
78
|
+
json: Boolean(parsed.flags.json),
|
|
79
|
+
};
|
|
73
80
|
|
|
74
81
|
const DEFAULT_NAME = 'my-vibe-project';
|
|
75
82
|
|
|
@@ -99,8 +106,8 @@ if (argName || skipPrompts) {
|
|
|
99
106
|
|
|
100
107
|
console.log(pc.dim('────────────────────────────────────────────'));
|
|
101
108
|
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/')}`);
|
|
109
|
+
console.log(` Directory ${pc.green(targetDir)}`);
|
|
110
|
+
console.log(` Creates ${pc.cyan('CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, tests/')}`);
|
|
104
111
|
if (generationOptions.dryRun) {
|
|
105
112
|
console.log(` Mode ${pc.yellow('dry-run')}`);
|
|
106
113
|
}
|
|
@@ -108,9 +115,12 @@ if (argName || skipPrompts) {
|
|
|
108
115
|
if (generationOptions.withOptions.length > 0) {
|
|
109
116
|
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
110
117
|
}
|
|
111
|
-
if (generationOptions.withoutOptions.length > 0) {
|
|
112
|
-
console.log(` Without ${pc.cyan(generationOptions.withoutOptions.join(','))}`);
|
|
113
|
-
}
|
|
118
|
+
if (generationOptions.withoutOptions.length > 0) {
|
|
119
|
+
console.log(` Without ${pc.cyan(generationOptions.withoutOptions.join(','))}`);
|
|
120
|
+
}
|
|
121
|
+
if (generationOptions.externalOptions.length > 0) {
|
|
122
|
+
console.log(` Recommend ${pc.cyan(generationOptions.externalOptions.join(','))}`);
|
|
123
|
+
}
|
|
114
124
|
if (generationOptions.preset) {
|
|
115
125
|
console.log(` Preset ${pc.cyan(generationOptions.preset)}`);
|
|
116
126
|
}
|
|
@@ -131,19 +141,54 @@ if (argName || skipPrompts) {
|
|
|
131
141
|
console.log(pc.dim(` Project: ${projectName} (default)`));
|
|
132
142
|
}
|
|
133
143
|
|
|
134
|
-
try {
|
|
135
|
-
targetDir = await askTargetDir(projectName);
|
|
136
|
-
} catch {
|
|
144
|
+
try {
|
|
145
|
+
targetDir = await askTargetDir(projectName);
|
|
146
|
+
} catch {
|
|
137
147
|
targetDir = `./${projectName}`;
|
|
138
148
|
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
139
|
-
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const scan = scanTarget(targetDir);
|
|
152
|
+
printScan(scan);
|
|
153
|
+
|
|
154
|
+
if (!generationOptions.dryRun && !conflictPolicyProvided && scan.needsConflictPolicy) {
|
|
155
|
+
try {
|
|
156
|
+
generationOptions.onConflict = await askConflictPolicy(scan);
|
|
157
|
+
} catch {
|
|
158
|
+
generationOptions.onConflict = 'skip';
|
|
159
|
+
console.log(pc.dim(' Conflicts: skip (preserve existing files default)'));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const optionFlagsProvided = generationOptions.withOptions.length > 0
|
|
164
|
+
|| generationOptions.withoutOptions.length > 0
|
|
165
|
+
|| generationOptions.externalOptions.length > 0
|
|
166
|
+
|| generationOptions.preset;
|
|
167
|
+
|
|
168
|
+
if (!optionFlagsProvided) {
|
|
169
|
+
try {
|
|
170
|
+
const selected = await askOptionalSelections(getOptionalCatalog());
|
|
171
|
+
generationOptions.withOptions = selected.withOptions;
|
|
172
|
+
generationOptions.externalOptions = selected.externalOptions;
|
|
173
|
+
} catch {
|
|
174
|
+
generationOptions.withOptions = [];
|
|
175
|
+
generationOptions.externalOptions = [];
|
|
176
|
+
console.log(pc.dim(' Optional: none (default)'));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
140
179
|
|
|
141
180
|
console.log('');
|
|
142
181
|
console.log(pc.dim('────────────────────────────────────────────'));
|
|
143
182
|
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)}`);
|
|
183
|
+
console.log(` Directory ${pc.green(targetDir)}`);
|
|
184
|
+
console.log(` Creates ${pc.cyan('CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, tests/')}`);
|
|
185
|
+
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
186
|
+
if (generationOptions.withOptions.length > 0) {
|
|
187
|
+
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
188
|
+
}
|
|
189
|
+
if (generationOptions.externalOptions.length > 0) {
|
|
190
|
+
console.log(` Recommend ${pc.cyan(generationOptions.externalOptions.join(','))}`);
|
|
191
|
+
}
|
|
147
192
|
console.log(pc.dim('────────────────────────────────────────────'));
|
|
148
193
|
console.log('');
|
|
149
194
|
|
|
@@ -204,10 +249,11 @@ function printResult(result, targetDir) {
|
|
|
204
249
|
|
|
205
250
|
printWarnings(result);
|
|
206
251
|
|
|
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(`
|
|
252
|
+
console.log(pc.bold('Next steps:'));
|
|
253
|
+
console.log(` ${pc.cyan(`cd ${targetDir}`)}`);
|
|
254
|
+
console.log(` ${pc.cyan('claude')} # Start Claude Code`);
|
|
255
|
+
console.log(` ${pc.cyan('codex')} # Or start Codex`);
|
|
256
|
+
console.log(` Tell your agent: "${pc.yellow('Read Harness/SETUP.md. Bootstrap this project from idea to first vertical slice.')}"`);
|
|
211
257
|
console.log('');
|
|
212
258
|
console.log(pc.dim(' Harness/SETUP.md is temporary. Delete it after initialization.'));
|
|
213
259
|
console.log('');
|
|
@@ -259,10 +305,10 @@ function parseArgs(args) {
|
|
|
259
305
|
flags.yes = true;
|
|
260
306
|
} else if (arg === '--dry-run') {
|
|
261
307
|
flags.dryRun = true;
|
|
262
|
-
} else if (arg === '--list-options') {
|
|
263
|
-
flags.listOptions = true;
|
|
264
|
-
} else if (arg === '--json') {
|
|
265
|
-
flags.json = true;
|
|
308
|
+
} else if (arg === '--list-options') {
|
|
309
|
+
flags.listOptions = true;
|
|
310
|
+
} else if (arg === '--json') {
|
|
311
|
+
flags.json = true;
|
|
266
312
|
} else if (arg === '--on-conflict') {
|
|
267
313
|
const parsedValue = readValue('--on-conflict', i);
|
|
268
314
|
flags.onConflict = parsedValue.value;
|
|
@@ -281,9 +327,22 @@ function parseArgs(args) {
|
|
|
281
327
|
if (parsedValue.value !== undefined) flags.without.push(parsedValue.value);
|
|
282
328
|
i = parsedValue.nextIndex;
|
|
283
329
|
} 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 === '--
|
|
330
|
+
const value = readEqualsValue('--without', arg.slice('--without='.length));
|
|
331
|
+
if (value !== undefined) flags.without.push(value);
|
|
332
|
+
} else if (arg === '--recommend') {
|
|
333
|
+
const parsedValue = readValue('--recommend', i);
|
|
334
|
+
if (parsedValue.value !== undefined) {
|
|
335
|
+
if (!flags.recommend) flags.recommend = [];
|
|
336
|
+
flags.recommend.push(parsedValue.value);
|
|
337
|
+
}
|
|
338
|
+
i = parsedValue.nextIndex;
|
|
339
|
+
} else if (arg.startsWith('--recommend=')) {
|
|
340
|
+
const value = readEqualsValue('--recommend', arg.slice('--recommend='.length));
|
|
341
|
+
if (value !== undefined) {
|
|
342
|
+
if (!flags.recommend) flags.recommend = [];
|
|
343
|
+
flags.recommend.push(value);
|
|
344
|
+
}
|
|
345
|
+
} else if (arg === '--preset') {
|
|
287
346
|
const parsedValue = readValue('--preset', i);
|
|
288
347
|
flags.preset = parsedValue.value;
|
|
289
348
|
i = parsedValue.nextIndex;
|
|
@@ -308,13 +367,21 @@ function printOptions() {
|
|
|
308
367
|
console.log(` ${pc.cyan(skill.id)} - ${skill.description}`);
|
|
309
368
|
}
|
|
310
369
|
|
|
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
|
-
|
|
317
|
-
|
|
370
|
+
console.log('');
|
|
371
|
+
console.log(pc.bold('Presets:'));
|
|
372
|
+
for (const [name, skills] of Object.entries(catalog.presets)) {
|
|
373
|
+
console.log(` ${pc.cyan(name)} - ${skills.join(', ')}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (catalog.externalRecommendations?.length) {
|
|
377
|
+
console.log('');
|
|
378
|
+
console.log(pc.bold('External recommendations:'));
|
|
379
|
+
for (const item of catalog.externalRecommendations) {
|
|
380
|
+
console.log(` ${pc.cyan(item.id)} - ${item.description} (${item.installMode})`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
console.log('');
|
|
384
|
+
}
|
|
318
385
|
|
|
319
386
|
function printSummary(summary) {
|
|
320
387
|
console.log(` created ${pc.green(summary.created)}`);
|
|
@@ -345,11 +412,54 @@ function printWarnings(result) {
|
|
|
345
412
|
}
|
|
346
413
|
}
|
|
347
414
|
|
|
348
|
-
function printJsonResult(result) {
|
|
415
|
+
function printJsonResult(result) {
|
|
349
416
|
// Remove `created` array from output — it is already in the plan, avoid duplication
|
|
350
417
|
const { created, ...rest } = result;
|
|
351
418
|
console.log(JSON.stringify(rest, null, 2));
|
|
352
419
|
if (!result.success) {
|
|
353
420
|
process.exit(1);
|
|
354
421
|
}
|
|
355
|
-
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function scanTarget(targetDir) {
|
|
425
|
+
const resolvedDir = path.resolve(process.cwd(), targetDir);
|
|
426
|
+
const exists = fs.existsSync(resolvedDir);
|
|
427
|
+
const isDirectory = exists && fs.statSync(resolvedDir).isDirectory();
|
|
428
|
+
const entries = isDirectory
|
|
429
|
+
? fs.readdirSync(resolvedDir)
|
|
430
|
+
: [];
|
|
431
|
+
const hasHarness = isDirectory && fs.existsSync(path.join(resolvedDir, 'Harness'));
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
resolvedDir,
|
|
435
|
+
exists,
|
|
436
|
+
isDirectory,
|
|
437
|
+
entries,
|
|
438
|
+
hasHarness,
|
|
439
|
+
hasClaude: isDirectory && fs.existsSync(path.join(resolvedDir, 'CLAUDE.md')),
|
|
440
|
+
hasAgents: isDirectory && fs.existsSync(path.join(resolvedDir, 'AGENTS.md')),
|
|
441
|
+
hasAgentSkills: isDirectory && fs.existsSync(path.join(resolvedDir, '.agents')),
|
|
442
|
+
hasCodex: isDirectory && fs.existsSync(path.join(resolvedDir, '.codex')),
|
|
443
|
+
hasDocs: isDirectory && fs.existsSync(path.join(resolvedDir, 'docs')),
|
|
444
|
+
needsConflictPolicy: exists && (!isDirectory || entries.length > 0 || hasHarness),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function printScan(scan) {
|
|
449
|
+
if (!scan.exists) return;
|
|
450
|
+
|
|
451
|
+
console.log('');
|
|
452
|
+
console.log(pc.bold('Root scan:'));
|
|
453
|
+
console.log(` Directory ${pc.dim(scan.resolvedDir)}`);
|
|
454
|
+
if (!scan.isDirectory) {
|
|
455
|
+
console.log(` Conflict ${pc.yellow('target path exists and is not a directory')}`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
console.log(` Entries ${pc.cyan(scan.entries.length)}`);
|
|
459
|
+
if (scan.hasHarness) console.log(` Harness ${pc.yellow('exists')}`);
|
|
460
|
+
if (scan.hasClaude) console.log(` CLAUDE.md ${pc.yellow('exists')}`);
|
|
461
|
+
if (scan.hasAgents) console.log(` AGENTS.md ${pc.yellow('exists')}`);
|
|
462
|
+
if (scan.hasAgentSkills) console.log(` .agents/ ${pc.yellow('exists')}`);
|
|
463
|
+
if (scan.hasCodex) console.log(` .codex/ ${pc.yellow('exists')}`);
|
|
464
|
+
if (scan.hasDocs) console.log(` docs/ ${pc.dim('project-owned; Harness stays in Harness/')}`);
|
|
465
|
+
}
|