create-harness-vibe-coding 0.8.17 → 0.8.18
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/CHANGELOG.md +9 -0
- package/README-CN.md +2 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/generator.js +613 -97
- package/src/index.js +173 -42
- package/src/prompts.js +18 -0
- package/templates/common/.claude/commands/wf-command-create.md +58 -0
- package/templates/common/.claude/commands/wf-help.md +4 -0
- package/templates/common/.claude/commands/wf-task-archive.md +26 -0
- package/templates/common/.claude/commands/wf-task-list.md +24 -0
- package/templates/common/.claude/commands/wf-task-record.md +24 -0
- package/templates/common/.claude/rules/ecc/common.md +1 -1
- package/templates/common/.claude/skills/wf-agents-docs/SKILL.md +15 -30
- package/templates/common/.claude/skills/wf-command-create/SKILL.md +37 -0
- package/templates/common/.claude/skills/wf-max/SKILL.md +1 -1
- package/templates/common/.claude/skills/wf-review/SKILL.md +29 -2
- package/templates/common/.claude/skills/wf-task-archive/SKILL.md +28 -0
- package/templates/common/.claude/skills/wf-task-list/SKILL.md +28 -0
- package/templates/common/.claude/skills/wf-task-record/SKILL.md +28 -0
- package/templates/common/.harness-version +66 -32
- package/templates/common/.opencode/commands/wf-command-create.md +61 -0
- package/templates/common/.opencode/commands/wf-help.md +4 -0
- package/templates/common/.opencode/commands/wf-task-archive.md +29 -0
- package/templates/common/.opencode/commands/wf-task-list.md +27 -0
- package/templates/common/.opencode/commands/wf-task-record.md +27 -0
- package/templates/common/CLAUDE.md +8 -6
- package/templates/common/Harness/MEMORY.md +9 -0
- package/templates/common/Harness/README.md +17 -36
- package/templates/common/Harness/ownership.manifest.json +87 -2
- package/templates/common/Harness/scripts/task-state.mjs +395 -5
- package/templates/common/Harness/scripts/validate-harness.mjs +411 -46
- package/templates/common/Harness/scripts/wf-remove.mjs +34 -2
- package/templates/common/Harness/specs/guides/SETUP.md +8 -0
- package/templates/common/Harness/specs/protocols/MEMORY_PROTOCOL.md +15 -0
- package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +9 -3
- package/templates/common/Harness/specs/runtime/command-surface.json +215 -0
- package/templates/common/Harness/specs/runtime/subagents.md +6 -0
- package/templates/common/Harness/specs/workflows/WF-MAX.md +5 -0
- package/templates/common/Harness/specs/workflows/WF-STATE.md +66 -0
- package/templates/common/Harness/tasks/_template/STATE.json +6 -0
package/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import pc from 'picocolors';
|
|
7
|
-
import { askConflictPolicy, askOptionalSelections, askProjectName, askTargetDir } from './prompts.js';
|
|
7
|
+
import { askConflictPolicy, askInstallScope, askOptionalSelections, askProjectName, askTargetDir } from './prompts.js';
|
|
8
8
|
import { generate, getOptionalCatalog } from './generator.js';
|
|
9
9
|
|
|
10
10
|
const UPDATE_SUCCESS_STATUSES = new Set(['up-to-date', 'update-available', 'partial-update']);
|
|
@@ -45,6 +45,9 @@ if (showHelp) {
|
|
|
45
45
|
console.log(' --without <id,id> Remove optional workflow skills selected by --preset or --with');
|
|
46
46
|
console.log(' --recommend <id,id> Record recommendation-only external capabilities');
|
|
47
47
|
console.log(' --preset <name> Add a built-in optional workflow preset');
|
|
48
|
+
console.log(' --install-scope <scope> project or global (default: project)');
|
|
49
|
+
console.log(' --global-dir <dir> Global Harness runtime directory for --install-scope global');
|
|
50
|
+
console.log(' --host-global-dir <dir> Base directory for Claude/Codex/OpenCode global copies');
|
|
48
51
|
console.log(' --list-options Print optional workflow skills and presets');
|
|
49
52
|
console.log(' --json Output machine-readable JSON (use with --dry-run for planning)');
|
|
50
53
|
console.log('');
|
|
@@ -58,8 +61,9 @@ if (showHelp) {
|
|
|
58
61
|
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --dry-run');
|
|
59
62
|
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --on-conflict skip');
|
|
60
63
|
console.log(' npx create-harness-vibe-coding@latest web ./web -y --with ts-react-frontend,ui-ux-review');
|
|
61
|
-
console.log(' npx create-harness-vibe-coding@latest web ./web -y --preset web-app');
|
|
62
|
-
console.log(' npx create-harness-vibe-coding@latest api ./api -y --preset fullstack --without github-pr-review');
|
|
64
|
+
console.log(' npx create-harness-vibe-coding@latest web ./web -y --preset web-app');
|
|
65
|
+
console.log(' npx create-harness-vibe-coding@latest api ./api -y --preset fullstack --without github-pr-review');
|
|
66
|
+
console.log(' npx create-harness-vibe-coding@latest app ./app -y --install-scope global');
|
|
63
67
|
console.log('');
|
|
64
68
|
process.exit(0);
|
|
65
69
|
}
|
|
@@ -80,6 +84,9 @@ const generationOptions = {
|
|
|
80
84
|
withoutOptions: parsed.flags.without || [],
|
|
81
85
|
externalOptions: parsed.flags.recommend || [],
|
|
82
86
|
preset: parsed.flags.preset,
|
|
87
|
+
installScope: parsed.flags.installScope || 'project',
|
|
88
|
+
globalDir: parsed.flags.globalDir,
|
|
89
|
+
hostGlobalDir: parsed.flags.hostGlobalDir,
|
|
83
90
|
json: Boolean(parsed.flags.json),
|
|
84
91
|
};
|
|
85
92
|
|
|
@@ -137,9 +144,19 @@ if (argName || skipPrompts) {
|
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
console.log(pc.dim('────────────────────────────────────────────'));
|
|
140
|
-
console.log(` Project ${pc.green(projectName)}`);
|
|
147
|
+
console.log(` Project ${pc.green(projectName)}`);
|
|
141
148
|
console.log(` Directory ${pc.green(targetDir)}`);
|
|
142
|
-
console.log(`
|
|
149
|
+
console.log(` Scope ${pc.green(generationOptions.installScope)}`);
|
|
150
|
+
if (generationOptions.globalDir) {
|
|
151
|
+
console.log(` Global dir ${pc.green(generationOptions.globalDir)}`);
|
|
152
|
+
}
|
|
153
|
+
if (generationOptions.hostGlobalDir) {
|
|
154
|
+
console.log(` Host dir ${pc.green(generationOptions.hostGlobalDir)}`);
|
|
155
|
+
}
|
|
156
|
+
const creates = generationOptions.installScope === 'global'
|
|
157
|
+
? 'project bridge/state/settings + global runtime + Claude/Codex/OpenCode host copies'
|
|
158
|
+
: 'CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, .opencode/, opencode.json, tests/';
|
|
159
|
+
console.log(` Creates ${pc.cyan(creates)}`);
|
|
143
160
|
if (generationOptions.dryRun) {
|
|
144
161
|
console.log(` Mode ${pc.yellow('dry-run')}`);
|
|
145
162
|
}
|
|
@@ -176,8 +193,15 @@ if (argName || skipPrompts) {
|
|
|
176
193
|
try {
|
|
177
194
|
targetDir = await askTargetDir(projectName);
|
|
178
195
|
} catch {
|
|
179
|
-
targetDir = `./${projectName}`;
|
|
180
|
-
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
196
|
+
targetDir = `./${projectName}`;
|
|
197
|
+
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
generationOptions.installScope = await askInstallScope();
|
|
202
|
+
} catch {
|
|
203
|
+
generationOptions.installScope = 'project';
|
|
204
|
+
console.log(pc.dim(' Scope: project (default)'));
|
|
181
205
|
}
|
|
182
206
|
|
|
183
207
|
const scan = scanTarget(targetDir);
|
|
@@ -219,9 +243,19 @@ if (argName || skipPrompts) {
|
|
|
219
243
|
|
|
220
244
|
console.log('');
|
|
221
245
|
console.log(pc.dim('────────────────────────────────────────────'));
|
|
222
|
-
console.log(` Project ${pc.green(projectName)}`);
|
|
246
|
+
console.log(` Project ${pc.green(projectName)}`);
|
|
223
247
|
console.log(` Directory ${pc.green(targetDir)}`);
|
|
224
|
-
console.log(`
|
|
248
|
+
console.log(` Scope ${pc.green(generationOptions.installScope)}`);
|
|
249
|
+
if (generationOptions.globalDir) {
|
|
250
|
+
console.log(` Global dir ${pc.green(generationOptions.globalDir)}`);
|
|
251
|
+
}
|
|
252
|
+
if (generationOptions.hostGlobalDir) {
|
|
253
|
+
console.log(` Host dir ${pc.green(generationOptions.hostGlobalDir)}`);
|
|
254
|
+
}
|
|
255
|
+
const creates = generationOptions.installScope === 'global'
|
|
256
|
+
? 'project bridge/state + global runtime + Claude/Codex/OpenCode host copies'
|
|
257
|
+
: 'CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, .opencode/, opencode.json, tests/';
|
|
258
|
+
console.log(` Creates ${pc.cyan(creates)}`);
|
|
225
259
|
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
226
260
|
if (generationOptions.withOptions.length > 0) {
|
|
227
261
|
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
@@ -237,11 +271,19 @@ if (argName || skipPrompts) {
|
|
|
237
271
|
printResult(preview, targetDir);
|
|
238
272
|
}
|
|
239
273
|
|
|
240
|
-
console.log(pc.yellow('Planned changes: no files have been written yet.'));
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
274
|
+
console.log(pc.yellow('Planned changes: no files have been written yet.'));
|
|
275
|
+
console.log(pc.bold('Project plan:'));
|
|
276
|
+
printSummary(preview.summary);
|
|
277
|
+
printPlan(preview.plan);
|
|
278
|
+
if (preview.globalPlan) {
|
|
279
|
+
console.log(pc.bold('\nGlobal runtime plan:'));
|
|
280
|
+
console.log(` Directory ${pc.cyan(preview.globalDir)}`);
|
|
281
|
+
printSummary(preview.globalSummary);
|
|
282
|
+
printPlan(preview.globalPlan);
|
|
283
|
+
}
|
|
284
|
+
printHostPlans(preview.hostPlans, preview.hostSummary);
|
|
285
|
+
printWarnings(preview);
|
|
286
|
+
console.log('');
|
|
245
287
|
|
|
246
288
|
if (generationOptions.dryRun) {
|
|
247
289
|
process.exit(0);
|
|
@@ -270,11 +312,19 @@ if (argName || skipPrompts) {
|
|
|
270
312
|
|
|
271
313
|
function printResult(result, targetDir) {
|
|
272
314
|
if (result.success) {
|
|
273
|
-
if (result.dryRun) {
|
|
274
|
-
console.log(pc.yellow('\nDry run: no files or directories were written.'));
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
315
|
+
if (result.dryRun) {
|
|
316
|
+
console.log(pc.yellow('\nDry run: no files or directories were written.'));
|
|
317
|
+
console.log(pc.bold('Project plan:'));
|
|
318
|
+
printSummary(result.summary);
|
|
319
|
+
printPlan(result.plan);
|
|
320
|
+
if (result.globalPlan) {
|
|
321
|
+
console.log(pc.bold('\nGlobal runtime plan:'));
|
|
322
|
+
console.log(` Directory ${pc.cyan(result.globalDir)}`);
|
|
323
|
+
printSummary(result.globalSummary);
|
|
324
|
+
printPlan(result.globalPlan);
|
|
325
|
+
}
|
|
326
|
+
printHostPlans(result.hostPlans, result.hostSummary);
|
|
327
|
+
if (result.warnings.length > 0) {
|
|
278
328
|
console.log(pc.yellow('\nWarning(s):'));
|
|
279
329
|
for (const warning of result.warnings) {
|
|
280
330
|
console.log(pc.yellow(` - ${warning}`));
|
|
@@ -283,11 +333,18 @@ function printResult(result, targetDir) {
|
|
|
283
333
|
console.log('');
|
|
284
334
|
return;
|
|
285
335
|
}
|
|
286
|
-
|
|
287
|
-
console.log(pc.green('\nGeneration complete.\n'));
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
336
|
+
|
|
337
|
+
console.log(pc.green('\nGeneration complete.\n'));
|
|
338
|
+
console.log(pc.bold('Project install:'));
|
|
339
|
+
printSummary(result.summary);
|
|
340
|
+
if (result.globalSummary) {
|
|
341
|
+
console.log(pc.bold('Global runtime:'));
|
|
342
|
+
console.log(` Directory ${pc.cyan(result.globalDir)}`);
|
|
343
|
+
printSummary(result.globalSummary);
|
|
344
|
+
}
|
|
345
|
+
printHostPlans(result.hostPlans, result.hostSummary, { summaryOnly: true });
|
|
346
|
+
|
|
347
|
+
printWarnings(result);
|
|
291
348
|
|
|
292
349
|
console.log(pc.bold('Next steps:'));
|
|
293
350
|
console.log(` ${pc.cyan(`cd ${targetDir}`)}`);
|
|
@@ -384,13 +441,31 @@ function parseArgs(args) {
|
|
|
384
441
|
flags.recommend.push(value);
|
|
385
442
|
}
|
|
386
443
|
} else if (arg === '--preset') {
|
|
387
|
-
const parsedValue = readValue('--preset', i);
|
|
388
|
-
flags.preset = parsedValue.value;
|
|
389
|
-
i = parsedValue.nextIndex;
|
|
390
|
-
} else if (arg.startsWith('--preset=')) {
|
|
391
|
-
flags.preset = readEqualsValue('--preset', arg.slice('--preset='.length));
|
|
392
|
-
} else if (arg
|
|
393
|
-
|
|
444
|
+
const parsedValue = readValue('--preset', i);
|
|
445
|
+
flags.preset = parsedValue.value;
|
|
446
|
+
i = parsedValue.nextIndex;
|
|
447
|
+
} else if (arg.startsWith('--preset=')) {
|
|
448
|
+
flags.preset = readEqualsValue('--preset', arg.slice('--preset='.length));
|
|
449
|
+
} else if (arg === '--install-scope') {
|
|
450
|
+
const parsedValue = readValue('--install-scope', i);
|
|
451
|
+
flags.installScope = parsedValue.value;
|
|
452
|
+
i = parsedValue.nextIndex;
|
|
453
|
+
} else if (arg.startsWith('--install-scope=')) {
|
|
454
|
+
flags.installScope = readEqualsValue('--install-scope', arg.slice('--install-scope='.length));
|
|
455
|
+
} else if (arg === '--global-dir') {
|
|
456
|
+
const parsedValue = readValue('--global-dir', i);
|
|
457
|
+
flags.globalDir = parsedValue.value;
|
|
458
|
+
i = parsedValue.nextIndex;
|
|
459
|
+
} else if (arg.startsWith('--global-dir=')) {
|
|
460
|
+
flags.globalDir = readEqualsValue('--global-dir', arg.slice('--global-dir='.length));
|
|
461
|
+
} else if (arg === '--host-global-dir') {
|
|
462
|
+
const parsedValue = readValue('--host-global-dir', i);
|
|
463
|
+
flags.hostGlobalDir = parsedValue.value;
|
|
464
|
+
i = parsedValue.nextIndex;
|
|
465
|
+
} else if (arg.startsWith('--host-global-dir=')) {
|
|
466
|
+
flags.hostGlobalDir = readEqualsValue('--host-global-dir', arg.slice('--host-global-dir='.length));
|
|
467
|
+
} else if (arg.startsWith('-')) {
|
|
468
|
+
errors.push(`Unknown flag "${arg}"`);
|
|
394
469
|
} else {
|
|
395
470
|
positionals.push(arg);
|
|
396
471
|
}
|
|
@@ -434,17 +509,30 @@ function printSummary(summary) {
|
|
|
434
509
|
console.log('');
|
|
435
510
|
}
|
|
436
511
|
|
|
437
|
-
function printPlan(plan) {
|
|
438
|
-
for (const [label, files] of Object.entries(plan)) {
|
|
439
|
-
if (!files.length) continue;
|
|
512
|
+
function printPlan(plan) {
|
|
513
|
+
for (const [label, files] of Object.entries(plan)) {
|
|
514
|
+
if (!files.length) continue;
|
|
440
515
|
console.log(` ${label}:`);
|
|
441
516
|
for (const file of files) {
|
|
442
517
|
console.log(` - ${file}`);
|
|
443
518
|
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function printHostPlans(hostPlans = [], hostSummary = [], { summaryOnly = false } = {}) {
|
|
523
|
+
if (!hostPlans?.length) return;
|
|
524
|
+
|
|
525
|
+
const summariesByHost = new Map((hostSummary || []).map(item => [item.host, item.summary]));
|
|
526
|
+
console.log(pc.bold('\nHost-global copies:'));
|
|
527
|
+
for (const hostPlan of hostPlans) {
|
|
528
|
+
console.log(` ${hostPlan.host} ${pc.cyan(hostPlan.root)}`);
|
|
529
|
+
const summary = summariesByHost.get(hostPlan.host);
|
|
530
|
+
if (summary) printSummary(summary);
|
|
531
|
+
if (!summaryOnly) printPlan(hostPlan.plan);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function printWarnings(result) {
|
|
448
536
|
if (!result.warnings.length) return;
|
|
449
537
|
|
|
450
538
|
console.log(pc.yellow('\nWarning(s):'));
|
|
@@ -455,7 +543,7 @@ function printWarnings(result) {
|
|
|
455
543
|
|
|
456
544
|
function printJsonResult(result) {
|
|
457
545
|
// Remove `created` array from output — it is already in the plan, avoid duplication
|
|
458
|
-
const { created, ...rest } = result;
|
|
546
|
+
const { created, globalCreated, hostCreated, ...rest } = result;
|
|
459
547
|
console.log(JSON.stringify(rest, null, 2));
|
|
460
548
|
if (!result.success) {
|
|
461
549
|
process.exit(1);
|
|
@@ -703,12 +791,44 @@ function createJsonScan(scan) {
|
|
|
703
791
|
}
|
|
704
792
|
|
|
705
793
|
function createAgentGuidance(result, { projectName, targetDir, options, scan }) {
|
|
706
|
-
const
|
|
794
|
+
const projectAttentionFiles = [...new Set([
|
|
707
795
|
...(result.plan?.conflict || []),
|
|
708
796
|
...(result.plan?.skip || []),
|
|
709
797
|
])].sort();
|
|
710
|
-
const
|
|
711
|
-
|
|
798
|
+
const globalAttentionFiles = [...new Set([
|
|
799
|
+
...(result.globalPlan?.conflict || []),
|
|
800
|
+
...(result.globalPlan?.skip || []),
|
|
801
|
+
])].sort();
|
|
802
|
+
const hostAttentionFiles = (result.hostPlans || []).flatMap(hostPlan => (
|
|
803
|
+
[...new Set([
|
|
804
|
+
...(hostPlan.plan?.conflict || []),
|
|
805
|
+
...(hostPlan.plan?.skip || []),
|
|
806
|
+
])].sort().map(file => ({ host: hostPlan.host, root: hostPlan.root, file }))
|
|
807
|
+
));
|
|
808
|
+
const aiMergeRequired = [
|
|
809
|
+
...projectAttentionFiles.map(file => createFileGuidance(file)),
|
|
810
|
+
...globalAttentionFiles.map(file => {
|
|
811
|
+
const guidance = createFileGuidance(file);
|
|
812
|
+
return {
|
|
813
|
+
...guidance,
|
|
814
|
+
file: `global:${guidance.file}`,
|
|
815
|
+
scope: 'global-runtime',
|
|
816
|
+
reason: `Global runtime file in ${result.globalDir || 'the selected global directory'} needs review. ${guidance.reason}`,
|
|
817
|
+
};
|
|
818
|
+
}),
|
|
819
|
+
...hostAttentionFiles.map(({ host, root, file }) => {
|
|
820
|
+
const guidance = createFileGuidance(file);
|
|
821
|
+
return {
|
|
822
|
+
...guidance,
|
|
823
|
+
file: `host:${host}:${guidance.file}`,
|
|
824
|
+
scope: `host-global:${host}`,
|
|
825
|
+
reason: `Host-global ${host} file in ${root} needs review. ${guidance.reason}`,
|
|
826
|
+
};
|
|
827
|
+
}),
|
|
828
|
+
];
|
|
829
|
+
const hasBlockingConflicts = (result.plan?.conflict || []).length > 0
|
|
830
|
+
|| (result.globalPlan?.conflict || []).length > 0
|
|
831
|
+
|| (result.hostPlans || []).some(hostPlan => (hostPlan.plan?.conflict || []).length > 0);
|
|
712
832
|
const safeMergeCommand = commandFor(projectName, targetDir, {
|
|
713
833
|
...options,
|
|
714
834
|
dryRun: false,
|
|
@@ -769,6 +889,14 @@ function createAgentGuidance(result, { projectName, targetDir, options, scan })
|
|
|
769
889
|
mkdir: result.plan?.mkdir?.length || 0,
|
|
770
890
|
backup: result.plan?.backup?.length || 0,
|
|
771
891
|
overwrite: result.plan?.overwrite?.length || 0,
|
|
892
|
+
globalCreate: result.globalPlan?.create?.length || 0,
|
|
893
|
+
globalMkdir: result.globalPlan?.mkdir?.length || 0,
|
|
894
|
+
globalBackup: result.globalPlan?.backup?.length || 0,
|
|
895
|
+
globalOverwrite: result.globalPlan?.overwrite?.length || 0,
|
|
896
|
+
hostCreate: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.create?.length || 0), 0),
|
|
897
|
+
hostMkdir: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.mkdir?.length || 0), 0),
|
|
898
|
+
hostBackup: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.backup?.length || 0), 0),
|
|
899
|
+
hostOverwrite: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.overwrite?.length || 0), 0),
|
|
772
900
|
},
|
|
773
901
|
aiMergeRequired,
|
|
774
902
|
next,
|
|
@@ -839,6 +967,9 @@ function commandFor(projectName, targetDir, options) {
|
|
|
839
967
|
if (options.withoutOptions?.length) args.push('--without', options.withoutOptions.join(','));
|
|
840
968
|
if (options.externalOptions?.length) args.push('--recommend', options.externalOptions.join(','));
|
|
841
969
|
if (options.preset) args.push('--preset', options.preset);
|
|
970
|
+
if (options.installScope && options.installScope !== 'project') args.push('--install-scope', options.installScope);
|
|
971
|
+
if (options.globalDir) args.push('--global-dir', options.globalDir);
|
|
972
|
+
if (options.hostGlobalDir) args.push('--host-global-dir', options.hostGlobalDir);
|
|
842
973
|
if (options.json) args.push('--json');
|
|
843
974
|
|
|
844
975
|
return args.map(shellQuoteArg).join(' ');
|
package/src/prompts.js
CHANGED
|
@@ -40,6 +40,24 @@ export async function askTargetDir(projectName) {
|
|
|
40
40
|
return dir.trim();
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export async function askInstallScope() {
|
|
44
|
+
const scope = await p.select({
|
|
45
|
+
message: 'Harness install scope?',
|
|
46
|
+
initialValue: 'project',
|
|
47
|
+
options: [
|
|
48
|
+
{ value: 'project', label: 'Project-local', hint: 'full Harness scaffold in this project' },
|
|
49
|
+
{ value: 'global', label: 'Global + project state', hint: 'shared runtime plus project-local tasks/memory' },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (p.isCancel(scope)) {
|
|
54
|
+
p.cancel('Cancelled');
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return scope;
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
export async function askConflictPolicy(scan) {
|
|
44
62
|
const reason = scan.hasHarness
|
|
45
63
|
? 'Target already has Harness/. Choose how to handle existing files.'
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# /wf-command-create
|
|
2
|
+
|
|
3
|
+
Create or modify a Harness wf-* command surface atomically. Do not invoke a skill or start WF mode.
|
|
4
|
+
|
|
5
|
+
## Classification
|
|
6
|
+
|
|
7
|
+
DIRECT command. It is a Harness maintenance command: it must create or resume a task capsule, then update every command surface declared by `Harness/specs/runtime/command-surface.json`. Never loads `Harness/MEMORY.md` as part of command routing.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
/wf-command-create <wf-command-id> [--direct|--workflow] [--task <task-id>]
|
|
12
|
+
|
|
13
|
+
- Command id must be `wf` or `wf-*` kebab-case.
|
|
14
|
+
- Use an existing task capsule when `--task` is supplied or when the active task clearly matches.
|
|
15
|
+
- Otherwise create a `task-<verb>-<noun>[-detail]` capsule with `node Harness/scripts/task-state.mjs record <task-id> --create --apply --json`.
|
|
16
|
+
- Record the command id, classification, and validation evidence in the task capsule.
|
|
17
|
+
|
|
18
|
+
## Required Surface Checklist
|
|
19
|
+
|
|
20
|
+
Update all applicable files before returning:
|
|
21
|
+
|
|
22
|
+
- `Harness/specs/runtime/command-surface.json`
|
|
23
|
+
- `.claude/commands/<id>.md`
|
|
24
|
+
- `.opencode/commands/<id>.md`
|
|
25
|
+
- `.claude/skills/<id>/SKILL.md` when Codex compatibility or workflow routing is needed
|
|
26
|
+
- `.agents/skills/<id>/SKILL.md` mirror when a Claude skill exists
|
|
27
|
+
- Matching `templates/common/...` files
|
|
28
|
+
- `CLAUDE.md` and `templates/common/CLAUDE.md`
|
|
29
|
+
- `.claude/rules/ecc/common.md` and template mirror
|
|
30
|
+
- `.claude/commands/wf-help.md`, `.opencode/commands/wf-help.md`, and template mirrors
|
|
31
|
+
- `Harness/README.md`, `Harness/MEMORY.md`, and template mirrors
|
|
32
|
+
- `Harness/scripts/validate-harness.mjs` and template mirror
|
|
33
|
+
- `Harness/scripts/wf-remove.mjs` and template mirror
|
|
34
|
+
- `tests/generator.test.js`, `tests/anti-drift.test.js`, and `tests/validate-harness.test.js`
|
|
35
|
+
- `Harness/.harness-version`, `templates/common/.harness-version`, and ownership manifests via `node scripts/build-version.mjs`
|
|
36
|
+
|
|
37
|
+
## Validation
|
|
38
|
+
|
|
39
|
+
Run, at minimum:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm test
|
|
43
|
+
node Harness/scripts/validate-harness.mjs
|
|
44
|
+
node templates/common/Harness/scripts/validate-harness.mjs
|
|
45
|
+
node Harness/scripts/context-budget.mjs --json
|
|
46
|
+
node scripts/build-version.mjs --check
|
|
47
|
+
npm run check:mirrors
|
|
48
|
+
git diff --check
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If a check is blocked by an existing unrelated repository issue, record the command, failure, and reason in the task capsule.
|
|
52
|
+
|
|
53
|
+
## Return
|
|
54
|
+
|
|
55
|
+
- Command id and classification.
|
|
56
|
+
- Task capsule path.
|
|
57
|
+
- Files changed by surface category.
|
|
58
|
+
- Verification results.
|
|
@@ -19,6 +19,10 @@ which returns this same table without loading `Harness/MEMORY.md` or entering WF
|
|
|
19
19
|
| `/wf-readme <task>` | workflow skill | `/wf-readme polish quickstart` | Preserve, merge, or improve README docs without trampling existing project documentation. |
|
|
20
20
|
| `/wf-update` | direct command | `/wf-update` | Check/apply Harness scaffold updates with safe file classification and conflict handling. |
|
|
21
21
|
| `/wf-remove` | workflow skill | `/wf-remove` | Safely remove Harness files while preserving project/user data unless explicitly purged. |
|
|
22
|
+
| `/wf-task-record <task-id>` | direct command | `/wf-task-record my-feature --create` | Record user intent into a task capsule (wraps task-state.mjs). |
|
|
23
|
+
| `/wf-task-list` | direct command | `/wf-task-list` | List all task capsules with status, phase, and dependencies. |
|
|
24
|
+
| `/wf-task-archive [--apply]` | direct command | `/wf-task-archive --apply` | Archive completed task capsules (dry-run by default). |
|
|
25
|
+
| `/wf-command-create <wf-command-id>` | direct command | `/wf-command-create wf-report --direct` | Create or modify wf-* command surfaces atomically from the command registry. |
|
|
22
26
|
|
|
23
27
|
Source of truth: `Harness/README.md#Skill Commands` plus installed skills under
|
|
24
28
|
`.claude/skills/` (Claude Code and OpenCode adapters) or `.agents/skills/`
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# /wf-task-archive
|
|
2
|
+
|
|
3
|
+
Archive completed/obsolete task capsules to `Harness/tasks/_archive/`. Do not invoke a skill or start WF mode.
|
|
4
|
+
|
|
5
|
+
## Classification
|
|
6
|
+
|
|
7
|
+
DIRECT command. Wraps `node Harness/scripts/task-state.mjs archive`. Never loads Harness/MEMORY.md.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
/wf-task-archive [--dry-run] [--apply] [--task <id>] [--keep <n>]
|
|
12
|
+
|
|
13
|
+
- Defaults to dry-run (`--dry-run`) without `--apply`.
|
|
14
|
+
- With `--apply`: moves eligible task directories into yearly subdirectories under `_archive/`.
|
|
15
|
+
- With `--task <id>`: targets a specific task for archive eligibility check.
|
|
16
|
+
- With `--keep <n>`: keeps N most recent non-archived task capsules (default 5).
|
|
17
|
+
- Tasks with active, blocked, in_progress, running, pending, or needs-user-decision status are never auto-archived.
|
|
18
|
+
|
|
19
|
+
## Execution
|
|
20
|
+
|
|
21
|
+
Run: `node Harness/scripts/task-state.mjs archive [--dry-run] [--apply] [--task <id>] [--keep <n>] [--json]`
|
|
22
|
+
|
|
23
|
+
## Return
|
|
24
|
+
|
|
25
|
+
- JSON output with archive plan: scanned, archiveable, toArchive, kept, skipped counts and per-task results.
|
|
26
|
+
- If the command fails, report the error and do not retry.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# /wf-task-list
|
|
2
|
+
|
|
3
|
+
List Harness task capsules with state, phase, status, and dependency info. Do not invoke a skill or start WF mode.
|
|
4
|
+
|
|
5
|
+
## Classification
|
|
6
|
+
|
|
7
|
+
DIRECT command. Wraps `node Harness/scripts/task-state.mjs list --json`. Never loads Harness/MEMORY.md.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
/wf-task-list [--json]
|
|
12
|
+
|
|
13
|
+
- By default, returns a human-readable listing of all task capsules.
|
|
14
|
+
- With --json, returns structured JSON output.
|
|
15
|
+
- Shows active, open, blocked, verified tasks with dependency info.
|
|
16
|
+
|
|
17
|
+
## Execution
|
|
18
|
+
|
|
19
|
+
Run: `node Harness/scripts/task-state.mjs list --json`
|
|
20
|
+
|
|
21
|
+
## Return
|
|
22
|
+
|
|
23
|
+
- JSON output of all task capsules with status, phase, dependencies, and archive eligibility.
|
|
24
|
+
- If the command fails, report the error and do not retry.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# /wf-task-record
|
|
2
|
+
|
|
3
|
+
Record a task into a Harness task capsule. Do not invoke a skill or start WF mode.
|
|
4
|
+
|
|
5
|
+
## Classification
|
|
6
|
+
|
|
7
|
+
DIRECT command. Wraps `node Harness/scripts/task-state.mjs record`. Never loads Harness/MEMORY.md.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
/wf-task-record [<task-id>] [--title <slug>] [--note <text>] [--context <text>] [--new] [--create] [--status <status>] [--mode <mode>] [--text "description"]
|
|
12
|
+
|
|
13
|
+
- With <task-id>: record to that task (existing semantics).
|
|
14
|
+
- Without <task-id>: --title/--note/--context required. Deterministically matches open tasks; updates if unique match found; creates new if no match; fails if ambiguous unless --new.
|
|
15
|
+
- Never uses LLM or embeddings — slug + keyword overlap only.
|
|
16
|
+
|
|
17
|
+
## Execution
|
|
18
|
+
|
|
19
|
+
Run: `node Harness/scripts/task-state.mjs record [<task-id>] [--title <slug>] [--note <text>] [--context <text>] [--new] [--create] [--apply] [--text "..."] [--status <status>] [--mode <mode>] [--json]`
|
|
20
|
+
|
|
21
|
+
## Return
|
|
22
|
+
|
|
23
|
+
- Output of `node Harness/scripts/task-state.mjs record ... --json`
|
|
24
|
+
- If the command fails, report the error and do not retry.
|
|
@@ -8,7 +8,7 @@ alwaysApply: true
|
|
|
8
8
|
## Context
|
|
9
9
|
|
|
10
10
|
- Start with `CLAUDE.md`. When `Harness/` exists, also read `Harness/memory/startup-hints.md` (L2 lightweight digest, not full router).
|
|
11
|
-
- When the user explicitly invokes a workflow command (`/wf-*`, `$wf-*`, or `/skills wf-*`), excluding `/wf-help`, `$wf-help`, `/skills wf-help`, `/wf-update`, `$wf-update`, and `/skills wf-
|
|
11
|
+
- When the user explicitly invokes a workflow command (`/wf-*`, `$wf-*`, or `/skills wf-*`), excluding `/wf-help`, `$wf-help`, `/skills wf-help`, `/wf-update`, `$wf-update`, `/skills wf-update`, `/wf-task-record`, `$wf-task-record`, `/skills wf-task-record`, `/wf-task-list`, `$wf-task-list`, `/skills wf-task-list`, `/wf-task-archive`, `$wf-task-archive`, `/skills wf-task-archive`, `/wf-command-create`, `$wf-command-create`, and `/skills wf-command-create`, load `Harness/MEMORY.md` and `Harness/README.md`.
|
|
12
12
|
- For simple single-step tasks without `/wf-*`, operate in direct mode: skip the Harness router and execute directly.
|
|
13
13
|
- Do not bulk-read `Harness/`. Load by router trigger.
|
|
14
14
|
- Keep `Harness/tasks/<task-id>/PROGRESS.md` and `Harness/tasks/<task-id>/PLAN.md` current when work has multiple steps, files, or agents.
|
|
@@ -9,13 +9,10 @@ Use this skill before shelling out to `claude`, `codex`, or `opencode` from Harn
|
|
|
9
9
|
|
|
10
10
|
## Source Order
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
2. Check official docs for flags that affect cost, auth, JSON, resume, tools/MCP, or telemetry.
|
|
14
|
-
3. When adding automation, record command, source, stdout/stderr shape, and failed patterns.
|
|
12
|
+
Prefer installed help (`claude --help`, `codex exec --help`, `opencode run --help`), then official docs for cost/auth/JSON/resume/tools/telemetry; record command, source, stdout/stderr shape, and failures.
|
|
15
13
|
|
|
16
14
|
## Claude Code CLI
|
|
17
15
|
|
|
18
|
-
- Interactive: `claude`.
|
|
19
16
|
- Non-interactive JSON: pipe ASCII or UTF-8-safe stdin into `claude -p --output-format json`.
|
|
20
17
|
- Stream JSON requires verbose mode: `claude -p --output-format stream-json --verbose`.
|
|
21
18
|
- Continue/resume: `claude -c -p "..."` or `claude -p --resume <session-id> "..."`; for PowerShell automation, prefer stdin and validate non-empty JSON before parsing.
|
|
@@ -26,19 +23,15 @@ Use this skill before shelling out to `claude`, `codex`, or `opencode` from Harn
|
|
|
26
23
|
|
|
27
24
|
## Codex CLI
|
|
28
25
|
|
|
29
|
-
- Interactive: `codex`.
|
|
30
26
|
- Non-interactive: `codex exec "task"`.
|
|
31
|
-
-
|
|
32
|
-
- Prompt plus stdin context: `some-command | codex exec "summarize this output"`.
|
|
27
|
+
- Stdin modes: `cat prompt.txt | codex exec -`; `some-command | codex exec "summarize this output"`.
|
|
33
28
|
- Machine output: `codex exec --json "task"` emits JSONL events; parse `turn.completed.usage`, including `cached_input_tokens` when present.
|
|
34
29
|
- Resume: `codex exec resume --last "..."` or `codex exec resume <SESSION_ID> "..."`.
|
|
35
30
|
- Permissions: default is read-only; set `--sandbox workspace-write` only when edits are required. Use `--ignore-user-config` / `--ignore-rules` for controlled automation.
|
|
36
31
|
|
|
37
32
|
## OpenCode CLI
|
|
38
33
|
|
|
39
|
-
-
|
|
40
|
-
- Non-interactive: `opencode run [message..]`.
|
|
41
|
-
- JSON events: `opencode run --format json "task"`.
|
|
34
|
+
- Non-interactive: `opencode run [message..]`; JSON events: `opencode run --format json "task"`.
|
|
42
35
|
- Resume: `opencode run --continue "..."` or `opencode run --session <id> "..."`.
|
|
43
36
|
- Peer role: `opencode run --agent reviewer --dir . "review prompt"`.
|
|
44
37
|
- Reuse a server to avoid MCP cold boot: `opencode serve`, then `opencode run --attach http://localhost:4096 "task"`.
|
|
@@ -48,31 +41,27 @@ Use this skill before shelling out to `claude`, `codex`, or `opencode` from Harn
|
|
|
48
41
|
|
|
49
42
|
- Prefer stdin over trailing prompt args for `claude -p` in PowerShell.
|
|
50
43
|
- Use ASCII prompts or explicitly UTF-8-safe input for automated probes.
|
|
51
|
-
- Do not trust exit code alone. Fail on empty/non-JSON stdout
|
|
44
|
+
- Do not trust exit code alone. Fail on empty/non-JSON stdout, error/budget/fallback terminal fields, or missing final model text.
|
|
52
45
|
- Avoid naming function parameters `$Args`; PowerShell treats `$Args` specially.
|
|
53
46
|
- Store telemetry outside the repo, e.g. `$HOME/.claude/cache-telemetry/*.json`, so git status does not perturb prefixes.
|
|
54
47
|
|
|
55
48
|
## Evidence-Packet Review Pattern
|
|
56
49
|
|
|
57
|
-
For peer review, route smokes, cache analysis, and audits, gather evidence
|
|
58
|
-
first; the peer judges only the bounded packet.
|
|
50
|
+
For peer review, route smokes, cache analysis, and audits, gather evidence first; the peer judges only the bounded packet.
|
|
59
51
|
|
|
60
|
-
- Gather paths, line snippets, command names, exits, and invariants with `rg`,
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
and screenshots unless they are the evidence.
|
|
64
|
-
- Prefer no tools for judgment-only review; otherwise allow only read-only
|
|
65
|
-
tools and name the exact read set.
|
|
52
|
+
- Gather paths, line snippets, command names, exits, and invariants with `rg`, `node` scripts, validators, or small reads.
|
|
53
|
+
- Send only that packet. Exclude full docs, raw logs, timestamps, session IDs, and screenshots unless they are the evidence.
|
|
54
|
+
- Prefer no tools for judgment-only review; otherwise allow only read-only tools and name the exact read set.
|
|
66
55
|
- Controller accepts, rejects, or escalates findings. Peers do not own scope.
|
|
56
|
+
- Fail on empty/non-JSON stdout, explicit error events, budget errors, fallback warnings, or missing final model text.
|
|
57
|
+
- For `claude -p --output-format json`, check `is_error`, `subtype`, and `result` fields before treating output as review evidence.
|
|
58
|
+
- For `opencode run --format json`, extract `text` from JSONL events; the stream is not a single review result.
|
|
67
59
|
|
|
68
60
|
## No Scratch-File Rule
|
|
69
61
|
|
|
70
|
-
- Do not write CLI probe output under `%TEMP%`, `$env:TEMP`, `/tmp`, or other
|
|
71
|
-
system temp directories.
|
|
72
|
-
- Prefer stdout, JSON/JSONL streaming, or in-memory parsing.
|
|
62
|
+
- Do not write CLI probe output under `%TEMP%`, `$env:TEMP`, `/tmp`, or other system temp directories; prefer stdout, JSON/JSONL streaming, or in-memory parsing.
|
|
73
63
|
- Persistent repo evidence goes under `Harness/tasks/<task-id>/evidence/`.
|
|
74
|
-
- Cache telemetry may live under `$HOME/.claude/cache-telemetry/` to avoid repo
|
|
75
|
-
prompt-cache churn.
|
|
64
|
+
- Cache telemetry may live under `$HOME/.claude/cache-telemetry/` to avoid repo prompt-cache churn.
|
|
76
65
|
- Do not create prompt temp files. Use stdin.
|
|
77
66
|
|
|
78
67
|
## Subagent Output Contract
|
|
@@ -102,8 +91,7 @@ Follow `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`:
|
|
|
102
91
|
## Batch-Test Pattern
|
|
103
92
|
|
|
104
93
|
1. Probe command availability with `Get-Command claude,codex,opencode -ErrorAction SilentlyContinue`.
|
|
105
|
-
2. Build a compact evidence packet before invoking peer agents; use the peer
|
|
106
|
-
only for judgment unless the test explicitly requires live agent discovery.
|
|
94
|
+
2. Build a compact evidence packet before invoking peer agents; use the peer only for judgment unless the test explicitly requires live agent discovery.
|
|
107
95
|
3. Run a cold turn and capture session id.
|
|
108
96
|
4. Resume that session for two warm turns.
|
|
109
97
|
5. For each turn record input, cache creation, cache read, ratio, cost, model/session id, and exact flags.
|
|
@@ -111,9 +99,6 @@ Follow `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`:
|
|
|
111
99
|
|
|
112
100
|
## Official References
|
|
113
101
|
|
|
114
|
-
- Claude Code CLI
|
|
115
|
-
- Claude Code prompt caching: https://code.claude.com/docs/en/prompt-caching
|
|
116
|
-
- Claude Code status line schema: https://code.claude.com/docs/en/statusline
|
|
102
|
+
- Claude Code CLI/cache/statusline: https://code.claude.com/docs/en/cli-reference
|
|
117
103
|
- Codex CLI: https://developers.openai.com/codex/cli
|
|
118
|
-
- Codex non-interactive mode: https://learn.chatgpt.com/docs/non-interactive-mode
|
|
119
104
|
- OpenCode CLI: https://opencode.ai/docs/cli/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wf-command-create
|
|
3
|
+
description: Create or modify Harness wf-* commands atomically. Use for $wf-command-create or /skills wf-command-create in Codex, and for /wf-command-create in Claude Code/OpenCode. Direct/compat maintenance command; creates or resumes a task capsule but does not enter WF mode.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# WF Command Create Adapter
|
|
7
|
+
|
|
8
|
+
This skill is a Codex compatibility shim for the direct `/wf-command-create`
|
|
9
|
+
maintenance command. It does not start WF mode or load `Harness/MEMORY.md`.
|
|
10
|
+
|
|
11
|
+
## Invocation
|
|
12
|
+
|
|
13
|
+
- Codex: `$wf-command-create` or `/skills` then choose `wf-command-create`.
|
|
14
|
+
- Claude Code: `/wf-command-create` direct command from `.claude/commands/wf-command-create.md`.
|
|
15
|
+
- OpenCode: `/wf-command-create` direct command from `.opencode/commands/wf-command-create.md`.
|
|
16
|
+
|
|
17
|
+
## Load
|
|
18
|
+
|
|
19
|
+
- `.claude/commands/wf-command-create.md`
|
|
20
|
+
- `Harness/specs/runtime/command-surface.json`
|
|
21
|
+
|
|
22
|
+
## Rules
|
|
23
|
+
|
|
24
|
+
Execute the command instructions from `.claude/commands/wf-command-create.md`.
|
|
25
|
+
|
|
26
|
+
- Create or resume the task capsule first.
|
|
27
|
+
- Update `command-surface.json` before creating command files.
|
|
28
|
+
- Keep `.agents/skills/<id>/SKILL.md` byte-identical to `.claude/skills/<id>/SKILL.md`.
|
|
29
|
+
- Keep `.opencode/commands/<id>.md` body-identical to `.claude/commands/<id>.md`.
|
|
30
|
+
- Run the validation list from the command file or record why a check could not complete.
|
|
31
|
+
|
|
32
|
+
## Return
|
|
33
|
+
|
|
34
|
+
- Task capsule path
|
|
35
|
+
- Command classification
|
|
36
|
+
- Changed surface checklist
|
|
37
|
+
- Verification results
|