create-harness-vibe-coding 0.8.1 → 0.8.3

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 (34) hide show
  1. package/README-CN.md +15 -11
  2. package/README.md +21 -12
  3. package/package.json +1 -1
  4. package/src/index.js +210 -10
  5. package/templates/common/.claude/agents/tdd-guide.md +55 -0
  6. package/templates/common/.claude/settings.json +22 -0
  7. package/templates/common/.claude/skills/tdd/SKILL.md +30 -0
  8. package/templates/common/.claude/skills/wf-auto/SKILL.md +107 -0
  9. package/templates/common/.claude/skills/wf-auto-spark/SKILL.md +39 -0
  10. package/templates/common/.claude/skills/wf-max/SKILL.md +10 -2
  11. package/templates/common/.claude/skills/wf-remove/SKILL.md +12 -5
  12. package/templates/common/.claude/skills/wf-update/SKILL.md +12 -5
  13. package/templates/common/.codex/hooks.json +59 -37
  14. package/templates/common/.harness-version +48 -25
  15. package/templates/common/AGENTS.md +5 -5
  16. package/templates/common/CLAUDE.md +12 -17
  17. package/templates/common/Harness/ECC-GUIDE.md +246 -0
  18. package/templates/common/Harness/README.md +129 -130
  19. package/templates/common/Harness/TDD-GUIDE.md +83 -0
  20. package/templates/common/Harness/WF-AUTO-SPARK.md +297 -0
  21. package/templates/common/Harness/WF-AUTO.md +508 -0
  22. package/templates/common/Harness/WF-MAX.md +24 -0
  23. package/templates/common/Harness/context-loading.md +38 -1
  24. package/templates/common/Harness/dispatch.md +40 -40
  25. package/templates/common/Harness/subagents.md +7 -21
  26. package/templates/common/Harness/tasks/_template/NAMING.md +47 -0
  27. package/templates/common/MEMORY.md +73 -66
  28. package/templates/common/SETUP.md +100 -78
  29. package/templates/common/scripts/validate-harness.mjs +92 -58
  30. package/templates/common/scripts/wf-mode-hook.mjs +642 -318
  31. package/templates/common/scripts/wf-remove.mjs +301 -81
  32. package/templates/common/scripts/wf-statusline.ps1 +62 -38
  33. package/templates/common/scripts/wf-statusline.sh +67 -48
  34. package/templates/common/scripts/wf-update-check.mjs +179 -81
@@ -10,10 +10,12 @@
10
10
  * Usage:
11
11
  * node Harness/scripts/wf-remove.mjs # dry-run: show plan
12
12
  * node Harness/scripts/wf-remove.mjs --apply # apply SAFE, prompt for MODIFIED
13
- * node Harness/scripts/wf-remove.mjs --yes # auto-yes for SAFE only (non-interactive)
14
- * node Harness/scripts/wf-remove.mjs --json # JSON plan for AI consumption
15
- * node Harness/scripts/wf-remove.mjs --apply --yes # auto-apply SAFE, skip MODIFIED, report
16
- */
13
+ * node Harness/scripts/wf-remove.mjs --yes # auto-yes for SAFE only (non-interactive)
14
+ * node Harness/scripts/wf-remove.mjs --json # JSON plan for AI consumption
15
+ * node Harness/scripts/wf-remove.mjs --apply --yes # auto-apply SAFE, skip MODIFIED, report
16
+ * node Harness/scripts/wf-remove.mjs --apply --yes --purge-user-data --keep-tasks
17
+ * # remove Harness project facts, keep task records
18
+ */
17
19
 
18
20
  import { readFileSync, writeFileSync, existsSync, unlinkSync, rmdirSync, readdirSync, lstatSync } from 'fs';
19
21
  import { createHash } from 'crypto';
@@ -44,10 +46,10 @@ const HARNESS_PREFIXES = [
44
46
  ];
45
47
 
46
48
  /** Files the user owns — NEVER remove. Consistent with wf-update-check PRESERVE_PATTERNS. */
47
- const USER_DATA_PATTERNS = [
48
- /^Harness\/PROGRESS\.md$/,
49
- /^Harness\/tasks\//,
50
- /^Harness\/memory\//,
49
+ const USER_DATA_PATTERNS = [
50
+ /^Harness\/PROGRESS\.md$/,
51
+ /^Harness\/tasks\//,
52
+ /^Harness\/memory\//,
51
53
  /^Harness\/research\/PRD\.md$/,
52
54
  /^Harness\/research\/research-results\.md$/,
53
55
  /^Harness\/architecture\.md$/,
@@ -57,16 +59,44 @@ const USER_DATA_PATTERNS = [
57
59
  /^README\.md$/,
58
60
  /^\.gitignore$/,
59
61
  /^package\.json$/,
60
- /^package-lock\.json$/,
61
- ];
62
-
63
- function isUserData(file) {
64
- return USER_DATA_PATTERNS.some(p => p.test(file));
65
- }
66
-
67
- function isHarnessOwned(file) {
68
- return HARNESS_PREFIXES.some(p => file.startsWith(p));
69
- }
62
+ /^package-lock\.json$/,
63
+ ];
64
+
65
+ /** Harness user-data paths that can be removed only with explicit purge flags. */
66
+ const PURGEABLE_HARNESS_DATA_PATTERNS = [
67
+ /^Harness\/PROGRESS\.md$/,
68
+ /^Harness\/tasks\//,
69
+ /^Harness\/memory\//,
70
+ /^Harness\/research\/PRD\.md$/,
71
+ /^Harness\/research\/research-results\.md$/,
72
+ /^Harness\/architecture\.md$/,
73
+ /^Harness\/workflows\//,
74
+ /^Harness\/features\//,
75
+ /^Harness\/domain\//,
76
+ ];
77
+
78
+ /** Framework task scaffolding is not a real task record. */
79
+ const FRAMEWORK_TASK_PATTERNS = [
80
+ /^Harness\/tasks\/_template\//,
81
+ /^Harness\/tasks\/auto\//,
82
+ ];
83
+
84
+ function isUserData(file) {
85
+ return USER_DATA_PATTERNS.some(p => p.test(file));
86
+ }
87
+
88
+ function isHarnessOwned(file) {
89
+ return HARNESS_PREFIXES.some(p => file.startsWith(p));
90
+ }
91
+
92
+ function isFrameworkTaskFile(file) {
93
+ return FRAMEWORK_TASK_PATTERNS.some(p => p.test(file));
94
+ }
95
+
96
+ function isPurgeableHarnessData(file, { keepTasks }) {
97
+ if (keepTasks && /^Harness\/tasks\//.test(file) && !isFrameworkTaskFile(file)) return false;
98
+ return PURGEABLE_HARNESS_DATA_PATTERNS.some(p => p.test(file));
99
+ }
70
100
 
71
101
  /** Framework files that should always exist (not part of removal). */
72
102
  const KEEP_FRAMEWORK = new Set([
@@ -127,15 +157,82 @@ function safePath(file) {
127
157
  return resolved;
128
158
  }
129
159
 
130
- function sha256File(path) {
131
- if (!existsSync(path)) return null;
132
- let content = readFileSync(path, 'utf-8');
133
- content = content.replace(/\r\n/g, '\n');
134
- return 'sha256-' + createHash('sha256').update(content).digest('hex');
135
- }
136
-
137
- function removeEmptyDirs(startDir) {
138
- if (!existsSync(startDir)) return;
160
+ function sha256File(path) {
161
+ if (!existsSync(path)) return null;
162
+ let content = readFileSync(path, 'utf-8');
163
+ content = content.replace(/\r\n/g, '\n');
164
+ return 'sha256-' + createHash('sha256').update(content).digest('hex');
165
+ }
166
+
167
+ function addExistingFile(allFiles, file, scanIssues = []) {
168
+ const diskPath = safePath(file);
169
+ if (!diskPath || !existsSync(diskPath)) return;
170
+ try {
171
+ if (!lstatSync(diskPath).isDirectory()) {
172
+ allFiles.add(file);
173
+ }
174
+ } catch (e) {
175
+ scanIssues.push({ file, reason: `scan failed: ${e.message}` });
176
+ }
177
+ }
178
+
179
+ function addFilesUnder(allFiles, dir, scanIssues = []) {
180
+ const diskPath = safePath(dir);
181
+ if (!diskPath || !existsSync(diskPath)) return;
182
+
183
+ let entries;
184
+ try {
185
+ if (!lstatSync(diskPath).isDirectory()) return;
186
+ entries = readdirSync(diskPath);
187
+ } catch (e) {
188
+ scanIssues.push({ file: dir, reason: `scan failed: ${e.message}` });
189
+ return;
190
+ }
191
+
192
+ for (const entry of entries) {
193
+ const rel = `${dir}/${entry}`.replace(/\\/g, '/').replace(/\/+/g, '/');
194
+ const full = safePath(rel);
195
+ if (!full) continue;
196
+ let stat;
197
+ try {
198
+ stat = lstatSync(full);
199
+ } catch (e) {
200
+ scanIssues.push({ file: rel, reason: `scan failed: ${e.message}` });
201
+ continue;
202
+ }
203
+ if (stat.isDirectory() && !stat.isSymbolicLink()) {
204
+ addFilesUnder(allFiles, rel, scanIssues);
205
+ } else if (!stat.isDirectory()) {
206
+ allFiles.add(rel);
207
+ }
208
+ }
209
+ }
210
+
211
+ function parseRepeatedFlag(args, flagName) {
212
+ const values = [];
213
+ for (let i = 0; i < args.length; i += 1) {
214
+ const arg = args[i];
215
+ if (arg === flagName && args[i + 1] && !args[i + 1].startsWith('--')) {
216
+ values.push(args[i + 1]);
217
+ i += 1;
218
+ } else if (arg.startsWith(`${flagName}=`)) {
219
+ values.push(arg.slice(flagName.length + 1));
220
+ }
221
+ }
222
+ return values.map(v => v.replace(/\\/g, '/').replace(/\/+/g, '/'));
223
+ }
224
+
225
+ function removePlannedFile(file) {
226
+ const diskPath = safePath(file);
227
+ if (!diskPath) return { ok: false, reason: 'path traversal rejected' };
228
+ if (!existsSync(diskPath)) return { ok: false, reason: 'not on disk' };
229
+ if (lstatSync(diskPath).isDirectory()) return { ok: false, reason: 'is directory' };
230
+ unlinkSync(diskPath);
231
+ return { ok: true };
232
+ }
233
+
234
+ function removeEmptyDirs(startDir) {
235
+ if (!existsSync(startDir)) return;
139
236
  try {
140
237
  const entries = readdirSync(startDir);
141
238
  for (const e of entries) {
@@ -164,10 +261,13 @@ async function askUser(question) {
164
261
  // ── Main ───────────────────────────────────────────────────────────
165
262
 
166
263
  async function main() {
167
- const args = process.argv.slice(2);
168
- const apply = args.includes('--apply');
169
- const yes = args.includes('--yes');
170
- const jsonOut = args.includes('--json');
264
+ const args = process.argv.slice(2);
265
+ const apply = args.includes('--apply');
266
+ const yes = args.includes('--yes');
267
+ const jsonOut = args.includes('--json');
268
+ const purgeUserData = args.includes('--purge-user-data') || args.includes('--purge');
269
+ const keepTasks = args.includes('--keep-tasks');
270
+ const deleteModified = new Set(parseRepeatedFlag(args, '--delete-modified'));
171
271
 
172
272
  // 1. Build file inventory
173
273
  const localVersion = existsSync(VERSION_FILE)
@@ -175,8 +275,28 @@ async function main() {
175
275
  : { checksums: {} };
176
276
  const storedChecksums = localVersion.checksums || {};
177
277
 
178
- // All files from checksums + known framework patterns
179
- const allFiles = new Set(Object.keys(storedChecksums));
278
+ // All files from checksums + known framework patterns
279
+ const allFiles = new Set(Object.keys(storedChecksums));
280
+ const scanIssues = [];
281
+
282
+ // Preserve/purge candidates may be excluded from checksums, so discover them
283
+ // from disk. This keeps JSON plans honest about what will remain.
284
+ for (const file of [
285
+ 'Harness/PROGRESS.md',
286
+ 'Harness/architecture.md',
287
+ ]) {
288
+ addExistingFile(allFiles, file, scanIssues);
289
+ }
290
+ for (const dir of [
291
+ 'Harness/tasks',
292
+ 'Harness/memory',
293
+ 'Harness/research',
294
+ 'Harness/workflows',
295
+ 'Harness/features',
296
+ 'Harness/domain',
297
+ ]) {
298
+ addFilesUnder(allFiles, dir, scanIssues);
299
+ }
180
300
 
181
301
  // Add framework files that might not be in checksums (newer additions)
182
302
  const extraPatterns = [
@@ -185,11 +305,15 @@ async function main() {
185
305
  '.claude/skills/wf-auto/SKILL.md',
186
306
  '.agents/skills/wf-auto/SKILL.md',
187
307
  'Harness/WF-AUTO.md',
188
- 'Harness/tasks/auto/PROGRESS.md',
189
- 'Harness/tasks/auto/PLAN.md',
190
- 'Harness/scripts/wf-update-check.mjs',
191
- 'Harness/scripts/wf-remove.mjs',
192
- ];
308
+ 'Harness/tasks/auto/PROGRESS.md',
309
+ 'Harness/tasks/auto/PLAN.md',
310
+ 'Harness/tasks/_template/ARTIFACTS.md',
311
+ 'Harness/tasks/_template/NOTES.md',
312
+ 'Harness/tasks/_template/PLAN.md',
313
+ 'Harness/tasks/_template/PROGRESS.md',
314
+ 'Harness/scripts/wf-update-check.mjs',
315
+ 'Harness/scripts/wf-remove.mjs',
316
+ ];
193
317
  for (const f of extraPatterns) {
194
318
  if (existsSync(resolve(ROOT, f))) allFiles.add(f);
195
319
  }
@@ -198,7 +322,9 @@ async function main() {
198
322
  const safe = []; // SAFE — matches checksum, auto-remove
199
323
  const modified = []; // MODIFIED — user edited, must confirm
200
324
  const user = []; // USER — never remove
201
- const skipped = []; // File not on disk, traversal rejected, or framework-keep
325
+ const skipped = []; // File not on disk, traversal rejected, or framework-keep
326
+ const purge = []; // Explicitly requested Harness user-data removal
327
+ skipped.push(...scanIssues);
202
328
 
203
329
  for (const file of [...allFiles].sort()) {
204
330
  // Canonical normalization for classification
@@ -209,7 +335,14 @@ async function main() {
209
335
  continue;
210
336
  }
211
337
 
212
- if (isUserData(canonical)) {
338
+ if (isUserData(canonical)) {
339
+ if (purgeUserData && isPurgeableHarnessData(canonical, { keepTasks })) {
340
+ purge.push({
341
+ file,
342
+ reason: keepTasks && isFrameworkTaskFile(canonical) ? 'framework task scaffold' : 'purge requested',
343
+ });
344
+ continue;
345
+ }
213
346
  user.push({ file, reason: 'user data — NEVER removed' });
214
347
  continue;
215
348
  }
@@ -259,17 +392,38 @@ async function main() {
259
392
  }
260
393
 
261
394
  // 3. Output plan
262
- if (jsonOut) {
263
- console.log(JSON.stringify({
264
- safe: safe.map(s => s.file),
265
- modified: modified.map(m => m.file),
266
- user: user.map(u => u.file),
267
- totalRemove: safe.length + modified.length,
268
- totalSafe: safe.length,
269
- totalModified: modified.length,
270
- }, null, 2));
271
- return;
272
- }
395
+ if (jsonOut) {
396
+ console.log(JSON.stringify({
397
+ safe: safe.map(s => s.file),
398
+ modified: modified.map(m => m.file),
399
+ user: user.map(u => u.file),
400
+ purge: purge.map(p => p.file),
401
+ skipped: skipped.map(s => s.file),
402
+ options: {
403
+ purgeUserData,
404
+ keepTasks,
405
+ deleteModified: [...deleteModified],
406
+ },
407
+ agent: {
408
+ sourceOfTruth: 'Use this JSON plan first. Script handles safe/purge lists; AI decides modified files and whether purge-user-data is intended.',
409
+ safeRemoveCommand: 'node Harness/scripts/wf-remove.mjs --apply --yes',
410
+ thoroughRemoveCommand: 'node Harness/scripts/wf-remove.mjs --apply --yes --purge-user-data --keep-tasks',
411
+ deleteModifiedCommandExample: 'node Harness/scripts/wf-remove.mjs --apply --yes --delete-modified CLAUDE.md --delete-modified Harness/README.md',
412
+ aiDecisionRequired: modified.map(m => ({
413
+ file: m.file,
414
+ reason: m.reason,
415
+ defaultAction: 'keep',
416
+ })),
417
+ preservedByDefault: user.map(u => u.file),
418
+ },
419
+ totalRemove: safe.length + modified.length + purge.length,
420
+ totalSafe: safe.length,
421
+ totalModified: modified.length,
422
+ totalPurge: purge.length,
423
+ totalPreserved: user.length,
424
+ }, null, 2));
425
+ return;
426
+ }
273
427
 
274
428
  console.log('\n🧹 WF-REMOVE — Harness framework removal plan\n');
275
429
 
@@ -293,7 +447,13 @@ async function main() {
293
447
  console.log('');
294
448
  }
295
449
 
296
- console.log(`Summary: ${safe.length} safe, ${modified.length} need confirm, ${user.length} preserved, ${skipped.length} skipped\n`);
450
+ if (purge.length > 0) {
451
+ console.log(`PURGE (${purge.length} files - explicit user-data cleanup):`);
452
+ for (const p of purge) console.log(` x ${p.file} [${p.reason}]`);
453
+ console.log('');
454
+ }
455
+
456
+ console.log(`Summary: ${safe.length} safe, ${modified.length} need confirm, ${purge.length} purge, ${user.length} preserved, ${skipped.length} skipped\n`);
297
457
 
298
458
  if (!apply) {
299
459
  console.log('DRY-RUN. Use --apply to execute the removal.\n');
@@ -301,30 +461,47 @@ async function main() {
301
461
  }
302
462
 
303
463
  // 4. Apply — remove SAFE files automatically (rehash before unlink)
304
- let safeRemoved = 0;
464
+ let safeRemoved = 0;
465
+ let safeFailed = 0;
305
466
  for (const s of safe) {
306
467
  const diskPath = safePath(s.file);
307
- if (!diskPath) { console.error(` ✗ Traversal rejected: ${s.file}`); continue; }
468
+ if (!diskPath) { console.error(` ✗ Traversal rejected: ${s.file}`); safeFailed++; continue; }
469
+ if (!existsSync(diskPath)) continue;
308
470
  // Re-verify hash hasn't changed since classification
309
471
  const currentHash = sha256File(diskPath);
310
472
  if (currentHash !== s.storedHash) {
311
- console.log(` ⊘ Skipped (modified since classification): ${s.file}`);
312
- continue;
473
+ console.log(` ⊘ Skipped (modified since classification): ${s.file}`);
474
+ safeFailed++;
475
+ continue;
313
476
  }
314
477
  try {
315
478
  unlinkSync(diskPath);
316
479
  safeRemoved++;
317
480
  } catch (e) {
318
- console.error(` ✗ Failed to remove: ${s.file} — ${e.message}`);
481
+ console.error(` ✗ Failed to remove: ${s.file} — ${e.message}`);
482
+ safeFailed++;
319
483
  }
320
484
  }
321
485
  console.log(`✓ Removed ${safeRemoved} safe files.`);
322
486
 
323
- // 5. Handle MODIFIED files — prompt user
324
- let modifiedRemoved = 0;
325
- let modifiedKept = 0;
326
-
327
- for (const m of modified) {
487
+ // 5. Handle MODIFIED files — prompt user
488
+ let modifiedRemoved = 0;
489
+ let modifiedKept = 0;
490
+ let modifiedFailed = 0;
491
+
492
+ for (const m of modified) {
493
+ const canonicalModified = m.file.replace(/\\/g, '/').replace(/\/+/g, '/');
494
+ if (deleteModified.has(canonicalModified)) {
495
+ const result = removePlannedFile(m.file);
496
+ if (result.ok) {
497
+ console.log(` Deleted modified by decision: ${m.file}`);
498
+ modifiedRemoved++;
499
+ } else {
500
+ console.error(` Failed modified decision: ${m.file} - ${result.reason}`);
501
+ modifiedFailed++;
502
+ }
503
+ continue;
504
+ }
328
505
  if (yes) {
329
506
  // Non-interactive mode: skip all modified
330
507
  console.log(` ⊘ Skipped (modified): ${m.file}`);
@@ -338,22 +515,53 @@ async function main() {
338
515
 
339
516
  const answer = await askUser(' Choose [d/k]: ');
340
517
  if (answer === 'd' || answer === 'delete') {
341
- const diskPath = safePath(m.file);
342
- if (!diskPath) { console.error(` ✗ Traversal rejected: ${m.file}`); continue; }
343
- try {
344
- unlinkSync(diskPath);
345
- console.log(` ✓ Deleted: ${m.file}`);
346
- modifiedRemoved++;
347
- } catch (e) {
348
- console.error(` ✗ Failed: ${m.file} — ${e.message}`);
349
- }
518
+ const diskPath = safePath(m.file);
519
+ if (!diskPath) { console.error(` ✗ Traversal rejected: ${m.file}`); modifiedFailed++; continue; }
520
+ try {
521
+ unlinkSync(diskPath);
522
+ console.log(` ✓ Deleted: ${m.file}`);
523
+ modifiedRemoved++;
524
+ } catch (e) {
525
+ console.error(` ✗ Failed: ${m.file} — ${e.message}`);
526
+ modifiedFailed++;
527
+ }
350
528
  } else {
351
529
  console.log(` ⊘ Kept: ${m.file}`);
352
530
  modifiedKept++;
353
- }
354
- }
355
-
356
- // 6. Cleanup empty directories
531
+ }
532
+ }
533
+
534
+ // 5.5 Handle explicit purge files. These are Harness-scoped user-data files
535
+ // only included when --purge-user-data was requested.
536
+ let purgeRemoved = 0;
537
+ let purgeKept = 0;
538
+ let purgeFailed = 0;
539
+ for (const p of purge) {
540
+ if (!purgeUserData) {
541
+ purgeKept++;
542
+ continue;
543
+ }
544
+ if (!yes) {
545
+ console.log(`\nPURGE ${p.file}`);
546
+ console.log(` Reason: ${p.reason}`);
547
+ console.log(` [D]elete [K]eep (default)`);
548
+ const answer = await askUser(' Choose [d/k]: ');
549
+ if (!(answer === 'd' || answer === 'delete')) {
550
+ console.log(` Kept purge candidate: ${p.file}`);
551
+ purgeKept++;
552
+ continue;
553
+ }
554
+ }
555
+ const result = removePlannedFile(p.file);
556
+ if (result.ok) {
557
+ purgeRemoved++;
558
+ } else {
559
+ console.error(` Failed purge: ${p.file} - ${result.reason}`);
560
+ purgeFailed++;
561
+ }
562
+ }
563
+
564
+ // 6. Cleanup empty directories
357
565
  for (const dir of CLEANUP_DIRS) {
358
566
  removeEmptyDirs(resolve(ROOT, dir));
359
567
  }
@@ -362,12 +570,24 @@ async function main() {
362
570
  // Remove root .claude/ if empty (but this is rare)
363
571
  removeEmptyDirs(resolve(ROOT, '.claude'));
364
572
 
365
- // 7. Remove version file if everything is gone
366
- if (existsSync(VERSION_FILE)) {
367
- const harPath = resolve(ROOT, 'Harness');
368
- if (!existsSync(harPath) || (existsSync(harPath) && readdirSync(harPath).filter(f => f !== '.harness-version').length === 0)) {
369
- unlinkSync(VERSION_FILE);
370
- console.log('✓ Removed .harness-version (Harness directory empty).');
573
+ // 7. Remove version file if everything is gone
574
+ if (existsSync(VERSION_FILE)) {
575
+ const harPath = resolve(ROOT, 'Harness');
576
+ const removalIncomplete = safeFailed > 0
577
+ || modifiedFailed > 0
578
+ || purgeFailed > 0
579
+ || modifiedKept > 0
580
+ || purgeKept > 0;
581
+ if (purgeUserData) {
582
+ if (removalIncomplete) {
583
+ console.log('Kept .harness-version because some planned removals failed or were kept.');
584
+ } else {
585
+ unlinkSync(VERSION_FILE);
586
+ console.log('Removed .harness-version (purge mode).');
587
+ }
588
+ } else if (!existsSync(harPath) || (existsSync(harPath) && readdirSync(harPath).filter(f => f !== '.harness-version').length === 0)) {
589
+ unlinkSync(VERSION_FILE);
590
+ console.log('✓ Removed .harness-version (Harness directory empty).');
371
591
  }
372
592
  }
373
593
 
@@ -1,38 +1,62 @@
1
- #!/usr/bin/env pwsh
2
- # wf-statusline.ps1 — Windows statusline badge for WF-MAX / WF-REVIEW
3
- # Reads Harness/.runtime/current-mode.json and outputs a colored badge.
4
- #
5
- # Usage in .claude/settings.json:
6
- # "statusLine": { "type": "command", "command": "pwsh -File Harness/scripts/wf-statusline.ps1" }
7
- #
8
- # Security: refuses symlinks, validates JSON, caps read size.
9
-
10
- param()
11
-
12
- $MODE_FILE = Join-Path $PSScriptRoot '..' '.runtime' 'current-mode.json'
13
-
14
- # Refuse symlinks / missing file
15
- if (-not (Test-Path $MODE_FILE -PathType Leaf)) { exit 0 }
16
- $item = Get-Item $MODE_FILE -Force
17
- if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { exit 0 }
18
-
19
- # Size cap
20
- if ($item.Length -gt 4096) { exit 0 }
21
-
22
- try {
23
- $json = Get-Content $MODE_FILE -Raw -ErrorAction Stop | ConvertFrom-Json
24
- } catch {
25
- exit 0 # Silent-fail — never crash statusline
26
- }
27
-
28
- if (-not $json.active -or $json.role -ne 'ceo') { exit 0 }
29
-
30
- switch ($json.mode) {
31
- 'wf-max' {
32
- $phase = if ($json.phase) { ":$($json.phase -replace 'W\d_','W')" } else { '' }
33
- Write-Host -NoNewline "`e[38;5;75m[WF-MAX$phase]`e[0m"
34
- }
35
- 'wf-review' {
36
- Write-Host -NoNewline "`e[38;5;178m[WF-REVIEW]`e[0m"
37
- }
38
- }
1
+ #!/usr/bin/env pwsh
2
+ # wf-statusline.ps1 — Statusline badge for WF-MAX / WF-REVIEW modes
3
+ # Displays mode + agentRole from Harness/.runtime/current-mode.json
4
+ # Role-aware: shows all roles (CEO/MGR/WRK/REV), not just CEO.
5
+ #
6
+ # Usage in .claude/settings.json:
7
+ # "statusLine": { "type": "command", "command": "pwsh -File Harness/scripts/wf-statusline.ps1" }
8
+ #
9
+ # Security: refuses symlinks, validates JSON, caps read size.
10
+
11
+ param()
12
+
13
+ $MODE_FILE = Join-Path $PSScriptRoot '..' '.runtime' 'current-mode.json'
14
+
15
+ # Refuse symlinks / missing file
16
+ if (-not (Test-Path $MODE_FILE -PathType Leaf)) { exit 0 }
17
+ $item = Get-Item $MODE_FILE -Force
18
+ if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { exit 0 }
19
+
20
+ # Size cap
21
+ if ($item.Length -gt 16384) { exit 0 }
22
+
23
+ try {
24
+ $json = Get-Content $MODE_FILE -Raw -ErrorAction Stop | ConvertFrom-Json
25
+ } catch {
26
+ exit 0 # Silent-fail
27
+ }
28
+
29
+ if (-not $json.active) { exit 0 }
30
+
31
+ # Use agentRole first, fall back to legacy 'role' field
32
+ $agentRole = if ($json.agentRole) { $json.agentRole } else { $json.role }
33
+
34
+ switch ($json.mode) {
35
+ 'wf-max' {
36
+ $phase = if ($json.phase) { ":$($json.phase -replace 'W\d_','W')" } else { '' }
37
+ $roleShort = switch ($agentRole) {
38
+ 'ceo' { 'CEO' }
39
+ 'manager' { 'MGR' }
40
+ 'worker' { 'WRK' }
41
+ 'reviewer' { 'REV' }
42
+ default { '' }
43
+ }
44
+ $badge = if ($roleShort) { "[WF-MAX:${roleShort}${phase}]" } else { "[WF-MAX${phase}]" }
45
+ Write-Host -NoNewline "`e[38;5;75m${badge}`e[0m"
46
+ }
47
+ 'wf-review' {
48
+ Write-Host -NoNewline "`e[38;5;178m[WF-REVIEW]`e[0m"
49
+ }
50
+ 'wf-auto-spark' {
51
+ Write-Host -NoNewline "`e[38;5;213m[SPARK]`e[0m"
52
+ }
53
+ 'wf-auto' {
54
+ Write-Host -NoNewline "`e[38;5;111m[WF-AUTO]`e[0m"
55
+ }
56
+ 'wf' {
57
+ Write-Host -NoNewline "`e[38;5;108m[WF]`e[0m"
58
+ }
59
+ 'wf-learn' {
60
+ Write-Host -NoNewline "`e[38;5;222m[WF-LEARN]`e[0m"
61
+ }
62
+ }