create-harness-vibe-coding 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generator.js +466 -466
- package/src/index.js +355 -355
- package/templates/common/.claude/agents/architect.md +31 -35
- package/templates/common/.claude/agents/context-master.md +0 -1
- package/templates/common/.claude/agents/debugger.md +0 -1
- package/templates/common/.claude/agents/docs-researcher.md +41 -43
- package/templates/common/.claude/agents/implementer.md +0 -1
- package/templates/common/.claude/agents/memory-master.md +0 -1
- package/templates/common/.claude/agents/planner.md +0 -1
- package/templates/common/.claude/agents/researcher.md +0 -1
- package/templates/common/.claude/agents/reviewer.md +34 -35
- package/templates/common/.claude/agents/test-writer.md +0 -1
- package/templates/common/.claude/agents/verifier.md +0 -1
- package/templates/common/.claude/commands/wf-max.md +7 -0
- package/templates/common/.claude/commands/{update.md → wf-update.md} +4 -0
- package/templates/common/.claude/commands/wf.md +10 -3
- package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +2 -1
- package/templates/common/.claude/skills/wf-max/SKILL.md +29 -70
- package/templates/common/.claude/skills/{readme-optimizer → wf-readme}/SKILL.md +1 -1
- package/templates/common/.claude/skills/wf-review/SKILL.md +50 -50
- package/templates/common/.claude/skills/wf-update/SKILL.md +58 -58
- package/templates/common/CLAUDE.md +77 -76
- package/templates/common/MEMORY.md +73 -76
- package/templates/common/README.md +1 -1
- package/templates/common/SETUP.md +273 -341
- package/templates/common/docs/README.md +131 -145
- package/templates/common/docs/harness/WF.md +13 -1
- package/templates/common/docs/harness/agent-workflow.md +94 -94
- package/templates/common/docs/harness/architecture.md +1 -1
- package/templates/common/docs/harness/context-loading.md +104 -108
- package/templates/common/docs/harness/extension.md +70 -79
- package/templates/common/docs/harness/lifecycle.md +33 -33
- package/templates/common/docs/harness/subagents.md +1 -1
- package/templates/common/docs/research/PRD.md +65 -65
- package/templates/common/docs/research/README.md +169 -169
- package/templates/common/scripts/validate-harness.mjs +439 -460
- package/templates/optional/skills/browser-e2e/.claude/skills/browser-e2e/SKILL.md +42 -42
- package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +30 -0
- package/templates/optional/skills/browser-e2e/docs/workflows/browser-e2e.md +1 -1
- package/templates/optional/skills/github-pr-review/.claude/skills/github-pr-review/SKILL.md +40 -40
- package/templates/optional/skills/python-backend/.claude/skills/python-backend/SKILL.md +40 -40
- package/templates/optional/skills/ts-react-frontend/.claude/skills/ts-react-frontend/SKILL.md +43 -43
- package/templates/optional/skills/ui-ux-review/.claude/skills/ui-ux-review/SKILL.md +40 -40
- package/templates/common/.claude/skills/harness-build-loop/SKILL.md +0 -23
- package/templates/common/.claude/skills/harness-context/SKILL.md +0 -26
- package/templates/common/.claude/skills/harness-lifecycle/SKILL.md +0 -20
- package/templates/common/.claude/skills/harness-research/SKILL.md +0 -30
- package/templates/common/.claude/skills/harness-router/SKILL.md +0 -16
- package/templates/common/.claude/skills/wf-mode/SKILL.md +0 -55
- package/templates/common/docs/domain/ports.md +0 -76
- package/templates/common/docs/features/_template.md +0 -177
- package/templates/common/docs/harness/PLAN.md +0 -52
- package/templates/common/docs/harness/data-flow.md +0 -59
- package/templates/common/docs/harness/state-machines.md +0 -58
- /package/templates/common/.claude/commands/{learn.md → wf-learn.md} +0 -0
|
@@ -1,460 +1,439 @@
|
|
|
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',
|
|
25
|
-
'debugger',
|
|
26
|
-
'reviewer',
|
|
27
|
-
'verifier',
|
|
28
|
-
'memory-master',
|
|
29
|
-
'context-master',
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const commonSkills = [
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'wf-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'Harness/
|
|
66
|
-
'Harness/
|
|
67
|
-
'Harness/
|
|
68
|
-
'Harness/
|
|
69
|
-
'Harness/
|
|
70
|
-
'Harness/
|
|
71
|
-
'Harness/
|
|
72
|
-
'Harness/
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'Harness
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
'Harness/
|
|
80
|
-
'Harness/research/PRD.md',
|
|
81
|
-
'Harness/
|
|
82
|
-
'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
function
|
|
120
|
-
const
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
.
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
for (const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
requireText('CLAUDE.md',
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (!
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (!
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
requireText('
|
|
404
|
-
requireText('
|
|
405
|
-
requireText('Harness/
|
|
406
|
-
requireText('Harness/
|
|
407
|
-
requireText('Harness/
|
|
408
|
-
requireText('Harness/
|
|
409
|
-
requireText('Harness/
|
|
410
|
-
requireText('Harness/
|
|
411
|
-
requireText('Harness/
|
|
412
|
-
requireText('Harness/
|
|
413
|
-
requireText('Harness/
|
|
414
|
-
requireText('Harness/
|
|
415
|
-
requireText('
|
|
416
|
-
requireText('
|
|
417
|
-
requireText('
|
|
418
|
-
requireText('.
|
|
419
|
-
requireText('.
|
|
420
|
-
requireText('
|
|
421
|
-
requireText('
|
|
422
|
-
requireText('
|
|
423
|
-
requireText('
|
|
424
|
-
requireText('
|
|
425
|
-
requireText('
|
|
426
|
-
requireText('Harness/
|
|
427
|
-
requireText('Harness/
|
|
428
|
-
requireText('
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
requireText('CLAUDE.md', '/wf update', 'wf update startup instruction');
|
|
441
|
-
requireText('Harness/README.md', 'Need harness update', 'update routing row');
|
|
442
|
-
requireText('Harness/WF-MAX.md', 'write-set coloring', 'WF-MAX coloring algorithm');
|
|
443
|
-
requireText('Harness/WF-MAX.md', 'wave dispatch', 'WF-MAX wave dispatch');
|
|
444
|
-
requireText('Harness/README.md', '/wf-max', 'wf max router alias');
|
|
445
|
-
requireText('Harness/README.md', 'WF-MAX.md', 'WF-MAX router reference');
|
|
446
|
-
requireText('Harness/subagents.md', 'Max parallelism', 'subagents max parallelism row');
|
|
447
|
-
requireText('Harness/dispatch.md', 'Concurrency group', 'dispatch concurrency group field');
|
|
448
|
-
requireText('Harness/dispatch.md', 'File claim', 'dispatch file claim field');
|
|
449
|
-
requireText('CLAUDE.md', '/wf-max', 'wf max startup instruction');
|
|
450
|
-
|
|
451
|
-
if (errors.length) {
|
|
452
|
-
console.error(`Harness validation failed${strict ? ' (strict)' : ''}:`);
|
|
453
|
-
for (const error of errors) console.error(`- ${error}`);
|
|
454
|
-
process.exit(1);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
console.log(`Harness validation passed${strict ? ' (strict)' : ''}.`);
|
|
458
|
-
if (!strict) {
|
|
459
|
-
console.log('Tip: run `node Harness/scripts/validate-harness.mjs --strict` after bootstrap to check unresolved project placeholders.');
|
|
460
|
-
}
|
|
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',
|
|
25
|
+
'debugger',
|
|
26
|
+
'reviewer',
|
|
27
|
+
'verifier',
|
|
28
|
+
'memory-master',
|
|
29
|
+
'context-master',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const commonSkills = [
|
|
33
|
+
'wf-update',
|
|
34
|
+
'wf-max',
|
|
35
|
+
'wf-review',
|
|
36
|
+
'wf-learn',
|
|
37
|
+
'subagent-orchestrator',
|
|
38
|
+
'wf-readme',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const memoryFiles = [
|
|
42
|
+
'Harness/memory/tool-usage-reflections.md',
|
|
43
|
+
'Harness/memory/user-corrections-preferences.md',
|
|
44
|
+
'Harness/memory/agent-lessons-patterns.md',
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const required = [
|
|
48
|
+
'AGENTS.md',
|
|
49
|
+
'CLAUDE.md',
|
|
50
|
+
'README.md',
|
|
51
|
+
'Harness/MEMORY.md',
|
|
52
|
+
'Harness/WF.md',
|
|
53
|
+
'Harness/WF-MAX.md',
|
|
54
|
+
...memoryFiles,
|
|
55
|
+
'.claude/settings.json',
|
|
56
|
+
'.claude/commands/wf.md',
|
|
57
|
+
'.claude/commands/wf-max.md',
|
|
58
|
+
'.claude/rules/ecc/common.md',
|
|
59
|
+
...commonAgents.map(agent => `.claude/agents/${agent}.md`),
|
|
60
|
+
...commonSkills.map(skill => `.claude/skills/${skill}/SKILL.md`),
|
|
61
|
+
'Harness/README.md',
|
|
62
|
+
'Harness/PROGRESS.md',
|
|
63
|
+
'Harness/lifecycle.md',
|
|
64
|
+
'Harness/subagents.md',
|
|
65
|
+
'Harness/dispatch.md',
|
|
66
|
+
'Harness/extension.md',
|
|
67
|
+
'Harness/context-loading.md',
|
|
68
|
+
'Harness/agent-workflow.md',
|
|
69
|
+
'Harness/architecture.md',
|
|
70
|
+
'Harness/research/README.md',
|
|
71
|
+
'Harness/research/research-results.md',
|
|
72
|
+
'Harness/research/PRD.md',
|
|
73
|
+
'.claude/skills/wf-update/SKILL.md',
|
|
74
|
+
'.claude/commands/wf-update.md',
|
|
75
|
+
'Harness/.harness-version',
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const projectFacts = [
|
|
79
|
+
'Harness/PROGRESS.md',
|
|
80
|
+
'Harness/research/PRD.md',
|
|
81
|
+
'Harness/research/research-results.md',
|
|
82
|
+
'Harness/architecture.md',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const contextPacks = [
|
|
86
|
+
'Explorer Pass:',
|
|
87
|
+
'Planner:',
|
|
88
|
+
'Researcher:',
|
|
89
|
+
'Docs Researcher:',
|
|
90
|
+
'Architect:',
|
|
91
|
+
'Test Writer:',
|
|
92
|
+
'Implementer:',
|
|
93
|
+
'Reviewer:',
|
|
94
|
+
'Debugger:',
|
|
95
|
+
'Verifier:',
|
|
96
|
+
'Memory Master:',
|
|
97
|
+
'Context Master:',
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const durableCommunicationDocs = [
|
|
101
|
+
'Harness/README.md',
|
|
102
|
+
'Harness/subagents.md',
|
|
103
|
+
'Harness/dispatch.md',
|
|
104
|
+
'Harness/context-loading.md',
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const errors = [];
|
|
108
|
+
|
|
109
|
+
function read(rel) {
|
|
110
|
+
const file = path.join(root, rel);
|
|
111
|
+
return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function requireText(rel, text, label = text) {
|
|
115
|
+
const body = read(rel);
|
|
116
|
+
if (body && !body.includes(text)) errors.push(`${rel} missing ${label}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function frontmatterField(text, field) {
|
|
120
|
+
const match = text.match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
|
|
121
|
+
return match ? match[1].trim() : '';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function listDirectories(rel) {
|
|
125
|
+
const dir = path.join(root, rel);
|
|
126
|
+
if (!fs.existsSync(dir)) return [];
|
|
127
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
128
|
+
.filter(entry => entry.isDirectory())
|
|
129
|
+
.map(entry => entry.name);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function listMarkdownFiles(rel) {
|
|
133
|
+
const dir = path.join(root, rel);
|
|
134
|
+
if (!fs.existsSync(dir)) return [];
|
|
135
|
+
const normalizedRel = rel.replaceAll(path.sep, '/');
|
|
136
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
137
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
138
|
+
.map(entry => `${normalizedRel}/${entry.name}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function unresolvedTemplatePlaceholders(text) {
|
|
142
|
+
const placeholders = [];
|
|
143
|
+
const pattern = /\{\{([^{}\r\n]+)\}\}/g;
|
|
144
|
+
|
|
145
|
+
for (const match of text.matchAll(pattern)) {
|
|
146
|
+
const token = match[1].trim();
|
|
147
|
+
if (token === '...') continue;
|
|
148
|
+
placeholders.push(`{{${token}}}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return [...new Set(placeholders)];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function registeredSkillFiles(...texts) {
|
|
155
|
+
const files = new Set();
|
|
156
|
+
const pattern = /(?:\.\.\/)?(\.claude\/skills\/[a-z0-9-]+\/SKILL\.md)/g;
|
|
157
|
+
|
|
158
|
+
for (const text of texts) {
|
|
159
|
+
for (const match of text.matchAll(pattern)) {
|
|
160
|
+
files.add(match[1]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return [...files].sort();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function registeredWorkflowFiles(...texts) {
|
|
168
|
+
const files = new Set();
|
|
169
|
+
const pattern = /(?:Harness\/)?workflows\/([A-Za-z0-9._-]+\.md)/g;
|
|
170
|
+
|
|
171
|
+
for (const text of texts) {
|
|
172
|
+
for (const match of text.matchAll(pattern)) {
|
|
173
|
+
files.add(`Harness/workflows/${match[1]}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return [...files].sort();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const rel of required) {
|
|
181
|
+
if (!fs.existsSync(path.join(root, rel))) {
|
|
182
|
+
errors.push(`missing required file: ${rel}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Task capsule template files
|
|
187
|
+
const taskTemplateDir = path.join(root, 'Harness', 'tasks', '_template');
|
|
188
|
+
if (!fs.existsSync(taskTemplateDir)) {
|
|
189
|
+
errors.push('missing directory: Harness/tasks/_template/');
|
|
190
|
+
} else {
|
|
191
|
+
for (const f of ['PROGRESS.md', 'PLAN.md']) {
|
|
192
|
+
if (!fs.existsSync(path.join(taskTemplateDir, f))) {
|
|
193
|
+
errors.push(`missing task template file: Harness/tasks/_template/${f}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Cross-reference: DONE files in task PLAN.md must exist on disk
|
|
199
|
+
const taskDirs = fs.existsSync(path.join(root, 'Harness', 'tasks'))
|
|
200
|
+
? fs.readdirSync(path.join(root, 'Harness', 'tasks'), { withFileTypes: true })
|
|
201
|
+
.filter(e => e.isDirectory() && e.name !== '_template')
|
|
202
|
+
.map(e => e.name)
|
|
203
|
+
: [];
|
|
204
|
+
for (const taskDir of taskDirs) {
|
|
205
|
+
const planPath = `Harness/tasks/${taskDir}/PLAN.md`;
|
|
206
|
+
const planText = read(planPath);
|
|
207
|
+
if (!planText) continue;
|
|
208
|
+
const donePattern = /`([^`]+\.(?:md|mjs|js|ts|json|html|css))`[^\n]*DONE/gi;
|
|
209
|
+
for (const match of planText.matchAll(donePattern)) {
|
|
210
|
+
const claimedFile = match[1];
|
|
211
|
+
if (!fs.existsSync(path.join(root, claimedFile))) {
|
|
212
|
+
errors.push(`${planPath} claims '${claimedFile}' is DONE but file does not exist`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (fs.existsSync(path.join(root, 'Harness/research/scaffolds.md'))) {
|
|
218
|
+
errors.push('legacy research file should be renamed: Harness/research/scaffolds.md -> Harness/research/research-results.md');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (strict) {
|
|
222
|
+
console.log('Strict placeholder scope:');
|
|
223
|
+
for (const rel of projectFacts) {
|
|
224
|
+
console.log(`- ${rel}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (const rel of projectFacts) {
|
|
228
|
+
const text = read(rel);
|
|
229
|
+
for (const placeholder of unresolvedTemplatePlaceholders(text)) {
|
|
230
|
+
errors.push(`template placeholder remains in project fact file: ${rel}: ${placeholder}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const docsReadme = read('Harness/README.md');
|
|
236
|
+
if (docsReadme) {
|
|
237
|
+
for (const marker of ['## Keyword Routing', '## Load By Task', 'When to Read', 'Keywords']) {
|
|
238
|
+
if (!docsReadme.includes(marker)) errors.push(`Harness/README.md missing router marker: ${marker}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (const rel of durableCommunicationDocs) {
|
|
243
|
+
requireText(rel, 'project files are the only durable communication channel', 'durable filesystem communication invariant');
|
|
244
|
+
requireText(rel, 'chat/subagent transcript state is non-authoritative', 'non-authoritative transcript invariant');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
requireText('CLAUDE.md', 'same tool/use pattern fails 3+ times', 'tool reflection trigger');
|
|
248
|
+
requireText('CLAUDE.md', 'user corrects the same assumption/pattern 2+ times', 'user correction reflection trigger');
|
|
249
|
+
requireText('CLAUDE.md', 'If `Harness/` exists, this repository is governed by the Harness contract', 'Harness binding contract');
|
|
250
|
+
requireText('CLAUDE.md', 'Harness/MEMORY.md` is the memory/resource router', 'memory/resource router');
|
|
251
|
+
requireText('CLAUDE.md', 'Harness/README.md#Load By Task', 'Harness task router');
|
|
252
|
+
requireText('CLAUDE.md', 'Harness/SETUP.md` exists, follow it before normal project work', 'setup bootstrap contract');
|
|
253
|
+
requireText('CLAUDE.md', 'subagent-orchestrator` and `Harness/subagents.md', 'subagent orchestrator entry trigger');
|
|
254
|
+
requireText('CLAUDE.md', 'Harness/PROGRESS.md` is the global task index', 'PROGRESS global task index');
|
|
255
|
+
requireText('CLAUDE.md', 'Harness/tasks/', 'task capsule directory reference');
|
|
256
|
+
requireText('CLAUDE.md', 'Subagents are readers and reporters', 'subagent state committer rule');
|
|
257
|
+
for (const heading of ['## 2. Think Before Coding', '## 3. Simplicity First', '## 4. Surgical Changes', '## 5. Goal-Driven Execution']) {
|
|
258
|
+
requireText('CLAUDE.md', heading, `Karpathy-style rule heading: ${heading}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Root PROGRESS.md structure check
|
|
262
|
+
const progress = read('Harness/PROGRESS.md');
|
|
263
|
+
if (progress) {
|
|
264
|
+
for (const heading of ['## Active Task', '## Task Index', '## Cross-Task Decisions']) {
|
|
265
|
+
if (!progress.includes(heading)) errors.push(`Harness/PROGRESS.md missing heading: ${heading}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Legacy PLAN.md deprecation check (removed — stub only)
|
|
270
|
+
|
|
271
|
+
const dispatch = read('Harness/dispatch.md');
|
|
272
|
+
if (dispatch) {
|
|
273
|
+
for (const agent of commonAgents) {
|
|
274
|
+
if (!dispatch.includes(`\`${agent}\``)) errors.push(`Harness/dispatch.md missing common agent: ${agent}`);
|
|
275
|
+
}
|
|
276
|
+
if (!dispatch.includes('## Handoff Format')) errors.push('Harness/dispatch.md missing heading: ## Handoff Format');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const contextLoading = read('Harness/context-loading.md');
|
|
280
|
+
if (contextLoading) {
|
|
281
|
+
if (!contextLoading.includes('Harness/README.md` is the primary router')) {
|
|
282
|
+
errors.push('Harness/context-loading.md must declare Harness/README.md as the primary router');
|
|
283
|
+
}
|
|
284
|
+
for (const pack of contextPacks) {
|
|
285
|
+
if (!contextLoading.includes(pack)) errors.push(`Harness/context-loading.md missing subagent pack: ${pack}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const memory = read('Harness/MEMORY.md');
|
|
290
|
+
if (memory) {
|
|
291
|
+
for (const agent of commonAgents) {
|
|
292
|
+
const rel = `.claude/agents/${agent}.md`;
|
|
293
|
+
if (!memory.includes(rel)) errors.push(`Harness/MEMORY.md missing agent registration: ${rel}`);
|
|
294
|
+
}
|
|
295
|
+
for (const skill of commonSkills) {
|
|
296
|
+
const rel = `.claude/skills/${skill}/SKILL.md`;
|
|
297
|
+
if (!memory.includes(rel)) errors.push(`Harness/MEMORY.md missing skill registration: ${rel}`);
|
|
298
|
+
}
|
|
299
|
+
for (const rel of memoryFiles) {
|
|
300
|
+
const relativeRel = rel.replace(/^Harness\//, '');
|
|
301
|
+
if (!memory.includes(rel) && !memory.includes(relativeRel)) {
|
|
302
|
+
errors.push(`Harness/MEMORY.md missing memory file registration: ${rel}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const workflow of listMarkdownFiles('Harness/workflows')) {
|
|
308
|
+
const relativeWorkflow = workflow.replace(/^Harness\//, '');
|
|
309
|
+
if (!docsReadme.includes(workflow) && !docsReadme.includes(relativeWorkflow) && !memory.includes(workflow) && !memory.includes(relativeWorkflow)) {
|
|
310
|
+
errors.push(`workflow is not registered in Harness/README.md or Harness/MEMORY.md: ${workflow}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
for (const skillFile of registeredSkillFiles(docsReadme, memory)) {
|
|
315
|
+
if (!fs.existsSync(path.join(root, skillFile))) {
|
|
316
|
+
errors.push(`registered skill file is missing: ${skillFile}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
for (const workflowFile of registeredWorkflowFiles(docsReadme, memory)) {
|
|
321
|
+
if (!fs.existsSync(path.join(root, workflowFile))) {
|
|
322
|
+
errors.push(`registered workflow file is missing: ${workflowFile}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function requireUiSelectorContract(rel) {
|
|
327
|
+
const text = read(rel);
|
|
328
|
+
if (!text) return;
|
|
329
|
+
|
|
330
|
+
const markers = [
|
|
331
|
+
'data-testid',
|
|
332
|
+
'accessible labels/roles',
|
|
333
|
+
'inputs, buttons, filters, rows, empty/error/loading states',
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
if (markers.some(marker => !text.includes(marker))) {
|
|
337
|
+
errors.push(`${rel} missing stable UI selector contract`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
requireUiSelectorContract('Harness/workflows/browser-e2e.md');
|
|
342
|
+
requireUiSelectorContract('Harness/workflows/ts-react-frontend.md');
|
|
343
|
+
|
|
344
|
+
for (const skill of commonSkills) {
|
|
345
|
+
const rel = `.claude/skills/${skill}/SKILL.md`;
|
|
346
|
+
const text = read(rel);
|
|
347
|
+
if (!text) continue;
|
|
348
|
+
if (frontmatterField(text, 'name') !== skill) errors.push(`${rel} frontmatter name does not match directory`);
|
|
349
|
+
if (!frontmatterField(text, 'description')) errors.push(`${rel} missing frontmatter field: description`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (const skill of listDirectories('.claude/skills')) {
|
|
353
|
+
if (commonSkills.includes(skill)) continue;
|
|
354
|
+
|
|
355
|
+
const rel = `.claude/skills/${skill}/SKILL.md`;
|
|
356
|
+
const text = read(rel);
|
|
357
|
+
if (!text) continue;
|
|
358
|
+
|
|
359
|
+
if (frontmatterField(text, 'name') !== skill) errors.push(`${rel} frontmatter name does not match directory`);
|
|
360
|
+
if (!frontmatterField(text, 'description')) errors.push(`${rel} missing frontmatter field: description`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (const agent of commonAgents) {
|
|
364
|
+
const rel = `.claude/agents/${agent}.md`;
|
|
365
|
+
const text = read(rel);
|
|
366
|
+
if (!text) continue;
|
|
367
|
+
|
|
368
|
+
for (const field of ['name', 'description', 'tools', 'model']) {
|
|
369
|
+
if (!frontmatterField(text, field)) errors.push(`${rel} missing frontmatter field: ${field}:`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (frontmatterField(text, 'name') !== agent) {
|
|
373
|
+
errors.push(`${rel} frontmatter name does not match filename`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const skill = frontmatterField(text, 'skills');
|
|
377
|
+
if (skill && !commonSkills.includes(skill)) {
|
|
378
|
+
errors.push(`${rel} references unknown skill: ${skill}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (skill && !fs.existsSync(path.join(root, `.claude/skills/${skill}/SKILL.md`))) {
|
|
382
|
+
errors.push(`${rel} references missing skill file: .claude/skills/${skill}/SKILL.md`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
requireText('Harness/extension.md', 'Skills should extend the harness');
|
|
387
|
+
requireText('Harness/agent-workflow.md', 'Harness/tasks/<task-id>/PROGRESS.md');
|
|
388
|
+
requireText('Harness/research/README.md', 'research-results.md');
|
|
389
|
+
requireText('Harness/WF.md', 'Ralph-style harness loop', 'WF loop description');
|
|
390
|
+
requireText('Harness/WF.md', 'Heartbeat Protocol', 'heartbeat protocol');
|
|
391
|
+
requireText('Harness/WF.md', 'WF mode requires multi-subagent orchestration by default', 'WF multi-subagent default');
|
|
392
|
+
requireText('Harness/WF.md', 'Explicit `/wf`, `wf mode`, `workflow mode`, or `wk mode` MUST spawn at least 3 distinct subagents', 'explicit WF/WK subagent minimum');
|
|
393
|
+
requireText('Harness/WF.md', '.claude/agents/', 'WF built-in agent roster path');
|
|
394
|
+
requireText('Harness/WF.md', 'Collaboration decision tree', 'WF decision tree');
|
|
395
|
+
requireText('Harness/WF.md', 'Harness/tasks/', 'WF task directory reference');
|
|
396
|
+
requireText('Harness/README.md', '`wf mode`, `workflow mode`, or `wk mode`', 'WF/WK router aliases');
|
|
397
|
+
requireText('Harness/README.md', 'explicit WF/WK loads subagent docs immediately', 'explicit WF/WK router output');
|
|
398
|
+
// harness-* wrapper skills removed — routing table in README.md is the single source
|
|
399
|
+
// wf-mode skill removed — WF rules now live directly in Harness/WF.md and .claude/commands/wf.md
|
|
400
|
+
requireText('.claude/skills/subagent-orchestrator/SKILL.md', 'Harness/subagents.md', 'subagent-orchestrator loads subagents doc');
|
|
401
|
+
requireText('.claude/skills/subagent-orchestrator/SKILL.md', '.claude/agents/', 'subagent-orchestrator built-in agent roster path');
|
|
402
|
+
requireText('.claude/skills/subagent-orchestrator/SKILL.md', '`workflow mode`, `wk mode`', 'subagent-orchestrator WF/WK aliases');
|
|
403
|
+
requireText('.claude/skills/wf-readme/SKILL.md', 'README.md', 'wf-readme loads README');
|
|
404
|
+
requireText('.claude/skills/wf-readme/SKILL.md', 'Harness/architecture.md', 'wf-readme links architecture docs');
|
|
405
|
+
requireText('Harness/subagents.md', '## Source Attribution', 'subagent source attribution');
|
|
406
|
+
requireText('Harness/subagents.md', 'npx skills find', 'find-skills discovery attribution');
|
|
407
|
+
requireText('Harness/subagents.md', 'superpowers:dispatching-parallel-agents', 'parallel-agent source attribution');
|
|
408
|
+
requireText('Harness/subagents.md', 'superpowers:subagent-driven-development', 'subagent-driven source attribution');
|
|
409
|
+
requireText('Harness/subagents.md', '## Built-in Agent Roster', 'built-in agent roster');
|
|
410
|
+
requireText('Harness/subagents.md', '## WF Default Fan-Out', 'WF default fan-out');
|
|
411
|
+
requireText('Harness/subagents.md', 'concrete conditions', 'subagent decision tree');
|
|
412
|
+
requireText('Harness/subagents.md', 'parallel planner/researcher/docs-researcher/architect subagents', 'WF roster orchestration shape');
|
|
413
|
+
requireText('Harness/subagents.md', '## Efficiency Ladder', 'subagent efficiency ladder');
|
|
414
|
+
requireText('Harness/subagents.md', '## Review Gates', 'subagent review gates');
|
|
415
|
+
requireText('Harness/architecture.md', '## 2. Interface Decoupling', 'architecture interface decoupling');
|
|
416
|
+
requireText('Harness/architecture.md', '## 3. State Design', 'architecture state design');
|
|
417
|
+
requireText('Harness/architecture.md', 'Avoid speculative abstraction', 'anti-overengineering architecture rule');
|
|
418
|
+
requireText('CLAUDE.md', 'Use explicit interfaces or state models only when they protect a real boundary', 'CLAUDE interface/state simplicity rule');
|
|
419
|
+
requireText('CLAUDE.md', '/wf-update', 'wf update startup instruction');
|
|
420
|
+
requireText('Harness/README.md', 'Need harness update', 'update routing row');
|
|
421
|
+
requireText('Harness/WF-MAX.md', 'write-set coloring', 'WF-MAX coloring algorithm');
|
|
422
|
+
requireText('Harness/WF-MAX.md', 'wave dispatch', 'WF-MAX wave dispatch');
|
|
423
|
+
requireText('Harness/README.md', '/wf-max', 'wf max router alias');
|
|
424
|
+
requireText('Harness/README.md', 'WF-MAX.md', 'WF-MAX router reference');
|
|
425
|
+
requireText('Harness/subagents.md', 'Max parallelism', 'subagents max parallelism row');
|
|
426
|
+
requireText('Harness/dispatch.md', 'Concurrency group', 'dispatch concurrency group field');
|
|
427
|
+
requireText('Harness/dispatch.md', 'File claim', 'dispatch file claim field');
|
|
428
|
+
requireText('CLAUDE.md', '/wf-max', 'wf max startup instruction');
|
|
429
|
+
|
|
430
|
+
if (errors.length) {
|
|
431
|
+
console.error(`Harness validation failed${strict ? ' (strict)' : ''}:`);
|
|
432
|
+
for (const error of errors) console.error(`- ${error}`);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
console.log(`Harness validation passed${strict ? ' (strict)' : ''}.`);
|
|
437
|
+
if (!strict) {
|
|
438
|
+
console.log('Tip: run `node Harness/scripts/validate-harness.mjs --strict` after bootstrap to check unresolved project placeholders.');
|
|
439
|
+
}
|