create-harness-vibe-coding 0.8.8 → 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.
- package/README-CN.md +7 -1
- package/README.md +21 -2
- package/docs/images/harness-icon.png +0 -0
- package/package.json +2 -1
- package/src/generator.js +36 -0
- package/src/index.js +72 -4
- package/templates/common/.claude/agents/architect.md +31 -31
- package/templates/common/.claude/agents/codebase-explorer.md +45 -0
- package/templates/common/.claude/agents/docs-researcher.md +41 -41
- package/templates/common/.claude/agents/memory-master.md +26 -8
- package/templates/common/.claude/agents/reviewer.md +34 -34
- package/templates/common/.claude/agents/task-scribe.md +70 -0
- package/templates/common/.claude/commands/wf-help.md +8 -4
- package/templates/common/.claude/commands/wf-update.md +24 -0
- package/templates/common/.claude/rules/ecc/common.md +15 -2
- package/templates/common/.claude/settings.json +13 -0
- package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +8 -4
- package/templates/common/.claude/skills/wf/SKILL.md +15 -8
- package/templates/common/.claude/skills/wf-auto/SKILL.md +3 -2
- package/templates/common/.claude/skills/wf-learn/SKILL.md +9 -2
- package/templates/common/.claude/skills/wf-max/SKILL.md +23 -5
- package/templates/common/.claude/skills/wf-update/SKILL.md +15 -2
- package/templates/common/.codex/hooks.json +17 -0
- package/templates/common/.harness-version +89 -44
- package/templates/common/.opencode/agents/architect.md +24 -24
- package/templates/common/.opencode/agents/codebase-explorer.md +45 -0
- package/templates/common/.opencode/agents/docs-researcher.md +34 -34
- package/templates/common/.opencode/agents/memory-master.md +26 -8
- package/templates/common/.opencode/agents/reviewer.md +27 -27
- package/templates/common/.opencode/agents/task-scribe.md +70 -0
- package/templates/common/.opencode/commands/wf-auto-spark.md +15 -0
- package/templates/common/.opencode/commands/wf-auto.md +15 -0
- package/templates/common/.opencode/commands/wf-help.md +8 -4
- package/templates/common/.opencode/commands/wf-learn.md +15 -0
- package/templates/common/.opencode/commands/wf-max.md +15 -0
- package/templates/common/.opencode/commands/wf-readme.md +15 -0
- package/templates/common/.opencode/commands/wf-remove.md +15 -0
- package/templates/common/.opencode/commands/wf-review.md +15 -0
- package/templates/common/.opencode/commands/wf-update.md +24 -0
- package/templates/common/.opencode/commands/wf.md +15 -0
- package/templates/common/.opencode/plugins/harness-wf-status.mjs +135 -0
- package/templates/common/AGENTS.md +2 -29
- package/templates/common/CLAUDE.md +35 -6
- package/templates/common/Harness/ACCEPTANCE_PROTOCOL.md +2 -2
- package/templates/common/{MEMORY.md → Harness/MEMORY.md} +16 -3
- package/templates/common/Harness/MEMORY_PROTOCOL.md +80 -30
- package/templates/common/Harness/README.md +50 -22
- package/templates/common/{SETUP.md → Harness/SETUP.md} +278 -276
- package/templates/common/Harness/TASK_ARCHIVE.md +56 -0
- package/templates/common/Harness/WF-AUTO-SPARK.md +5 -14
- package/templates/common/Harness/WF-AUTO.md +13 -76
- package/templates/common/Harness/WF-KERNEL.md +189 -0
- package/templates/common/Harness/WF-MAX.md +60 -328
- package/templates/common/Harness/WF-STATE.md +83 -0
- package/templates/common/Harness/WF.md +117 -237
- package/templates/common/Harness/agent-workflow.md +2 -2
- package/templates/common/Harness/context-loading.md +3 -3
- package/templates/common/Harness/dispatch.md +43 -35
- package/templates/common/Harness/scripts/archive-tasks.mjs +239 -0
- package/templates/common/{scripts → Harness/scripts}/scan-clean.mjs +28 -1
- package/templates/common/{scripts → Harness/scripts}/validate-harness.mjs +909 -699
- package/templates/common/Harness/scripts/wf-auto-update-prompt.mjs +258 -0
- package/templates/common/{scripts → Harness/scripts}/wf-update-check.mjs +40 -7
- package/templates/common/Harness/subagents.md +28 -27
- package/templates/common/Harness/tasks/_template/PLAN.md +5 -0
- package/templates/common/Harness/tasks/_template/STATE.json +23 -0
- package/templates/common/README.md +1 -1
- package/templates/common/memory/agent-lessons-patterns.md +9 -8
- package/templates/common/memory/routes.md +43 -0
- package/templates/common/memory/startup-hints.md +32 -0
- package/templates/common/memory/tool-usage-reflections.md +9 -8
- package/templates/common/memory/user-corrections-preferences.md +11 -9
- package/templates/optional/catalog.json +8 -0
- package/templates/optional/skills/browser-e2e/.claude/skills/browser-e2e/SKILL.md +42 -42
- package/templates/optional/skills/browser-e2e/.opencode/commands/wf-browser.md +15 -0
- 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/{scripts → Harness/scripts}/wf-remove.mjs +0 -0
|
@@ -1,699 +1,909 @@
|
|
|
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
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'wf
|
|
41
|
-
'
|
|
42
|
-
'wf-
|
|
43
|
-
'wf-
|
|
44
|
-
'
|
|
45
|
-
'wf-
|
|
46
|
-
'
|
|
47
|
-
'wf-
|
|
48
|
-
'wf-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
'Harness/
|
|
88
|
-
'Harness/
|
|
89
|
-
'Harness/
|
|
90
|
-
'Harness/
|
|
91
|
-
|
|
92
|
-
'
|
|
93
|
-
'
|
|
94
|
-
'
|
|
95
|
-
'
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
|
|
100
|
-
'
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
'Harness/PROGRESS.md',
|
|
107
|
-
'Harness/
|
|
108
|
-
'Harness/
|
|
109
|
-
'Harness/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
'
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
'
|
|
120
|
-
'
|
|
121
|
-
'
|
|
122
|
-
'
|
|
123
|
-
'
|
|
124
|
-
'
|
|
125
|
-
];
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
'Harness/
|
|
129
|
-
'Harness/
|
|
130
|
-
'Harness/
|
|
131
|
-
'Harness/
|
|
132
|
-
];
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
return
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (!fs.existsSync(path.join(
|
|
299
|
-
errors.push(
|
|
300
|
-
}
|
|
301
|
-
}
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (!
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (!
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
requireText('Harness/
|
|
620
|
-
requireText('Harness/
|
|
621
|
-
requireText('Harness/
|
|
622
|
-
requireText('Harness/WF
|
|
623
|
-
requireText('Harness/
|
|
624
|
-
requireText('Harness/
|
|
625
|
-
requireText('Harness/
|
|
626
|
-
requireText('Harness/
|
|
627
|
-
requireText('Harness/
|
|
628
|
-
requireText('Harness/
|
|
629
|
-
requireText('Harness/
|
|
630
|
-
requireText('Harness/
|
|
631
|
-
requireText('Harness/
|
|
632
|
-
requireText('Harness/
|
|
633
|
-
requireText('Harness/
|
|
634
|
-
requireText('
|
|
635
|
-
requireText('
|
|
636
|
-
requireText('.claude/skills/
|
|
637
|
-
requireText('.claude/skills/wf
|
|
638
|
-
requireText('.
|
|
639
|
-
requireText('.claude/skills/
|
|
640
|
-
requireText('.claude/
|
|
641
|
-
requireText('.claude/
|
|
642
|
-
requireText('.claude/
|
|
643
|
-
requireText('.claude/
|
|
644
|
-
requireText('
|
|
645
|
-
requireText('
|
|
646
|
-
requireText('Harness/
|
|
647
|
-
requireText('Harness/
|
|
648
|
-
requireText('Harness/subagents.md', '
|
|
649
|
-
requireText('Harness/
|
|
650
|
-
requireText('Harness/
|
|
651
|
-
requireText('
|
|
652
|
-
requireText('
|
|
653
|
-
requireText('
|
|
654
|
-
requireText('
|
|
655
|
-
requireText('
|
|
656
|
-
requireText('
|
|
657
|
-
requireText('Harness/
|
|
658
|
-
requireText('Harness/
|
|
659
|
-
requireText('
|
|
660
|
-
requireText('Harness/README.md', '
|
|
661
|
-
requireText('Harness/README.md', '
|
|
662
|
-
requireText('Harness/WF-
|
|
663
|
-
requireText('Harness/WF-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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',
|
|
27
|
+
'debugger',
|
|
28
|
+
'reviewer',
|
|
29
|
+
'verifier',
|
|
30
|
+
'reflector',
|
|
31
|
+
'memory-master',
|
|
32
|
+
'context-master',
|
|
33
|
+
'explore-manager',
|
|
34
|
+
'architect-manager',
|
|
35
|
+
'implement-manager',
|
|
36
|
+
'review-manager',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const commonSkills = [
|
|
40
|
+
'wf',
|
|
41
|
+
'tdd',
|
|
42
|
+
'wf-update',
|
|
43
|
+
'wf-max',
|
|
44
|
+
'wf-review',
|
|
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',
|
|
76
|
+
'Harness/MEMORY.md',
|
|
77
|
+
'Harness/WF.md',
|
|
78
|
+
'Harness/WF-MAX.md',
|
|
79
|
+
'Harness/ACCEPTANCE_PROTOCOL.md',
|
|
80
|
+
'Harness/AGENT_ISOLATION.md',
|
|
81
|
+
'Harness/HARNESS_BRIDGE.md',
|
|
82
|
+
'Harness/DEBUG_PROTOCOL.md',
|
|
83
|
+
'Harness/MEMORY_PROTOCOL.md',
|
|
84
|
+
'Harness/templates/PRD.template.md',
|
|
85
|
+
'Harness/templates/ACCEPTANCE.template.md',
|
|
86
|
+
'Harness/templates/UI_CONTRACT.template.md',
|
|
87
|
+
'Harness/templates/API_CONTRACT.template.md',
|
|
88
|
+
'Harness/templates/TEST_PLAN.template.md',
|
|
89
|
+
'Harness/templates/PLAYWRIGHT_SPEC.template.ts',
|
|
90
|
+
'Harness/templates/VALIDATION_REPORT.template.md',
|
|
91
|
+
...memoryFiles,
|
|
92
|
+
'.codex/config.toml',
|
|
93
|
+
'opencode.json',
|
|
94
|
+
'.claude/settings.json',
|
|
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`),
|
|
100
|
+
'.claude/rules/ecc/common.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',
|
|
117
|
+
'Harness/research/PRD.md',
|
|
118
|
+
'Harness/scripts/wf-update-check.mjs',
|
|
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
|
+
|
|
163
|
+
function requireText(rel, text, label = text) {
|
|
164
|
+
const body = read(rel);
|
|
165
|
+
if (body && !body.includes(text)) errors.push(`${rel} missing ${label}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function forbidText(rel, text, label = text) {
|
|
169
|
+
const body = read(rel);
|
|
170
|
+
if (body && body.includes(text)) errors.push(`${rel} contains forbidden ${label}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
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
|
+
|
|
243
|
+
function registeredWorkflowFiles(...texts) {
|
|
244
|
+
const files = new Set();
|
|
245
|
+
const pattern = /(?:Harness\/)?workflows\/([A-Za-z0-9._-]+\.md)/g;
|
|
246
|
+
|
|
247
|
+
for (const text of texts) {
|
|
248
|
+
for (const match of text.matchAll(pattern)) {
|
|
249
|
+
files.add(`Harness/workflows/${match[1]}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return [...files].sort();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function listedWorkflowCommands(...texts) {
|
|
257
|
+
const commands = new Set();
|
|
258
|
+
const pattern = /`\/(wf(?:-[a-z0-9]+)?)(?:\s+[^`]*)?`/g;
|
|
259
|
+
|
|
260
|
+
for (const text of texts) {
|
|
261
|
+
for (const match of text.matchAll(pattern)) {
|
|
262
|
+
const command = `/${match[1]}`;
|
|
263
|
+
if (command === '/wf-help') continue;
|
|
264
|
+
if (command === '/wf-browser') continue; // optional workflow, not a required skill
|
|
265
|
+
commands.add(command);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return [...commands].sort();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (const rel of required) {
|
|
273
|
+
if (!fs.existsSync(path.join(root, rel))) {
|
|
274
|
+
errors.push(`missing required file: ${rel}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const removedHookArtifacts = [
|
|
279
|
+
'Harness/HOOK_PROTOCOL.md',
|
|
280
|
+
'Harness/scripts/wf-mode-hook.mjs',
|
|
281
|
+
'Harness/scripts/wf-statusline.sh',
|
|
282
|
+
'Harness/scripts/wf-statusline.ps1',
|
|
283
|
+
'tests/e2e-wf-hooks.test.mjs',
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
for (const rel of removedHookArtifacts) {
|
|
287
|
+
if (fs.existsSync(path.join(root, rel))) {
|
|
288
|
+
errors.push(`removed hook artifact should not exist: ${rel}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
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');
|
|
392
|
+
requireText('Harness/SETUP.md', 'Harness/MEMORY_PROTOCOL.md', 'setup memory protocol reference');
|
|
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
|
+
|
|
454
|
+
for (const skillFile of registeredSkillFiles(docsReadme, memory)) {
|
|
455
|
+
if (!fs.existsSync(path.join(root, skillFile))) {
|
|
456
|
+
errors.push(`registered skill file is missing: ${skillFile}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
for (const command of listedWorkflowCommands(docsReadme, read('.claude/commands/wf-help.md'))) {
|
|
461
|
+
const skill = command.slice(1);
|
|
462
|
+
const claudeSkill = `.claude/skills/${skill}/SKILL.md`;
|
|
463
|
+
const codexSkill = `.agents/skills/${skill}/SKILL.md`;
|
|
464
|
+
if (!fs.existsSync(path.join(root, claudeSkill))) {
|
|
465
|
+
errors.push(`command docs list missing Claude skill: ${command} -> ${claudeSkill}`);
|
|
466
|
+
}
|
|
467
|
+
if (!fs.existsSync(path.join(root, codexSkill))) {
|
|
468
|
+
errors.push(`command docs list missing Codex skill: ${command} -> ${codexSkill}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const workflowFile of registeredWorkflowFiles(docsReadme, memory)) {
|
|
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
|
+
|
|
617
|
+
requireText('Harness/extension.md', 'Skills should extend the harness');
|
|
618
|
+
requireText('Harness/agent-workflow.md', 'Harness/tasks/<task-id>/PROGRESS.md');
|
|
619
|
+
requireText('Harness/README.md', 'Task records are compact by default', 'compact task record router rule');
|
|
620
|
+
requireText('Harness/agent-workflow.md', 'AC record size', 'compact AC record size rule');
|
|
621
|
+
requireText('Harness/ACCEPTANCE_PROTOCOL.md', 'Default to 1-3 concise ACs', 'compact AC default');
|
|
622
|
+
requireText('Harness/WF.md', 'task-scribe', 'compact heartbeat rule');
|
|
623
|
+
requireText('Harness/tasks/_template/PLAN.md', 'Compact task record', 'compact task PLAN template');
|
|
624
|
+
requireText('Harness/tasks/_template/PLAN.md', 'Default: keep 1-3 concise ACs', 'compact task PLAN AC guidance');
|
|
625
|
+
requireText('Harness/tasks/_template/PROGRESS.md', 'Compact heartbeat', 'compact task PROGRESS template');
|
|
626
|
+
requireText('Harness/agent-workflow.md', 'TDD-GUIDE.md', 'agent workflow loads TDD guide');
|
|
627
|
+
requireText('Harness/agent-workflow.md', 'real user-path test', 'agent workflow real user path requirement');
|
|
628
|
+
requireText('Harness/research/README.md', 'research-results.md');
|
|
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');
|
|
633
|
+
requireText('Harness/WF.md', 'Harness/tasks/', 'WF task directory reference');
|
|
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');
|
|
648
|
+
requireText('Harness/subagents.md', '## Built-in Agent Roster', 'built-in agent roster');
|
|
649
|
+
requireText('Harness/subagents.md', '## WF Default Fan-Out', 'WF default fan-out');
|
|
650
|
+
requireText('Harness/subagents.md', 'concrete conditions', 'subagent decision tree');
|
|
651
|
+
requireText('Harness/subagents.md', 'parallel planner/researcher/docs-researcher/architect subagents', 'WF roster orchestration shape');
|
|
652
|
+
requireText('Harness/subagents.md', '`reflector`', 'reflector agent roster');
|
|
653
|
+
requireText('Harness/subagents.md', 'reflector PASS', 'reflector review gate');
|
|
654
|
+
requireText('Harness/subagents.md', '## Efficiency Ladder', 'subagent efficiency ladder');
|
|
655
|
+
requireText('Harness/subagents.md', '## Review Gates', 'subagent review gates');
|
|
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');
|
|
662
|
+
requireText('Harness/WF-MAX.md', 'three-layer architecture', 'WF-MAX three-layer architecture');
|
|
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');
|
|
668
|
+
requireText('Harness/WF-MAX.md', 'claude -p', 'WF-MAX Claude CLI overflow');
|
|
669
|
+
requireText('Harness/WF-MAX.md', 'codex exec', 'WF-MAX Codex CLI overflow');
|
|
670
|
+
requireText('Harness/WF-MAX.md', 'agents.max_threads', 'WF-MAX Codex max_threads config');
|
|
671
|
+
requireText('Harness/WF-MAX.md', 'agents.max_depth', 'WF-MAX Codex max_depth config');
|
|
672
|
+
requireText('Harness/WF-MAX.md', 'max_threads = 12', 'WF-MAX Codex scaffold max_threads default');
|
|
673
|
+
requireText('Harness/WF-MAX.md', 'max_depth = 1', 'WF-MAX Codex scaffold max_depth default');
|
|
674
|
+
requireText('Harness/WF-MAX.md', 'Ask the user before raising', 'WF-MAX asks before raising Codex thread cap');
|
|
675
|
+
requireText('Harness/WF-MAX.md', 'Close completed agents', 'WF-MAX close completed agents before overflow');
|
|
676
|
+
requireText('Harness/WF-MAX.md', 'Codex++', 'WF-MAX forbids relying on Codex++ as stable capacity');
|
|
677
|
+
requireText('.claude/skills/wf-max/SKILL.md', 'agents.max_threads', 'wf-max skill Codex thread config');
|
|
678
|
+
requireText('.claude/skills/wf-max/SKILL.md', 'max_threads = 12', 'wf-max skill Codex scaffold max_threads default');
|
|
679
|
+
requireText('.claude/skills/wf-max/SKILL.md', 'ask the user before raising', 'wf-max skill asks before raising Codex thread cap');
|
|
680
|
+
requireText('.claude/skills/wf-max/SKILL.md', 'Codex++', 'wf-max skill forbids Codex++ capacity assumption');
|
|
681
|
+
requireText('.codex/config.toml', '[agents]', 'Codex agents config table');
|
|
682
|
+
requireText('.codex/config.toml', 'max_threads = 12', 'Codex scaffold max_threads default');
|
|
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');
|
|
686
|
+
requireText('Harness/README.md', '/wf-max', 'wf max router alias');
|
|
687
|
+
requireText('Harness/README.md', 'WF-MAX.md', 'WF-MAX router reference');
|
|
688
|
+
requireText('Harness/README.md', 'ACCEPTANCE_PROTOCOL.md', 'acceptance protocol router reference');
|
|
689
|
+
requireText('Harness/README.md', 'TDD-GUIDE.md', 'TDD guide router reference');
|
|
690
|
+
requireText('Harness/ACCEPTANCE_PROTOCOL.md', 'PRD-GATE', 'acceptance PRD gate');
|
|
691
|
+
requireText('Harness/ACCEPTANCE_PROTOCOL.md', 'AC-GATE', 'acceptance AC gate');
|
|
692
|
+
requireText('Harness/ACCEPTANCE_PROTOCOL.md', 'Acceptance Result', 'acceptance result matrix');
|
|
693
|
+
requireText('Harness/ACCEPTANCE_PROTOCOL.md', 'Syntax-only checks', 'syntax-only checks are not browser acceptance evidence');
|
|
694
|
+
requireText('Harness/AGENT_ISOLATION.md', 'implementer', 'agent isolation implementer rule');
|
|
695
|
+
requireText('Harness/HARNESS_BRIDGE.md', 'Network Trace Collector', 'harness bridge network trace collector');
|
|
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');
|
|
707
|
+
requireText('Harness/WF-AUTO.md', 'Inherited WF/WF-MAX Constraints', 'wf-auto inherited WF/WF-MAX constraints');
|
|
708
|
+
requireText('Harness/WF-AUTO.md', 'Mini PRD -> AC IDs -> test/validation plan -> implementer -> verifier', 'wf-auto per-cycle WF chain');
|
|
709
|
+
requireText('Harness/WF-AUTO.md', 'reflector PASS', 'wf-auto reflector gate');
|
|
710
|
+
requireText('Harness/WF-AUTO-SPARK.md', 'Inherited Execution Chain', 'wf-auto-spark inherited execution chain');
|
|
711
|
+
requireText('Harness/WF-AUTO-SPARK.md', 'External spark search replaces discovery only', 'wf-auto-spark discovery-only inheritance');
|
|
712
|
+
requireText('Harness/WF-AUTO-SPARK.md', 'reflector PASS', 'wf-auto-spark reflector gate');
|
|
713
|
+
requireText('Harness/MEMORY_PROTOCOL.md', 'Scenario Memory Hints', 'memory protocol scenario hints');
|
|
714
|
+
requireText('Harness/MEMORY_PROTOCOL.md', 'WF closeout', 'memory protocol WF closeout injection row');
|
|
715
|
+
requireText('Harness/MEMORY_PROTOCOL.md', 'memory-master owns writes', 'memory protocol memory write ownership');
|
|
716
|
+
requireText('Harness/TDD-GUIDE.md', 'Browser/UI Acceptance TDD Gate', 'browser UI acceptance TDD gate');
|
|
717
|
+
requireText('Harness/TDD-GUIDE.md', 'syntax-only', 'syntax-only acceptance prohibition');
|
|
718
|
+
requireText('Harness/TDD-GUIDE.md', 'Playwright/CDP', 'Playwright/CDP acceptance requirement');
|
|
719
|
+
requireText('Harness/TDD-GUIDE.md', 'AC-by-AC result matrix', 'AC-by-AC TDD evidence matrix');
|
|
720
|
+
requireText('Harness/tasks/_template/PLAN.md', 'Expanded evidence required when triggered', 'task template expanded evidence trigger');
|
|
721
|
+
requireText('Harness/templates/TEST_PLAN.template.md', 'syntax-only checks', 'test plan template syntax-only prohibition');
|
|
722
|
+
requireText('.claude/skills/tdd/SKILL.md', 'Harness/ACCEPTANCE_PROTOCOL.md', 'tdd skill loads acceptance protocol');
|
|
723
|
+
requireText('.claude/skills/tdd/SKILL.md', 'Harness/HARNESS_BRIDGE.md', 'tdd skill loads harness bridge');
|
|
724
|
+
requireText('.claude/skills/tdd/SKILL.md', 'No syntax-only acceptance', 'tdd skill forbids syntax-only acceptance');
|
|
725
|
+
requireText('.claude/skills/wf-remove/SKILL.md', 'User-facing removal is the slash/skill command', 'wf-remove slash command is user-facing');
|
|
726
|
+
requireText('.claude/skills/wf-remove/SKILL.md', 'agent-internal execution steps', 'wf-remove script commands are agent-internal');
|
|
727
|
+
requireText('.claude/skills/wf-remove/SKILL.md', 'verify residual discovery folders', 'wf-remove residual discovery verification');
|
|
728
|
+
requireText('.claude/agents/tdd-guide.md', 'Browser Acceptance Rules', 'tdd-guide browser acceptance rules');
|
|
729
|
+
requireText('.claude/agents/tdd-guide.md', 'real user actions', 'tdd-guide real user action requirement');
|
|
730
|
+
requireText('.claude/agents/test-writer.md', 'Harness/ACCEPTANCE_PROTOCOL.md', 'test-writer loads acceptance protocol');
|
|
731
|
+
requireText('.claude/agents/test-writer.md', 'real user-path test', 'test-writer real user path requirement');
|
|
732
|
+
requireText('.claude/agents/test-writer.md', 'network URL, method, payload', 'test-writer network assertion requirement');
|
|
733
|
+
requireText('.claude/agents/reflector.md', 'PASS, RETURN_TO_DEBUG, or BLOCKED', 'reflector verdict contract');
|
|
734
|
+
requireText('Harness/DEBUG_PROTOCOL.md', 'Layer Classification', 'debug layer classification');
|
|
735
|
+
requireText('Harness/MEMORY_PROTOCOL.md', 'AC ID', 'memory AC traceability');
|
|
736
|
+
requireText('Harness/subagents.md', 'Max parallelism', 'subagents max parallelism row');
|
|
737
|
+
requireText('Harness/dispatch.md', 'Concurrency group', 'dispatch concurrency group field');
|
|
738
|
+
requireText('Harness/dispatch.md', 'File claim', 'dispatch file claim field');
|
|
739
|
+
requireText('CLAUDE.md', '/wf-max', 'wf max startup instruction');
|
|
740
|
+
requireText('.claude/commands/wf-help.md', 'Do not invoke a skill', 'wf-help direct command boundary');
|
|
741
|
+
requireText('.claude/commands/wf-help.md', '| `/wf-help` |', 'wf-help command row');
|
|
742
|
+
requireText('.claude/commands/wf-help.md', '| `/wf-max <task>` |', 'wf-help wf-max row');
|
|
743
|
+
requireText('.claude/commands/wf-help.md', '| `/wf-auto` |', 'wf-help wf-auto row');
|
|
744
|
+
requireText('.claude/commands/wf-help.md', '| `/wf-readme <task>` |', 'wf-help wf-readme row');
|
|
745
|
+
requireText('Harness/README.md', '## Direct Commands', 'direct commands section');
|
|
746
|
+
requireText('Harness/README.md', '.claude/commands/wf-help.md', 'wf-help router reference');
|
|
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');
|
|
749
|
+
requireText('Harness/README.md', 'no runtime hook state', 'README no-hook runtime statement');
|
|
750
|
+
requireText('Harness/WF-AUTO.md', 'WF-AUTO Hook Exception', 'wf-auto-only hook exception');
|
|
751
|
+
requireText('Harness/WF-AUTO.md', 'only `/wf-auto` may use a runtime hook', 'wf-auto-only hook boundary');
|
|
752
|
+
forbidText('CLAUDE.md', 'Enforced by hooks', 'WF-MAX hook enforcement claim');
|
|
753
|
+
forbidText('Harness/WF.md', 'MUST use at least 3 distinct role passes', 'old WF role-pass minimum');
|
|
754
|
+
forbidText('Harness/WF.md', 'at least three distinct role passes', 'old WF role-pass wording');
|
|
755
|
+
forbidText('Harness/dispatch.md', 'requires ≥3 distinct subagents', 'old dispatch WF role minimum');
|
|
756
|
+
forbidText('CLAUDE.md', 'WF-MAX hooks', 'WF-MAX hook enforcement claim');
|
|
757
|
+
forbidText('Harness/README.md', 'PreToolUse hook', 'WF-MAX PreToolUse hook claim');
|
|
758
|
+
forbidText('Harness/README.md', 'SessionStart hook', 'WF-MAX SessionStart hook claim');
|
|
759
|
+
forbidText('Harness/README.md', 'hook-managed', 'hook-managed runtime claim');
|
|
760
|
+
forbidText('Harness/README.md', 'HOOK_PROTOCOL.md', 'removed hook protocol reference');
|
|
761
|
+
forbidText('Harness/WF-AUTO.md', 'Hook-Assisted Long Loop', 'wf-auto hook loop section');
|
|
762
|
+
forbidText('Harness/dispatch.md', 'removes the cap entirely', 'unbounded runtime-cap claim');
|
|
763
|
+
forbidText('Harness/WF-MAX.md', 'no hard agent cap; recursion governed', 'unbounded agent-cap claim');
|
|
764
|
+
forbidText('Harness/MEMORY_PROTOCOL.md', 'Hooks may', 'hook-triggered memory claim');
|
|
765
|
+
forbidText('.claude/settings.json', 'wf-mode-hook.mjs', 'Claude WF hook command registration');
|
|
766
|
+
forbidText('.codex/hooks.json', 'wf-mode-hook.mjs', 'Codex WF hook command registration');
|
|
767
|
+
const codexHookConfig = read('.codex/hooks.json');
|
|
768
|
+
const claudeSettings = read('.claude/settings.json');
|
|
769
|
+
if (codexHookConfig && !codexHookConfig.includes('wf-auto')) {
|
|
770
|
+
errors.push('.codex/hooks.json may only exist for a wf-auto hook configuration');
|
|
771
|
+
}
|
|
772
|
+
if (claudeSettings.includes('"hooks"') && !claudeSettings.includes('wf-auto')) {
|
|
773
|
+
errors.push('.claude/settings.json hooks may only be used for wf-auto');
|
|
774
|
+
}
|
|
775
|
+
if (read('.codex/config.toml').includes('hooks = true') && !codexHookConfig.includes('wf-auto')) {
|
|
776
|
+
errors.push('.codex/config.toml may enable hooks only with a wf-auto hook configuration');
|
|
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
|
+
|
|
900
|
+
if (errors.length) {
|
|
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
|
+
}
|