create-harness-vibe-coding 0.8.7 → 0.8.9

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 (126) hide show
  1. package/README-CN.md +163 -105
  2. package/README.md +179 -244
  3. package/bin/create-harness-vibe-coding.js +2 -2
  4. package/docs/images/harness-architecture-light.png +0 -0
  5. package/docs/images/harness-architecture.drawio +164 -0
  6. package/docs/images/harness-icon.png +0 -0
  7. package/package.json +47 -44
  8. package/src/generator.js +41 -5
  9. package/src/index.js +86 -13
  10. package/src/prompts.js +37 -37
  11. package/templates/common/.claude/agents/architect-manager.md +45 -45
  12. package/templates/common/.claude/agents/architect.md +31 -31
  13. package/templates/common/.claude/agents/codebase-explorer.md +45 -0
  14. package/templates/common/.claude/agents/context-master.md +75 -75
  15. package/templates/common/.claude/agents/debugger.md +41 -41
  16. package/templates/common/.claude/agents/docs-researcher.md +41 -41
  17. package/templates/common/.claude/agents/explore-manager.md +41 -41
  18. package/templates/common/.claude/agents/implement-manager.md +49 -49
  19. package/templates/common/.claude/agents/implementer.md +40 -40
  20. package/templates/common/.claude/agents/memory-master.md +82 -64
  21. package/templates/common/.claude/agents/planner.md +34 -34
  22. package/templates/common/.claude/agents/researcher.md +41 -41
  23. package/templates/common/.claude/agents/review-manager.md +56 -56
  24. package/templates/common/.claude/agents/reviewer.md +34 -34
  25. package/templates/common/.claude/agents/task-scribe.md +70 -0
  26. package/templates/common/.claude/agents/verifier.md +29 -29
  27. package/templates/common/.claude/commands/wf-help.md +9 -5
  28. package/templates/common/.claude/commands/wf-update.md +24 -0
  29. package/templates/common/.claude/rules/ecc/common.md +57 -44
  30. package/templates/common/.claude/settings.json +13 -0
  31. package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +8 -4
  32. package/templates/common/.claude/skills/wf/SKILL.md +15 -8
  33. package/templates/common/.claude/skills/wf-auto/SKILL.md +10 -7
  34. package/templates/common/.claude/skills/wf-learn/SKILL.md +9 -2
  35. package/templates/common/.claude/skills/wf-max/SKILL.md +23 -5
  36. package/templates/common/.claude/skills/wf-readme/SKILL.md +49 -49
  37. package/templates/common/.claude/skills/wf-remove/SKILL.md +7 -7
  38. package/templates/common/.claude/skills/wf-update/SKILL.md +15 -2
  39. package/templates/common/.codex/hooks.json +17 -0
  40. package/templates/common/.harness-version +130 -45
  41. package/templates/common/.opencode/agents/architect-manager.md +52 -0
  42. package/templates/common/.opencode/agents/architect.md +35 -0
  43. package/templates/common/.opencode/agents/codebase-explorer.md +45 -0
  44. package/templates/common/.opencode/agents/context-master.md +81 -0
  45. package/templates/common/.opencode/agents/debugger.md +43 -0
  46. package/templates/common/.opencode/agents/docs-researcher.md +42 -0
  47. package/templates/common/.opencode/agents/explore-manager.md +49 -0
  48. package/templates/common/.opencode/agents/implement-manager.md +56 -0
  49. package/templates/common/.opencode/agents/implementer.md +42 -0
  50. package/templates/common/.opencode/agents/memory-master.md +88 -0
  51. package/templates/common/.opencode/agents/planner.md +38 -0
  52. package/templates/common/.opencode/agents/reflector.md +39 -0
  53. package/templates/common/.opencode/agents/researcher.md +42 -0
  54. package/templates/common/.opencode/agents/review-manager.md +63 -0
  55. package/templates/common/.opencode/agents/reviewer.md +37 -0
  56. package/templates/common/.opencode/agents/task-scribe.md +70 -0
  57. package/templates/common/.opencode/agents/tdd-guide.md +83 -0
  58. package/templates/common/.opencode/agents/test-writer.md +54 -0
  59. package/templates/common/.opencode/agents/verifier.md +37 -0
  60. package/templates/common/.opencode/commands/wf-auto-spark.md +15 -0
  61. package/templates/common/.opencode/commands/wf-auto.md +15 -0
  62. package/templates/common/.opencode/commands/wf-help.md +27 -0
  63. package/templates/common/.opencode/commands/wf-learn.md +15 -0
  64. package/templates/common/.opencode/commands/wf-max.md +15 -0
  65. package/templates/common/.opencode/commands/wf-readme.md +15 -0
  66. package/templates/common/.opencode/commands/wf-remove.md +15 -0
  67. package/templates/common/.opencode/commands/wf-review.md +15 -0
  68. package/templates/common/.opencode/commands/wf-update.md +24 -0
  69. package/templates/common/.opencode/commands/wf.md +15 -0
  70. package/templates/common/.opencode/plugins/harness-wf-status.mjs +135 -0
  71. package/templates/common/AGENTS.md +2 -29
  72. package/templates/common/CLAUDE.md +114 -88
  73. package/templates/common/Harness/ACCEPTANCE_PROTOCOL.md +2 -2
  74. package/templates/common/{MEMORY.md → Harness/MEMORY.md} +17 -4
  75. package/templates/common/Harness/MEMORY_PROTOCOL.md +80 -30
  76. package/templates/common/Harness/PROGRESS.md +17 -17
  77. package/templates/common/Harness/README.md +58 -19
  78. package/templates/common/{SETUP.md → Harness/SETUP.md} +278 -276
  79. package/templates/common/Harness/TASK_ARCHIVE.md +56 -0
  80. package/templates/common/Harness/WF-AUTO-ANGLES.md +170 -0
  81. package/templates/common/Harness/WF-AUTO-SPARK.md +10 -19
  82. package/templates/common/Harness/WF-AUTO.md +93 -167
  83. package/templates/common/Harness/WF-KERNEL.md +189 -0
  84. package/templates/common/Harness/WF-MAX.md +60 -328
  85. package/templates/common/Harness/WF-STATE.md +83 -0
  86. package/templates/common/Harness/WF.md +117 -237
  87. package/templates/common/Harness/agent-workflow.md +2 -2
  88. package/templates/common/Harness/architecture.md +124 -124
  89. package/templates/common/Harness/context-loading.md +111 -111
  90. package/templates/common/Harness/dispatch.md +43 -35
  91. package/templates/common/Harness/extension.md +66 -66
  92. package/templates/common/Harness/lifecycle.md +20 -20
  93. package/templates/common/Harness/research/PRD.md +56 -56
  94. package/templates/common/Harness/research/README.md +169 -169
  95. package/templates/common/Harness/research/research-results.md +66 -66
  96. package/templates/common/Harness/scripts/archive-tasks.mjs +239 -0
  97. package/templates/common/{scripts → Harness/scripts}/scan-clean.mjs +443 -416
  98. package/templates/common/{scripts → Harness/scripts}/validate-harness.mjs +691 -452
  99. package/templates/common/Harness/scripts/wf-auto-update-prompt.mjs +258 -0
  100. package/templates/common/{scripts → Harness/scripts}/wf-remove.mjs +56 -39
  101. package/templates/common/{scripts → Harness/scripts}/wf-update-check.mjs +632 -599
  102. package/templates/common/Harness/subagents.md +215 -214
  103. package/templates/common/Harness/tasks/_template/ARTIFACTS.md +2 -2
  104. package/templates/common/Harness/tasks/_template/NOTES.md +2 -2
  105. package/templates/common/Harness/tasks/_template/PLAN.md +5 -0
  106. package/templates/common/Harness/tasks/_template/STATE.json +23 -0
  107. package/templates/common/README.md +37 -37
  108. package/templates/common/memory/agent-lessons-patterns.md +22 -21
  109. package/templates/common/memory/routes.md +43 -0
  110. package/templates/common/memory/startup-hints.md +32 -0
  111. package/templates/common/memory/tool-usage-reflections.md +22 -21
  112. package/templates/common/memory/user-corrections-preferences.md +23 -21
  113. package/templates/common/opencode.json +19 -0
  114. package/templates/optional/catalog.json +49 -33
  115. package/templates/optional/skills/browser-e2e/.claude/skills/browser-e2e/SKILL.md +42 -42
  116. package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +193 -193
  117. package/templates/optional/skills/browser-e2e/.opencode/commands/wf-browser.md +15 -0
  118. package/templates/optional/skills/browser-e2e/Harness/workflows/browser-e2e.md +48 -48
  119. package/templates/optional/skills/github-pr-review/.claude/skills/github-pr-review/SKILL.md +40 -40
  120. package/templates/optional/skills/github-pr-review/Harness/workflows/github-pr-review.md +28 -28
  121. package/templates/optional/skills/python-backend/.claude/skills/python-backend/SKILL.md +40 -40
  122. package/templates/optional/skills/python-backend/Harness/workflows/python-backend.md +34 -34
  123. package/templates/optional/skills/ts-react-frontend/.claude/skills/ts-react-frontend/SKILL.md +43 -43
  124. package/templates/optional/skills/ts-react-frontend/Harness/workflows/ts-react-frontend.md +34 -34
  125. package/templates/optional/skills/ui-ux-review/.claude/skills/ui-ux-review/SKILL.md +40 -40
  126. package/templates/optional/skills/ui-ux-review/Harness/workflows/ui-ux-review.md +26 -26
@@ -1,63 +1,78 @@
1
- #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
-
5
- const root = process.cwd();
6
- const args = new Set(process.argv.slice(2));
7
- const strict = args.has('--strict') || args.has('--post-bootstrap');
8
-
9
- if (args.has('--help') || args.has('-h')) {
10
- console.log(`Usage: node Harness/scripts/validate-harness.mjs [--strict]
11
-
12
- Default mode checks scaffold structure, links, agents, and skills.
13
- --strict also fails when project fact docs still contain unresolved {{TOKEN}} placeholders.
14
- Literal explanatory {{...}} text is allowed.`);
15
- process.exit(0);
16
- }
17
-
18
- const commonAgents = [
19
- 'researcher',
20
- 'docs-researcher',
21
- 'planner',
22
- 'architect',
23
- 'test-writer',
24
- 'implementer',
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const root = process.cwd();
6
+ const args = new Set(process.argv.slice(2));
7
+ const strict = args.has('--strict') || args.has('--post-bootstrap');
8
+
9
+ if (args.has('--help') || args.has('-h')) {
10
+ console.log(`Usage: node Harness/scripts/validate-harness.mjs [--strict]
11
+
12
+ Default mode checks scaffold structure, links, agents, and skills.
13
+ --strict also fails when project fact docs still contain unresolved {{TOKEN}} placeholders.
14
+ Literal explanatory {{...}} text is allowed.`);
15
+ process.exit(0);
16
+ }
17
+
18
+ const commonAgents = [
19
+ 'task-scribe',
20
+ 'codebase-explorer',
21
+ 'researcher',
22
+ 'docs-researcher',
23
+ 'planner',
24
+ 'architect',
25
+ 'test-writer',
26
+ 'implementer',
25
27
  'debugger',
26
28
  'reviewer',
27
29
  'verifier',
28
30
  'reflector',
29
31
  'memory-master',
30
- 'context-master',
31
- 'explore-manager',
32
- 'architect-manager',
33
- 'implement-manager',
34
- 'review-manager',
35
- ];
36
-
32
+ 'context-master',
33
+ 'explore-manager',
34
+ 'architect-manager',
35
+ 'implement-manager',
36
+ 'review-manager',
37
+ ];
38
+
37
39
  const commonSkills = [
38
40
  'wf',
39
41
  'tdd',
40
42
  'wf-update',
41
43
  'wf-max',
42
44
  'wf-review',
43
- 'wf-learn',
44
- 'subagent-orchestrator',
45
- 'wf-readme',
46
- 'wf-remove',
47
- 'wf-auto',
48
- 'wf-auto-spark',
49
- ];
50
-
51
- const memoryFiles = [
52
- 'Harness/memory/tool-usage-reflections.md',
53
- 'Harness/memory/user-corrections-preferences.md',
54
- 'Harness/memory/agent-lessons-patterns.md',
55
- ];
56
-
57
- const required = [
58
- 'AGENTS.md',
59
- 'CLAUDE.md',
60
- 'README.md',
45
+ 'wf-learn',
46
+ 'subagent-orchestrator',
47
+ 'wf-readme',
48
+ 'wf-remove',
49
+ 'wf-auto',
50
+ 'wf-auto-spark',
51
+ ];
52
+
53
+ const opencodeWorkflowCommands = [
54
+ 'wf',
55
+ 'wf-max',
56
+ 'wf-auto',
57
+ 'wf-auto-spark',
58
+ 'wf-learn',
59
+ 'wf-review',
60
+ 'wf-readme',
61
+ 'wf-remove',
62
+ ];
63
+
64
+ const memoryFiles = [
65
+ 'Harness/memory/startup-hints.md',
66
+ 'Harness/memory/routes.md',
67
+ 'Harness/memory/tool-usage-reflections.md',
68
+ 'Harness/memory/user-corrections-preferences.md',
69
+ 'Harness/memory/agent-lessons-patterns.md',
70
+ ];
71
+
72
+ const required = [
73
+ 'AGENTS.md',
74
+ 'CLAUDE.md',
75
+ 'README.md',
61
76
  'Harness/MEMORY.md',
62
77
  'Harness/WF.md',
63
78
  'Harness/WF-MAX.md',
@@ -75,66 +90,76 @@ const required = [
75
90
  'Harness/templates/VALIDATION_REPORT.template.md',
76
91
  ...memoryFiles,
77
92
  '.codex/config.toml',
93
+ 'opencode.json',
78
94
  '.claude/settings.json',
79
95
  '.claude/commands/wf-help.md',
96
+ '.claude/commands/wf-update.md',
97
+ '.opencode/commands/wf-help.md',
98
+ '.opencode/commands/wf-update.md',
99
+ ...opencodeWorkflowCommands.map(command => `.opencode/commands/${command}.md`),
80
100
  '.claude/rules/ecc/common.md',
81
- ...commonAgents.map(agent => `.claude/agents/${agent}.md`),
82
- ...commonSkills.map(skill => `.claude/skills/${skill}/SKILL.md`),
83
- ...commonSkills.map(skill => `.agents/skills/${skill}/SKILL.md`),
84
- 'Harness/README.md',
85
- 'Harness/PROGRESS.md',
86
- 'Harness/lifecycle.md',
87
- 'Harness/subagents.md',
88
- 'Harness/dispatch.md',
89
- 'Harness/extension.md',
90
- 'Harness/context-loading.md',
91
- 'Harness/agent-workflow.md',
92
- 'Harness/architecture.md',
93
- 'Harness/research/README.md',
94
- 'Harness/research/research-results.md',
101
+ ...commonAgents.map(agent => `.claude/agents/${agent}.md`),
102
+ ...commonAgents.map(agent => `.opencode/agents/${agent}.md`),
103
+ ...commonSkills.map(skill => `.claude/skills/${skill}/SKILL.md`),
104
+ ...commonSkills.map(skill => `.agents/skills/${skill}/SKILL.md`),
105
+ 'Harness/README.md',
106
+ 'Harness/PROGRESS.md',
107
+ 'Harness/lifecycle.md',
108
+ 'Harness/subagents.md',
109
+ 'Harness/dispatch.md',
110
+ 'Harness/extension.md',
111
+ 'Harness/context-loading.md',
112
+ 'Harness/WF-KERNEL.md',
113
+ 'Harness/agent-workflow.md',
114
+ 'Harness/architecture.md',
115
+ 'Harness/research/README.md',
116
+ 'Harness/research/research-results.md',
95
117
  'Harness/research/PRD.md',
96
118
  'Harness/scripts/wf-update-check.mjs',
97
- 'Harness/scripts/wf-remove.mjs',
98
- 'Harness/scripts/scan-clean.mjs',
99
- 'Harness/.harness-version',
100
- ];
101
-
102
- const projectFacts = [
103
- 'Harness/PROGRESS.md',
104
- 'Harness/research/PRD.md',
105
- 'Harness/research/research-results.md',
106
- 'Harness/architecture.md',
107
- ];
108
-
109
- const contextPacks = [
110
- 'Explorer Pass:',
111
- 'Planner:',
112
- 'Researcher:',
113
- 'Docs Researcher:',
114
- 'Architect:',
115
- 'Test Writer:',
116
- 'Implementer', // matches "Implementer:", "Implementer (Frontend):", "Implementer (Backend):"
117
- 'Reviewer:',
118
- 'Debugger:',
119
- 'Verifier:',
120
- 'Memory Master:',
121
- 'Context Master:',
122
- ];
123
-
124
- const durableCommunicationDocs = [
125
- 'Harness/README.md',
126
- 'Harness/subagents.md',
127
- 'Harness/dispatch.md',
128
- 'Harness/context-loading.md',
129
- ];
130
-
131
- const errors = [];
132
-
133
- function read(rel) {
134
- const file = path.join(root, rel);
135
- return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
136
- }
137
-
119
+ 'Harness/scripts/wf-remove.mjs',
120
+ 'Harness/scripts/scan-clean.mjs',
121
+ 'Harness/scripts/archive-tasks.mjs',
122
+ 'Harness/WF-STATE.md',
123
+ 'Harness/TASK_ARCHIVE.md',
124
+ 'Harness/.harness-version',
125
+ ];
126
+
127
+ const projectFacts = [
128
+ 'Harness/PROGRESS.md',
129
+ 'Harness/research/PRD.md',
130
+ 'Harness/research/research-results.md',
131
+ 'Harness/architecture.md',
132
+ ];
133
+
134
+ const contextPacks = [
135
+ 'Explorer Pass:',
136
+ 'Planner:',
137
+ 'Researcher:',
138
+ 'Docs Researcher:',
139
+ 'Architect:',
140
+ 'Test Writer:',
141
+ 'Implementer', // matches "Implementer:", "Implementer (Frontend):", "Implementer (Backend):"
142
+ 'Reviewer:',
143
+ 'Debugger:',
144
+ 'Verifier:',
145
+ 'Memory Master:',
146
+ 'Context Master:',
147
+ ];
148
+
149
+ const durableCommunicationDocs = [
150
+ 'Harness/README.md',
151
+ 'Harness/subagents.md',
152
+ 'Harness/dispatch.md',
153
+ 'Harness/context-loading.md',
154
+ ];
155
+
156
+ const errors = [];
157
+
158
+ function read(rel) {
159
+ const file = path.join(root, rel);
160
+ return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
161
+ }
162
+
138
163
  function requireText(rel, text, label = text) {
139
164
  const body = read(rel);
140
165
  if (body && !body.includes(text)) errors.push(`${rel} missing ${label}`);
@@ -145,85 +170,85 @@ function forbidText(rel, text, label = text) {
145
170
  if (body && body.includes(text)) errors.push(`${rel} contains forbidden ${label}`);
146
171
  }
147
172
 
148
- function frontmatterField(text, field) {
149
- const match = text.match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
150
- return match ? match[1].trim() : '';
151
- }
152
-
153
- function listDirectories(rel) {
154
- const dir = path.join(root, rel);
155
- if (!fs.existsSync(dir)) return [];
156
- return fs.readdirSync(dir, { withFileTypes: true })
157
- .filter(entry => entry.isDirectory())
158
- .map(entry => entry.name);
159
- }
160
-
161
- function listMarkdownFiles(rel) {
162
- const dir = path.join(root, rel);
163
- if (!fs.existsSync(dir)) return [];
164
- const normalizedRel = rel.replaceAll(path.sep, '/');
165
- return fs.readdirSync(dir, { withFileTypes: true })
166
- .filter(entry => entry.isFile() && entry.name.endsWith('.md'))
167
- .map(entry => `${normalizedRel}/${entry.name}`);
168
- }
169
-
170
- // Task naming convention: task-<verb>-<noun>[-detail], kebab-case, ≤46 chars
171
- // Prefix "task-" required for new tasks. Existing tasks without prefix grandfathered (warning only).
172
- // Reserved: _template (system), auto (auto-mode capsule)
173
- const TASK_NAME_RE = /^task-[a-z]+(-[a-z0-9]+){1,4}$/;
174
- const TASK_NAME_MAX = 46; // "task-" (5) + ≤40 chars body + 1 safety = 46
175
- const TASK_RESERVED = new Set(['_template', 'auto']);
176
-
177
- function validateTaskName(name, strict) {
178
- if (TASK_RESERVED.has(name)) return null;
179
- if (name.startsWith('_')) return `Task name "${name}" — leading underscore reserved for system dirs`;
180
- if (name.length > TASK_NAME_MAX) return `Task name "${name}" — ${name.length} chars, max ${TASK_NAME_MAX}`;
181
- if (!TASK_NAME_RE.test(name)) {
182
- // Grandfather: existing tasks without "task-" prefix get a warning, not an error
183
- if (/^[a-z]+(-[a-z0-9]+){1,4}$/.test(name)) {
184
- if (strict) return `Task name "${name}" — missing "task-" prefix (required for new tasks). Rename to "task-${name}".`;
185
- return null; // non-strict: allow grandfathered names
186
- }
187
- return `Task name "${name}" — must be task-<verb>-<noun>[-detail], kebab-case, 2-5 words after prefix`;
188
- }
189
- return null;
190
- }
191
-
192
- function unresolvedTemplatePlaceholders(text) {
193
- const placeholders = [];
194
- const pattern = /\{\{([^{}\r\n]+)\}\}/g;
195
-
196
- for (const match of text.matchAll(pattern)) {
197
- const token = match[1].trim();
198
- if (token === '...') continue;
199
- placeholders.push(`{{${token}}}`);
200
- }
201
-
202
- return [...new Set(placeholders)];
203
- }
204
-
205
- function registeredSkillFiles(...texts) {
206
- const files = new Set();
207
- const pattern = /(?:\.\.\/)?((?:\.claude|\.agents)\/skills\/[a-z0-9-]+\/SKILL\.md)/g;
208
-
209
- for (const text of texts) {
210
- for (const match of text.matchAll(pattern)) {
211
- files.add(match[1]);
212
- }
213
- }
214
-
215
- return [...files].sort();
216
- }
217
-
173
+ function frontmatterField(text, field) {
174
+ const match = text.match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
175
+ return match ? match[1].trim() : '';
176
+ }
177
+
178
+ function listDirectories(rel) {
179
+ const dir = path.join(root, rel);
180
+ if (!fs.existsSync(dir)) return [];
181
+ return fs.readdirSync(dir, { withFileTypes: true })
182
+ .filter(entry => entry.isDirectory())
183
+ .map(entry => entry.name);
184
+ }
185
+
186
+ function listMarkdownFiles(rel) {
187
+ const dir = path.join(root, rel);
188
+ if (!fs.existsSync(dir)) return [];
189
+ const normalizedRel = rel.replaceAll(path.sep, '/');
190
+ return fs.readdirSync(dir, { withFileTypes: true })
191
+ .filter(entry => entry.isFile() && entry.name.endsWith('.md'))
192
+ .map(entry => `${normalizedRel}/${entry.name}`);
193
+ }
194
+
195
+ // Task naming convention: task-<verb>-<noun>[-detail], kebab-case, ≤46 chars
196
+ // Prefix "task-" required for new tasks. Existing tasks without prefix grandfathered (warning only).
197
+ // Reserved: _template (system), auto (auto-mode capsule)
198
+ const TASK_NAME_RE = /^task-[a-z]+(-[a-z0-9]+){1,4}$/;
199
+ const TASK_NAME_MAX = 46; // "task-" (5) + ≤40 chars body + 1 safety = 46
200
+ const TASK_RESERVED = new Set(['_template', 'auto', '_archive']);
201
+
202
+ function validateTaskName(name, strict) {
203
+ if (TASK_RESERVED.has(name)) return null;
204
+ if (name.startsWith('_')) return `Task name "${name}" — leading underscore reserved for system dirs`;
205
+ if (name.length > TASK_NAME_MAX) return `Task name "${name}" — ${name.length} chars, max ${TASK_NAME_MAX}`;
206
+ if (!TASK_NAME_RE.test(name)) {
207
+ // Grandfather: existing tasks without "task-" prefix get a warning, not an error
208
+ if (/^[a-z]+(-[a-z0-9]+){1,4}$/.test(name)) {
209
+ if (strict) return `Task name "${name}" — missing "task-" prefix (required for new tasks). Rename to "task-${name}".`;
210
+ return null; // non-strict: allow grandfathered names
211
+ }
212
+ return `Task name "${name}" — must be task-<verb>-<noun>[-detail], kebab-case, 2-5 words after prefix`;
213
+ }
214
+ return null;
215
+ }
216
+
217
+ function unresolvedTemplatePlaceholders(text) {
218
+ const placeholders = [];
219
+ const pattern = /\{\{([^{}\r\n]+)\}\}/g;
220
+
221
+ for (const match of text.matchAll(pattern)) {
222
+ const token = match[1].trim();
223
+ if (token === '...') continue;
224
+ placeholders.push(`{{${token}}}`);
225
+ }
226
+
227
+ return [...new Set(placeholders)];
228
+ }
229
+
230
+ function registeredSkillFiles(...texts) {
231
+ const files = new Set();
232
+ const pattern = /(?:\.\.\/)?((?:\.claude|\.agents)\/skills\/[a-z0-9-]+\/SKILL\.md)/g;
233
+
234
+ for (const text of texts) {
235
+ for (const match of text.matchAll(pattern)) {
236
+ files.add(match[1]);
237
+ }
238
+ }
239
+
240
+ return [...files].sort();
241
+ }
242
+
218
243
  function registeredWorkflowFiles(...texts) {
219
244
  const files = new Set();
220
245
  const pattern = /(?:Harness\/)?workflows\/([A-Za-z0-9._-]+\.md)/g;
221
-
222
- for (const text of texts) {
223
- for (const match of text.matchAll(pattern)) {
224
- files.add(`Harness/workflows/${match[1]}`);
225
- }
226
- }
246
+
247
+ for (const text of texts) {
248
+ for (const match of text.matchAll(pattern)) {
249
+ files.add(`Harness/workflows/${match[1]}`);
250
+ }
251
+ }
227
252
 
228
253
  return [...files].sort();
229
254
  }
@@ -236,13 +261,14 @@ function listedWorkflowCommands(...texts) {
236
261
  for (const match of text.matchAll(pattern)) {
237
262
  const command = `/${match[1]}`;
238
263
  if (command === '/wf-help') continue;
264
+ if (command === '/wf-browser') continue; // optional workflow, not a required skill
239
265
  commands.add(command);
240
266
  }
241
267
  }
242
268
 
243
269
  return [...commands].sort();
244
270
  }
245
-
271
+
246
272
  for (const rel of required) {
247
273
  if (!fs.existsSync(path.join(root, rel))) {
248
274
  errors.push(`missing required file: ${rel}`);
@@ -262,140 +288,169 @@ for (const rel of removedHookArtifacts) {
262
288
  errors.push(`removed hook artifact should not exist: ${rel}`);
263
289
  }
264
290
  }
265
-
266
- // Task capsule template files
267
- const taskTemplateDir = path.join(root, 'Harness', 'tasks', '_template');
268
- if (!fs.existsSync(taskTemplateDir)) {
269
- errors.push('missing directory: Harness/tasks/_template/');
270
- } else {
271
- for (const f of ['PROGRESS.md', 'PLAN.md']) {
272
- if (!fs.existsSync(path.join(taskTemplateDir, f))) {
273
- errors.push(`missing task template file: Harness/tasks/_template/${f}`);
274
- }
275
- }
276
- }
277
-
278
- // Cross-reference: DONE files in task PLAN.md must exist on disk
279
- const taskDirs = fs.existsSync(path.join(root, 'Harness', 'tasks'))
280
- ? fs.readdirSync(path.join(root, 'Harness', 'tasks'), { withFileTypes: true })
281
- .filter(e => e.isDirectory() && e.name !== '_template')
282
- .map(e => e.name)
283
- : [];
284
- for (const taskDir of taskDirs) {
285
- // Validate task naming convention
286
- const nameErr = validateTaskName(taskDir, strict);
287
- if (nameErr) errors.push(nameErr);
288
-
289
- const planPath = `Harness/tasks/${taskDir}/PLAN.md`;
290
- const planText = read(planPath);
291
- if (!planText) continue;
292
- const donePattern = /`([^`]+\.(?:md|mjs|js|ts|json|html|css))`[^\n]*DONE/gi;
293
- for (const match of planText.matchAll(donePattern)) {
294
- const claimedFile = match[1];
295
- if (!fs.existsSync(path.join(root, claimedFile))) {
296
- errors.push(`${planPath} claims '${claimedFile}' is DONE but file does not exist`);
297
- }
298
- }
299
- }
300
-
301
- if (fs.existsSync(path.join(root, 'Harness/research/scaffolds.md'))) {
302
- errors.push('legacy research file should be renamed: Harness/research/scaffolds.md -> Harness/research/research-results.md');
303
- }
304
-
305
- if (strict) {
306
- console.log('Strict placeholder scope:');
307
- for (const rel of projectFacts) {
308
- console.log(`- ${rel}`);
309
- }
310
-
311
- for (const rel of projectFacts) {
312
- const text = read(rel);
313
- for (const placeholder of unresolvedTemplatePlaceholders(text)) {
314
- errors.push(`template placeholder remains in project fact file: ${rel}: ${placeholder}`);
315
- }
316
- }
317
- }
318
-
319
- const docsReadme = read('Harness/README.md');
320
- if (docsReadme) {
321
- for (const marker of ['## Keyword Routing', '## Load By Task', 'When to Read', 'Keywords']) {
322
- if (!docsReadme.includes(marker)) errors.push(`Harness/README.md missing router marker: ${marker}`);
323
- }
324
- }
325
-
326
- for (const rel of durableCommunicationDocs) {
327
- requireText(rel, 'project files are the only durable communication channel', 'durable filesystem communication invariant');
328
- requireText(rel, 'chat/subagent transcript state is non-authoritative', 'non-authoritative transcript invariant');
329
- }
330
-
331
- requireText('CLAUDE.md', 'same tool/use pattern fails 3+ times', 'tool reflection trigger');
332
- requireText('CLAUDE.md', 'user corrects the same assumption/pattern 2+ times', 'user correction reflection trigger');
333
- requireText('CLAUDE.md', 'If `Harness/` exists, this repository is governed by the Harness contract', 'Harness binding contract');
334
- requireText('CLAUDE.md', 'Harness/MEMORY.md` is the memory/resource router', 'memory/resource router');
335
- requireText('CLAUDE.md', 'Harness/README.md#Load By Task', 'Harness task router');
336
- requireText('CLAUDE.md', 'Harness/SETUP.md` exists, follow it before normal project work', 'setup bootstrap contract');
291
+
292
+ // Task capsule template files
293
+ const taskTemplateDir = path.join(root, 'Harness', 'tasks', '_template');
294
+ if (!fs.existsSync(taskTemplateDir)) {
295
+ errors.push('missing directory: Harness/tasks/_template/');
296
+ } else {
297
+ for (const f of ['PROGRESS.md', 'PLAN.md']) {
298
+ if (!fs.existsSync(path.join(taskTemplateDir, f))) {
299
+ errors.push(`missing task template file: Harness/tasks/_template/${f}`);
300
+ }
301
+ }
302
+ }
303
+
304
+ // Cross-reference: DONE files in task PLAN.md must exist on disk
305
+ const taskDirs = fs.existsSync(path.join(root, 'Harness', 'tasks'))
306
+ ? fs.readdirSync(path.join(root, 'Harness', 'tasks'), { withFileTypes: true })
307
+ .filter(e => e.isDirectory() && e.name !== '_template')
308
+ .map(e => e.name)
309
+ : [];
310
+ for (const taskDir of taskDirs) {
311
+ // Validate task naming convention
312
+ const nameErr = validateTaskName(taskDir, strict);
313
+ if (nameErr) errors.push(nameErr);
314
+
315
+ const planPath = `Harness/tasks/${taskDir}/PLAN.md`;
316
+ const planText = read(planPath);
317
+ if (!planText) continue;
318
+ const donePattern = /`([^`]+\.(?:md|mjs|js|ts|json|html|css))`[^\n]*DONE/gi;
319
+ for (const match of planText.matchAll(donePattern)) {
320
+ const claimedFile = match[1];
321
+ if (!fs.existsSync(path.join(root, claimedFile))) {
322
+ errors.push(`${planPath} claims '${claimedFile}' is DONE but file does not exist`);
323
+ }
324
+ }
325
+ }
326
+
327
+ if (fs.existsSync(path.join(root, 'Harness/research/scaffolds.md'))) {
328
+ errors.push('legacy research file should be renamed: Harness/research/scaffolds.md -> Harness/research/research-results.md');
329
+ }
330
+
331
+ if (strict) {
332
+ console.log('Strict placeholder scope:');
333
+ for (const rel of projectFacts) {
334
+ console.log(`- ${rel}`);
335
+ }
336
+
337
+ for (const rel of projectFacts) {
338
+ const text = read(rel);
339
+ for (const placeholder of unresolvedTemplatePlaceholders(text)) {
340
+ errors.push(`template placeholder remains in project fact file: ${rel}: ${placeholder}`);
341
+ }
342
+ }
343
+ }
344
+
345
+ const docsReadme = read('Harness/README.md');
346
+ if (docsReadme) {
347
+ for (const marker of ['## Keyword Routing', '## Load By Task', 'When to Read', 'Keywords']) {
348
+ if (!docsReadme.includes(marker)) errors.push(`Harness/README.md missing router marker: ${marker}`);
349
+ }
350
+ }
351
+
352
+ for (const rel of durableCommunicationDocs) {
353
+ requireText(rel, 'project files are the only durable communication channel', 'durable filesystem communication invariant');
354
+ requireText(rel, 'chat/subagent transcript state is non-authoritative', 'non-authoritative transcript invariant');
355
+ }
356
+
357
+ requireText('Harness/MEMORY_PROTOCOL.md', 'L1/L2/L3 Memory Architecture', 'L2/L3 memory architecture section');
358
+ requireText('Harness/MEMORY_PROTOCOL.md', 'Memory Candidate Detection', 'memory candidate detection section');
359
+ requireText('Harness/MEMORY_PROTOCOL.md', 'explicit user preference', 'explicit user preference immediate write rule');
360
+ requireText('Harness/MEMORY_PROTOCOL.md', 'Memory Routing (L3)', 'memory routing section');
361
+ requireText('Harness/MEMORY_PROTOCOL.md', 'Scenario pack', 'route scoring scenario pack');
362
+ requireText('Harness/MEMORY_PROTOCOL.md', 'same tool or command pattern fails 3+ times', 'tool reflection trigger');
363
+ requireText('Harness/MEMORY_PROTOCOL.md', 'user corrects the same assumption or preference 2+ times', 'user correction reflection trigger');
364
+ requireText('CLAUDE.md', 'startup-hints.md', 'CLAUDE startup-hints routing');
365
+ requireText('.claude/rules/ecc/common.md', 'startup-hints.md', 'ECC startup-hints routing');
366
+ requireText('.claude/rules/ecc/common.md', 'memory candidates', 'ECC memory candidate detection');
367
+ requireText('.claude/rules/ecc/common.md', 'remember', 'ECC explicit memory trigger');
368
+ requireText('.claude/agents/memory-master.md', 'remember', 'memory-master explicit trigger');
369
+ requireText('.claude/agents/memory-master.md', 'compact format', 'memory-master compact format');
370
+ requireText('.claude/agents/memory-master.md', 'superseded', 'memory-master date only for superseded');
371
+ requireText('.opencode/agents/memory-master.md', 'remember', 'OpenCode memory-master explicit trigger');
372
+ requireText('.claude/skills/wf-learn/SKILL.md', 'without waiting for', 'wf-learn explicit preference immediate write');
373
+ requireText('.agents/skills/wf-learn/SKILL.md', 'without waiting for', 'Codex wf-learn explicit preference immediate write');
374
+ requireText('Harness/memory/startup-hints.md', 'Memory Candidate Detection', 'startup-hints memory candidate section');
375
+ requireText('Harness/memory/startup-hints.md', '记住', 'startup-hints chinese triggers');
376
+ requireText('Harness/memory/routes.md', 'signals', 'routes has signals column');
377
+ requireText('Harness/memory/routes.md', 'avoid', 'routes has avoid column');
378
+ requireText('Harness/memory/user-corrections-preferences.md', 'supersedes', 'memory template date only for superseded');
379
+ requireText('Harness/memory/tool-usage-reflections.md', 'supersedes', 'tool reflections template date rule');
380
+ requireText('Harness/memory/agent-lessons-patterns.md', 'supersedes', 'agent lessons template date rule');
381
+ requireText('Harness/MEMORY.md', 'startup-hints.md', 'MEMORY.md startup-hints registration');
382
+ requireText('Harness/MEMORY.md', 'Memory routes', 'MEMORY.md routes.md registration');
383
+ requireText('CLAUDE.md', 'If `Harness/` exists, this repository is governed by the Harness contract', 'Harness binding contract');
384
+ requireText('CLAUDE.md', 'memory and resource router', 'memory/resource router');
385
+ requireText('CLAUDE.md', '## 5a. Low-Noise Progress', 'low-noise progress section');
386
+ requireText('CLAUDE.md', 'Keep intermediate user updates to 1-2 short sentences', 'low-noise intermediate update rule');
387
+ requireText('.claude/rules/ecc/common.md', '## Low-Noise Progress', 'ECC low-noise progress section');
388
+ requireText('.claude/rules/ecc/common.md', 'Do not recap plans, paste logs, or narrate obvious file reads', 'ECC low-noise no-recap rule');
389
+ requireText('Harness/README.md', 'Load By Task', 'Harness task router');
390
+ requireText('CLAUDE.md', 'Harness/SETUP.md` is a bootstrap-only document', 'setup bootstrap-only contract');
391
+ forbidText('CLAUDE.md', 'follow it before normal project work', 'installed-project SETUP hot-path routing');
337
392
  requireText('Harness/SETUP.md', 'Harness/MEMORY_PROTOCOL.md', 'setup memory protocol reference');
338
- requireText('CLAUDE.md', 'subagent-orchestrator` and `Harness/subagents.md', 'subagent orchestrator entry trigger');
339
- requireText('CLAUDE.md', 'Harness/PROGRESS.md` is the global task index', 'PROGRESS global task index');
340
- requireText('CLAUDE.md', 'Harness/tasks/', 'task capsule directory reference');
341
- requireText('CLAUDE.md', 'Subagents are readers and reporters', 'subagent state committer rule');
342
- for (const heading of ['## 2. Think Before Coding', '## 3. Simplicity First', '## 4. Surgical Changes', '## 5. Goal-Driven Execution']) {
343
- requireText('CLAUDE.md', heading, `Karpathy-style rule heading: ${heading}`);
344
- }
345
-
346
- // Root PROGRESS.md structure check
347
- const progress = read('Harness/PROGRESS.md');
348
- if (progress) {
349
- for (const heading of ['## Active Task', '## Task Index', '## Cross-Task Decisions']) {
350
- if (!progress.includes(heading)) errors.push(`Harness/PROGRESS.md missing heading: ${heading}`);
351
- }
352
- }
353
-
354
- // Legacy PLAN.md deprecation check (removed — stub only)
355
-
356
- const dispatch = read('Harness/dispatch.md');
357
- if (dispatch) {
358
- for (const agent of commonAgents) {
359
- if (!dispatch.includes(`\`${agent}\``)) errors.push(`Harness/dispatch.md missing common agent: ${agent}`);
360
- }
361
- if (!dispatch.includes('## Handoff Format')) errors.push('Harness/dispatch.md missing heading: ## Handoff Format');
362
- }
363
-
364
- const contextLoading = read('Harness/context-loading.md');
365
- if (contextLoading) {
366
- if (!contextLoading.includes('Harness/README.md` is the primary router')) {
367
- errors.push('Harness/context-loading.md must declare Harness/README.md as the primary router');
368
- }
369
- for (const pack of contextPacks) {
370
- if (!contextLoading.includes(pack)) errors.push(`Harness/context-loading.md missing subagent pack: ${pack}`);
371
- }
372
- }
373
-
374
- const memory = read('Harness/MEMORY.md');
375
- if (memory) {
376
- for (const agent of commonAgents) {
377
- const rel = `.claude/agents/${agent}.md`;
378
- if (!memory.includes(rel)) errors.push(`Harness/MEMORY.md missing agent registration: ${rel}`);
379
- }
380
- for (const skill of commonSkills) {
381
- const rel = `.claude/skills/${skill}/SKILL.md`;
382
- if (!memory.includes(rel)) errors.push(`Harness/MEMORY.md missing skill registration: ${rel}`);
383
- }
384
- for (const rel of memoryFiles) {
385
- const relativeRel = rel.replace(/^Harness\//, '');
386
- if (!memory.includes(rel) && !memory.includes(relativeRel)) {
387
- errors.push(`Harness/MEMORY.md missing memory file registration: ${rel}`);
388
- }
389
- }
390
- }
391
-
392
- for (const workflow of listMarkdownFiles('Harness/workflows')) {
393
- const relativeWorkflow = workflow.replace(/^Harness\//, '');
394
- if (!docsReadme.includes(workflow) && !docsReadme.includes(relativeWorkflow) && !memory.includes(workflow) && !memory.includes(relativeWorkflow)) {
395
- errors.push(`workflow is not registered in Harness/README.md or Harness/MEMORY.md: ${workflow}`);
396
- }
397
- }
398
-
393
+ requireText('Harness/README.md', 'subagent', 'subagent orchestrator entry trigger');
394
+ requireText('Harness/README.md', 'PROGRESS.md', 'PROGRESS global task index');
395
+ requireText('CLAUDE.md', 'Harness/tasks/', 'task capsule directory reference');
396
+ requireText('Harness/subagents.md', 'Subagents are readers and reporters', 'subagent state committer rule');
397
+ for (const heading of ['## 2. Think Before Coding', '## 3. Simplicity First', '## 4. Surgical Changes', '## 5. Goal-Driven Execution']) {
398
+ requireText('CLAUDE.md', heading, `Karpathy-style rule heading: ${heading}`);
399
+ }
400
+
401
+ // Root PROGRESS.md structure check
402
+ const progress = read('Harness/PROGRESS.md');
403
+ if (progress) {
404
+ for (const heading of ['## Active Task', '## Task Index', '## Cross-Task Decisions']) {
405
+ if (!progress.includes(heading)) errors.push(`Harness/PROGRESS.md missing heading: ${heading}`);
406
+ }
407
+ }
408
+
409
+ // Legacy PLAN.md deprecation check (removed — stub only)
410
+
411
+ const dispatch = read('Harness/dispatch.md');
412
+ if (dispatch) {
413
+ for (const agent of commonAgents) {
414
+ if (!dispatch.includes(`\`${agent}\``)) errors.push(`Harness/dispatch.md missing common agent: ${agent}`);
415
+ }
416
+ if (!dispatch.includes('## Handoff Format')) errors.push('Harness/dispatch.md missing heading: ## Handoff Format');
417
+ }
418
+
419
+ const contextLoading = read('Harness/context-loading.md');
420
+ if (contextLoading) {
421
+ if (!contextLoading.includes('Harness/README.md` is the primary Harness documentation router')) {
422
+ errors.push('Harness/context-loading.md must declare Harness/README.md as the primary Harness documentation router');
423
+ }
424
+ for (const pack of contextPacks) {
425
+ if (!contextLoading.includes(pack)) errors.push(`Harness/context-loading.md missing subagent pack: ${pack}`);
426
+ }
427
+ }
428
+
429
+ const memory = read('Harness/MEMORY.md');
430
+ if (memory) {
431
+ for (const agent of commonAgents) {
432
+ const rel = `.claude/agents/${agent}.md`;
433
+ if (!memory.includes(rel)) errors.push(`Harness/MEMORY.md missing agent registration: ${rel}`);
434
+ }
435
+ for (const skill of commonSkills) {
436
+ const rel = `.claude/skills/${skill}/SKILL.md`;
437
+ if (!memory.includes(rel)) errors.push(`Harness/MEMORY.md missing skill registration: ${rel}`);
438
+ }
439
+ for (const rel of memoryFiles) {
440
+ const relativeRel = rel.replace(/^Harness\//, '');
441
+ if (!memory.includes(rel) && !memory.includes(relativeRel)) {
442
+ errors.push(`Harness/MEMORY.md missing memory file registration: ${rel}`);
443
+ }
444
+ }
445
+ }
446
+
447
+ for (const workflow of listMarkdownFiles('Harness/workflows')) {
448
+ const relativeWorkflow = workflow.replace(/^Harness\//, '');
449
+ if (!docsReadme.includes(workflow) && !docsReadme.includes(relativeWorkflow) && !memory.includes(workflow) && !memory.includes(relativeWorkflow)) {
450
+ errors.push(`workflow is not registered in Harness/README.md or Harness/MEMORY.md: ${workflow}`);
451
+ }
452
+ }
453
+
399
454
  for (const skillFile of registeredSkillFiles(docsReadme, memory)) {
400
455
  if (!fs.existsSync(path.join(root, skillFile))) {
401
456
  errors.push(`registered skill file is missing: ${skillFile}`);
@@ -415,131 +470,181 @@ for (const command of listedWorkflowCommands(docsReadme, read('.claude/commands/
415
470
  }
416
471
 
417
472
  for (const workflowFile of registeredWorkflowFiles(docsReadme, memory)) {
418
- if (!fs.existsSync(path.join(root, workflowFile))) {
419
- // Optional workflows (browser-e2e, ts-react-frontend, etc.) may not be installed
420
- continue;
421
- }
422
- }
423
-
424
- function requireUiSelectorContract(rel) {
425
- const text = read(rel);
426
- if (!text) return;
427
-
428
- const markers = [
429
- 'data-testid',
430
- 'accessible labels/roles',
431
- 'inputs, buttons, filters, rows, empty/error/loading states',
432
- ];
433
-
434
- if (markers.some(marker => !text.includes(marker))) {
435
- errors.push(`${rel} missing stable UI selector contract`);
436
- }
437
- }
438
-
439
- requireUiSelectorContract('Harness/workflows/browser-e2e.md');
440
- requireUiSelectorContract('Harness/workflows/ts-react-frontend.md');
441
-
442
- for (const skill of commonSkills) {
443
- const rel = `.claude/skills/${skill}/SKILL.md`;
444
- const text = read(rel);
445
- if (!text) continue;
446
- if (frontmatterField(text, 'name') !== skill) errors.push(`${rel} frontmatter name does not match directory`);
447
- if (!frontmatterField(text, 'description')) errors.push(`${rel} missing frontmatter field: description`);
448
- }
449
-
450
- for (const skill of commonSkills) {
451
- const claudeRel = `.claude/skills/${skill}/SKILL.md`;
452
- const codexRel = `.agents/skills/${skill}/SKILL.md`;
453
- const claudeText = read(claudeRel);
454
- const codexText = read(codexRel);
455
- if (!claudeText || !codexText) continue;
456
- if (claudeText !== codexText) errors.push(`${codexRel} must mirror ${claudeRel}`);
457
- }
458
-
459
- for (const skill of listDirectories('.claude/skills')) {
460
- if (commonSkills.includes(skill)) continue;
461
-
462
- const rel = `.claude/skills/${skill}/SKILL.md`;
463
- const text = read(rel);
464
- if (!text) continue;
465
-
466
- if (frontmatterField(text, 'name') !== skill) errors.push(`${rel} frontmatter name does not match directory`);
467
- if (!frontmatterField(text, 'description')) errors.push(`${rel} missing frontmatter field: description`);
468
- }
469
-
470
- for (const skill of listDirectories('.agents/skills')) {
471
- const rel = `.agents/skills/${skill}/SKILL.md`;
472
- const text = read(rel);
473
- if (!text) continue;
474
-
475
- if (frontmatterField(text, 'name') !== skill) errors.push(`${rel} frontmatter name does not match directory`);
476
- if (!frontmatterField(text, 'description')) errors.push(`${rel} missing frontmatter field: description`);
477
-
478
- const claudeMirror = `.claude/skills/${skill}/SKILL.md`;
479
- if (fs.existsSync(path.join(root, claudeMirror)) && text !== read(claudeMirror)) {
480
- errors.push(`${rel} must mirror ${claudeMirror}`);
481
- }
482
- }
483
-
484
- for (const agent of commonAgents) {
485
- const rel = `.claude/agents/${agent}.md`;
486
- const text = read(rel);
487
- if (!text) continue;
488
-
489
- for (const field of ['name', 'description', 'tools', 'model']) {
490
- if (!frontmatterField(text, field)) errors.push(`${rel} missing frontmatter field: ${field}:`);
491
- }
492
-
493
- if (frontmatterField(text, 'name') !== agent) {
494
- errors.push(`${rel} frontmatter name does not match filename`);
495
- }
496
-
497
- const skill = frontmatterField(text, 'skills');
498
- if (skill && !commonSkills.includes(skill)) {
499
- errors.push(`${rel} references unknown skill: ${skill}`);
500
- }
501
-
502
- if (skill && !fs.existsSync(path.join(root, `.claude/skills/${skill}/SKILL.md`))) {
503
- errors.push(`${rel} references missing skill file: .claude/skills/${skill}/SKILL.md`);
504
- }
505
- }
506
-
473
+ if (!fs.existsSync(path.join(root, workflowFile))) {
474
+ // Optional workflows (browser-e2e, ts-react-frontend, etc.) may not be installed
475
+ continue;
476
+ }
477
+ }
478
+
479
+ function requireUiSelectorContract(rel) {
480
+ const text = read(rel);
481
+ if (!text) return;
482
+
483
+ const markers = [
484
+ 'data-testid',
485
+ 'accessible labels/roles',
486
+ 'inputs, buttons, filters, rows, empty/error/loading states',
487
+ ];
488
+
489
+ if (markers.some(marker => !text.includes(marker))) {
490
+ errors.push(`${rel} missing stable UI selector contract`);
491
+ }
492
+ }
493
+
494
+ requireUiSelectorContract('Harness/workflows/browser-e2e.md');
495
+ requireUiSelectorContract('Harness/workflows/ts-react-frontend.md');
496
+
497
+ for (const skill of commonSkills) {
498
+ const rel = `.claude/skills/${skill}/SKILL.md`;
499
+ const text = read(rel);
500
+ if (!text) continue;
501
+ if (frontmatterField(text, 'name') !== skill) errors.push(`${rel} frontmatter name does not match directory`);
502
+ if (!frontmatterField(text, 'description')) errors.push(`${rel} missing frontmatter field: description`);
503
+ }
504
+
505
+ for (const skill of commonSkills) {
506
+ const claudeRel = `.claude/skills/${skill}/SKILL.md`;
507
+ const codexRel = `.agents/skills/${skill}/SKILL.md`;
508
+ const claudeText = read(claudeRel);
509
+ const codexText = read(codexRel);
510
+ if (!claudeText || !codexText) continue;
511
+ if (claudeText !== codexText) errors.push(`${codexRel} must mirror ${claudeRel}`);
512
+ }
513
+
514
+ for (const skill of listDirectories('.claude/skills')) {
515
+ if (commonSkills.includes(skill)) continue;
516
+
517
+ const rel = `.claude/skills/${skill}/SKILL.md`;
518
+ const text = read(rel);
519
+ if (!text) continue;
520
+
521
+ if (frontmatterField(text, 'name') !== skill) errors.push(`${rel} frontmatter name does not match directory`);
522
+ if (!frontmatterField(text, 'description')) errors.push(`${rel} missing frontmatter field: description`);
523
+ }
524
+
525
+ for (const skill of listDirectories('.agents/skills')) {
526
+ const rel = `.agents/skills/${skill}/SKILL.md`;
527
+ const text = read(rel);
528
+ if (!text) continue;
529
+
530
+ if (frontmatterField(text, 'name') !== skill) errors.push(`${rel} frontmatter name does not match directory`);
531
+ if (!frontmatterField(text, 'description')) errors.push(`${rel} missing frontmatter field: description`);
532
+
533
+ const claudeMirror = `.claude/skills/${skill}/SKILL.md`;
534
+ if (fs.existsSync(path.join(root, claudeMirror)) && text !== read(claudeMirror)) {
535
+ errors.push(`${rel} must mirror ${claudeMirror}`);
536
+ }
537
+ }
538
+
539
+ for (const agent of commonAgents) {
540
+ const rel = `.claude/agents/${agent}.md`;
541
+ const text = read(rel);
542
+ if (!text) continue;
543
+
544
+ for (const field of ['name', 'description', 'tools', 'model']) {
545
+ if (!frontmatterField(text, field)) errors.push(`${rel} missing frontmatter field: ${field}:`);
546
+ }
547
+
548
+ if (frontmatterField(text, 'name') !== agent) {
549
+ errors.push(`${rel} frontmatter name does not match filename`);
550
+ }
551
+
552
+ const skill = frontmatterField(text, 'skills');
553
+ if (skill && !commonSkills.includes(skill)) {
554
+ errors.push(`${rel} references unknown skill: ${skill}`);
555
+ }
556
+
557
+ if (skill && !fs.existsSync(path.join(root, `.claude/skills/${skill}/SKILL.md`))) {
558
+ errors.push(`${rel} references missing skill file: .claude/skills/${skill}/SKILL.md`);
559
+ }
560
+ }
561
+
562
+ for (const agent of commonAgents) {
563
+ const rel = `.opencode/agents/${agent}.md`;
564
+ const text = read(rel);
565
+ if (!text) continue;
566
+
567
+ for (const field of ['description', 'mode']) {
568
+ if (!frontmatterField(text, field)) errors.push(`${rel} missing frontmatter field: ${field}:`);
569
+ }
570
+
571
+ const mode = frontmatterField(text, 'mode');
572
+ if (mode && mode !== 'subagent') {
573
+ errors.push(`${rel} frontmatter mode should be subagent (got: ${mode})`);
574
+ }
575
+ }
576
+
577
+ // OpenCode mirror sync: .claude is the only editable source; .opencode files are
578
+ // format-converted mirrors. Frontmatter is platform-specific, but description and
579
+ // markdown body must match the .claude source exactly (edit .claude, then copy).
580
+ function markdownBody(text) {
581
+ return text.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '').replace(/\r\n/g, '\n').trim();
582
+ }
583
+
584
+ for (const opencodeRel of listMarkdownFiles('.opencode/agents')) {
585
+ const fileName = opencodeRel.split('/').pop();
586
+ const claudeRel = `.claude/agents/${fileName}`;
587
+ const opencodeText = read(opencodeRel);
588
+ if (!opencodeText) continue;
589
+ const claudeText = read(claudeRel);
590
+ if (!claudeText) {
591
+ errors.push(`${opencodeRel} has no .claude source: ${claudeRel} (add the .claude agent first)`);
592
+ continue;
593
+ }
594
+ if (frontmatterField(opencodeText, 'description') !== frontmatterField(claudeText, 'description')) {
595
+ errors.push(`${opencodeRel} description must mirror ${claudeRel} (edit the .claude source, then copy)`);
596
+ }
597
+ if (markdownBody(opencodeText) !== markdownBody(claudeText)) {
598
+ errors.push(`${opencodeRel} body must mirror ${claudeRel} (edit the .claude source, then copy)`);
599
+ }
600
+ }
601
+
602
+ for (const claudeRel of listMarkdownFiles('.claude/commands')) {
603
+ const fileName = claudeRel.split('/').pop();
604
+ const opencodeRel = `.opencode/commands/${fileName}`;
605
+ const claudeText = read(claudeRel);
606
+ if (!claudeText) continue;
607
+ const opencodeText = read(opencodeRel);
608
+ if (!opencodeText) {
609
+ errors.push(`missing OpenCode command mirror: ${opencodeRel} (copy from ${claudeRel})`);
610
+ continue;
611
+ }
612
+ if (markdownBody(opencodeText) !== markdownBody(claudeText)) {
613
+ errors.push(`${opencodeRel} body must mirror ${claudeRel} (edit the .claude source, then copy)`);
614
+ }
615
+ }
616
+
507
617
  requireText('Harness/extension.md', 'Skills should extend the harness');
508
618
  requireText('Harness/agent-workflow.md', 'Harness/tasks/<task-id>/PROGRESS.md');
509
- requireText('CLAUDE.md', 'Keep task records compact', 'compact task record startup rule');
510
619
  requireText('Harness/README.md', 'Task records are compact by default', 'compact task record router rule');
511
620
  requireText('Harness/agent-workflow.md', 'AC record size', 'compact AC record size rule');
512
621
  requireText('Harness/ACCEPTANCE_PROTOCOL.md', 'Default to 1-3 concise ACs', 'compact AC default');
513
- requireText('Harness/WF.md', 'Do not paste command logs or subagent transcripts', 'compact heartbeat rule');
622
+ requireText('Harness/WF.md', 'task-scribe', 'compact heartbeat rule');
514
623
  requireText('Harness/tasks/_template/PLAN.md', 'Compact task record', 'compact task PLAN template');
515
624
  requireText('Harness/tasks/_template/PLAN.md', 'Default: keep 1-3 concise ACs', 'compact task PLAN AC guidance');
516
625
  requireText('Harness/tasks/_template/PROGRESS.md', 'Compact heartbeat', 'compact task PROGRESS template');
517
626
  requireText('Harness/agent-workflow.md', 'TDD-GUIDE.md', 'agent workflow loads TDD guide');
518
627
  requireText('Harness/agent-workflow.md', 'real user-path test', 'agent workflow real user path requirement');
519
628
  requireText('Harness/research/README.md', 'research-results.md');
520
- requireText('Harness/WF.md', 'Ralph-style harness loop', 'WF loop description');
521
- requireText('Harness/WF.md', 'Heartbeat Protocol', 'heartbeat protocol');
522
- requireText('Harness/WF.md', 'WF mode requires the complete role chain by default', 'WF complete role chain default');
523
- requireText('Harness/WF.md', 'MUST cover every mandatory role class before closeout', 'explicit WF/WK complete role chain');
524
- requireText('Harness/WF.md', 'Independent Validation', 'WF independent validation role');
525
- requireText('Harness/WF.md', 'reflector', 'WF reflector gate');
526
- requireText('Harness/WF.md', '.claude/agents/', 'WF built-in agent roster path');
527
- requireText('Harness/WF.md', 'Collaboration decision tree', 'WF decision tree');
629
+ requireText('Harness/WF.md', 'Orchestration Loop', 'WF orchestration loop');
630
+ requireText('Harness/WF.md', 'WF-KERNEL.md', 'WF references WF-KERNEL');
631
+ requireText('Harness/WF.md', 'Cross-review and reflector NOT mandatory', 'WF-Light no mandatory cross-review');
632
+ requireText('Harness/WF.md', '`.claude/agents/`', 'WF agent roster path');
528
633
  requireText('Harness/WF.md', 'Harness/tasks/', 'WF task directory reference');
529
- requireText('Harness/WF.md', 'IMPLEMENT-GATE', 'WF implement gate naming');
530
- requireText('Harness/README.md', '`wf mode`, `workflow mode`, or `wk mode`', 'WF/WK router aliases');
531
- requireText('Harness/README.md', 'explicit WF/WK loads subagent docs immediately', 'explicit WF/WK router output');
532
- requireText('.claude/skills/wf/SKILL.md', 'Harness/WF.md', 'wf skill loads core WF doc');
533
- requireText('.agents/skills/wf/SKILL.md', 'Harness/WF.md', 'Codex wf skill loads core WF doc');
534
- requireText('.claude/skills/subagent-orchestrator/SKILL.md', 'Harness/subagents.md', 'subagent-orchestrator loads subagents doc');
535
- requireText('.claude/skills/subagent-orchestrator/SKILL.md', '.claude/agents/', 'subagent-orchestrator built-in agent roster path');
536
- requireText('.claude/skills/subagent-orchestrator/SKILL.md', '`workflow mode`, or `wk mode`', 'subagent-orchestrator WF/WK aliases');
537
- requireText('.claude/skills/wf-readme/SKILL.md', 'README.md', 'wf-readme loads README');
538
- requireText('.claude/skills/wf-readme/SKILL.md', 'Harness/architecture.md', 'wf-readme links architecture docs');
539
- requireText('Harness/subagents.md', '## Source Attribution', 'subagent source attribution');
540
- requireText('Harness/subagents.md', 'npx skills find', 'find-skills discovery attribution');
541
- requireText('Harness/subagents.md', 'superpowers:dispatching-parallel-agents', 'parallel-agent source attribution');
542
- requireText('Harness/subagents.md', 'superpowers:subagent-driven-development', 'subagent-driven source attribution');
634
+ requireText('Harness/README.md', 'explicit WF token', 'WF explicit contract');
635
+ requireText('Harness/README.md', 'tier-gated acceptance', 'WF tiered acceptance router output');
636
+ requireText('.claude/skills/wf/SKILL.md', 'Harness/WF.md', 'wf skill loads core WF doc');
637
+ requireText('.claude/skills/wf/SKILL.md', 'Tier-aware acceptance', 'wf skill tier-aware acceptance');
638
+ requireText('.agents/skills/wf/SKILL.md', 'Harness/WF.md', 'Codex wf skill loads core WF doc');
639
+ requireText('.claude/skills/subagent-orchestrator/SKILL.md', 'Harness/subagents.md', 'subagent-orchestrator loads subagents doc');
640
+ requireText('.claude/skills/subagent-orchestrator/SKILL.md', '.claude/agents/', 'subagent-orchestrator built-in agent roster path');
641
+ requireText('.claude/skills/subagent-orchestrator/SKILL.md', 'WF/WF-MAX requires tier-specific', 'subagent-orchestrator WF tier-specific contract');
642
+ requireText('.claude/skills/wf-readme/SKILL.md', 'README.md', 'wf-readme loads README');
643
+ requireText('.claude/skills/wf-readme/SKILL.md', 'Harness/architecture.md', 'wf-readme links architecture docs');
644
+ requireText('Harness/subagents.md', '## Source Attribution', 'subagent source attribution');
645
+ requireText('Harness/subagents.md', 'npx skills find', 'find-skills discovery attribution');
646
+ requireText('Harness/subagents.md', 'superpowers:dispatching-parallel-agents', 'parallel-agent source attribution');
647
+ requireText('Harness/subagents.md', 'superpowers:subagent-driven-development', 'subagent-driven source attribution');
543
648
  requireText('Harness/subagents.md', '## Built-in Agent Roster', 'built-in agent roster');
544
649
  requireText('Harness/subagents.md', '## WF Default Fan-Out', 'WF default fan-out');
545
650
  requireText('Harness/subagents.md', 'concrete conditions', 'subagent decision tree');
@@ -548,25 +653,25 @@ requireText('Harness/subagents.md', '`reflector`', 'reflector agent roster');
548
653
  requireText('Harness/subagents.md', 'reflector PASS', 'reflector review gate');
549
654
  requireText('Harness/subagents.md', '## Efficiency Ladder', 'subagent efficiency ladder');
550
655
  requireText('Harness/subagents.md', '## Review Gates', 'subagent review gates');
551
- requireText('Harness/architecture.md', '## 2. Interface Decoupling', 'architecture interface decoupling');
552
- requireText('Harness/architecture.md', '## 3. State Design', 'architecture state design');
553
- requireText('Harness/architecture.md', 'Avoid speculative abstraction', 'anti-overengineering architecture rule');
554
- requireText('CLAUDE.md', 'Use explicit interfaces or state models only when they protect a real boundary', 'CLAUDE interface/state simplicity rule');
555
- requireText('CLAUDE.md', '/wf-update', 'wf update startup instruction');
556
- requireText('Harness/README.md', 'Need harness update', 'update routing row');
656
+ requireText('Harness/architecture.md', '## 2. Interface Decoupling', 'architecture interface decoupling');
657
+ requireText('Harness/architecture.md', '## 3. State Design', 'architecture state design');
658
+ requireText('Harness/architecture.md', 'Avoid speculative abstraction', 'anti-overengineering architecture rule');
659
+ requireText('CLAUDE.md', 'Use explicit interfaces or state models only when they protect a real boundary', 'CLAUDE interface/state simplicity rule');
660
+ requireText('Harness/README.md', '/wf-update', 'wf update startup instruction');
661
+ requireText('Harness/README.md', 'Need harness update', 'update routing row');
557
662
  requireText('Harness/WF-MAX.md', 'three-layer architecture', 'WF-MAX three-layer architecture');
558
- requireText('Harness/WF-MAX.md', 'agent role', 'WF-MAX agent role separation');
559
- requireText('Harness/WF-MAX.md', 'write-set coloring', 'WF-MAX coloring algorithm');
560
- requireText('Harness/WF-MAX.md', 'wave dispatch', 'WF-MAX wave dispatch');
561
- requireText('Harness/WF-MAX.md', 'strict superset', 'WF-MAX strict superset');
562
- requireText('Harness/WF-MAX.md', 'cross-CLI overflow', 'WF-MAX cross-CLI overflow');
663
+ requireText('Harness/WF-MAX.md', 'dispatch permissions', 'WF-MAX role separation');
664
+ requireText('Harness/WF-MAX.md', 'file_claim', 'WF-MAX coloring algorithm');
665
+ requireText('Harness/WF-MAX.md', 'CEO → Manager → Worker', 'WF-MAX wave dispatch');
666
+ requireText('Harness/WF-MAX.md', 'inherits the full', 'WF-MAX strict superset');
667
+ requireText('Harness/WF-MAX.md', 'Cross-CLI', 'WF-MAX cross-CLI overflow');
563
668
  requireText('Harness/WF-MAX.md', 'claude -p', 'WF-MAX Claude CLI overflow');
564
669
  requireText('Harness/WF-MAX.md', 'codex exec', 'WF-MAX Codex CLI overflow');
565
670
  requireText('Harness/WF-MAX.md', 'agents.max_threads', 'WF-MAX Codex max_threads config');
566
671
  requireText('Harness/WF-MAX.md', 'agents.max_depth', 'WF-MAX Codex max_depth config');
567
672
  requireText('Harness/WF-MAX.md', 'max_threads = 12', 'WF-MAX Codex scaffold max_threads default');
568
673
  requireText('Harness/WF-MAX.md', 'max_depth = 1', 'WF-MAX Codex scaffold max_depth default');
569
- requireText('Harness/WF-MAX.md', 'ask the user before raising `agents.max_threads`', 'WF-MAX asks before raising Codex thread cap');
674
+ requireText('Harness/WF-MAX.md', 'Ask the user before raising', 'WF-MAX asks before raising Codex thread cap');
570
675
  requireText('Harness/WF-MAX.md', 'Close completed agents', 'WF-MAX close completed agents before overflow');
571
676
  requireText('Harness/WF-MAX.md', 'Codex++', 'WF-MAX forbids relying on Codex++ as stable capacity');
572
677
  requireText('.claude/skills/wf-max/SKILL.md', 'agents.max_threads', 'wf-max skill Codex thread config');
@@ -576,6 +681,8 @@ requireText('.claude/skills/wf-max/SKILL.md', 'Codex++', 'wf-max skill forbids C
576
681
  requireText('.codex/config.toml', '[agents]', 'Codex agents config table');
577
682
  requireText('.codex/config.toml', 'max_threads = 12', 'Codex scaffold max_threads default');
578
683
  requireText('.codex/config.toml', 'max_depth = 1', 'Codex scaffold max_depth default');
684
+ requireText('opencode.json', '"$schema": "https://opencode.ai/config.json"', 'OpenCode config schema');
685
+ requireText('opencode.json', '.claude/rules/ecc/common.md', 'OpenCode instructions reference to ECC rules');
579
686
  requireText('Harness/README.md', '/wf-max', 'wf max router alias');
580
687
  requireText('Harness/README.md', 'WF-MAX.md', 'WF-MAX router reference');
581
688
  requireText('Harness/README.md', 'ACCEPTANCE_PROTOCOL.md', 'acceptance protocol router reference');
@@ -587,6 +694,16 @@ requireText('Harness/ACCEPTANCE_PROTOCOL.md', 'Syntax-only checks', 'syntax-only
587
694
  requireText('Harness/AGENT_ISOLATION.md', 'implementer', 'agent isolation implementer rule');
588
695
  requireText('Harness/HARNESS_BRIDGE.md', 'Network Trace Collector', 'harness bridge network trace collector');
589
696
  requireText('Harness/WF-AUTO.md', 'Intent Checkpoint', 'wf-auto intent checkpoint');
697
+ requireText('Harness/WF-AUTO.md', 'Adaptive Coverage Exhaustion Gate', 'wf-auto adaptive coverage exhaustion gate');
698
+ requireText('Harness/WF-AUTO.md', 'dynamic high-risk obligations', 'wf-auto dynamic high-risk obligations');
699
+ requireText('Harness/WF-AUTO.md', 'two different confirmation strategies', 'wf-auto two different confirmation strategies');
700
+ requireText('Harness/WF-AUTO-ANGLES.md', 'Selection algorithm', 'wf-auto adaptive angle selection');
701
+ requireText('Harness/WF-AUTO-ANGLES.md', 'Dynamic obligations', 'wf-auto dynamic obligations');
702
+ requireText('Harness/WF-AUTO-ANGLES.md', 'Common probe recipes', 'wf-auto common probe recipes');
703
+ forbidText('Harness/WF-AUTO.md', 'All 8 exhausted', 'stale eight-angle exhaustion rule');
704
+ forbidText('Harness/WF-AUTO.md', '3 consecutive all-exhausted rounds', 'stale three-consecutive-all-exhausted stop rule');
705
+ forbidText('Harness/WF-AUTO.md', '8-Angle Exhaustion Gate', 'stale eight-angle exhaustion gate name');
706
+ forbidText('Harness/WF-AUTO-SPARK.md', '8 parallel external searches', 'stale eight-spark fixed-count search');
590
707
  requireText('Harness/WF-AUTO.md', 'Inherited WF/WF-MAX Constraints', 'wf-auto inherited WF/WF-MAX constraints');
591
708
  requireText('Harness/WF-AUTO.md', 'Mini PRD -> AC IDs -> test/validation plan -> implementer -> verifier', 'wf-auto per-cycle WF chain');
592
709
  requireText('Harness/WF-AUTO.md', 'reflector PASS', 'wf-auto reflector gate');
@@ -617,8 +734,8 @@ requireText('.claude/agents/reflector.md', 'PASS, RETURN_TO_DEBUG, or BLOCKED',
617
734
  requireText('Harness/DEBUG_PROTOCOL.md', 'Layer Classification', 'debug layer classification');
618
735
  requireText('Harness/MEMORY_PROTOCOL.md', 'AC ID', 'memory AC traceability');
619
736
  requireText('Harness/subagents.md', 'Max parallelism', 'subagents max parallelism row');
620
- requireText('Harness/dispatch.md', 'Concurrency group', 'dispatch concurrency group field');
621
- requireText('Harness/dispatch.md', 'File claim', 'dispatch file claim field');
737
+ requireText('Harness/dispatch.md', 'Concurrency group', 'dispatch concurrency group field');
738
+ requireText('Harness/dispatch.md', 'File claim', 'dispatch file claim field');
622
739
  requireText('CLAUDE.md', '/wf-max', 'wf max startup instruction');
623
740
  requireText('.claude/commands/wf-help.md', 'Do not invoke a skill', 'wf-help direct command boundary');
624
741
  requireText('.claude/commands/wf-help.md', '| `/wf-help` |', 'wf-help command row');
@@ -627,8 +744,8 @@ requireText('.claude/commands/wf-help.md', '| `/wf-auto` |', 'wf-help wf-auto ro
627
744
  requireText('.claude/commands/wf-help.md', '| `/wf-readme <task>` |', 'wf-help wf-readme row');
628
745
  requireText('Harness/README.md', '## Direct Commands', 'direct commands section');
629
746
  requireText('Harness/README.md', '.claude/commands/wf-help.md', 'wf-help router reference');
630
- requireText('CLAUDE.md', 'three-layer architecture', 'CLAUDE.md three-layer role architecture');
631
- requireText('CLAUDE.md', 'not hook-enforced', 'CLAUDE no-hook role enforcement statement');
747
+ requireText('Harness/WF-MAX.md', 'three-layer architecture', 'CLAUDE.md three-layer role architecture');
748
+ requireText('Harness/README.md', 'no runtime hook state', 'CLAUDE no-hook role enforcement statement');
632
749
  requireText('Harness/README.md', 'no runtime hook state', 'README no-hook runtime statement');
633
750
  requireText('Harness/WF-AUTO.md', 'WF-AUTO Hook Exception', 'wf-auto-only hook exception');
634
751
  requireText('Harness/WF-AUTO.md', 'only `/wf-auto` may use a runtime hook', 'wf-auto-only hook boundary');
@@ -658,13 +775,135 @@ if (claudeSettings.includes('"hooks"') && !claudeSettings.includes('wf-auto')) {
658
775
  if (read('.codex/config.toml').includes('hooks = true') && !codexHookConfig.includes('wf-auto')) {
659
776
  errors.push('.codex/config.toml may enable hooks only with a wf-auto hook configuration');
660
777
  }
778
+
779
+ // AGENTS.md must be a thin shim — no Harness/WF-MAX/command table content
780
+ forbidText('AGENTS.md', 'WF-MAX Role Contract', 'AGENTS.md WF-MAX section');
781
+ forbidText('AGENTS.md', 'Key Commands', 'AGENTS.md command table');
782
+ forbidText('AGENTS.md', 'Harness/MEMORY.md', 'AGENTS.md Harness routing (must defer to CLAUDE.md)');
783
+ forbidText('AGENTS.md', 'Harness/README.md', 'AGENTS.md Harness routing (must defer to CLAUDE.md)');
784
+ requireText('AGENTS.md', 'CLAUDE.md', 'AGENTS.md CLAUDE.md reference');
785
+ requireText('AGENTS.md', 'compatibility entry', 'AGENTS.md Codex shim purpose');
786
+ requireText('AGENTS.md', 'single source', 'AGENTS.md CLAUDE.md as single source');
787
+
788
+ // Implicit WF trigger phrases forbidden in active runtime docs and SETUP
789
+ for (const rel of ['Harness/README.md', 'Harness/WF.md', 'Harness/context-loading.md', 'Harness/subagents.md', 'Harness/SETUP.md']) {
790
+ forbidText(rel, 'workflow mode', 'implicit WF trigger: workflow mode');
791
+ forbidText(rel, 'wk mode', 'implicit WF trigger: wk mode');
792
+ }
793
+ // SETUP must not require old WF complete-role-chain contract
794
+ requireText('Harness/SETUP.md', 'explicit WF entry only', 'SETUP explicit WF entry contract');
795
+ requireText('Harness/SETUP.md', 'WF-Light', 'SETUP WF-Light tier reference');
796
+ requireText('Harness/SETUP.md', 'WF-Max-Useful', 'SETUP WF-Max-Useful tier reference');
797
+
798
+ // Memory Preflight markers required in workflow skill adapters
799
+ for (const skill of ['wf', 'wf-max', 'subagent-orchestrator']) {
800
+ requireText(`.claude/skills/${skill}/SKILL.md`, 'Memory Preflight', `${skill} skill Memory Preflight section`);
801
+ }
802
+
803
+ // Direct command checks
804
+ requireText('Harness/README.md', '/wf-update', 'wf-update direct command reference');
805
+ requireText('.claude/commands/wf-update.md', 'Do not invoke a skill', 'wf-update direct command boundary');
806
+ requireText('.claude/commands/wf-help.md', 'direct command', 'wf-help wf-update direct command classification');
807
+
808
+ // wf-update skill must NOT claim Claude Code /wf-update as a skill invocation
809
+ // (allow mentions in the description/body that say "direct command" — those are correct)
810
+ forbidText('.claude/skills/wf-update/SKILL.md', 'Claude Code: use', 'wf-update skill Claude Code skill-claim');
811
+ forbidText('.claude/skills/wf-update/SKILL.md', 'Claude Code and Codex', 'wf-update skill old dual-platform claim');
812
+ requireText('.claude/skills/wf-update/SKILL.md', 'Codex compatibility', 'wf-update skill Codex compatibility statement');
813
+
814
+ // OpenCode command parity
815
+ requireText('.opencode/commands/wf-help.md', 'Do not invoke a skill', 'OpenCode wf-help direct command boundary');
816
+ requireText('.opencode/commands/wf-update.md', 'Do not invoke a skill', 'OpenCode wf-update direct command boundary');
817
+ for (const command of opencodeWorkflowCommands) {
818
+ requireText(`.opencode/commands/${command}.md`, 'workflow command', `OpenCode ${command} workflow command classification`);
819
+ requireText(`.opencode/commands/${command}.md`, `.claude/skills/${command}/SKILL.md`, `OpenCode ${command} wrapper skill routing`);
820
+ requireText(`.opencode/commands/${command}.md`, 'Harness/MEMORY.md', `OpenCode ${command} wrapper router load`);
821
+ }
822
+
823
+ // wf-max adapter must stay tier-aware, not the old unconditional contract
824
+ forbidText('.claude/skills/wf-max/SKILL.md', 'every WF role, gate, and acceptance rule still', 'old wf-max unconditional role/gate inheritance');
825
+ forbidText('.claude/skills/wf-max/SKILL.md', 'Final acceptance requires verifier evidence, cross-review, and reflector PASS', 'old wf-max unconditional final acceptance');
826
+ requireText('.claude/skills/wf-max/SKILL.md', 'Final acceptance is tier-aware', 'wf-max tier-aware final acceptance');
827
+
828
+ // New small-fast agents
829
+ requireText('.claude/agents/task-scribe.md', 'model: haiku', 'task-scribe must use haiku model');
830
+ requireText('.claude/agents/codebase-explorer.md', 'model: haiku', 'codebase-explorer must use haiku model');
831
+ requireText('.claude/agents/codebase-explorer.md', 'read-only', 'codebase-explorer read-only contract');
832
+
833
+ // WF-KERNEL.md required invariants
834
+ requireText('Harness/WF-KERNEL.md', 'Ready-Queue', 'WF-KERNEL ready-queue section');
835
+ requireText('Harness/WF-KERNEL.md', 'Role / Model Matrix', 'WF-KERNEL role-model matrix');
836
+ requireText('Harness/WF-KERNEL.md', 'Dispatch Packet', 'WF-KERNEL dispatch packet format');
837
+ requireText('Harness/WF-KERNEL.md', 'Task Type', 'WF-KERNEL task type routing');
838
+ requireText('Harness/WF-KERNEL.md', 'small-fast', 'WF-KERNEL small-fast tier mapping');
839
+
840
+ // WF variants reference WF-KERNEL
841
+ requireText('Harness/WF.md', 'WF-KERNEL.md', 'WF.md references WF-KERNEL');
842
+ requireText('Harness/WF-MAX.md', 'WF-KERNEL.md', 'WF-MAX.md references WF-KERNEL');
843
+
844
+ // Tier-aware acceptance: WF-Light must NOT require global cross-review/reflector
845
+ requireText('Harness/WF.md', 'Cross-review and reflector NOT mandatory', 'WF.md WF-Light no mandatory cross-review');
846
+ requireText('Harness/WF-KERNEL.md', 'Cross-review and reflector are NOT mandatory', 'WF-KERNEL WF-Light no mandatory cross-review');
847
+
848
+ // WF-MAX: maximum safe fan-out, not unconditional
849
+ requireText('Harness/WF-MAX.md', 'maximum safe fan-out', 'WF-MAX maximum safe fan-out');
850
+ requireText('Harness/WF-MAX.md', 'WF-Max-Useful', 'WF-MAX useful fan-out mode');
851
+ requireText('Harness/WF-MAX.md', 'WF-Max-Strict', 'WF-MAX strict override mode');
852
+
853
+ // task-scribe is task-state write exception
854
+ requireText('Harness/subagents.md', 'task-scribe', 'subagents.md task-scribe registration');
855
+ requireText('Harness/dispatch.md', 'task-scribe', 'dispatch.md task-scribe roster');
856
+ requireText('Harness/WF-KERNEL.md', 'task-scribe', 'WF-KERNEL task-scribe ownership');
857
+ forbidText('Harness/WF-KERNEL.md', 'Only the main agent writes task', 'old single-writer rule');
858
+
859
+ // Old WF contract must not return to hot-path docs:
860
+ // /wf is WF-KERNEL tiered orchestration, not a default complete role chain;
861
+ // /wf-max defaults to WF-Max-Useful, unconditional fan-out is strict-only.
862
+ const hotPathDocs = [
863
+ 'CLAUDE.md',
864
+ 'Harness/MEMORY.md',
865
+ 'Harness/README.md',
866
+ 'Harness/WF.md',
867
+ 'Harness/WF-MAX.md',
868
+ 'Harness/WF-AUTO.md',
869
+ 'Harness/WF-AUTO-SPARK.md',
870
+ 'Harness/WF-KERNEL.md',
871
+ 'Harness/agent-workflow.md',
872
+ 'Harness/dispatch.md',
873
+ 'Harness/subagents.md',
874
+ 'Harness/context-loading.md',
875
+ 'Harness/ACCEPTANCE_PROTOCOL.md',
876
+ ];
877
+ for (const rel of hotPathDocs) {
878
+ forbidText(rel, 'complete role chain mandatory', 'old /wf default complete-role-chain contract');
879
+ forbidText(rel, 'default complete role chain', 'old /wf default complete-role-chain contract');
880
+ forbidText(rel, 'requires the complete role chain by default', 'old /wf default complete-role-chain contract');
881
+ forbidText(rel, 'mandatory maximum fan-out', 'old /wf-max unconditional fan-out contract');
882
+ forbidText(rel, 'strict superset: complete role chain plus maximum parallelism', 'old /wf-max strict-superset contract');
883
+ }
884
+
885
+ // WF-STATE.md and TASK_ARCHIVE.md must be reachable from the entry router
886
+ requireText('CLAUDE.md', 'Harness/WF-STATE.md', 'CLAUDE.md WF-STATE routing');
887
+ requireText('CLAUDE.md', 'Harness/TASK_ARCHIVE.md', 'CLAUDE.md TASK_ARCHIVE routing');
888
+ requireText('Harness/README.md', 'WF-STATE.md', 'README WF-STATE routing');
889
+ requireText('Harness/TASK_ARCHIVE.md', 'archive-tasks.mjs', 'TASK_ARCHIVE script reference');
890
+
891
+ // Outer task capsule cap: keep Harness/tasks/ lean (see Harness/TASK_ARCHIVE.md)
892
+ const OUTER_TASK_CAP = 5;
893
+ const outerTasks = taskDirs.filter(name => !TASK_RESERVED.has(name) && !name.startsWith('_'));
894
+ if (outerTasks.length > OUTER_TASK_CAP) {
895
+ 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/TASK_ARCHIVE.md)`;
896
+ if (strict) errors.push(capMsg);
897
+ else console.warn(`Warning: ${capMsg}`);
898
+ }
899
+
661
900
  if (errors.length) {
662
- console.error(`Harness validation failed${strict ? ' (strict)' : ''}:`);
663
- for (const error of errors) console.error(`- ${error}`);
664
- process.exit(1);
665
- }
666
-
667
- console.log(`Harness validation passed${strict ? ' (strict)' : ''}.`);
668
- if (!strict) {
669
- console.log('Tip: run `node Harness/scripts/validate-harness.mjs --strict` after bootstrap to check unresolved project placeholders.');
670
- }
901
+ console.error(`Harness validation failed${strict ? ' (strict)' : ''}:`);
902
+ for (const error of errors) console.error(`- ${error}`);
903
+ process.exit(1);
904
+ }
905
+
906
+ console.log(`Harness validation passed${strict ? ' (strict)' : ''}.`);
907
+ if (!strict) {
908
+ console.log('Tip: run `node Harness/scripts/validate-harness.mjs --strict` after bootstrap to check unresolved project placeholders.');
909
+ }