create-harness-vibe-coding 0.8.7 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README-CN.md +163 -105
  2. package/README.md +179 -244
  3. package/bin/create-harness-vibe-coding.js +2 -2
  4. package/docs/images/harness-architecture-light.png +0 -0
  5. package/docs/images/harness-architecture.drawio +164 -0
  6. package/docs/images/harness-icon.png +0 -0
  7. package/package.json +47 -44
  8. package/src/generator.js +41 -5
  9. package/src/index.js +86 -13
  10. package/src/prompts.js +37 -37
  11. package/templates/common/.claude/agents/architect-manager.md +45 -45
  12. package/templates/common/.claude/agents/architect.md +31 -31
  13. package/templates/common/.claude/agents/codebase-explorer.md +45 -0
  14. package/templates/common/.claude/agents/context-master.md +75 -75
  15. package/templates/common/.claude/agents/debugger.md +41 -41
  16. package/templates/common/.claude/agents/docs-researcher.md +41 -41
  17. package/templates/common/.claude/agents/explore-manager.md +41 -41
  18. package/templates/common/.claude/agents/implement-manager.md +49 -49
  19. package/templates/common/.claude/agents/implementer.md +40 -40
  20. package/templates/common/.claude/agents/memory-master.md +82 -64
  21. package/templates/common/.claude/agents/planner.md +34 -34
  22. package/templates/common/.claude/agents/researcher.md +41 -41
  23. package/templates/common/.claude/agents/review-manager.md +56 -56
  24. package/templates/common/.claude/agents/reviewer.md +34 -34
  25. package/templates/common/.claude/agents/task-scribe.md +70 -0
  26. package/templates/common/.claude/agents/verifier.md +29 -29
  27. package/templates/common/.claude/commands/wf-help.md +9 -5
  28. package/templates/common/.claude/commands/wf-update.md +24 -0
  29. package/templates/common/.claude/rules/ecc/common.md +57 -44
  30. package/templates/common/.claude/settings.json +13 -0
  31. package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +8 -4
  32. package/templates/common/.claude/skills/wf/SKILL.md +15 -8
  33. package/templates/common/.claude/skills/wf-auto/SKILL.md +10 -7
  34. package/templates/common/.claude/skills/wf-learn/SKILL.md +9 -2
  35. package/templates/common/.claude/skills/wf-max/SKILL.md +23 -5
  36. package/templates/common/.claude/skills/wf-readme/SKILL.md +49 -49
  37. package/templates/common/.claude/skills/wf-remove/SKILL.md +7 -7
  38. package/templates/common/.claude/skills/wf-update/SKILL.md +15 -2
  39. package/templates/common/.codex/hooks.json +17 -0
  40. package/templates/common/.harness-version +130 -45
  41. package/templates/common/.opencode/agents/architect-manager.md +52 -0
  42. package/templates/common/.opencode/agents/architect.md +35 -0
  43. package/templates/common/.opencode/agents/codebase-explorer.md +45 -0
  44. package/templates/common/.opencode/agents/context-master.md +81 -0
  45. package/templates/common/.opencode/agents/debugger.md +43 -0
  46. package/templates/common/.opencode/agents/docs-researcher.md +42 -0
  47. package/templates/common/.opencode/agents/explore-manager.md +49 -0
  48. package/templates/common/.opencode/agents/implement-manager.md +56 -0
  49. package/templates/common/.opencode/agents/implementer.md +42 -0
  50. package/templates/common/.opencode/agents/memory-master.md +88 -0
  51. package/templates/common/.opencode/agents/planner.md +38 -0
  52. package/templates/common/.opencode/agents/reflector.md +39 -0
  53. package/templates/common/.opencode/agents/researcher.md +42 -0
  54. package/templates/common/.opencode/agents/review-manager.md +63 -0
  55. package/templates/common/.opencode/agents/reviewer.md +37 -0
  56. package/templates/common/.opencode/agents/task-scribe.md +70 -0
  57. package/templates/common/.opencode/agents/tdd-guide.md +83 -0
  58. package/templates/common/.opencode/agents/test-writer.md +54 -0
  59. package/templates/common/.opencode/agents/verifier.md +37 -0
  60. package/templates/common/.opencode/commands/wf-auto-spark.md +15 -0
  61. package/templates/common/.opencode/commands/wf-auto.md +15 -0
  62. package/templates/common/.opencode/commands/wf-help.md +27 -0
  63. package/templates/common/.opencode/commands/wf-learn.md +15 -0
  64. package/templates/common/.opencode/commands/wf-max.md +15 -0
  65. package/templates/common/.opencode/commands/wf-readme.md +15 -0
  66. package/templates/common/.opencode/commands/wf-remove.md +15 -0
  67. package/templates/common/.opencode/commands/wf-review.md +15 -0
  68. package/templates/common/.opencode/commands/wf-update.md +24 -0
  69. package/templates/common/.opencode/commands/wf.md +15 -0
  70. package/templates/common/.opencode/plugins/harness-wf-status.mjs +135 -0
  71. package/templates/common/AGENTS.md +2 -29
  72. package/templates/common/CLAUDE.md +114 -88
  73. package/templates/common/Harness/ACCEPTANCE_PROTOCOL.md +2 -2
  74. package/templates/common/{MEMORY.md → Harness/MEMORY.md} +17 -4
  75. package/templates/common/Harness/MEMORY_PROTOCOL.md +80 -30
  76. package/templates/common/Harness/PROGRESS.md +17 -17
  77. package/templates/common/Harness/README.md +58 -19
  78. package/templates/common/{SETUP.md → Harness/SETUP.md} +278 -276
  79. package/templates/common/Harness/TASK_ARCHIVE.md +56 -0
  80. package/templates/common/Harness/WF-AUTO-ANGLES.md +170 -0
  81. package/templates/common/Harness/WF-AUTO-SPARK.md +10 -19
  82. package/templates/common/Harness/WF-AUTO.md +93 -167
  83. package/templates/common/Harness/WF-KERNEL.md +189 -0
  84. package/templates/common/Harness/WF-MAX.md +60 -328
  85. package/templates/common/Harness/WF-STATE.md +83 -0
  86. package/templates/common/Harness/WF.md +117 -237
  87. package/templates/common/Harness/agent-workflow.md +2 -2
  88. package/templates/common/Harness/architecture.md +124 -124
  89. package/templates/common/Harness/context-loading.md +111 -111
  90. package/templates/common/Harness/dispatch.md +43 -35
  91. package/templates/common/Harness/extension.md +66 -66
  92. package/templates/common/Harness/lifecycle.md +20 -20
  93. package/templates/common/Harness/research/PRD.md +56 -56
  94. package/templates/common/Harness/research/README.md +169 -169
  95. package/templates/common/Harness/research/research-results.md +66 -66
  96. package/templates/common/Harness/scripts/archive-tasks.mjs +239 -0
  97. package/templates/common/{scripts → Harness/scripts}/scan-clean.mjs +443 -416
  98. package/templates/common/{scripts → Harness/scripts}/validate-harness.mjs +691 -452
  99. package/templates/common/Harness/scripts/wf-auto-update-prompt.mjs +258 -0
  100. package/templates/common/{scripts → Harness/scripts}/wf-remove.mjs +56 -39
  101. package/templates/common/{scripts → Harness/scripts}/wf-update-check.mjs +632 -599
  102. package/templates/common/Harness/subagents.md +215 -214
  103. package/templates/common/Harness/tasks/_template/ARTIFACTS.md +2 -2
  104. package/templates/common/Harness/tasks/_template/NOTES.md +2 -2
  105. package/templates/common/Harness/tasks/_template/PLAN.md +5 -0
  106. package/templates/common/Harness/tasks/_template/STATE.json +23 -0
  107. package/templates/common/README.md +37 -37
  108. package/templates/common/memory/agent-lessons-patterns.md +22 -21
  109. package/templates/common/memory/routes.md +43 -0
  110. package/templates/common/memory/startup-hints.md +32 -0
  111. package/templates/common/memory/tool-usage-reflections.md +22 -21
  112. package/templates/common/memory/user-corrections-preferences.md +23 -21
  113. package/templates/common/opencode.json +19 -0
  114. package/templates/optional/catalog.json +49 -33
  115. package/templates/optional/skills/browser-e2e/.claude/skills/browser-e2e/SKILL.md +42 -42
  116. package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +193 -193
  117. package/templates/optional/skills/browser-e2e/.opencode/commands/wf-browser.md +15 -0
  118. package/templates/optional/skills/browser-e2e/Harness/workflows/browser-e2e.md +48 -48
  119. package/templates/optional/skills/github-pr-review/.claude/skills/github-pr-review/SKILL.md +40 -40
  120. package/templates/optional/skills/github-pr-review/Harness/workflows/github-pr-review.md +28 -28
  121. package/templates/optional/skills/python-backend/.claude/skills/python-backend/SKILL.md +40 -40
  122. package/templates/optional/skills/python-backend/Harness/workflows/python-backend.md +34 -34
  123. package/templates/optional/skills/ts-react-frontend/.claude/skills/ts-react-frontend/SKILL.md +43 -43
  124. package/templates/optional/skills/ts-react-frontend/Harness/workflows/ts-react-frontend.md +34 -34
  125. package/templates/optional/skills/ui-ux-review/.claude/skills/ui-ux-review/SKILL.md +40 -40
  126. package/templates/optional/skills/ui-ux-review/Harness/workflows/ui-ux-review.md +26 -26
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * wf-auto-update-prompt.mjs
4
+ *
5
+ * Lightweight hook helper for Claude Code, Codex, and OpenCode.
6
+ * It checks the installed Harness update plan and emits a prompt reminder only
7
+ * when an update is available. It never applies updates.
8
+ */
9
+
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
11
+ import { dirname, resolve } from 'path';
12
+ import { spawnSync } from 'child_process';
13
+
14
+ const DEFAULT_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
15
+ const DEFAULT_NOTICE_INTERVAL_MS = 60 * 60 * 1000;
16
+ const args = process.argv.slice(2);
17
+ const format = readFlagValue('--format') || 'plain';
18
+ const force = args.includes('--force');
19
+ const modeOnly = args.includes('--mode-only');
20
+ const input = readStdinJson();
21
+ const root = findRoot();
22
+
23
+ function readFlagValue(flagName) {
24
+ const index = args.indexOf(flagName);
25
+ if (index === -1) return null;
26
+ const value = args[index + 1];
27
+ return value && !value.startsWith('--') ? value : null;
28
+ }
29
+
30
+ function readStdinJson() {
31
+ try {
32
+ if (process.stdin.isTTY) return {};
33
+ const raw = readFileSync(0, 'utf-8').trim();
34
+ return raw ? JSON.parse(raw) : {};
35
+ } catch {
36
+ return {};
37
+ }
38
+ }
39
+
40
+ function findRoot() {
41
+ const candidates = [
42
+ process.env.WF_ROOT,
43
+ input.cwd,
44
+ input.workspace,
45
+ process.cwd(),
46
+ ].filter(Boolean);
47
+
48
+ for (const candidate of candidates) {
49
+ const found = ascendToHarnessRoot(resolve(candidate));
50
+ if (found) return found;
51
+ }
52
+ return process.cwd();
53
+ }
54
+
55
+ function ascendToHarnessRoot(start) {
56
+ let current = start;
57
+ for (;;) {
58
+ if (existsSync(resolve(current, 'Harness', '.harness-version'))) return current;
59
+ const parent = dirname(current);
60
+ if (parent === current) return null;
61
+ current = parent;
62
+ }
63
+ }
64
+
65
+ function runtimeDir() {
66
+ return resolve(root, 'Harness', '.runtime');
67
+ }
68
+
69
+ function cachePath() {
70
+ return resolve(runtimeDir(), 'update-check.json');
71
+ }
72
+
73
+ function modePath() {
74
+ return resolve(runtimeDir(), 'current-mode.json');
75
+ }
76
+
77
+ function readJsonFile(file, fallback) {
78
+ try {
79
+ return JSON.parse(readFileSync(file, 'utf-8'));
80
+ } catch {
81
+ return fallback;
82
+ }
83
+ }
84
+
85
+ function writeJsonFile(file, value) {
86
+ mkdirSync(dirname(file), { recursive: true });
87
+ writeFileSync(file, JSON.stringify(value, null, 2) + '\n', 'utf-8');
88
+ }
89
+
90
+ function readVersion() {
91
+ return readJsonFile(resolve(root, 'Harness', '.harness-version'), null);
92
+ }
93
+
94
+ function shouldSkipPrompt() {
95
+ const prompt = String(input.prompt || input.message || '');
96
+ return /\b\/?wf-update\b|\$wf-update\b/.test(prompt);
97
+ }
98
+
99
+ function intervalFromEnv(name, fallback) {
100
+ const raw = process.env[name];
101
+ if (!raw) return fallback;
102
+ const parsed = Number(raw);
103
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
104
+ }
105
+
106
+ function runUpdateCheck() {
107
+ const script = resolve(root, 'Harness', 'scripts', 'wf-update-check.mjs');
108
+ if (!existsSync(script)) {
109
+ return { status: 'error', message: 'Harness/scripts/wf-update-check.mjs not found.' };
110
+ }
111
+ const result = spawnSync(process.execPath, [script, '--json'], {
112
+ cwd: root,
113
+ encoding: 'utf-8',
114
+ timeout: intervalFromEnv('WF_UPDATE_CHECK_TIMEOUT_MS', 30000),
115
+ env: process.env,
116
+ });
117
+
118
+ if (result.error) {
119
+ return { status: 'offline', message: result.error.message };
120
+ }
121
+
122
+ const stdout = (result.stdout || '').trim();
123
+ if (!stdout) {
124
+ return { status: 'error', message: `Update checker exited with no JSON output (${result.status ?? 'unknown'}).` };
125
+ }
126
+
127
+ try {
128
+ return JSON.parse(stdout);
129
+ } catch {
130
+ return { status: 'error', message: 'Update checker returned invalid JSON.' };
131
+ }
132
+ }
133
+
134
+ function isUpdateStatus(status) {
135
+ return status === 'update-available' || status === 'partial-update';
136
+ }
137
+
138
+ function buildMessage(update) {
139
+ const from = update.from || update.version || 'unknown';
140
+ const to = update.to || update.remote || 'unknown';
141
+ const parts = [
142
+ `Harness update available: ${from} -> ${to}.`,
143
+ `Before unrelated work, ask the user whether to run /wf-update.`,
144
+ ];
145
+ // Only include summary counts — never list individual conflict files in hook output
146
+ if (update.conflict > 0) {
147
+ parts.push(`${update.conflict} conflict file(s) will need agent/user merge decisions.`);
148
+ }
149
+ if ((update.updated > 0 || update.created > 0) && update.updated !== undefined) {
150
+ const safeCount = (update.updated || 0) + (update.created || 0);
151
+ parts.push(`${safeCount} safe file update(s) available.`);
152
+ }
153
+ // Max 2 lines total, never include plan details
154
+ return parts.slice(0, 2).join(' ');
155
+ }
156
+
157
+ function readModeLabel() {
158
+ const mode = readJsonFile(modePath(), null);
159
+ if (!mode || !mode.active || !mode.mode) return null;
160
+ return `${mode.mode} mode on`;
161
+ }
162
+
163
+ function emit(message, payload = {}) {
164
+ if (!message && !payload.modeLabel) return;
165
+
166
+ if (format === 'claude') {
167
+ const context = [message, payload.modeLabel].filter(Boolean).join('\n');
168
+ if (context) console.log(JSON.stringify({ additionalContext: context }));
169
+ return;
170
+ }
171
+
172
+ if (format === 'codex') {
173
+ const context = [message, payload.modeLabel].filter(Boolean).join('\n');
174
+ if (context) {
175
+ console.log(JSON.stringify({
176
+ hookSpecificOutput: {
177
+ hookEventName: input.hook_event_name || 'UserPromptSubmit',
178
+ additionalContext: context,
179
+ },
180
+ }));
181
+ }
182
+ return;
183
+ }
184
+
185
+ if (format === 'json') {
186
+ console.log(JSON.stringify({ message, ...payload }, null, 2));
187
+ return;
188
+ }
189
+
190
+ if (message) console.log(message);
191
+ if (payload.modeLabel) console.log(payload.modeLabel);
192
+ }
193
+
194
+ function main() {
195
+ if (!existsSync(resolve(root, 'Harness', '.harness-version'))) return;
196
+ const version = readVersion();
197
+ if (version?.autoCheck === false) return;
198
+
199
+ const modeLabel = readModeLabel();
200
+ if (modeOnly) {
201
+ emit('', { modeLabel, status: 'mode' });
202
+ return;
203
+ }
204
+
205
+ if (shouldSkipPrompt()) {
206
+ emit('', { modeLabel, status: 'skipped-update-command' });
207
+ return;
208
+ }
209
+
210
+ const now = Date.now();
211
+ const cache = readJsonFile(cachePath(), {});
212
+ const checkInterval = intervalFromEnv('WF_UPDATE_CHECK_INTERVAL_MS', DEFAULT_CHECK_INTERVAL_MS);
213
+ const noticeInterval = intervalFromEnv('WF_UPDATE_NOTICE_INTERVAL_MS', DEFAULT_NOTICE_INTERVAL_MS);
214
+
215
+ let update = cache.update;
216
+ const shouldCheck = force
217
+ || !cache.checkedAt
218
+ || now - Date.parse(cache.checkedAt) >= checkInterval;
219
+
220
+ if (shouldCheck) {
221
+ update = runUpdateCheck();
222
+ writeJsonFile(cachePath(), {
223
+ checkedAt: new Date(now).toISOString(),
224
+ noticedAt: cache.noticedAt || null,
225
+ update,
226
+ });
227
+ }
228
+
229
+ if (!isUpdateStatus(update?.status)) {
230
+ emit('', { modeLabel, status: update?.status || 'unknown' });
231
+ return;
232
+ }
233
+
234
+ const shouldNotice = force
235
+ || !cache.noticedAt
236
+ || now - Date.parse(cache.noticedAt) >= noticeInterval
237
+ || cache.update?.to !== update.to
238
+ || cache.update?.remote !== update.remote;
239
+
240
+ if (!shouldNotice) {
241
+ emit('', { modeLabel, status: update.status, suppressed: true });
242
+ return;
243
+ }
244
+
245
+ writeJsonFile(cachePath(), {
246
+ checkedAt: cache.checkedAt || new Date(now).toISOString(),
247
+ noticedAt: new Date(now).toISOString(),
248
+ update,
249
+ });
250
+
251
+ emit(buildMessage(update), { modeLabel, status: update.status, update });
252
+ }
253
+
254
+ try {
255
+ main();
256
+ } catch {
257
+ // Hooks must fail open. A broken update reminder should never block work.
258
+ }
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * wf-remove.mjs �?Safely remove Harness framework files.
3
+ * wf-remove.mjs �?Safely remove Harness framework files.
4
4
  *
5
5
  * Classifies all Harness-owned files into:
6
- * SAFE �?framework files matching stored checksums �?auto-remove
7
- * MODIFIED �?framework files edited by user �?MUST confirm
8
- * USER �?user data files �?NEVER remove
6
+ * SAFE �?framework files matching stored checksums �?auto-remove
7
+ * MODIFIED �?framework files edited by user �?MUST confirm
8
+ * USER �?user data files �?NEVER remove
9
9
  *
10
10
  * Usage:
11
11
  * node Harness/scripts/wf-remove.mjs # dry-run: show plan
@@ -38,6 +38,8 @@ const HARNESS_PREFIXES = [
38
38
  '.claude/',
39
39
  '.agents/',
40
40
  '.codex/',
41
+ '.opencode/',
42
+ 'opencode.json',
41
43
  'Harness/',
42
44
  'CLAUDE.md',
43
45
  'AGENTS.md',
@@ -45,7 +47,7 @@ const HARNESS_PREFIXES = [
45
47
  'tests/.gitkeep',
46
48
  ];
47
49
 
48
- /** Files the user owns �?NEVER remove. Consistent with wf-update-check PRESERVE_PATTERNS. */
50
+ /** Files the user owns �?NEVER remove. Consistent with wf-update-check PRESERVE_PATTERNS. */
49
51
  const USER_DATA_PATTERNS = [
50
52
  /^Harness\/PROGRESS\.md$/,
51
53
  /^Harness\/tasks\//,
@@ -144,13 +146,18 @@ const BUILT_IN_SKILL_NAMES = [
144
146
  ];
145
147
 
146
148
  const KNOWN_FRAMEWORK_FILES = new Set([
147
- ...BUILT_IN_AGENT_NAMES.map(name => `.claude/agents/${name}.md`),
149
+ ...BUILT_IN_AGENT_NAMES.flatMap(name => [
150
+ `.claude/agents/${name}.md`,
151
+ `.opencode/agents/${name}.md`,
152
+ ]),
148
153
  ...BUILT_IN_SKILL_NAMES.flatMap(name => [
149
154
  `.claude/skills/${name}/SKILL.md`,
150
155
  `.agents/skills/${name}/SKILL.md`,
151
156
  ]),
152
157
  '.claude/commands/wf-help.md',
158
+ '.opencode/commands/wf-help.md',
153
159
  '.claude/rules/ecc/common.md',
160
+ 'opencode.json',
154
161
  ]);
155
162
 
156
163
  function isKnownFrameworkFile(file) {
@@ -185,6 +192,10 @@ const CLEANUP_DIRS = [
185
192
  '.agents/skills',
186
193
  '.agents',
187
194
  '.codex',
195
+ '.opencode/commands',
196
+ '.opencode/agents',
197
+ '.opencode/skills',
198
+ '.opencode',
188
199
  '.claude/rules/ecc',
189
200
  '.claude/rules',
190
201
  'Harness/scripts',
@@ -305,7 +316,7 @@ function removeEmptyDirs(startDir) {
305
316
 
306
317
  async function askUser(question) {
307
318
  if (!process.stdin.isTTY) {
308
- console.log(' (non-interactive �?defaulting to KEEP)');
319
+ console.log(' (non-interactive �?defaulting to KEEP)');
309
320
  return 'k';
310
321
  }
311
322
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -378,9 +389,9 @@ async function main() {
378
389
  }
379
390
 
380
391
  // 2. Classify every file
381
- const safe = []; // SAFE �?matches checksum, auto-remove
382
- const modified = []; // MODIFIED �?user edited, must confirm
383
- const user = []; // USER �?never remove
392
+ const safe = []; // SAFE �?matches checksum, auto-remove
393
+ const modified = []; // MODIFIED �?user edited, must confirm
394
+ const user = []; // USER �?never remove
384
395
  const skipped = []; // File not on disk, traversal rejected, or framework-keep
385
396
  const purge = []; // Explicitly requested Harness user-data removal
386
397
  skipped.push(...scanIssues);
@@ -402,13 +413,13 @@ async function main() {
402
413
  });
403
414
  continue;
404
415
  }
405
- user.push({ file, reason: 'user data �?NEVER removed' });
416
+ user.push({ file, reason: 'user data �?NEVER removed' });
406
417
  continue;
407
418
  }
408
419
 
409
420
  // Only allow deletion of files under harness-owned prefixes
410
421
  if (!isHarnessOwned(canonical)) {
411
- user.push({ file, reason: 'outside harness prefix �?NEVER removed' });
422
+ user.push({ file, reason: 'outside harness prefix �?NEVER removed' });
412
423
  continue;
413
424
  }
414
425
 
@@ -427,12 +438,18 @@ async function main() {
427
438
 
428
439
  if (!storedHash) {
429
440
  if (isKnownFrameworkFile(canonical)) {
430
- safe.push({
431
- file,
432
- currentHash,
433
- storedHash: currentHash,
434
- reason: 'known framework file missing from legacy checksums',
435
- });
441
+ if (canonical === 'opencode.json') {
442
+ // opencode.json without provenance — may pre-date Harness install.
443
+ // Only auto-remove when checksum proves Harness created it.
444
+ modified.push({ file, currentHash, storedHash: 'none', reason: 'not in checksums (may be pre-existing user config)' });
445
+ } else {
446
+ safe.push({
447
+ file,
448
+ currentHash,
449
+ storedHash: currentHash,
450
+ reason: 'known framework file missing from legacy checksums',
451
+ });
452
+ }
436
453
  } else {
437
454
  modified.push({ file, currentHash, storedHash: 'none', reason: 'not in checksums' });
438
455
  }
@@ -449,7 +466,7 @@ async function main() {
449
466
  const settingsHash = sha256File(settingsFile);
450
467
  const storedSettingsHash = storedChecksums['.claude/settings.json'];
451
468
  if (storedSettingsHash && settingsHash !== storedSettingsHash) {
452
- // User modified settings �?move to modified
469
+ // User modified settings �?move to modified
453
470
  // Remove from safe if it was there
454
471
  const idx = safe.findIndex(s => s.file === '.claude/settings.json');
455
472
  if (idx >= 0) {
@@ -493,16 +510,16 @@ async function main() {
493
510
  return;
494
511
  }
495
512
 
496
- console.log('\n🧹 WF-REMOVE �?Harness framework removal plan\n');
513
+ console.log('\n🧹 WF-REMOVE �?Harness framework removal plan\n');
497
514
 
498
515
  if (safe.length > 0) {
499
- console.log(`�?SAFE (${safe.length} files �?auto-remove, unmodified framework):`);
500
- for (const s of safe) console.log(` �?${s.file}`);
516
+ console.log(`�?SAFE (${safe.length} files �?auto-remove, unmodified framework):`);
517
+ for (const s of safe) console.log(` �?${s.file}`);
501
518
  console.log('');
502
519
  }
503
520
 
504
521
  if (modified.length > 0) {
505
- console.log(`�?MODIFIED (${modified.length} files �?REQUIRE CONFIRMATION):`);
522
+ console.log(`�?MODIFIED (${modified.length} files �?REQUIRE CONFIRMATION):`);
506
523
  for (const m of modified) {
507
524
  console.log(` ? ${m.file} [${m.reason}]`);
508
525
  }
@@ -510,8 +527,8 @@ async function main() {
510
527
  }
511
528
 
512
529
  if (user.length > 0) {
513
- console.log(`🔒 USER DATA (${user.length} files �?NEVER removed):`);
514
- for (const u of user) console.log(` �?${u.file} [${u.reason}]`);
530
+ console.log(`🔒 USER DATA (${user.length} files �?NEVER removed):`);
531
+ for (const u of user) console.log(` �?${u.file} [${u.reason}]`);
515
532
  console.log('');
516
533
  }
517
534
 
@@ -528,17 +545,17 @@ async function main() {
528
545
  return;
529
546
  }
530
547
 
531
- // 4. Apply �?remove SAFE files automatically (rehash before unlink)
548
+ // 4. Apply �?remove SAFE files automatically (rehash before unlink)
532
549
  let safeRemoved = 0;
533
550
  let safeFailed = 0;
534
551
  for (const s of safe) {
535
552
  const diskPath = safePath(s.file);
536
- if (!diskPath) { console.error(` �?Traversal rejected: ${s.file}`); safeFailed++; continue; }
553
+ if (!diskPath) { console.error(` �?Traversal rejected: ${s.file}`); safeFailed++; continue; }
537
554
  if (!existsSync(diskPath)) continue;
538
555
  // Re-verify hash hasn't changed since classification
539
556
  const currentHash = sha256File(diskPath);
540
557
  if (currentHash !== s.storedHash) {
541
- console.log(` �?Skipped (modified since classification): ${s.file}`);
558
+ console.log(` �?Skipped (modified since classification): ${s.file}`);
542
559
  safeFailed++;
543
560
  continue;
544
561
  }
@@ -546,13 +563,13 @@ async function main() {
546
563
  unlinkSync(diskPath);
547
564
  safeRemoved++;
548
565
  } catch (e) {
549
- console.error(` �?Failed to remove: ${s.file} �?${e.message}`);
566
+ console.error(` �?Failed to remove: ${s.file} �?${e.message}`);
550
567
  safeFailed++;
551
568
  }
552
569
  }
553
- console.log(`�?Removed ${safeRemoved} safe files.`);
570
+ console.log(`�?Removed ${safeRemoved} safe files.`);
554
571
 
555
- // 5. Handle MODIFIED files �?prompt user
572
+ // 5. Handle MODIFIED files �?prompt user
556
573
  let modifiedRemoved = 0;
557
574
  let modifiedKept = 0;
558
575
  let modifiedFailed = 0;
@@ -572,7 +589,7 @@ async function main() {
572
589
  }
573
590
  if (yes) {
574
591
  // Non-interactive mode: skip all modified
575
- console.log(` �?Skipped (modified): ${m.file}`);
592
+ console.log(` �?Skipped (modified): ${m.file}`);
576
593
  modifiedKept++;
577
594
  continue;
578
595
  }
@@ -584,17 +601,17 @@ async function main() {
584
601
  const answer = await askUser(' Choose [d/k]: ');
585
602
  if (answer === 'd' || answer === 'delete') {
586
603
  const diskPath = safePath(m.file);
587
- if (!diskPath) { console.error(` �?Traversal rejected: ${m.file}`); modifiedFailed++; continue; }
604
+ if (!diskPath) { console.error(` �?Traversal rejected: ${m.file}`); modifiedFailed++; continue; }
588
605
  try {
589
606
  unlinkSync(diskPath);
590
- console.log(` �?Deleted: ${m.file}`);
607
+ console.log(` �?Deleted: ${m.file}`);
591
608
  modifiedRemoved++;
592
609
  } catch (e) {
593
- console.error(` �?Failed: ${m.file} �?${e.message}`);
610
+ console.error(` �?Failed: ${m.file} �?${e.message}`);
594
611
  modifiedFailed++;
595
612
  }
596
613
  } else {
597
- console.log(` �?Kept: ${m.file}`);
614
+ console.log(` �?Kept: ${m.file}`);
598
615
  modifiedKept++;
599
616
  }
600
617
  }
@@ -655,7 +672,7 @@ async function main() {
655
672
  }
656
673
  } else if (!existsSync(harPath) || (existsSync(harPath) && readdirSync(harPath).filter(f => f !== '.harness-version').length === 0)) {
657
674
  unlinkSync(VERSION_FILE);
658
- console.log('�?Removed .harness-version (Harness directory empty).');
675
+ console.log('�?Removed .harness-version (Harness directory empty).');
659
676
  }
660
677
  }
661
678
 
@@ -674,7 +691,7 @@ async function main() {
674
691
  if (pruned > 0) {
675
692
  versionData.checksums = checksums;
676
693
  writeFileSync(VERSION_FILE, JSON.stringify(versionData, null, 2) + '\n', 'utf-8');
677
- console.log(`�?Pruned ${pruned} stale checksum(s) from .harness-version.`);
694
+ console.log(`�?Pruned ${pruned} stale checksum(s) from .harness-version.`);
678
695
  }
679
696
  }
680
697
 
@@ -688,7 +705,7 @@ async function main() {
688
705
  // Strip the harness section: everything from "## 1. Harness Binding" to the next "## "
689
706
  const cleaned = content.replace(/## 1\. Harness Binding[\s\S]*?(?=## 2\.)/, '');
690
707
  writeFileSync(claudePath, cleaned, 'utf-8');
691
- console.log('�?Stripped Harness binding section from CLAUDE.md.');
708
+ console.log('�?Stripped Harness binding section from CLAUDE.md.');
692
709
  }
693
710
  }
694
711
  }