create-harness-vibe-coding 0.8.16 → 0.8.17

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 (52) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README-CN.md +98 -127
  3. package/README.md +61 -42
  4. package/package.json +1 -1
  5. package/src/generator.js +15 -24
  6. package/src/index.js +1 -1
  7. package/templates/common/.claude/commands/wf-auto-spark.md +16 -0
  8. package/templates/common/.claude/commands/wf-auto.md +16 -0
  9. package/templates/{optional/skills/browser-e2e/.opencode → common/.claude}/commands/wf-browser.md +1 -1
  10. package/templates/common/.claude/commands/wf-help.md +9 -6
  11. package/templates/common/.claude/commands/wf-learn.md +16 -0
  12. package/templates/common/.claude/commands/wf-max.md +20 -0
  13. package/templates/common/.claude/commands/wf-readme.md +16 -0
  14. package/templates/common/.claude/commands/wf-remove.md +16 -0
  15. package/templates/common/.claude/commands/wf-review.md +16 -0
  16. package/templates/common/.claude/commands/wf.md +16 -0
  17. package/templates/common/.claude/rules/ecc/common.md +1 -1
  18. package/templates/common/.claude/skills/wf-browser/SKILL.md +176 -0
  19. package/templates/common/.claude/skills/wf-help/SKILL.md +30 -0
  20. package/templates/common/.claude/skills/wf-max/SKILL.md +21 -7
  21. package/templates/common/.codex/config.toml +3 -5
  22. package/templates/common/.harness-version +69 -36
  23. package/templates/common/.opencode/agents/architect-manager.md +7 -1
  24. package/templates/common/.opencode/agents/explore-manager.md +10 -1
  25. package/templates/common/.opencode/agents/implement-manager.md +5 -3
  26. package/templates/common/.opencode/agents/review-manager.md +5 -2
  27. package/templates/common/.opencode/commands/wf-browser.md +16 -0
  28. package/templates/common/.opencode/commands/wf-help.md +9 -6
  29. package/templates/common/.opencode/commands/wf-max.md +12 -7
  30. package/templates/common/CLAUDE.md +2 -2
  31. package/templates/common/Harness/MEMORY.md +3 -3
  32. package/templates/common/Harness/README.md +12 -11
  33. package/templates/common/Harness/ownership.manifest.json +77 -14
  34. package/templates/common/Harness/scripts/archive-tasks.mjs +12 -220
  35. package/templates/common/Harness/scripts/scan-clean.mjs +3 -9
  36. package/templates/common/Harness/scripts/task-state.mjs +889 -0
  37. package/templates/common/Harness/scripts/validate-harness.mjs +233 -28
  38. package/templates/common/Harness/scripts/wf-remove.mjs +3 -3
  39. package/templates/common/Harness/scripts/wf-update-check.mjs +3 -0
  40. package/templates/common/Harness/specs/guides/SETUP.md +2 -2
  41. package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +43 -23
  42. package/templates/common/Harness/specs/runtime/dispatch.md +2 -2
  43. package/templates/common/Harness/specs/runtime/subagents.md +9 -5
  44. package/templates/common/Harness/specs/workflows/WF-AUTO.md +6 -6
  45. package/templates/common/Harness/specs/workflows/WF-KERNEL.md +1 -1
  46. package/templates/common/Harness/specs/workflows/WF-MAX.md +30 -2
  47. package/templates/common/Harness/specs/workflows/WF-STATE.md +89 -36
  48. package/templates/common/opencode.json +1 -0
  49. package/templates/optional/catalog.json +2 -9
  50. package/templates/optional/skills/browser-e2e/.claude/skills/browser-e2e/SKILL.md +0 -42
  51. package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +0 -201
  52. package/templates/optional/skills/browser-e2e/Harness/workflows/browser-e2e.md +0 -119
@@ -38,11 +38,13 @@ const commonAgents = [
38
38
 
39
39
  const commonSkills = [
40
40
  'wf',
41
+ 'wf-help',
41
42
  'tdd',
42
43
  'wf-update',
43
44
  'wf-max',
44
45
  'wf-review',
45
46
  'wf-learn',
47
+ 'wf-browser',
46
48
  'subagent-orchestrator',
47
49
  'wf-readme',
48
50
  'wf-agents-docs',
@@ -51,19 +53,22 @@ const commonSkills = [
51
53
  'wf-auto-spark',
52
54
  ];
53
55
 
54
- const opencodeWorkflowCommands = [
56
+ const workflowCommands = [
55
57
  'wf',
56
58
  'wf-max',
57
59
  'wf-auto',
58
60
  'wf-auto-spark',
59
61
  'wf-learn',
60
62
  'wf-review',
63
+ 'wf-browser',
61
64
  'wf-readme',
62
65
  'wf-remove',
63
66
  ];
64
67
 
68
+ const opencodeWorkflowCommands = workflowCommands;
69
+
65
70
  const cacheDisciplinedSkills = commonSkills.filter(skill => (
66
- skill === 'subagent-orchestrator' || skill.startsWith('wf')
71
+ skill === 'subagent-orchestrator' || (skill.startsWith('wf') && skill !== 'wf-help')
67
72
  ));
68
73
 
69
74
  const memoryFiles = [
@@ -95,10 +100,12 @@ const required = [
95
100
  'Harness/templates/VALIDATION_REPORT.template.md',
96
101
  ...memoryFiles,
97
102
  '.codex/config.toml',
103
+ '.codex/hooks.json',
98
104
  'opencode.json',
99
105
  '.claude/settings.json',
100
106
  '.claude/commands/wf-help.md',
101
107
  '.claude/commands/wf-update.md',
108
+ ...workflowCommands.map(command => `.claude/commands/${command}.md`),
102
109
  '.opencode/commands/wf-help.md',
103
110
  '.opencode/commands/wf-update.md',
104
111
  ...opencodeWorkflowCommands.map(command => `.opencode/commands/${command}.md`),
@@ -129,6 +136,7 @@ const required = [
129
136
  'Harness/scripts/wf-update-check.mjs',
130
137
  'Harness/scripts/wf-remove.mjs',
131
138
  'Harness/scripts/scan-clean.mjs',
139
+ 'Harness/scripts/task-state.mjs',
132
140
  'Harness/scripts/archive-tasks.mjs',
133
141
  'Harness/specs/workflows/WF-STATE.md',
134
142
  'Harness/specs/protocols/TASK_ARCHIVE.md',
@@ -206,6 +214,19 @@ function forbidText(rel, text, label = text) {
206
214
  if (body && body.includes(text)) errors.push(`${rel} contains forbidden ${label}`);
207
215
  }
208
216
 
217
+ function activeToml(rel) {
218
+ return read(rel)
219
+ .split(/\r?\n/)
220
+ .map(line => line.trim())
221
+ .filter(line => line && !line.startsWith('#'))
222
+ .join('\n');
223
+ }
224
+
225
+ function forbidActiveToml(rel, pattern, label) {
226
+ const body = activeToml(rel);
227
+ if (body && pattern.test(body)) errors.push(`${rel} contains active forbidden ${label}`);
228
+ }
229
+
209
230
  function frontmatterField(text, field) {
210
231
  const match = text.match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
211
232
  return match ? match[1].trim() : '';
@@ -234,6 +255,28 @@ function listMarkdownFiles(rel) {
234
255
  const TASK_NAME_RE = /^task-[a-z]+(-[a-z0-9]+){1,4}$/;
235
256
  const TASK_NAME_MAX = 46; // "task-" (5) + ≤40 chars body + 1 safety = 46
236
257
  const TASK_RESERVED = new Set(['_template', 'auto', '_archive']);
258
+ const TASK_SAFE_ARCHIVE_STATUSES = new Set(['complete', 'verified', 'archived', 'abandoned', 'obsolete', 'done', 'closed', 'closeout']);
259
+ const TASK_NEVER_ARCHIVE_STATUSES = new Set(['active', 'blocked', 'in_progress', 'running', 'pending', 'needs-user-decision']);
260
+ const TASK_PHASE_ALIASES = new Map([
261
+ ['implementation', 'implement'],
262
+ ['build', 'implement'],
263
+ ['validation', 'verify'],
264
+ ['complete', 'verified'],
265
+ ['done', 'verified'],
266
+ ['closed', 'closeout'],
267
+ ]);
268
+ const TASK_STATUS_ALIASES = new Map([
269
+ ['in-progress', 'in_progress'],
270
+ ['inprogress', 'in_progress'],
271
+ ['needs_user_decision', 'needs-user-decision'],
272
+ ['needs-user', 'needs-user-decision'],
273
+ ]);
274
+ const TASK_VALID_PHASES = new Set([
275
+ 'intake', 'clarify', 'requirements', 'prd', 'acceptance', 'plan', 'explore',
276
+ 'implement', 'verify', 'review', 'fix', 'reflect', 'closeout', 'blocked',
277
+ 'archived', 'verified',
278
+ ]);
279
+ const TASK_VALID_STATUSES = new Set([...TASK_SAFE_ARCHIVE_STATUSES, ...TASK_NEVER_ARCHIVE_STATUSES, 'skipped', 'failed']);
237
280
 
238
281
  function validateTaskName(name, strict) {
239
282
  if (TASK_RESERVED.has(name)) return null;
@@ -250,6 +293,57 @@ function validateTaskName(name, strict) {
250
293
  return null;
251
294
  }
252
295
 
296
+ function normalizeTaskToken(value, validSet, aliasMap) {
297
+ if (value === null || value === undefined) return '';
298
+ const raw = String(value).trim().toLowerCase();
299
+ if (!raw) return '';
300
+ const compact = raw.replace(/\s+/g, '-');
301
+ const direct = aliasMap.get(compact) || compact;
302
+ if (validSet.has(direct)) return direct;
303
+ const tokens = raw
304
+ .replace(/[`*_()[\]{}:]/g, ' ')
305
+ .replace(/[^a-z0-9_-]+/g, ' ')
306
+ .split(/\s+/)
307
+ .map(token => token.trim())
308
+ .filter(Boolean);
309
+ for (const token of tokens) {
310
+ const canonical = aliasMap.get(token) || token;
311
+ if (validSet.has(canonical)) return canonical;
312
+ }
313
+ return '';
314
+ }
315
+
316
+ function normalizeTaskStatus(value) {
317
+ return normalizeTaskToken(value, TASK_VALID_STATUSES, TASK_STATUS_ALIASES);
318
+ }
319
+
320
+ function normalizeTaskPhase(value) {
321
+ return normalizeTaskToken(value, TASK_VALID_PHASES, TASK_PHASE_ALIASES);
322
+ }
323
+
324
+ function taskStateIssue(message) {
325
+ if (strict) errors.push(message);
326
+ else console.warn(`Warning: ${message}`);
327
+ }
328
+
329
+ function parseRootTaskProgress(text) {
330
+ const activeMatch = text.match(/## Active Task\s*\r?\n+(?:\s*\r?\n)?\s*-\s*([^\r\n]+)/);
331
+ const rawActive = activeMatch ? activeMatch[1].trim() : '';
332
+ const activeTask = rawActive && !/^none$/i.test(rawActive) ? rawActive : null;
333
+ const rows = [];
334
+ const taskIndexMatch = text.match(/## Task Index\s*\r?\n([\s\S]*?)(?=\r?\n## |\s*$)/);
335
+ if (taskIndexMatch) {
336
+ for (const line of taskIndexMatch[1].split(/\r?\n/)) {
337
+ const trimmed = line.trim();
338
+ if (!trimmed.startsWith('|')) continue;
339
+ if (/^\|\s*-+/.test(trimmed) || /^\|\s*ID\s*\|/i.test(trimmed)) continue;
340
+ const cells = trimmed.split('|').slice(1, -1).map(cell => cell.trim());
341
+ if (cells.length >= 4 && cells[0]) rows.push({ id: cells[0], phase: cells[2] });
342
+ }
343
+ }
344
+ return { activeTask, rows };
345
+ }
346
+
253
347
  function unresolvedTemplatePlaceholders(text) {
254
348
  const placeholders = [];
255
349
  const pattern = /\{\{([^{}\r\n]+)\}\}/g;
@@ -297,7 +391,6 @@ function listedWorkflowCommands(...texts) {
297
391
  for (const match of text.matchAll(pattern)) {
298
392
  const command = `/${match[1]}`;
299
393
  if (command === '/wf-help') continue;
300
- if (command === '/wf-browser') continue; // optional workflow, not a required skill
301
394
  commands.add(command);
302
395
  }
303
396
  }
@@ -463,7 +556,7 @@ requireText('CLAUDE.md', 'Keep intermediate user updates to 1-2 short sentences'
463
556
  requireText('.claude/rules/ecc/common.md', '## Low-Noise Progress', 'ECC low-noise progress section');
464
557
  requireText('.claude/rules/ecc/common.md', "Match the user's language for user-facing prose", 'ECC user-facing language match rule');
465
558
  requireText('.claude/rules/ecc/common.md', 'Do not recap plans, paste logs, or narrate obvious file reads', 'ECC low-noise no-recap rule');
466
- requireText('.claude/rules/ecc/common.md', 'excluding `/wf-help` and `/wf-update`', 'ECC direct command exemption');
559
+ requireText('.claude/rules/ecc/common.md', 'excluding `/wf-help`, `$wf-help`, `/skills wf-help`, `/wf-update`, `$wf-update`, and `/skills wf-update`', 'ECC direct command exemption');
467
560
  requireText('Harness/README.md', 'Load By Task', 'Harness task router');
468
561
  requireText('Harness/README.md', 'Need context/cache/token efficiency', 'cache/token router row');
469
562
  requireText('Harness/specs/runtime/context-loading.md', 'Context Tiers', 'context tier load budget section');
@@ -582,7 +675,7 @@ for (const command of listedWorkflowCommands(docsReadme, read('.claude/commands/
582
675
 
583
676
  for (const workflowFile of registeredWorkflowFiles(docsReadme, memory)) {
584
677
  if (!fs.existsSync(path.join(root, workflowFile))) {
585
- // Optional workflows (browser-e2e, ts-react-frontend, etc.) may not be installed
678
+ // Optional workflows (ts-react-frontend, ui-ux-review, etc.) may not be installed
586
679
  continue;
587
680
  }
588
681
  }
@@ -602,9 +695,14 @@ function requireUiSelectorContract(rel) {
602
695
  }
603
696
  }
604
697
 
605
- requireUiSelectorContract('Harness/workflows/browser-e2e.md');
698
+ requireUiSelectorContract('.claude/skills/wf-browser/SKILL.md');
699
+ requireUiSelectorContract('.agents/skills/wf-browser/SKILL.md');
606
700
  requireUiSelectorContract('Harness/workflows/ts-react-frontend.md');
607
701
 
702
+ if (fs.existsSync(path.join(root, 'Harness/workflows/browser-e2e.md'))) {
703
+ errors.push('retired optional workflow must not exist: Harness/workflows/browser-e2e.md');
704
+ }
705
+
608
706
  for (const skill of commonSkills) {
609
707
  const rel = `.claude/skills/${skill}/SKILL.md`;
610
708
  const text = read(rel);
@@ -784,28 +882,56 @@ requireText('Harness/specs/workflows/WF-MAX.md', 'inherits the full', 'WF-MAX st
784
882
  requireText('Harness/specs/workflows/WF-MAX.md', 'Cross-CLI', 'WF-MAX cross-CLI overflow');
785
883
  requireText('Harness/specs/workflows/WF-MAX.md', 'claude -p', 'WF-MAX Claude CLI overflow');
786
884
  requireText('Harness/specs/workflows/WF-MAX.md', 'codex exec', 'WF-MAX Codex CLI overflow');
787
- requireText('Harness/specs/workflows/WF-MAX.md', 'agents.max_threads', 'WF-MAX Codex max_threads config');
788
- requireText('Harness/specs/workflows/WF-MAX.md', 'agents.max_depth', 'WF-MAX Codex max_depth config');
789
- requireText('Harness/specs/workflows/WF-MAX.md', 'max_threads = 12', 'WF-MAX Codex scaffold max_threads default');
790
- requireText('Harness/specs/workflows/WF-MAX.md', 'max_depth = 1', 'WF-MAX Codex scaffold max_depth default');
791
- requireText('Harness/specs/workflows/WF-MAX.md', 'Ask the user before raising', 'WF-MAX asks before raising Codex thread cap');
885
+ requireText('Harness/specs/workflows/WF-MAX.md', 'agents.max_threads', 'WF-MAX Codex legacy max_threads alias');
886
+ requireText('Harness/specs/workflows/WF-MAX.md', 'max_concurrent_threads_per_session', 'WF-MAX Codex concurrent thread config');
887
+ requireText('Harness/specs/workflows/WF-MAX.md', 'Mandatory Fan-Out Contract', 'WF-MAX mandatory fan-out attempt contract');
888
+ requireText('Harness/specs/workflows/WF-MAX.md', 'fanoutAttempted: true', 'WF-MAX records fan-out attempt');
889
+ requireText('Harness/specs/workflows/WF-MAX.md', 'Runtime Capacity Map', 'WF-MAX runtime capacity map');
890
+ requireText('Harness/specs/workflows/WF-MAX.md', 'CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION', 'WF-MAX Claude Code subagent cap docs');
891
+ requireText('Harness/specs/workflows/WF-MAX.md', 'CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS', 'WF-MAX Claude Code concurrent subagent cap docs');
892
+ requireText('Harness/specs/workflows/WF-MAX.md', 'CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH', 'WF-MAX Claude Code spawn-depth cap docs');
893
+ requireText('Harness/specs/workflows/WF-MAX.md', 'Do not scaffold Codex scalar agent caps', 'WF-MAX Codex config compatibility guard');
894
+ requireText('Harness/specs/workflows/WF-MAX.md', 'subagent_depth = 2', 'WF-MAX OpenCode subagent depth default');
895
+ requireText('Harness/specs/workflows/WF-MAX.md', 'asks the user before any project/global Codex config change', 'WF-MAX asks before changing Codex config');
792
896
  requireText('Harness/specs/workflows/WF-MAX.md', 'Close completed agents', 'WF-MAX close completed agents before overflow');
793
897
  requireText('Harness/specs/workflows/WF-MAX.md', 'Codex++', 'WF-MAX forbids relying on Codex++ as stable capacity');
794
898
  requireText('Harness/specs/workflows/WF-MAX.md', 'does not authorize CEO source edits', 'WF-MAX useful-degrade CEO source-edit boundary');
795
899
  requireText('Harness/specs/workflows/WF-MAX.md', '## Cross-Task Decisions', 'WF-MAX PROGRESS heading preservation');
796
- requireText('.claude/skills/wf-max/SKILL.md', 'agents.max_threads', 'wf-max skill Codex thread config');
797
- requireText('.claude/skills/wf-max/SKILL.md', 'max_threads = 12', 'wf-max skill Codex scaffold max_threads default');
900
+ requireText('.claude/skills/wf-max/SKILL.md', 'agents.max_concurrent_threads_per_session', 'wf-max skill Codex thread config');
901
+ requireText('.claude/skills/wf-max/SKILL.md', 'agents.max_threads', 'wf-max skill Codex legacy alias');
902
+ requireText('.claude/skills/wf-max/SKILL.md', 'Do not scaffold scalar `[agents]` caps', 'wf-max skill Codex config compatibility guard');
903
+ requireText('.claude/skills/wf-max/SKILL.md', 'CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION', 'wf-max skill Claude Code subagent cap');
904
+ requireText('.claude/skills/wf-max/SKILL.md', 'CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS', 'wf-max skill Claude Code concurrent subagent cap');
905
+ requireText('.claude/skills/wf-max/SKILL.md', 'CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH', 'wf-max skill Claude Code spawn-depth cap');
906
+ requireText('.claude/skills/wf-max/SKILL.md', 'subagent_depth = 2', 'wf-max skill OpenCode subagent depth default');
798
907
  requireText('.claude/skills/wf-max/SKILL.md', 'ask the user before raising', 'wf-max skill asks before raising Codex thread cap');
799
908
  requireText('.claude/skills/wf-max/SKILL.md', 'Codex++', 'wf-max skill forbids Codex++ capacity assumption');
800
909
  requireText('.claude/skills/wf-max/SKILL.md', taskIdConvention, 'wf-max skill task-id naming convention');
801
910
  requireText('.agents/skills/wf-max/SKILL.md', taskIdConvention, 'Codex wf-max skill task-id naming convention');
802
911
  requireText('.claude/skills/wf-max/SKILL.md', 'does not authorize CEO source edits', 'wf-max skill useful-degrade CEO source-edit boundary');
803
912
  requireText('.claude/skills/wf-max/SKILL.md', '## Cross-Task Decisions', 'wf-max skill PROGRESS heading preservation');
804
- requireText('.codex/config.toml', '[agents]', 'Codex agents config table');
805
- requireText('.codex/config.toml', 'max_threads = 12', 'Codex scaffold max_threads default');
806
- requireText('.codex/config.toml', 'max_depth = 1', 'Codex scaffold max_depth default');
913
+ requireText('.codex/config.toml', 'intentionally avoids WF-MAX capacity defaults', 'Codex config compatibility note');
914
+ forbidActiveToml('.codex/config.toml', /^\[agents\]$/m, 'Codex project config agents table');
915
+ forbidActiveToml('.codex/config.toml', /^\s*max_concurrent_threads_per_session\s*=/m, 'Codex project config concurrent thread scalar');
916
+ forbidActiveToml('.codex/config.toml', /^\s*max_depth\s*=/m, 'Codex project config max_depth scalar');
917
+ forbidActiveToml('.codex/config.toml', /^\s*max_threads\s*=/m, 'Codex project config legacy max_threads scalar');
807
918
  requireText('opencode.json', '"$schema": "https://opencode.ai/config.json"', 'OpenCode config schema');
808
919
  requireText('opencode.json', '.claude/rules/ecc/common.md', 'OpenCode instructions reference to ECC rules');
920
+ requireText('opencode.json', '"subagent_depth": 2', 'OpenCode WF-MAX subagent nesting depth');
921
+ requireText('.opencode/commands/wf-max.md', 'MUST attempt native runtime subagent fan-out', 'OpenCode wf-max command fan-out attempt');
922
+ requireText('.opencode/commands/wf-max.md', 'subagent_depth >= 2', 'OpenCode wf-max command subagent depth requirement');
923
+ requireText('.opencode/commands/wf-max.md', 'fanoutAttempted: true', 'OpenCode wf-max command records fan-out attempt');
924
+ requireText('.opencode/agents/explore-manager.md', 'task:', 'OpenCode explore-manager task permission');
925
+ requireText('.opencode/agents/explore-manager.md', '"codebase-explorer": allow', 'OpenCode explore-manager child allowlist');
926
+ requireText('.opencode/agents/explore-manager.md', '"git status*": allow', 'OpenCode explore-manager read-only git status');
927
+ requireText('.opencode/agents/explore-manager.md', '"git diff*": allow', 'OpenCode explore-manager read-only git diff');
928
+ forbidText('.opencode/agents/explore-manager.md', '"git *": allow', 'OpenCode explore-manager broad git permission');
929
+ requireText('.opencode/agents/architect-manager.md', '"architect": allow', 'OpenCode architect-manager child allowlist');
930
+ requireText('.opencode/agents/implement-manager.md', '"implementer": allow', 'OpenCode implement-manager child allowlist');
931
+ forbidText('.opencode/agents/implement-manager.md', '"node *": allow', 'OpenCode implement-manager broad node permission');
932
+ forbidText('.opencode/agents/implement-manager.md', '"npm *": allow', 'OpenCode implement-manager broad npm permission');
933
+ requireText('.opencode/agents/review-manager.md', '"reviewer": allow', 'OpenCode review-manager child allowlist');
934
+ forbidText('.opencode/agents/review-manager.md', '"node *": allow', 'OpenCode review-manager broad node permission');
809
935
  requireText('Harness/README.md', '/wf-max', 'wf max router alias');
810
936
  requireText('Harness/README.md', 'WF-MAX.md', 'WF-MAX router reference');
811
937
  requireText('Harness/README.md', 'ACCEPTANCE_PROTOCOL.md', 'acceptance protocol router reference');
@@ -893,13 +1019,14 @@ requireText('.claude/commands/wf-help.md', '| `/wf-help` |', 'wf-help command ro
893
1019
  requireText('.claude/commands/wf-help.md', '| `/wf-max <task>` |', 'wf-help wf-max row');
894
1020
  requireText('.claude/commands/wf-help.md', '| `/wf-auto` |', 'wf-help wf-auto row');
895
1021
  requireText('.claude/commands/wf-help.md', '| `/wf-readme <task>` |', 'wf-help wf-readme row');
1022
+ forbidText('.claude/commands/wf-help.md', '.opencode/skills/', 'wf-help nonexistent OpenCode skills directory claim');
1023
+ forbidText('.opencode/commands/wf-help.md', '.opencode/skills/', 'OpenCode wf-help nonexistent skills directory claim');
896
1024
  requireText('Harness/README.md', '## Direct Commands', 'direct commands section');
897
1025
  requireText('Harness/README.md', '.claude/commands/wf-help.md', 'wf-help router reference');
898
1026
  requireText('Harness/specs/workflows/WF-MAX.md', 'three-layer architecture', 'CLAUDE.md three-layer role architecture');
899
- requireText('Harness/README.md', 'no runtime hook state', 'CLAUDE no-hook role enforcement statement');
900
- requireText('Harness/README.md', 'no runtime hook state', 'README no-hook runtime statement');
901
- requireText('Harness/specs/workflows/WF-AUTO.md', 'WF-AUTO Hook Exception', 'wf-auto-only hook exception');
902
- requireText('Harness/specs/workflows/WF-AUTO.md', 'only `/wf-auto` may use a runtime hook', 'wf-auto-only hook boundary');
1027
+ requireText('Harness/README.md', 'role enforcement has no runtime hook state', 'CLAUDE no-hook role enforcement statement');
1028
+ requireText('Harness/specs/workflows/WF-AUTO.md', 'Runtime Hook Boundaries', 'runtime hook boundaries');
1029
+ requireText('Harness/specs/workflows/WF-AUTO.md', 'only `/wf-auto` may use a runtime hook to drive auto-optimization', 'wf-auto-only hook boundary');
903
1030
  forbidText('CLAUDE.md', 'Enforced by hooks', 'WF-MAX hook enforcement claim');
904
1031
  forbidText('Harness/specs/workflows/WF.md', 'MUST use at least 3 distinct role passes', 'old WF role-pass minimum');
905
1032
  forbidText('Harness/specs/workflows/WF.md', 'at least three distinct role passes', 'old WF role-pass wording');
@@ -919,18 +1046,20 @@ const codexHookConfig = read('.codex/hooks.json');
919
1046
  const claudeSettings = read('.claude/settings.json');
920
1047
  requireText('.codex/hooks.json', '"SessionStart"', 'Codex startup-only update hook');
921
1048
  requireText('.claude/settings.json', '"SessionStart"', 'Claude startup-only update hook');
922
- forbidText('.codex/hooks.json', 'UserPromptSubmit', 'Codex turn-by-turn update hook');
1049
+ forbidText('.codex/hooks.json', 'UserPromptSubmit', 'Codex turn-by-turn status hook');
1050
+ forbidText('.codex/hooks.json', '"Stop"', 'Codex stop status hook');
1051
+ forbidText('.codex/hooks.json', 'wf-status.mjs', 'removed Codex WF status script');
923
1052
  forbidText('.claude/settings.json', 'UserPromptSubmit', 'Claude turn-by-turn update hook');
924
1053
  requireText('.opencode/plugins/harness-wf-status.mjs', 'opencode.startup', 'OpenCode startup-only update check');
925
1054
  forbidText('.opencode/plugins/harness-wf-status.mjs', "'chat.message'", 'OpenCode turn-by-turn update hook');
926
- if (codexHookConfig && !codexHookConfig.includes('wf-auto')) {
927
- errors.push('.codex/hooks.json may only exist for a wf-auto hook configuration');
1055
+ if (codexHookConfig && !codexHookConfig.includes('wf-auto-update-prompt.mjs')) {
1056
+ errors.push('.codex/hooks.json missing startup wf-auto update check');
928
1057
  }
929
1058
  if (claudeSettings.includes('"hooks"') && !claudeSettings.includes('wf-auto')) {
930
1059
  errors.push('.claude/settings.json hooks may only be used for wf-auto');
931
1060
  }
932
- if (read('.codex/config.toml').includes('hooks = true') && !codexHookConfig.includes('wf-auto')) {
933
- errors.push('.codex/config.toml may enable hooks only with a wf-auto hook configuration');
1061
+ if (read('.codex/config.toml').includes('hooks = true') && !codexHookConfig.includes('wf-auto-update-prompt.mjs')) {
1062
+ errors.push('.codex/config.toml may enable hooks only with a Harness hook configuration');
934
1063
  }
935
1064
 
936
1065
  // AGENTS.md must be a thin shim — no Harness/WF-MAX/command table content
@@ -966,6 +1095,17 @@ requireText('.claude/skills/wf-auto/SKILL.md', 'Harness/tasks/auto/PROGRESS.md',
966
1095
  requireText('.claude/skills/wf-auto/SKILL.md', 'evidence ledger path/summary', 'wf-auto skill return evidence ledger path');
967
1096
  if (fs.existsSync(path.join(root, '.claude/skills/wf-browser/SKILL.md'))) {
968
1097
  requireText('.claude/skills/wf-browser/SKILL.md', 'Cache Discipline', 'wf-browser skill cache discipline');
1098
+ requireText('.claude/skills/wf-browser/SKILL.md', 'Browser Evidence Contract', 'wf-browser browser evidence contract');
1099
+ requireText('.claude/skills/wf-browser/SKILL.md', 'Controllable UI Contract', 'wf-browser controllable UI contract');
1100
+ requireText('.claude/skills/wf-browser/SKILL.md', 'Browser Use CLI', 'wf-browser current Browser Use CLI guidance');
1101
+ requireText('.claude/skills/wf-browser/SKILL.md', 'browser-use --doctor', 'wf-browser Browser Use doctor command');
1102
+ requireText('.claude/skills/wf-browser/SKILL.md', 'browser-use skill', 'wf-browser Browser Use skill command');
1103
+ requireText('.claude/skills/wf-browser/SKILL.md', 'new_tab("https://example.com")', 'wf-browser Browser Use script-style example');
1104
+ requireText('.claude/skills/wf-browser/SKILL.md', 'capture_screenshot', 'wf-browser Browser Use screenshot helper');
1105
+ requireText('.claude/skills/wf-browser/SKILL.md', 'old `browser-use open/state/click/screenshot/input/wait` subcommands are removed', 'wf-browser removed old Browser Use subcommands');
1106
+ requireText('.claude/skills/wf-browser/SKILL.md', 'data-testid', 'wf-browser data-testid guidance');
1107
+ requireText('.claude/skills/wf-browser/SKILL.md', 'accessible labels/roles', 'wf-browser accessible selector guidance');
1108
+ requireText('.claude/skills/wf-browser/SKILL.md', 'inputs, buttons, filters, rows, empty/error/loading states', 'wf-browser controllable UI coverage targets');
969
1109
  }
970
1110
 
971
1111
  // Direct command checks
@@ -988,8 +1128,9 @@ for (const rel of ['.claude/commands/wf-update.md', '.opencode/commands/wf-updat
988
1128
  requireText('Harness/scripts/wf-update-check.mjs', 'releaseHighlights', 'wf-update-check release highlights metadata');
989
1129
  requireText('Harness/scripts/wf-update-check.mjs', 'updateReportRequired', 'wf-update-check user update report requirement');
990
1130
  requireText('.claude/commands/wf-help.md', 'direct command', 'wf-help wf-update direct command classification');
991
- requireText('.claude/commands/wf-help.md', '/wf-browser', 'wf-help optional browser workflow row');
992
- requireText('.opencode/commands/wf-help.md', '/wf-browser', 'OpenCode wf-help optional browser workflow row');
1131
+ requireText('.claude/commands/wf-help.md', '$wf-help', 'wf-help Codex compatibility usage');
1132
+ requireText('.claude/commands/wf-help.md', '/wf-browser', 'wf-help built-in browser workflow row');
1133
+ requireText('.opencode/commands/wf-help.md', '/wf-browser', 'OpenCode wf-help built-in browser workflow row');
993
1134
 
994
1135
  // wf-update skill must NOT claim Claude Code /wf-update as a skill invocation
995
1136
  // (allow mentions in the description/body that say "direct command" — those are correct)
@@ -1080,12 +1221,76 @@ requireText('CLAUDE.md', 'Harness/specs/workflows/WF-STATE.md', 'CLAUDE.md WF-ST
1080
1221
  requireText('CLAUDE.md', 'Harness/specs/protocols/TASK_ARCHIVE.md', 'CLAUDE.md TASK_ARCHIVE routing');
1081
1222
  requireText('Harness/README.md', 'WF-STATE.md', 'README WF-STATE routing');
1082
1223
  requireText('Harness/specs/protocols/TASK_ARCHIVE.md', 'archive-tasks.mjs', 'TASK_ARCHIVE script reference');
1224
+ requireText('Harness/specs/workflows/WF-STATE.md', 'task-state.mjs', 'WF-STATE task-state script contract');
1225
+ requireText('Harness/specs/protocols/TASK_ARCHIVE.md', 'task-state.mjs', 'TASK_ARCHIVE task-state script reference');
1226
+
1227
+ // Task-state consistency: prompt text is not authoritative for machine state.
1228
+ const rootTaskProgress = parseRootTaskProgress(read('Harness/PROGRESS.md'));
1229
+ const outerTaskSet = new Set(taskDirs.filter(name => !TASK_RESERVED.has(name) && !name.startsWith('_')));
1230
+ const rootTaskRows = new Set(rootTaskProgress.rows.map(row => row.id));
1231
+
1232
+ if (rootTaskProgress.activeTask && !outerTaskSet.has(rootTaskProgress.activeTask)) {
1233
+ taskStateIssue(`Harness/PROGRESS.md Active Task "${rootTaskProgress.activeTask}" does not exist under Harness/tasks/`);
1234
+ }
1235
+
1236
+ for (const row of rootTaskProgress.rows) {
1237
+ if (!outerTaskSet.has(row.id)) {
1238
+ taskStateIssue(`Harness/PROGRESS.md Task Index row has no outer task directory: ${row.id}`);
1239
+ }
1240
+ if (row.phase && !normalizeTaskPhase(row.phase)) {
1241
+ taskStateIssue(`Harness/PROGRESS.md Task Index row has unknown phase for ${row.id}: ${row.phase}`);
1242
+ }
1243
+ }
1244
+
1245
+ const activeStateTasks = [];
1246
+ for (const taskDir of outerTaskSet) {
1247
+ if (!rootTaskRows.has(taskDir)) {
1248
+ taskStateIssue(`Harness/tasks/${taskDir}/ is missing from Harness/PROGRESS.md Task Index`);
1249
+ }
1250
+
1251
+ const statePath = path.join(root, 'Harness', 'tasks', taskDir, 'STATE.json');
1252
+ if (!fs.existsSync(statePath)) {
1253
+ taskStateIssue(`Harness/tasks/${taskDir}/STATE.json is missing; run node Harness/scripts/task-state.mjs reconcile --apply`);
1254
+ continue;
1255
+ }
1256
+
1257
+ let state;
1258
+ try {
1259
+ state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
1260
+ } catch (err) {
1261
+ taskStateIssue(`Harness/tasks/${taskDir}/STATE.json is invalid JSON: ${err.message}`);
1262
+ continue;
1263
+ }
1264
+
1265
+ if (state.taskId && state.taskId !== taskDir) {
1266
+ taskStateIssue(`Harness/tasks/${taskDir}/STATE.json taskId "${state.taskId}" does not match directory`);
1267
+ }
1268
+ const status = normalizeTaskStatus(state.status);
1269
+ const phase = normalizeTaskPhase(state.phase);
1270
+ if (state.status && !status) {
1271
+ taskStateIssue(`Harness/tasks/${taskDir}/STATE.json has unknown status "${state.status}"`);
1272
+ }
1273
+ if (state.phase && !phase) {
1274
+ taskStateIssue(`Harness/tasks/${taskDir}/STATE.json has unknown phase "${state.phase}"`);
1275
+ }
1276
+ if (status === 'active') activeStateTasks.push(taskDir);
1277
+ if (status === 'active' && taskDir !== rootTaskProgress.activeTask) {
1278
+ taskStateIssue(`Harness/tasks/${taskDir}/STATE.json is active but Harness/PROGRESS.md Active Task is ${rootTaskProgress.activeTask || 'None'}; run node Harness/scripts/task-state.mjs reconcile --apply`);
1279
+ }
1280
+ if (taskDir === rootTaskProgress.activeTask && status && status !== 'active') {
1281
+ taskStateIssue(`Harness/PROGRESS.md Active Task points to ${taskDir}, but STATE.json status is "${status}"; run node Harness/scripts/task-state.mjs reconcile --apply`);
1282
+ }
1283
+ }
1284
+
1285
+ if (activeStateTasks.length > 1) {
1286
+ taskStateIssue(`Multiple STATE.json files are active: ${activeStateTasks.join(', ')}; run node Harness/scripts/task-state.mjs reconcile --apply`);
1287
+ }
1083
1288
 
1084
1289
  // Outer task capsule cap: keep Harness/tasks/ lean (see Harness/specs/protocols/TASK_ARCHIVE.md)
1085
1290
  const OUTER_TASK_CAP = 5;
1086
1291
  const outerTasks = taskDirs.filter(name => !TASK_RESERVED.has(name) && !name.startsWith('_'));
1087
1292
  if (outerTasks.length > OUTER_TASK_CAP) {
1088
- const capMsg = `Harness/tasks/ has ${outerTasks.length} outer task capsules (cap ${OUTER_TASK_CAP}); archive completed tasks with node Harness/scripts/archive-tasks.mjs --apply (see Harness/specs/protocols/TASK_ARCHIVE.md)`;
1293
+ const capMsg = `Harness/tasks/ has ${outerTasks.length} outer task capsules (cap ${OUTER_TASK_CAP}); archive completed tasks with node Harness/scripts/task-state.mjs archive --apply (compat: node Harness/scripts/archive-tasks.mjs --apply; see Harness/specs/protocols/TASK_ARCHIVE.md)`;
1089
1294
  if (strict) errors.push(capMsg);
1090
1295
  else console.warn(`Warning: ${capMsg}`);
1091
1296
  }
@@ -130,7 +130,6 @@ const BUILT_IN_AGENT_NAMES = [
130
130
  ];
131
131
 
132
132
  const BUILT_IN_SKILL_NAMES = [
133
- 'browser-e2e',
134
133
  'github-pr-review',
135
134
  'python-backend',
136
135
  'subagent-orchestrator',
@@ -142,6 +141,7 @@ const BUILT_IN_SKILL_NAMES = [
142
141
  'wf-auto',
143
142
  'wf-auto-spark',
144
143
  'wf-browser',
144
+ 'wf-help',
145
145
  'wf-learn',
146
146
  'wf-max',
147
147
  'wf-readme',
@@ -172,12 +172,12 @@ function isKnownFrameworkFile(file) {
172
172
  /** Directories to clean up if empty after file removal. */
173
173
  const CLEANUP_DIRS = [
174
174
  '.claude/agents',
175
- '.claude/skills/browser-e2e',
176
175
  '.claude/skills/github-pr-review',
177
176
  '.claude/skills/python-backend',
178
177
  '.claude/skills/wf-auto',
179
178
  '.claude/skills/wf-auto-spark',
180
179
  '.claude/skills/wf-browser',
180
+ '.claude/skills/wf-help',
181
181
  '.claude/skills/tdd',
182
182
  '.claude/skills/ts-react-frontend',
183
183
  '.claude/skills/ui-ux-review',
@@ -191,12 +191,12 @@ const CLEANUP_DIRS = [
191
191
  '.claude/skills/wf-remove',
192
192
  '.claude/skills/subagent-orchestrator',
193
193
  '.claude/skills',
194
- '.agents/skills/browser-e2e',
195
194
  '.agents/skills/github-pr-review',
196
195
  '.agents/skills/python-backend',
197
196
  '.agents/skills/wf-auto',
198
197
  '.agents/skills/wf-auto-spark',
199
198
  '.agents/skills/wf-browser',
199
+ '.agents/skills/wf-help',
200
200
  '.agents/skills/tdd',
201
201
  '.agents/skills/ts-react-frontend',
202
202
  '.agents/skills/ui-ux-review',
@@ -149,8 +149,11 @@ function optionalSkillFromSource(source) {
149
149
  return match ? match[1] : null;
150
150
  }
151
151
 
152
+ const RETIRED_OPTION_IDS = new Set(['browser-e2e']);
153
+
152
154
  function isInstalledOptionalFile(file, localVersion) {
153
155
  const skillId = optionalSkillFromSource(localVersion?.sources?.[file]);
156
+ if (skillId && RETIRED_OPTION_IDS.has(skillId)) return false;
154
157
  return Boolean(skillId && selectedOptionIds(localVersion).has(skillId));
155
158
  }
156
159
 
@@ -152,7 +152,7 @@ The harness validator checks for specific structural invariants. When comparing
152
152
  | `Harness/tasks/<id>/PROGRESS.md` | `## Current Goal`, `## Phase`, `## Heartbeat`, `## Loaded Context` headings |
153
153
  | `Harness/tasks/<id>/PLAN.md` | `## Tasks`, `## Parallel Dispatch`, `## Subagent Synthesis`, `## Verification` headings |
154
154
  | `Harness/specs/guides/SETUP.md` | Retained install/bootstrap guide for new projects, existing projects, legacy upgrades, and Harness update decisions. Keep "Install or Upgrade Path" present. |
155
- | `Harness/workflows/browser-e2e.md` (if installed as optional) | `data-testid`, `accessible labels/roles`, and `inputs, buttons, filters, rows, empty/error/loading states` requirement |
155
+ | `.claude/skills/wf-browser/SKILL.md` and `.agents/skills/wf-browser/SKILL.md` | Built-in browser workflow requires `data-testid`, `accessible labels/roles`, and `inputs, buttons, filters, rows, empty/error/loading states` guidance |
156
156
  | `Harness/workflows/ts-react-frontend.md` (if installed as optional) | Same UI selector contract as above |
157
157
 
158
158
  **Files that do NOT need manual merge when the path does not already exist (auto-created by harness):**
@@ -172,7 +172,7 @@ The generated file list is authoritative by path, not by stale count labels. Cur
172
172
  Optional workflow examples:
173
173
 
174
174
  ```bash
175
- npx create-harness-vibe-coding@latest my-app ./my-app -y --with browser-e2e,ts-react-frontend
175
+ npx create-harness-vibe-coding@latest my-app ./my-app -y --with ts-react-frontend,ui-ux-review
176
176
  npx create-harness-vibe-coding@latest my-app ./my-app -y --preset web-app
177
177
  npx create-harness-vibe-coding@latest my-app ./my-app -y --recommend superpowers,codegraph
178
178
  ```
@@ -1,6 +1,7 @@
1
- # TASK_ARCHIVE Task Archive Mechanism
1
+ # TASK_ARCHIVE - Task Archive Mechanism
2
2
 
3
- Purpose: keep `Harness/tasks/` lean by archiving completed/abandoned tasks while preserving all evidence.
3
+ Purpose: keep `Harness/tasks/` lean by archiving completed/abandoned tasks while
4
+ preserving all evidence.
4
5
 
5
6
  ## Archive Location
6
7
 
@@ -9,44 +10,62 @@ Archived tasks move to `Harness/tasks/_archive/YYYY/<task-id>/`.
9
10
 
10
11
  ## What Is Never Archived
11
12
 
12
- - `Harness/tasks/_template/` scaffold template, never moved.
13
- - `Harness/tasks/auto/` WF-AUTO permanent state capsule, never moved unless explicitly allowed by WF-AUTO docs.
14
- - `Harness/tasks/_archive/` the archive directory itself.
15
- - Active, blocked, in-progress, or needs-user-decision tasks.
16
- - Tasks whose STATE.json status is `active`, `blocked`, `in_progress`, `running`, `pending`, or `needs-user-decision`.
13
+ - `Harness/tasks/_template/` - scaffold template, never moved.
14
+ - `Harness/tasks/auto/` - WF-AUTO permanent state capsule, never moved unless
15
+ explicitly allowed by WF-AUTO docs.
16
+ - `Harness/tasks/_archive/` - the archive directory itself.
17
+ - Active, blocked, in-progress, running, pending, or needs-user-decision tasks.
18
+ - Tasks whose STATE.json status is `active`, `blocked`, `in_progress`,
19
+ `running`, `pending`, or `needs-user-decision`.
17
20
 
18
21
  ## What May Be Archived
19
22
 
20
- Tasks whose STATE.json or PROGRESS.md status is: `complete`, `verified`, `archived`, `abandoned`, `obsolete`, `done`, `closed`, or `closeout`.
23
+ Tasks whose STATE.json or reconciled phase is: `complete`, `verified`,
24
+ `archived`, `abandoned`, `obsolete`, `done`, `closed`, or `closeout`.
21
25
 
22
- The script reads STATE.json `status`/`phase` first, then falls back to the first `- Phase:`, `Phase:`, or `Current:` marker in the task's PROGRESS.md (first word wins). Ambiguous phases are never auto-archived.
26
+ `Harness/scripts/task-state.mjs` reads STATE.json `status`/`phase` first, then
27
+ uses root/task PROGRESS phases only as reconciliation evidence. Missing or
28
+ invalid STATE.json is skipped until
29
+ `node Harness/scripts/task-state.mjs reconcile --apply` creates or repairs
30
+ machine state.
23
31
 
24
32
  ## Archive Process
25
33
 
26
- 1. Verify the task is not active/blocked.
34
+ 1. Verify the task is not active/blocked/running/pending.
27
35
  2. Ensure `Harness/tasks/_archive/YYYY/` exists.
28
- 3. Move `Harness/tasks/<task-id>/` → `Harness/tasks/_archive/YYYY/<task-id>/`.
29
- 4. Update the moved STATE.json: `status` → `archived`, `phase` → `archived`.
36
+ 3. Move `Harness/tasks/<task-id>/` to
37
+ `Harness/tasks/_archive/YYYY/<task-id>/`.
38
+ 4. Update the moved STATE.json: `status` to `archived`, `phase` to `archived`.
30
39
  5. Update `Harness/tasks/_archive/INDEX.md`.
31
- 6. Update `Harness/PROGRESS.md` Task Index remove or annotate `(archived)`.
40
+ 6. Rewrite `Harness/PROGRESS.md` Task Index from the remaining non-archived
41
+ task capsules.
32
42
 
33
43
  ## Retention
34
44
 
35
45
  - Archived tasks retain: PLAN, PROGRESS, STATE, ARTIFACTS, NOTES.
36
46
  - Do NOT delete historical evidence.
37
- - `Harness/PROGRESS.md` keeps the last 5 non-archived task entries in the Task Index.
38
- - When outer task capsules exceed 5 completed/abandoned/obsolete, archive the oldest.
39
- - The validator (`Harness/scripts/validate-harness.mjs`) warns when `Harness/tasks/` holds more than 5 outer task capsules (excluding `_archive`, `_template`, `auto`) and fails in `--strict` mode.
47
+ - `Harness/PROGRESS.md` keeps the last 5 non-archived task entries in the Task
48
+ Index.
49
+ - When outer task capsules exceed 5 completed/abandoned/obsolete tasks, archive
50
+ the oldest safe tasks.
51
+ - The validator (`Harness/scripts/validate-harness.mjs`) warns when
52
+ `Harness/tasks/` holds more than 5 outer task capsules (excluding `_archive`,
53
+ `_template`, `auto`) and fails in `--strict` mode.
40
54
 
41
55
  ## Script
42
56
 
43
- Use `Harness/scripts/archive-tasks.mjs`:
44
- - Default: dry-run
45
- - `--apply` to execute
46
- - `--keep 5` to set the non-archived task threshold
47
- - `--task <task-id>` to archive a specific task
48
- - `--json` for machine-readable output
49
- - `node Harness/scripts/archive-tasks.mjs --dry-run --json` must always run
57
+ Use `Harness/scripts/task-state.mjs archive`:
58
+
59
+ - Default: dry-run.
60
+ - `--apply` to execute.
61
+ - `--keep 5` to set the non-archived task threshold.
62
+ - `--task <task-id>` to archive a specific task.
63
+ - `--json` for machine-readable output.
64
+ - Run `node Harness/scripts/task-state.mjs archive --dry-run --json` before
65
+ applying a risky cleanup.
66
+
67
+ Compatibility entry: `Harness/scripts/archive-tasks.mjs` delegates to
68
+ `task-state.mjs archive` and accepts the same archive flags.
50
69
 
51
70
  ## Safety Rules
52
71
 
@@ -54,3 +73,4 @@ Use `Harness/scripts/archive-tasks.mjs`:
54
73
  - Confirm target is within `Harness/tasks/` before any move.
55
74
  - Do NOT recursively delete.
56
75
  - Tasks with `needs-user-decision` status are never auto-archived.
76
+ - Missing or invalid STATE.json is never archived without reconciliation.
@@ -13,7 +13,7 @@ Use when work needs parallel reading, independent review, cross-layer analysis,
13
13
  - Important assumptions, decisions, blockers, evidence, and handoffs must be written to `Harness/tasks/<task-id>/PROGRESS.md` and `Harness/tasks/<task-id>/PLAN.md`, the current feature doc, `Harness/MEMORY.md`, or `Harness/memory/*` as appropriate.
14
14
  - Resumable state is governed by [WF-STATE.md](../workflows/WF-STATE.md). On session start, the controller reads STATE.json before building a fresh dispatch table.
15
15
  - PRD-derived Acceptance Criteria are the source of truth. Dispatch packets must carry the relevant AC IDs and contracts.
16
- - Agent count: default (non-WF) <=3 active agents; `/wf` selects a tier dynamically per [WF-KERNEL.md](../workflows/WF-KERNEL.md) (Light/Standard/Full); `/wf-max` inherits the selected tier and adds maximum safe fan-out (WF-Max-Useful default; WF-Max-Strict explicit only), removing the Harness default cap through the span formula. Real concurrency is still bounded by runtime thread budget, config, billing, and local resources. Use current runtime subagents first, close completed agents, then peer-CLI overflow (`claude -p`, `codex exec`, or `opencode run --agent <role> --dir .`). Generated Codex config defaults to `agents.max_threads = 12` and `agents.max_depth = 1`; ask the user before raising `agents.max_threads` above that default. See [WF.md](../workflows/WF.md) and [WF-MAX.md](../workflows/WF-MAX.md).
16
+ - Agent count: default (non-WF) <=3 active agents; `/wf` selects a tier dynamically per [WF-KERNEL.md](../workflows/WF-KERNEL.md) (Light/Standard/Full); `/wf-max` inherits the selected tier and adds maximum safe fan-out (WF-Max-Useful default; WF-Max-Strict explicit only), removing the Harness default cap through the span formula. WF-MAX must attempt native subagent fan-out and record `fanoutAttempted: true` before any solo fallback. Real concurrency is still bounded by runtime thread budget, config, billing, and local resources. Use current runtime subagents first, close completed agents, then peer-CLI overflow (`claude -p`, `codex exec`, or `opencode run --agent <role> --dir .`). Do not scaffold Codex scalar `[agents]` capacity fields into project `.codex/config.toml`; probe the installed runtime and manage Codex caps through the dispatch ledger unless the installed version accepts the config shape. Generated OpenCode config defaults to `subagent_depth = 2` for manager -> worker nesting. See [WF.md](../workflows/WF.md) and [WF-MAX.md](../workflows/WF-MAX.md).
17
17
  - Read-only agents may run in parallel.
18
18
  - Writing agents run serially unless write sets are disjoint.
19
19
  - Use a worktree when two agents may touch overlapping files or long-running branches.
@@ -68,7 +68,7 @@ Objective: <one-sentence goal>
68
68
  TaskType: ui-browser | api-backend | architecture-migration | docs-readme | dependency-sdk | bug-fix | refactor | chore
69
69
  ModelTier: small-fast | standard | high-reasoning
70
70
  AgentName: <the agent file name, e.g. task-scribe, codebase-explorer, implementer>
71
- Skills: <which skills to activate, e.g. tdd, browser-e2e>
71
+ Skills: <which skills to activate, e.g. tdd, wf-browser>
72
72
  ECC: <which ECC rules to load, e.g. web/design-quality.md, python/fastapi.md>
73
73
  PRD: <path or task PLAN section containing Mini PRD>
74
74
  Acceptance IDs: <AC-001, AC-002, or "none" for non-behavioral work>