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