create-harness-vibe-coding 0.8.8 → 0.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README-CN.md +8 -2
  2. package/README.md +22 -3
  3. package/docs/images/harness-icon.png +0 -0
  4. package/package.json +3 -1
  5. package/src/generator.js +36 -0
  6. package/src/index.js +72 -4
  7. package/templates/common/.claude/agents/architect.md +31 -31
  8. package/templates/common/.claude/agents/codebase-explorer.md +45 -0
  9. package/templates/common/.claude/agents/docs-researcher.md +41 -41
  10. package/templates/common/.claude/agents/memory-master.md +26 -8
  11. package/templates/common/.claude/agents/reviewer.md +34 -34
  12. package/templates/common/.claude/agents/task-scribe.md +70 -0
  13. package/templates/common/.claude/commands/wf-help.md +9 -5
  14. package/templates/common/.claude/commands/wf-update.md +24 -0
  15. package/templates/common/.claude/rules/ecc/common.md +15 -2
  16. package/templates/common/.claude/settings.json +13 -0
  17. package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +10 -6
  18. package/templates/common/.claude/skills/wf/SKILL.md +15 -8
  19. package/templates/common/.claude/skills/wf-auto/SKILL.md +3 -2
  20. package/templates/common/.claude/skills/wf-learn/SKILL.md +9 -2
  21. package/templates/common/.claude/skills/wf-max/SKILL.md +26 -8
  22. package/templates/common/.claude/skills/wf-review/SKILL.md +63 -12
  23. package/templates/common/.claude/skills/wf-update/SKILL.md +15 -2
  24. package/templates/common/.codex/hooks.json +17 -0
  25. package/templates/common/.harness-version +93 -48
  26. package/templates/common/.opencode/agents/architect.md +24 -24
  27. package/templates/common/.opencode/agents/codebase-explorer.md +45 -0
  28. package/templates/common/.opencode/agents/docs-researcher.md +34 -34
  29. package/templates/common/.opencode/agents/memory-master.md +26 -8
  30. package/templates/common/.opencode/agents/reviewer.md +27 -27
  31. package/templates/common/.opencode/agents/task-scribe.md +70 -0
  32. package/templates/common/.opencode/commands/wf-auto-spark.md +15 -0
  33. package/templates/common/.opencode/commands/wf-auto.md +15 -0
  34. package/templates/common/.opencode/commands/wf-help.md +9 -5
  35. package/templates/common/.opencode/commands/wf-learn.md +15 -0
  36. package/templates/common/.opencode/commands/wf-max.md +15 -0
  37. package/templates/common/.opencode/commands/wf-readme.md +15 -0
  38. package/templates/common/.opencode/commands/wf-remove.md +15 -0
  39. package/templates/common/.opencode/commands/wf-review.md +15 -0
  40. package/templates/common/.opencode/commands/wf-update.md +24 -0
  41. package/templates/common/.opencode/commands/wf.md +15 -0
  42. package/templates/common/.opencode/plugins/harness-wf-status.mjs +126 -0
  43. package/templates/common/AGENTS.md +2 -29
  44. package/templates/common/CLAUDE.md +35 -6
  45. package/templates/common/Harness/ACCEPTANCE_PROTOCOL.md +2 -2
  46. package/templates/common/{MEMORY.md → Harness/MEMORY.md} +17 -4
  47. package/templates/common/Harness/MEMORY_PROTOCOL.md +80 -30
  48. package/templates/common/Harness/README.md +53 -25
  49. package/templates/common/{SETUP.md → Harness/SETUP.md} +278 -276
  50. package/templates/common/Harness/TASK_ARCHIVE.md +56 -0
  51. package/templates/common/Harness/WF-AUTO-ANGLES.md +1 -1
  52. package/templates/common/Harness/WF-AUTO-SPARK.md +5 -14
  53. package/templates/common/Harness/WF-AUTO.md +22 -84
  54. package/templates/common/Harness/WF-KERNEL.md +189 -0
  55. package/templates/common/Harness/WF-MAX.md +60 -328
  56. package/templates/common/Harness/WF-STATE.md +83 -0
  57. package/templates/common/Harness/WF.md +117 -237
  58. package/templates/common/Harness/agent-workflow.md +2 -2
  59. package/templates/common/Harness/context-loading.md +3 -3
  60. package/templates/common/Harness/dispatch.md +43 -35
  61. package/templates/common/Harness/scripts/archive-tasks.mjs +239 -0
  62. package/templates/common/{scripts → Harness/scripts}/scan-clean.mjs +29 -1
  63. package/templates/common/{scripts → Harness/scripts}/validate-harness.mjs +921 -699
  64. package/templates/common/Harness/scripts/wf-auto-update-prompt.mjs +258 -0
  65. package/templates/common/{scripts → Harness/scripts}/wf-remove.mjs +1 -0
  66. package/templates/common/{scripts → Harness/scripts}/wf-update-check.mjs +163 -52
  67. package/templates/common/Harness/subagents.md +36 -28
  68. package/templates/common/Harness/tasks/_template/PLAN.md +5 -0
  69. package/templates/common/Harness/tasks/_template/STATE.json +23 -0
  70. package/templates/common/README.md +2 -2
  71. package/templates/common/memory/agent-lessons-patterns.md +9 -8
  72. package/templates/common/memory/routes.md +43 -0
  73. package/templates/common/memory/startup-hints.md +32 -0
  74. package/templates/common/memory/tool-usage-reflections.md +9 -8
  75. package/templates/common/memory/user-corrections-preferences.md +11 -9
  76. package/templates/optional/catalog.json +8 -0
  77. package/templates/optional/skills/browser-e2e/.claude/skills/browser-e2e/SKILL.md +42 -42
  78. package/templates/optional/skills/browser-e2e/.opencode/commands/wf-browser.md +15 -0
  79. package/templates/optional/skills/github-pr-review/.claude/skills/github-pr-review/SKILL.md +40 -40
  80. package/templates/optional/skills/python-backend/.claude/skills/python-backend/SKILL.md +40 -40
  81. package/templates/optional/skills/ts-react-frontend/.claude/skills/ts-react-frontend/SKILL.md +43 -43
  82. package/templates/optional/skills/ui-ux-review/.claude/skills/ui-ux-review/SKILL.md +40 -40
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * wf-update-check.mjs Fast harness update comparison.
3
+ * wf-update-check.mjs - Fast harness update comparison.
4
4
  * Fetches remote checksums, compares locally, classifies all files instantly.
5
5
  * Only CONFLICT files need AI/user decision.
6
6
  *
7
7
  * Usage:
8
8
  * node Harness/scripts/wf-update-check.mjs # full dry-run plan
9
9
  * node Harness/scripts/wf-update-check.mjs --json # JSON output for AI consumption
10
+ * node Harness/scripts/wf-update-check.mjs --json # summary/counts + short agent hints only (token-safe)
11
+ * node Harness/scripts/wf-update-check.mjs --json --full-plan # also include full plan + conflict details (verbose)
10
12
  * node Harness/scripts/wf-update-check.mjs --apply-safe # apply SAFE+NEW, leave CONFLICT for AI
11
13
  * node Harness/scripts/wf-update-check.mjs --apply # apply only when no CONFLICT exists
14
+ *
15
+ * --full-plan and --verbose are aliases.
12
16
  */
13
17
 
14
18
  import { readFileSync, writeFileSync, existsSync, mkdirSync, lstatSync } from 'fs';
@@ -19,9 +23,13 @@ import { fileURLToPath } from 'url';
19
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
20
24
  const ROOT = process.env.WF_ROOT ? resolve(process.env.WF_ROOT) : resolve(__dirname, '..', '..');
21
25
  const VERSION_FILE = resolve(ROOT, 'Harness', '.harness-version');
22
- const DEFAULT_SOURCE_BASE = 'https://raw.githubusercontent.com/zingspark/create-harness-vibe-coding/main/templates/common/';
26
+ const GITHUB_REPO = 'zingspark/create-harness-vibe-coding';
27
+ const GITHUB_LATEST_STABLE = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
28
+ const RAW_GITHUB = `https://raw.githubusercontent.com/${GITHUB_REPO}`;
29
+ const TEMPLATE_SUBPATH = 'templates/common/';
30
+ const DEFAULT_SOURCE_BASE = `${RAW_GITHUB}/main/${TEMPLATE_SUBPATH}`;
23
31
 
24
- // ── Tier classification ──────────────────────────────────────────
32
+ // Tier classification
25
33
 
26
34
  /** Files we NEVER overwrite or delete. */
27
35
  const PRESERVE_PATTERNS = [
@@ -52,10 +60,15 @@ const OPTIONAL_REGISTRATION_FILES = new Set([
52
60
  'Harness/README.md',
53
61
  ]);
54
62
 
55
- // ── Helpers ────────────────────────────────────────────────────────
63
+ const BOOTSTRAP_ONLY_FILES = new Set([
64
+ 'Harness/SETUP.md',
65
+ ]);
66
+
67
+ // Helpers
56
68
 
57
69
  /** Reject paths that escape ROOT (traversal, absolute, .., etc.). */
58
70
  function safePath(file) {
71
+ if (/^[A-Za-z]:/.test(file)) return null;
59
72
  let normalized = file.replace(/\\/g, '/').replace(/^\/+/, '');
60
73
  if (/\/\//.test(normalized)) return null;
61
74
  if (normalized.split('/').some(p => p === '..')) return null;
@@ -132,7 +145,7 @@ function sha256(content) {
132
145
  function sha256File(path) {
133
146
  if (!existsSync(path)) return null;
134
147
  let content = readFileSync(path, 'utf-8');
135
- // Normalize CRLF LF
148
+ // Normalize CRLF to LF.
136
149
  content = content.replace(/\r\n/g, '\n');
137
150
  return sha256(content);
138
151
  }
@@ -142,16 +155,18 @@ function classify(file, localHash, storedHash) {
142
155
  for (const p of PRESERVE_PATTERNS) {
143
156
  if (p.test(file)) return 'PRESERVE';
144
157
  }
145
- // MERGE dual-purpose, check if user modified
158
+ // MERGE: dual-purpose, check if user modified.
146
159
  for (const p of MERGE_PATTERNS) {
147
160
  if (p.test(file)) {
148
161
  if (localHash === storedHash) return 'SAFE'; // unmodified, safe
149
162
  return 'CONFLICT'; // user modified, needs decision
150
163
  }
151
164
  }
152
- // Everything else is SAFE runtime file
153
- if (localHash === storedHash || localHash === null) return 'SAFE';
154
- return 'CONFLICT'; // modified runtime file unexpected
165
+ // Everything else is SAFE runtime file: always overwrite.
166
+ // Harness system files (scripts, skills, agents, commands, WF docs) are
167
+ // not user data; the template is authoritative. Only PRESERVE and MERGE
168
+ // files should ever require conflict resolution.
169
+ return 'SAFE';
155
170
  }
156
171
 
157
172
  async function fetchRemote(url, timeoutMs = 30000) {
@@ -170,7 +185,49 @@ async function fetchRemote(url, timeoutMs = 30000) {
170
185
  }
171
186
  }
172
187
 
173
- // ── Main ───────────────────────────────────────────────────────────
188
+ // Main
189
+
190
+ function isPrerelease(v) {
191
+ if (!v || typeof v !== 'string') return false;
192
+ return /-[0-9A-Za-z.-]+/.test(v.replace(/^[^0-9]*/, ''));
193
+ }
194
+
195
+ async function resolveStableSourceBase() {
196
+ try {
197
+ const controller = new AbortController();
198
+ const timer = setTimeout(() => controller.abort(), 15000);
199
+ let res;
200
+ try {
201
+ res = await fetch(GITHUB_LATEST_STABLE, {
202
+ signal: controller.signal,
203
+ headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'harness-wf-update-check' },
204
+ });
205
+ } finally {
206
+ clearTimeout(timer);
207
+ }
208
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
209
+ const data = JSON.parse(await res.text());
210
+ const tag = data && data.tag_name;
211
+ if (!tag || data.prerelease) return null;
212
+ const source = `${RAW_GITHUB}/${tag}/${TEMPLATE_SUBPATH}`;
213
+ const versionController = new AbortController();
214
+ const versionTimer = setTimeout(() => versionController.abort(), 15000);
215
+ try {
216
+ const versionRes = await fetch(source + '.harness-version', {
217
+ signal: versionController.signal,
218
+ headers: { 'User-Agent': 'harness-wf-update-check' },
219
+ });
220
+ if (!versionRes.ok) return null;
221
+ const raw = await versionRes.text();
222
+ if (isTemplate(raw)) return null;
223
+ } finally {
224
+ clearTimeout(versionTimer);
225
+ }
226
+ return source;
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
174
231
 
175
232
  async function main() {
176
233
  const args = process.argv.slice(2);
@@ -182,14 +239,32 @@ async function main() {
182
239
  const acceptMerged = readRepeatedFlagValues(args, '--accept-merged');
183
240
  const acceptTemplate = readRepeatedFlagValues(args, '--accept-template');
184
241
  const ignoreVersion = args.includes('--ignore-version') || args.includes('--force-check');
185
- const sourceBase = normalizeSourceBase(readFlagValue(args, '--source-base') || process.env.WF_SOURCE_BASE || DEFAULT_SOURCE_BASE);
242
+ const explicitSource = readFlagValue(args, '--source-base') || process.env.WF_SOURCE_BASE;
243
+ let sourceBase = normalizeSourceBase(explicitSource || DEFAULT_SOURCE_BASE);
244
+ let stableTagResolved = false;
245
+ if (!explicitSource) {
246
+ const resolved = await resolveStableSourceBase();
247
+ if (resolved) {
248
+ sourceBase = normalizeSourceBase(resolved);
249
+ stableTagResolved = true;
250
+ }
251
+ }
186
252
 
187
253
  // 1. Read local state
188
254
  if (!existsSync(VERSION_FILE)) {
189
255
  if (jsonOut) {
190
- console.log(JSON.stringify({ status: 'error', message: 'Local Harness/.harness-version not found.' }));
256
+ console.log(JSON.stringify({
257
+ status: 'error',
258
+ message: 'Local Harness/.harness-version not found. Is Harness installed?',
259
+ recovery: 'If this is an old Harness install that predates .harness-version, do not reinstall blindly. Restore Harness/.harness-version from backup if available. If the updater script is also missing, run from the project root: npx create-harness-vibe-coding@latest <name> . -y --on-conflict skip --json to restore missing updater infrastructure without overwriting user files.',
260
+ }));
191
261
  } else {
192
262
  console.error('ERROR: Harness/.harness-version not found. Is Harness installed?');
263
+ console.error(' If this is an old Harness install without version tracking, do not reinstall blindly.');
264
+ console.error(' Restore Harness/.harness-version from backup if available.');
265
+ console.error(' If the updater script is also missing, recover with:');
266
+ console.error(' npx create-harness-vibe-coding@latest <project-name> . -y --on-conflict skip --json');
267
+ console.error(' This restores missing updater infrastructure without overwriting user data.');
193
268
  }
194
269
  process.exitCode = 1;
195
270
  return;
@@ -200,10 +275,16 @@ async function main() {
200
275
  localVersion = JSON.parse(readFileSync(VERSION_FILE, 'utf-8'));
201
276
  } catch (e) {
202
277
  if (jsonOut) {
203
- console.log(JSON.stringify({ status: 'error', message: 'Failed to parse Harness/.harness-version: ' + e.message }));
278
+ console.log(JSON.stringify({
279
+ status: 'error',
280
+ message: 'Failed to parse Harness/.harness-version: ' + e.message,
281
+ recovery: 'The .harness-version file may be corrupted. Do not overwrite user files. Back up or move the corrupted Harness/.harness-version before manual recovery. If the updater script is also missing, run from the project root: npx create-harness-vibe-coding@latest <name> . -y --on-conflict skip --json to restore missing updater infrastructure.',
282
+ }));
204
283
  } else {
205
284
  console.error('ERROR: Failed to parse Harness/.harness-version:', e.message);
206
- console.error(' The file may be corrupted. If this is an old project, try reinstalling the harness.');
285
+ console.error(' The file may be corrupted. Back up or move Harness/.harness-version before manual recovery.');
286
+ console.error(' If the updater script is also missing, recover with:');
287
+ console.error(' npx create-harness-vibe-coding@latest <project-name> . -y --on-conflict skip --json');
207
288
  }
208
289
  process.exitCode = 1;
209
290
  return;
@@ -229,9 +310,9 @@ async function main() {
229
310
  if (jsonOut) {
230
311
  console.log(JSON.stringify({ status: 'template-remote', message: 'Remote .harness-version has not been generated yet.' }));
231
312
  } else {
232
- console.log(' Remote .harness-version is a template (contains {{placeholders}}).');
313
+ console.log('WARN: Remote .harness-version is a template (contains {{placeholders}}).');
233
314
  console.log(' The generate step has not been run on the remote repo. No update possible.');
234
- console.log(' This is expected during development the update mechanism works once the remote is live.');
315
+ console.log(' This is expected during development; the update mechanism works once the remote is live.');
235
316
  }
236
317
  process.exitCode = 1;
237
318
  return;
@@ -248,10 +329,10 @@ async function main() {
248
329
  return;
249
330
  }
250
331
 
251
- // Compare versions warn if remote is older (downgrade prevention)
332
+ // Compare versions and prevent downgrades from explicit custom sources.
252
333
  function parseSemver(v) {
253
334
  if (!v || typeof v !== 'string') return [0, 0, 0];
254
- return v.replace(/^[^0-9]*/, '').split('-')[0].split('.').map(Number);
335
+ return v.replace(/^[^0-9]*/, '').split('.').slice(0, 3).map(n => Number(n) || 0);
255
336
  }
256
337
  function cmpSemver(a, b) {
257
338
  const va = parseSemver(a), vb = parseSemver(b);
@@ -261,27 +342,38 @@ async function main() {
261
342
 
262
343
  const localGen = localVersion.generator || '0.0.0';
263
344
  const remoteGen = remoteVersion.generator || '0.0.0';
345
+
346
+ if (!ignoreVersion && isPrerelease(remoteGen)) {
347
+ if (jsonOut) {
348
+ console.log(JSON.stringify({ status: 'up-to-date', version: localGen, remote: remoteGen, sourceBase }));
349
+ } else {
350
+ console.log(`Already up to date (v${localGen}). Remote ${remoteGen} is a prerelease and is ignored.`);
351
+ }
352
+ return;
353
+ }
354
+
264
355
  const versionCmp = cmpSemver(remoteGen, localGen);
265
356
 
266
357
  if (!ignoreVersion && versionCmp <= 0) {
358
+ const reportDowngrade = !stableTagResolved;
267
359
  if (jsonOut) {
268
360
  console.log(JSON.stringify({
269
- status: versionCmp < 0 ? 'downgrade-refused' : 'up-to-date',
361
+ status: (versionCmp < 0 && reportDowngrade) ? 'downgrade-refused' : 'up-to-date',
270
362
  version: localGen,
271
363
  remote: remoteGen,
272
364
  sourceBase,
273
365
  }));
274
- } else if (versionCmp < 0) {
275
- console.log(`⚠ Remote (v${remoteGen}) is OLDER than local (v${localGen}). Downgrade refused.`);
366
+ } else if (versionCmp < 0 && reportDowngrade) {
367
+ console.log(`WARN: Remote (v${remoteGen}) is OLDER than local (v${localGen}). Downgrade refused.`);
276
368
  } else {
277
- console.log(`✅ Already up to date (v${localGen})`);
369
+ console.log(`Already up to date (v${localGen})`);
278
370
  }
279
- if (versionCmp < 0) process.exitCode = 1;
371
+ if (versionCmp < 0 && reportDowngrade) process.exitCode = 1;
280
372
  return;
281
373
  }
282
374
 
283
375
  if (ignoreVersion) {
284
- if (!jsonOut) console.log('🔧 Version check bypassed (--ignore-version). Comparing files anyway.');
376
+ if (!jsonOut) console.log('Version check bypassed (--ignore-version). Comparing files anyway.');
285
377
  }
286
378
 
287
379
  const remoteChecksums = remoteVersion.checksums || {};
@@ -320,16 +412,26 @@ async function main() {
320
412
  }
321
413
 
322
414
  function buildJsonPlan() {
415
+ const verbose = args.includes('--verbose') || args.includes('--full-plan');
323
416
  return {
324
417
  updated: plan.updated.map(withRemoteMeta),
325
418
  created: plan.created.map(withRemoteMeta),
326
419
  adopted: plan.adopted.map(withRemoteMeta),
327
- conflict: plan.conflict.map(withConflictActions),
420
+ conflict: verbose ? plan.conflict.map(withConflictActions) : plan.conflict.map(withConflictActions).slice(0, 5),
421
+ conflictTruncated: !verbose && plan.conflict.length > 5 ? plan.conflict.length - 5 : undefined,
328
422
  skipped: plan.skipped.map(withRemoteMeta),
329
423
  };
330
424
  }
331
425
 
332
426
  function buildAgentHints(jsonPlan) {
427
+ // Token-safe by default: omit the conflict array; full list available via --json --verbose (or --full-plan)
428
+ const verbose = args.includes('--verbose') || args.includes('--full-plan');
429
+ const maxConflictDetails = verbose ? Infinity : 5;
430
+ const totalConflicts = plan.conflict.length;
431
+ const conflicts = jsonPlan.conflict.slice(0, maxConflictDetails);
432
+ const truncated = totalConflicts > maxConflictDetails
433
+ ? totalConflicts - maxConflictDetails
434
+ : 0;
333
435
  return {
334
436
  mode: 'script-first-ai-conflicts',
335
437
  dryRunJsonCommand: 'node Harness/scripts/wf-update-check.mjs --json',
@@ -338,8 +440,10 @@ async function main() {
338
440
  finalizeCommand: 'node Harness/scripts/wf-update-check.mjs --finalize',
339
441
  partialUpdate: localVersion.partialUpdate || null,
340
442
  acceptedConflicts: localVersion.acceptedConflicts || {},
341
- aiMergeRequired: jsonPlan.conflict,
342
- aiMergeRequiredCount: jsonPlan.conflict.length,
443
+ // aiMergeRequired array is attached only in verbose mode to keep default output token-safe.
444
+ ...(verbose ? { aiMergeRequired: conflicts } : {}),
445
+ aiMergeRequiredCount: totalConflicts,
446
+ aiMergeRequiredTruncated: truncated > 0 ? truncated : undefined,
343
447
  conflictPolicy: 'Use the script for SAFE/NEW/adopted files. For each CONFLICT file, compare local content with templateHint/remoteUrl, then record the decision with --accept-local, --accept-merged, or --accept-template. Do not hand-edit Harness/.harness-version.',
344
448
  postUpdateCommands: [
345
449
  'node Harness/scripts/validate-harness.mjs',
@@ -438,8 +542,13 @@ async function main() {
438
542
  continue;
439
543
  }
440
544
 
545
+ if (BOOTSTRAP_ONLY_FILES.has(canonical) && localHash === null) {
546
+ plan.skipped.push({ file, reason: 'bootstrap-only file already removed locally' });
547
+ continue;
548
+ }
549
+
441
550
  if (!storedHash) {
442
- // New file from remote if local file exists, it's a CONFLICT
551
+ // New file from remote: if local file exists, it is a CONFLICT.
443
552
  if (localHash) {
444
553
  if (localHash === remoteHash) {
445
554
  plan.adopted.push({ file, localHash, remoteHash, reason: 'new remote file already matches local file' });
@@ -452,10 +561,10 @@ async function main() {
452
561
  plan.conflict.push({ file, localHash, storedHash: 'none', remoteHash, reason: 'new remote file conflicts with existing local file' });
453
562
  continue;
454
563
  }
455
- // New file still respect PRESERVE classification
564
+ // New file: still respect PRESERVE classification.
456
565
  const tier = classify(canonical, null, null);
457
566
  if (tier === 'PRESERVE') {
458
- plan.skipped.push({ file, reason: 'PRESERVE new file would overwrite user data' });
567
+ plan.skipped.push({ file, reason: 'PRESERVE: new file would overwrite user data' });
459
568
  } else {
460
569
  plan.created.push({ file, remoteHash });
461
570
  }
@@ -486,7 +595,7 @@ async function main() {
486
595
  const tier = classify(canonical, localHash, storedHash);
487
596
 
488
597
  if (tier === 'PRESERVE') {
489
- plan.skipped.push({ file, reason: 'PRESERVE user data' });
598
+ plan.skipped.push({ file, reason: 'PRESERVE: user data' });
490
599
  } else if (tier === 'SAFE') {
491
600
  if (localHash === remoteHash) {
492
601
  plan.skipped.push({ file, reason: 'already current' });
@@ -512,6 +621,7 @@ async function main() {
512
621
 
513
622
  // 3. Output
514
623
  if (jsonOut) {
624
+ const verbose = args.includes('--verbose') || args.includes('--full-plan');
515
625
  const jsonPlan = buildJsonPlan();
516
626
  console.log(JSON.stringify({
517
627
  status: localVersion.partialUpdate ? 'partial-update' : 'update-available',
@@ -525,20 +635,21 @@ async function main() {
525
635
  adopted: plan.adopted.length,
526
636
  conflict: plan.conflict.length,
527
637
  skipped: plan.skipped.length,
528
- plan: jsonPlan,
638
+ // Token-safe by default: attach the full plan only when --verbose / --full-plan is passed.
639
+ ...(verbose ? { plan: jsonPlan } : {}),
529
640
  agent: buildAgentHints(jsonPlan),
530
641
  }, null, 2));
531
642
  return;
532
643
  }
533
644
 
534
- console.log(`\n🔄 Update: v${localGen} v${remoteGen}`);
645
+ console.log(`\nUpdate: v${localGen} -> v${remoteGen}`);
535
646
  console.log(` ${plan.updated.length} safe update, ${plan.created.length} new, ${plan.conflict.length} conflict, ${plan.skipped.length} skipped\n`);
536
647
 
537
648
  // Show conflicts (these need AI/user decision)
538
649
  if (plan.conflict.length > 0) {
539
- console.log('CONFLICTS (need your decision):');
650
+ console.log('CONFLICTS (need your decision):');
540
651
  for (const c of plan.conflict) {
541
- console.log(` 📄 ${c.file} [${c.reason}]`);
652
+ console.log(` ! ${c.file} [${c.reason}]`);
542
653
  }
543
654
  if (plan.updated.length + plan.created.length > 0) {
544
655
  console.log(' Tip: run --apply-safe to apply SAFE/NEW files first, then merge conflicts.');
@@ -548,17 +659,17 @@ async function main() {
548
659
 
549
660
  // Show what will be auto-updated
550
661
  if (plan.updated.length + plan.created.length > 0) {
551
- console.log('AUTO (safe to apply):');
552
- for (const u of plan.updated) console.log(` ${u.file}`);
662
+ console.log('AUTO (safe to apply):');
663
+ for (const u of plan.updated) console.log(` ^ ${u.file}`);
553
664
  for (const c of plan.created) console.log(` + ${c.file}`);
554
665
  console.log('');
555
666
  }
556
667
 
557
668
  // 4. Apply if requested
558
669
  if (apply || applySafe) {
559
- // Refuse to apply when conflicts exist must resolve first
670
+ // Refuse to apply when conflicts exist; must resolve first.
560
671
  if (apply && !applySafe && plan.conflict.length > 0) {
561
- console.log(`❌ Cannot apply: ${plan.conflict.length} conflicts must be resolved first.`);
672
+ console.log(`Cannot apply: ${plan.conflict.length} conflicts must be resolved first.`);
562
673
  console.log(' Run --apply-safe to apply SAFE/NEW files first, or resolve conflicts manually then re-run --apply.');
563
674
  process.exitCode = 1;
564
675
  return plan;
@@ -574,23 +685,23 @@ async function main() {
574
685
  for (const u of plan.updated) {
575
686
  try {
576
687
  const dest = safePath(u.file);
577
- if (!dest) { console.error(` Traversal rejected: ${u.file}`); failed++; continue; }
578
- // Symlink rejection don't follow symlinks
688
+ if (!dest) { console.error(` x Traversal rejected: ${u.file}`); failed++; continue; }
689
+ // Symlink rejection: do not follow symlinks.
579
690
  if (lexists(dest)) {
580
- try { if (lstatSync(dest).isSymbolicLink()) { console.error(` Symlink rejected: ${u.file}`); failed++; continue; } } catch (_) {}
691
+ try { if (lstatSync(dest).isSymbolicLink()) { console.error(` x Symlink rejected: ${u.file}`); failed++; continue; } } catch (_) {}
581
692
  }
582
693
  const content = await fetchRemote(sourceBase + remotePath(u.file));
583
694
  const normalized = content.replace(/\r\n/g, '\n');
584
695
  const fetchedHash = sha256(normalized);
585
696
  if (fetchedHash !== u.remoteHash) {
586
- console.error(` Hash mismatch: ${u.file}`);
697
+ console.error(` x Hash mismatch: ${u.file}`);
587
698
  failed++; continue;
588
699
  }
589
700
  mkdirSync(dirname(dest), { recursive: true });
590
701
  writeFileSync(dest, normalized, 'utf-8');
591
702
  applied++;
592
703
  } catch (e) {
593
- console.error(` Failed: ${u.file} ${e.message}`);
704
+ console.error(` x Failed: ${u.file} - ${e.message}`);
594
705
  failed++;
595
706
  }
596
707
  }
@@ -598,25 +709,25 @@ async function main() {
598
709
  for (const c of plan.created) {
599
710
  try {
600
711
  const dest = safePath(c.file);
601
- if (!dest) { console.error(` Traversal rejected: ${c.file}`); failed++; continue; }
712
+ if (!dest) { console.error(` x Traversal rejected: ${c.file}`); failed++; continue; }
602
713
  // TOCTOU: recheck file didn't appear since planning
603
714
  if (lexists(dest)) {
604
- try { if (lstatSync(dest).isSymbolicLink()) { console.error(` Symlink rejected: ${c.file}`); failed++; continue; } } catch (_) {}
605
- console.error(` File created since plan: ${c.file} treating as CONFLICT`);
715
+ try { if (lstatSync(dest).isSymbolicLink()) { console.error(` x Symlink rejected: ${c.file}`); failed++; continue; } } catch (_) {}
716
+ console.error(` x File created since plan: ${c.file} - treating as CONFLICT`);
606
717
  failed++; continue;
607
718
  }
608
719
  const content = await fetchRemote(sourceBase + remotePath(c.file));
609
720
  const normalized = content.replace(/\r\n/g, '\n');
610
721
  const fetchedHash = sha256(normalized);
611
722
  if (fetchedHash !== c.remoteHash) {
612
- console.error(` Hash mismatch: ${c.file}`);
723
+ console.error(` x Hash mismatch: ${c.file}`);
613
724
  failed++; continue;
614
725
  }
615
726
  mkdirSync(dirname(dest), { recursive: true });
616
727
  writeFileSync(dest, normalized, 'utf-8');
617
728
  applied++;
618
729
  } catch (e) {
619
- console.error(` Failed: ${c.file} ${e.message}`);
730
+ console.error(` x Failed: ${c.file} - ${e.message}`);
620
731
  failed++;
621
732
  }
622
733
  }
@@ -643,12 +754,12 @@ async function main() {
643
754
  }
644
755
  writeFileSync(VERSION_FILE, JSON.stringify(localVersion, null, 2) + '\n', 'utf-8');
645
756
  if (plan.conflict.length === 0) {
646
- console.log(`✅ Applied ${applied} files. Version updated to ${remoteVersion.generator}.`);
757
+ console.log(`Applied ${applied} files. Version updated to ${remoteVersion.generator}.`);
647
758
  } else {
648
- console.log(`✅ Applied ${applied} SAFE/NEW files. Version remains ${localGen}; ${plan.conflict.length} conflicts still need merge.`);
759
+ console.log(`Applied ${applied} SAFE/NEW files. Version remains ${localGen}; ${plan.conflict.length} conflicts still need merge.`);
649
760
  }
650
761
  } else {
651
- console.log(`❌ ${failed} failures. NO files were version-tracked. Fix and re-run.`);
762
+ console.log(`${failed} failures. NO files were version-tracked. Fix and re-run.`);
652
763
  process.exitCode = 1;
653
764
  }
654
765
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  Purpose: coordinate subagents for speed without losing control of scope, evidence, or integration.
4
4
 
5
- Use this file when work needs multiple roles, parallel reading, independent review, broad context, repeated failures, or `/wf`.
5
+ Use this file when work needs multiple roles, parallel reading, independent review, broad context, repeated failures, or explicit `/wf` / `/wf-max` invocation.
6
6
 
7
7
  project files are the only durable communication channel; chat/subagent transcript state is non-authoritative. 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.
8
8
 
@@ -43,20 +43,22 @@ Subagents provide bounded work. They do not own final scope, architecture, relea
43
43
 
44
44
  Use the installed roster under `.claude/agents/` before inventing ad hoc roles.
45
45
 
46
- | Agent | Default Use |
47
- | --- | --- |
48
- | `planner` | decompose goals, map unknowns, define success criteria and write sets |
49
- | `researcher` | local/external ecosystem context, comparable projects, current facts |
50
- | `docs-researcher` | official docs, SDK/API behavior, browser/tool constraints |
51
- | `architect` | boundaries, interface decoupling, state ownership, data flow, migration risk |
52
- | `test-writer` | failing tests, manual check contracts, browser/API evidence plan |
53
- | `implementer` | bounded code or doc changes after the second plan |
54
- | `reviewer` | spec compliance, code quality, maintainability, security, missing tests |
55
- | `debugger` | reproduced failures, root cause isolation, smallest safe fix |
56
- | `verifier` | command execution, real browser/API checks, final evidence |
57
- | `reflector` | closeout synthesis, unresolved-risk check, acceptance gate verdict |
58
- | `memory-master` | write/consolidate memory entries, dedup, cross-project extraction; dispatched on repeated failures, user corrections, and WF closeout |
59
- | `context-master` | analyze context usage, recommend compression at ~85% window, extract durable session knowledge during closeout |
46
+ | Agent | Default Use | Model Tier |
47
+ | --- | --- | --- |
48
+ | `planner` | decompose goals, map unknowns, define success criteria and write sets | standard |
49
+ | `researcher` | local/external ecosystem context, comparable projects, current facts | standard |
50
+ | `docs-researcher` | official docs, SDK/API behavior, browser/tool constraints | standard |
51
+ | `architect` | boundaries, interface decoupling, state ownership, data flow, migration risk | standard |
52
+ | `test-writer` | failing tests, manual check contracts, browser/API evidence plan | standard |
53
+ | `implementer` | bounded code or doc changes after the second plan | standard |
54
+ | `reviewer` | spec compliance, code quality, maintainability, security, missing tests | standard |
55
+ | `debugger` | reproduced failures, root cause isolation, smallest safe fix | standard |
56
+ | `verifier` | command execution, real browser/API checks, final evidence | standard or small-fast |
57
+ | `reflector` | closeout synthesis, unresolved-risk check, acceptance gate verdict | standard |
58
+ | `task-scribe` | task state, heartbeat, dispatch ledger, evidence pointers NEVER source code | small-fast (haiku) |
59
+ | `codebase-explorer` | scoped read-only source exploration, file discovery, symbol tracing | small-fast (haiku) |
60
+ | `context-master` | analyze context usage, recommend compression at ~85% window, extract durable session knowledge during closeout | small-fast (haiku) |
61
+ | `memory-master` | write/consolidate memory entries, dedup, cross-project extraction; dispatched on repeated failures, user corrections, and WF closeout | standard |
60
62
 
61
63
  ## Acceptance Role Passes
62
64
 
@@ -80,8 +82,10 @@ Hard rule: implementer may not be the independent validator for the same AC ID.
80
82
 
81
83
  ## WF Default Fan-Out
82
84
 
83
- Explicit `/wf`, `wf mode`, `workflow mode`, or `wk mode` requires complete
84
- role-chain coverage from `.claude/agents/` before closeout: plan,
85
+ Explicit `/wf`, `$wf`, or `/skills wf` requires tier-specific role coverage per `Harness/WF.md`.
86
+ WF-Light: planner + test-writer + implementer + verifier.
87
+ WF-Standard: adds research/docs + one review lens.
88
+ WF-Full: complete role-chain coverage from `.claude/agents/` before closeout: plan,
85
89
  research/docs research as needed, architecture, test, implement, independent
86
90
  validation, cross-review, reflector, and accept.
87
91
 
@@ -102,7 +106,7 @@ Then add phase-specific agents:
102
106
  - `context-master` before closeout for knowledge extraction
103
107
  - `memory-master` after repeated failures and during closeout for consolidation
104
108
 
105
- Collaboration mode is determined by concrete conditions, not a fixed ratio. See `Harness/WF.md#Complete Role Chain Requirement` for the full decision tree. Summary: explicit WF/WK mode always uses the complete role chain. 3+ files or cross-layer work uses multi-agent orchestration. 1-2 local files, well-understood, not in WF mode can be solo. Repeated failure stops solo work and switches to multi-agent.
109
+ Collaboration mode is determined by WF tier and concrete conditions, not a fixed ratio. See `Harness/WF.md` for the tier decision guide. Summary: WF-Light uses minimal roles. WF-Standard uses moderate roles with one review lens. WF-Full always uses the complete role chain. Non-WF complex work may still use subagents and planning without entering any WF mode. Repeated failure after two attempts escalates to next tier.
106
110
 
107
111
  ## Efficiency Ladder
108
112
 
@@ -115,22 +119,19 @@ Choose the cheapest coordination level that is safe.
115
119
  | Parallel read-only | broad reading, research, architecture, multiple independent failures | 2-3 read-only agents |
116
120
  | Serial build lane | normal feature or fix | acceptance/contract -> test-writer -> implementer -> verifier evidence -> cross-review -> reflector -> acceptance |
117
121
  | Isolated lanes | disjoint write sets or competing approaches | separate worktrees, then review and merge |
118
- | Max parallelism | 5+ disjoint files, fan-out benefit > coordination cost | /wf max: write-set coloring -> wave dispatch -> parallel review |
122
+ | Max parallelism | 5+ disjoint files, fan-out benefit > coordination cost | /wf-max: write-set coloring -> wave dispatch -> parallel review |
119
123
 
120
124
  Max parallelism removes the Harness default cap, not the runtime's physical or
121
125
  account cap. For WF-MAX, record the current runtime budget, use native
122
126
  subagents first, close completed agents before declaring the pool exhausted,
123
- then overflow to the other CLI (`claude -p` or `codex exec`) with explicit
127
+ then overflow to a peer CLI (`claude -p`, `codex exec`, or `opencode run --agent <role> --dir .`) with explicit
124
128
  dispatch packets. Generated Harness Codex config defaults to
125
129
  `agents.max_threads = 12` and `agents.max_depth = 1`; if that becomes the
126
130
  bottleneck, ask the user before raising `agents.max_threads` and keep
127
131
  `max_depth = 1` unless recursive delegation is explicitly approved. Do not rely
128
132
  on undocumented fork/derive bypasses as stable capacity.
129
133
 
130
- Default for automatic WF triggers: 3-5 active read-only agents before second
131
- planning. For explicit WF/WK mode, never use the solo pass; schedule the
132
- complete role chain and use bounded role passes as the recorded fallback when
133
- subagents are unavailable.
134
+ Default for explicit WF invocation: tier-based. WF-Light: planner + test-writer + implementer + verifier (bounded passes acceptable). WF-Standard: adds research/docs + one independent review lens. WF-Full: complete role chain, use bounded role passes as the recorded fallback when subagents are unavailable.
134
135
 
135
136
  ## WF Orchestration Shape
136
137
 
@@ -148,10 +149,10 @@ controller intake
148
149
  -> close with evidence
149
150
  ```
150
151
 
151
- Use this shape for `/wf`, long tasks, multi-file changes, architecture work, migrations, browser/API behavior, or repeated failures.
152
+ Use this shape for explicit `/wf`, `/wf-max`, or non-WF complex work with subagents.
152
153
 
153
154
  ```text
154
- /wf max orchestration shape:
155
+ /wf-max orchestration shape:
155
156
  controller intake
156
157
  -> wave 0: max-parallel exploration (4-14 read-only agents)
157
158
  -> E-GATE: Exploration Gate - all questions answered, findings synthesized (per WF-MAX.md)
@@ -167,14 +168,14 @@ controller intake
167
168
 
168
169
  ## Dispatch Pack
169
170
 
170
- Use the canonical dispatch input and handoff format in `Harness/dispatch.md`. Every subagent dispatch must be self-contained - inject only the docs selected by `Harness/README.md` and `Harness/context-loading.md`.
171
+ Use the canonical dispatch input and handoff format in `Harness/dispatch.md`. Every subagent dispatch must be self-contained - inject only the docs selected by `Harness/README.md` and `Harness/context-loading.md`. On session start, the controller reads `Harness/tasks/<task-id>/STATE.json` per [WF-STATE.md](WF-STATE.md) to resume the dispatch ledger and ready queue.
171
172
 
172
173
  ## Parallelism Rules
173
174
 
174
175
  - Read-only agents may run in parallel.
175
176
  - Writing agents run serially unless write sets are disjoint and the controller has chosen an isolated worktree.
176
177
  - Reviewers may run in parallel after implementation, but spec compliance is evaluated before code-quality approval.
177
- - Subagents are readers and reporters. They return findings and PLAN patch suggestions. Only the controller (main agent) commits state changes to task files.
178
+ - Subagents are readers and reporters. They return findings and PLAN patch suggestions. Only the controller (main agent) or task-scribe commits state changes to task files. Production source agents (implementer, debugger, test-writer) never write task state unless explicitly dispatched as task-scribe.
178
179
  - Do not let two agents edit `Harness/tasks/<task-id>/PROGRESS.md`, `Harness/tasks/<task-id>/PLAN.md`, `Harness/MEMORY.md`, or `Harness/memory/*` concurrently. The controller writes durable state.
179
180
  - If two agents disagree, the controller records the conflict in `Harness/tasks/<task-id>/PLAN.md` and chooses the smallest reversible next step.
180
181
 
@@ -191,6 +192,13 @@ matrix from running behavior and evidence, not from the implementer's summary.
191
192
 
192
193
  If either reviewer finds issues, the implementer or debugger fixes them and the same gate runs again. Do not move to final acceptance with open critical/high findings or without reflector PASS.
193
194
 
195
+ For `/wf-review`, use the installed `reviewer` role before inventing any
196
+ ad hoc review prompt. If no peer CLI is available, dispatch `reviewer` as an
197
+ independent same-runtime subagent context; for broad WF-MAX review, dispatch
198
+ `review-manager` when the runtime supports nested reviewer fan-out. Reviewer
199
+ agents report suggestions only. The controller accepts, rejects, or escalates
200
+ each finding and owns the final decision.
201
+
194
202
  ## Subagent Status Handling
195
203
 
196
204
  | Status | Controller Action |
@@ -35,6 +35,11 @@ Forbidden:
35
35
  -
36
36
  - Truth files (PRD, ACs, UI/API contracts, test plan, validation report) unless a Change Request is recorded.
37
37
 
38
+ ## Memory Preflight
39
+
40
+ - Memory preflight: done | exempt | blocked
41
+ - Memory hints: none | <file/path + reason>
42
+
38
43
  ## Context
39
44
 
40
45
  - Loaded:
@@ -0,0 +1,23 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "taskId": "task-example",
4
+ "status": "active",
5
+ "mode": "direct",
6
+ "tier": "none",
7
+ "phase": "intake",
8
+ "gate": null,
9
+ "updatedAt": "2026-07-16T00:00:00.000Z",
10
+ "activeQuestion": null,
11
+ "nextAction": "Define goal and acceptance criteria",
12
+ "acceptance": [],
13
+ "queues": {
14
+ "ready": [],
15
+ "running": [],
16
+ "blocked": [],
17
+ "done": []
18
+ },
19
+ "dispatchLedger": [],
20
+ "decisions": [],
21
+ "risks": [],
22
+ "artifacts": []
23
+ }