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.
Files changed (41) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README-CN.md +2 -0
  3. package/README.md +2 -0
  4. package/package.json +1 -1
  5. package/src/generator.js +613 -97
  6. package/src/index.js +173 -42
  7. package/src/prompts.js +18 -0
  8. package/templates/common/.claude/commands/wf-command-create.md +58 -0
  9. package/templates/common/.claude/commands/wf-help.md +4 -0
  10. package/templates/common/.claude/commands/wf-task-archive.md +26 -0
  11. package/templates/common/.claude/commands/wf-task-list.md +24 -0
  12. package/templates/common/.claude/commands/wf-task-record.md +24 -0
  13. package/templates/common/.claude/rules/ecc/common.md +1 -1
  14. package/templates/common/.claude/skills/wf-agents-docs/SKILL.md +15 -30
  15. package/templates/common/.claude/skills/wf-command-create/SKILL.md +37 -0
  16. package/templates/common/.claude/skills/wf-max/SKILL.md +1 -1
  17. package/templates/common/.claude/skills/wf-review/SKILL.md +29 -2
  18. package/templates/common/.claude/skills/wf-task-archive/SKILL.md +28 -0
  19. package/templates/common/.claude/skills/wf-task-list/SKILL.md +28 -0
  20. package/templates/common/.claude/skills/wf-task-record/SKILL.md +28 -0
  21. package/templates/common/.harness-version +66 -32
  22. package/templates/common/.opencode/commands/wf-command-create.md +61 -0
  23. package/templates/common/.opencode/commands/wf-help.md +4 -0
  24. package/templates/common/.opencode/commands/wf-task-archive.md +29 -0
  25. package/templates/common/.opencode/commands/wf-task-list.md +27 -0
  26. package/templates/common/.opencode/commands/wf-task-record.md +27 -0
  27. package/templates/common/CLAUDE.md +8 -6
  28. package/templates/common/Harness/MEMORY.md +9 -0
  29. package/templates/common/Harness/README.md +17 -36
  30. package/templates/common/Harness/ownership.manifest.json +87 -2
  31. package/templates/common/Harness/scripts/task-state.mjs +395 -5
  32. package/templates/common/Harness/scripts/validate-harness.mjs +411 -46
  33. package/templates/common/Harness/scripts/wf-remove.mjs +34 -2
  34. package/templates/common/Harness/specs/guides/SETUP.md +8 -0
  35. package/templates/common/Harness/specs/protocols/MEMORY_PROTOCOL.md +15 -0
  36. package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +9 -3
  37. package/templates/common/Harness/specs/runtime/command-surface.json +215 -0
  38. package/templates/common/Harness/specs/runtime/subagents.md +6 -0
  39. package/templates/common/Harness/specs/workflows/WF-MAX.md +5 -0
  40. package/templates/common/Harness/specs/workflows/WF-STATE.md +66 -0
  41. 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(` Creates ${pc.cyan('CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, .opencode/, opencode.json, tests/')}`);
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(` Creates ${pc.cyan('CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, .opencode/, opencode.json, tests/')}`);
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
- printSummary(preview.summary);
242
- printPlan(preview.plan);
243
- printWarnings(preview);
244
- console.log('');
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
- printSummary(result.summary);
276
- printPlan(result.plan);
277
- if (result.warnings.length > 0) {
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
- printSummary(result.summary);
289
-
290
- printWarnings(result);
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.startsWith('-')) {
393
- errors.push(`Unknown flag "${arg}"`);
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 printWarnings(result) {
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 attentionFiles = [...new Set([
794
+ const projectAttentionFiles = [...new Set([
707
795
  ...(result.plan?.conflict || []),
708
796
  ...(result.plan?.skip || []),
709
797
  ])].sort();
710
- const aiMergeRequired = attentionFiles.map(file => createFileGuidance(file));
711
- const hasBlockingConflicts = (result.plan?.conflict || []).length > 0;
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-update`, load `Harness/MEMORY.md` and `Harness/README.md`.
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
- 1. Prefer installed help: `claude --help`, `codex exec --help`, `opencode run --help`.
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
- - Read stdin as the full prompt: `cat prompt.txt | codex exec -`.
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
- - Interactive: `opencode`.
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 or error/budget terminal fields.
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
- `node` scripts, validators, or small reads.
62
- - Send only that packet. Exclude full docs, raw logs, timestamps, session IDs,
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 reference: https://code.claude.com/docs/en/cli-reference
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