create-harness-vibe-coding 0.8.6 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-CN.md +157 -105
- package/README.md +160 -244
- package/bin/create-harness-vibe-coding.js +2 -2
- package/docs/images/harness-architecture-light.png +0 -0
- package/docs/images/harness-architecture.drawio +164 -0
- package/package.json +46 -44
- package/src/generator.js +30 -11
- package/src/index.js +143 -14
- package/src/prompts.js +37 -37
- package/templates/common/.claude/agents/architect-manager.md +45 -45
- package/templates/common/.claude/agents/context-master.md +75 -75
- package/templates/common/.claude/agents/debugger.md +41 -41
- package/templates/common/.claude/agents/explore-manager.md +41 -41
- package/templates/common/.claude/agents/implement-manager.md +49 -49
- package/templates/common/.claude/agents/implementer.md +40 -40
- package/templates/common/.claude/agents/memory-master.md +64 -64
- package/templates/common/.claude/agents/planner.md +34 -34
- package/templates/common/.claude/agents/reflector.md +35 -0
- package/templates/common/.claude/agents/researcher.md +41 -41
- package/templates/common/.claude/agents/review-manager.md +56 -56
- package/templates/common/.claude/agents/verifier.md +34 -32
- package/templates/common/.claude/commands/wf-help.md +2 -3
- package/templates/common/.claude/rules/ecc/common.md +44 -44
- package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +10 -4
- package/templates/common/.claude/skills/wf/SKILL.md +7 -3
- package/templates/common/.claude/skills/wf-auto/SKILL.md +61 -107
- package/templates/common/.claude/skills/wf-auto-spark/SKILL.md +19 -17
- package/templates/common/.claude/skills/wf-max/SKILL.md +40 -21
- package/templates/common/.claude/skills/wf-readme/SKILL.md +49 -49
- package/templates/common/.claude/skills/wf-remove/SKILL.md +7 -7
- package/templates/common/.claude/skills/wf-update/SKILL.md +9 -4
- package/templates/common/.codex/config.toml +5 -0
- package/templates/common/.harness-version +78 -36
- package/templates/common/.opencode/agents/architect-manager.md +52 -0
- package/templates/common/.opencode/agents/architect.md +35 -0
- package/templates/common/.opencode/agents/context-master.md +81 -0
- package/templates/common/.opencode/agents/debugger.md +43 -0
- package/templates/common/.opencode/agents/docs-researcher.md +42 -0
- package/templates/common/.opencode/agents/explore-manager.md +49 -0
- package/templates/common/.opencode/agents/implement-manager.md +56 -0
- package/templates/common/.opencode/agents/implementer.md +42 -0
- package/templates/common/.opencode/agents/memory-master.md +70 -0
- package/templates/common/.opencode/agents/planner.md +38 -0
- package/templates/common/.opencode/agents/reflector.md +39 -0
- package/templates/common/.opencode/agents/researcher.md +42 -0
- package/templates/common/.opencode/agents/review-manager.md +63 -0
- package/templates/common/.opencode/agents/reviewer.md +37 -0
- package/templates/common/.opencode/agents/tdd-guide.md +83 -0
- package/templates/common/.opencode/agents/test-writer.md +54 -0
- package/templates/common/.opencode/agents/verifier.md +37 -0
- package/templates/common/.opencode/commands/wf-help.md +23 -0
- package/templates/common/AGENTS.md +26 -25
- package/templates/common/CLAUDE.md +86 -88
- package/templates/common/Harness/ACCEPTANCE_PROTOCOL.md +12 -4
- package/templates/common/Harness/PROGRESS.md +17 -17
- package/templates/common/Harness/README.md +26 -16
- package/templates/common/Harness/WF-AUTO-ANGLES.md +170 -0
- package/templates/common/Harness/WF-AUTO-SPARK.md +23 -6
- package/templates/common/Harness/WF-AUTO.md +508 -493
- package/templates/common/Harness/WF-MAX.md +284 -232
- package/templates/common/Harness/WF.md +47 -29
- package/templates/common/Harness/agent-workflow.md +108 -76
- package/templates/common/Harness/architecture.md +124 -124
- package/templates/common/Harness/context-loading.md +111 -111
- package/templates/common/Harness/dispatch.md +96 -95
- package/templates/common/Harness/extension.md +67 -67
- package/templates/common/Harness/lifecycle.md +20 -20
- package/templates/common/Harness/research/PRD.md +56 -56
- package/templates/common/Harness/research/README.md +169 -169
- package/templates/common/Harness/research/research-results.md +66 -66
- package/templates/common/Harness/subagents.md +192 -170
- package/templates/common/Harness/tasks/_template/ARTIFACTS.md +3 -3
- package/templates/common/Harness/tasks/_template/NOTES.md +3 -3
- package/templates/common/Harness/tasks/_template/PLAN.md +53 -60
- package/templates/common/Harness/tasks/_template/PROGRESS.md +26 -29
- package/templates/common/MEMORY.md +27 -30
- package/templates/common/README.md +36 -36
- package/templates/common/SETUP.md +1 -1
- package/templates/common/memory/agent-lessons-patterns.md +21 -21
- package/templates/common/memory/tool-usage-reflections.md +21 -21
- package/templates/common/memory/user-corrections-preferences.md +21 -21
- package/templates/common/opencode.json +19 -0
- package/templates/common/scripts/scan-clean.mjs +487 -448
- package/templates/common/scripts/validate-harness.mjs +245 -146
- package/templates/common/scripts/wf-remove.mjs +311 -293
- package/templates/common/scripts/wf-update-check.mjs +511 -311
- package/templates/optional/catalog.json +41 -33
- package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +194 -194
- package/templates/optional/skills/browser-e2e/Harness/workflows/browser-e2e.md +105 -69
- package/templates/optional/skills/github-pr-review/Harness/workflows/github-pr-review.md +28 -28
- package/templates/optional/skills/python-backend/Harness/workflows/python-backend.md +34 -34
- package/templates/optional/skills/ts-react-frontend/Harness/workflows/ts-react-frontend.md +34 -34
- package/templates/optional/skills/ui-ux-review/Harness/workflows/ui-ux-review.md +26 -26
|
@@ -1,64 +1,66 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* wf-remove.mjs �?Safely remove Harness framework files.
|
|
4
|
-
*
|
|
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
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* node Harness/scripts/wf-remove.mjs # dry-run: show plan
|
|
12
|
-
* node Harness/scripts/wf-remove.mjs --apply # apply SAFE, prompt for MODIFIED
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* wf-remove.mjs �?Safely remove Harness framework files.
|
|
4
|
+
*
|
|
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
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node Harness/scripts/wf-remove.mjs # dry-run: show plan
|
|
12
|
+
* node Harness/scripts/wf-remove.mjs --apply # apply SAFE, prompt for MODIFIED
|
|
13
13
|
* node Harness/scripts/wf-remove.mjs --yes # auto-yes for SAFE only (non-interactive)
|
|
14
14
|
* node Harness/scripts/wf-remove.mjs --json # JSON plan for AI consumption
|
|
15
15
|
* node Harness/scripts/wf-remove.mjs --apply --yes # auto-apply SAFE, skip MODIFIED, report
|
|
16
16
|
* node Harness/scripts/wf-remove.mjs --apply --yes --purge-user-data --keep-tasks
|
|
17
17
|
* # remove Harness project facts, keep task records
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
import { readFileSync, writeFileSync, existsSync, unlinkSync, rmdirSync, readdirSync, lstatSync } from 'fs';
|
|
21
|
-
import { createHash } from 'crypto';
|
|
22
|
-
import { resolve, dirname, join, sep } from 'path';
|
|
23
|
-
import { fileURLToPath } from 'url';
|
|
24
|
-
import { createInterface } from 'readline';
|
|
25
|
-
|
|
19
|
+
|
|
20
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync, rmdirSync, readdirSync, lstatSync } from 'fs';
|
|
21
|
+
import { createHash } from 'crypto';
|
|
22
|
+
import { resolve, dirname, join, sep } from 'path';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
24
|
+
import { createInterface } from 'readline';
|
|
25
|
+
|
|
26
26
|
const SCRIPT_FILE = fileURLToPath(import.meta.url);
|
|
27
27
|
const __dirname = dirname(SCRIPT_FILE);
|
|
28
28
|
const SCRIPT_ROOT = resolve(__dirname, '..', '..');
|
|
29
|
-
const ROOT = process.env.WF_ROOT
|
|
30
|
-
? resolve(process.env.WF_ROOT)
|
|
31
|
-
: SCRIPT_ROOT;
|
|
32
|
-
const VERSION_FILE = resolve(ROOT, 'Harness', '.harness-version');
|
|
33
|
-
|
|
34
|
-
// ── Classification ─────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
/** Path prefixes that Harness framework is allowed to own. ANY file outside these is NEVER removed. */
|
|
29
|
+
const ROOT = process.env.WF_ROOT
|
|
30
|
+
? resolve(process.env.WF_ROOT)
|
|
31
|
+
: SCRIPT_ROOT;
|
|
32
|
+
const VERSION_FILE = resolve(ROOT, 'Harness', '.harness-version');
|
|
33
|
+
|
|
34
|
+
// ── Classification ─────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/** Path prefixes that Harness framework is allowed to own. ANY file outside these is NEVER removed. */
|
|
37
37
|
const HARNESS_PREFIXES = [
|
|
38
38
|
'.claude/',
|
|
39
39
|
'.agents/',
|
|
40
40
|
'.codex/',
|
|
41
|
+
'.opencode/',
|
|
42
|
+
'opencode.json',
|
|
41
43
|
'Harness/',
|
|
42
|
-
'CLAUDE.md',
|
|
43
|
-
'AGENTS.md',
|
|
44
|
-
'MEMORY.md',
|
|
45
|
-
'tests/.gitkeep',
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
/** Files the user owns �?NEVER remove. Consistent with wf-update-check PRESERVE_PATTERNS. */
|
|
44
|
+
'CLAUDE.md',
|
|
45
|
+
'AGENTS.md',
|
|
46
|
+
'MEMORY.md',
|
|
47
|
+
'tests/.gitkeep',
|
|
48
|
+
];
|
|
49
|
+
|
|
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\//,
|
|
52
54
|
/^Harness\/memory\//,
|
|
53
|
-
/^Harness\/research\/PRD\.md$/,
|
|
54
|
-
/^Harness\/research\/research-results\.md$/,
|
|
55
|
-
/^Harness\/architecture\.md$/,
|
|
56
|
-
/^Harness\/workflows\//,
|
|
57
|
-
/^Harness\/features\//,
|
|
58
|
-
/^Harness\/domain\//,
|
|
59
|
-
/^README\.md$/,
|
|
60
|
-
/^\.gitignore$/,
|
|
61
|
-
/^package\.json$/,
|
|
55
|
+
/^Harness\/research\/PRD\.md$/,
|
|
56
|
+
/^Harness\/research\/research-results\.md$/,
|
|
57
|
+
/^Harness\/architecture\.md$/,
|
|
58
|
+
/^Harness\/workflows\//,
|
|
59
|
+
/^Harness\/features\//,
|
|
60
|
+
/^Harness\/domain\//,
|
|
61
|
+
/^README\.md$/,
|
|
62
|
+
/^\.gitignore$/,
|
|
63
|
+
/^package\.json$/,
|
|
62
64
|
/^package-lock\.json$/,
|
|
63
65
|
];
|
|
64
66
|
|
|
@@ -114,6 +116,7 @@ const BUILT_IN_AGENT_NAMES = [
|
|
|
114
116
|
'implementer',
|
|
115
117
|
'memory-master',
|
|
116
118
|
'planner',
|
|
119
|
+
'reflector',
|
|
117
120
|
'researcher',
|
|
118
121
|
'review-manager',
|
|
119
122
|
'reviewer',
|
|
@@ -143,13 +146,18 @@ const BUILT_IN_SKILL_NAMES = [
|
|
|
143
146
|
];
|
|
144
147
|
|
|
145
148
|
const KNOWN_FRAMEWORK_FILES = new Set([
|
|
146
|
-
...BUILT_IN_AGENT_NAMES.
|
|
149
|
+
...BUILT_IN_AGENT_NAMES.flatMap(name => [
|
|
150
|
+
`.claude/agents/${name}.md`,
|
|
151
|
+
`.opencode/agents/${name}.md`,
|
|
152
|
+
]),
|
|
147
153
|
...BUILT_IN_SKILL_NAMES.flatMap(name => [
|
|
148
154
|
`.claude/skills/${name}/SKILL.md`,
|
|
149
155
|
`.agents/skills/${name}/SKILL.md`,
|
|
150
156
|
]),
|
|
151
157
|
'.claude/commands/wf-help.md',
|
|
158
|
+
'.opencode/commands/wf-help.md',
|
|
152
159
|
'.claude/rules/ecc/common.md',
|
|
160
|
+
'opencode.json',
|
|
153
161
|
]);
|
|
154
162
|
|
|
155
163
|
function isKnownFrameworkFile(file) {
|
|
@@ -158,16 +166,16 @@ function isKnownFrameworkFile(file) {
|
|
|
158
166
|
|
|
159
167
|
/** Directories to clean up if empty after file removal. */
|
|
160
168
|
const CLEANUP_DIRS = [
|
|
161
|
-
'.claude/agents',
|
|
169
|
+
'.claude/agents',
|
|
162
170
|
'.claude/skills/wf-auto',
|
|
163
171
|
'.claude/skills/wf-browser',
|
|
164
172
|
'.claude/skills/tdd',
|
|
165
173
|
'.claude/skills/wf',
|
|
166
174
|
'.claude/skills/wf-learn',
|
|
167
|
-
'.claude/skills/wf-max',
|
|
168
|
-
'.claude/skills/wf-readme',
|
|
169
|
-
'.claude/skills/wf-review',
|
|
170
|
-
'.claude/skills/wf-update',
|
|
175
|
+
'.claude/skills/wf-max',
|
|
176
|
+
'.claude/skills/wf-readme',
|
|
177
|
+
'.claude/skills/wf-review',
|
|
178
|
+
'.claude/skills/wf-update',
|
|
171
179
|
'.claude/skills/wf-remove',
|
|
172
180
|
'.claude/skills/subagent-orchestrator',
|
|
173
181
|
'.agents/skills/wf-auto',
|
|
@@ -184,34 +192,38 @@ const CLEANUP_DIRS = [
|
|
|
184
192
|
'.agents/skills',
|
|
185
193
|
'.agents',
|
|
186
194
|
'.codex',
|
|
195
|
+
'.opencode/commands',
|
|
196
|
+
'.opencode/agents',
|
|
197
|
+
'.opencode/skills',
|
|
198
|
+
'.opencode',
|
|
187
199
|
'.claude/rules/ecc',
|
|
188
|
-
'.claude/rules',
|
|
189
|
-
'Harness/scripts',
|
|
190
|
-
'Harness/research',
|
|
191
|
-
'Harness/workflows',
|
|
192
|
-
'Harness/domain',
|
|
193
|
-
'Harness/features',
|
|
194
|
-
'Harness/memory',
|
|
195
|
-
'Harness/tasks/_template',
|
|
196
|
-
'Harness/tasks/auto',
|
|
197
|
-
'Harness/tasks',
|
|
198
|
-
'Harness',
|
|
199
|
-
];
|
|
200
|
-
|
|
201
|
-
// ── Helpers ────────────────────────────────────────────────────────
|
|
202
|
-
|
|
203
|
-
/** Reject paths that escape ROOT (traversal, absolute, .., etc.). Sync with wf-update-check. */
|
|
204
|
-
function safePath(file) {
|
|
205
|
-
let normalized = file.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
206
|
-
if (normalized.includes('//')) return null; // double slash bypass
|
|
207
|
-
if (normalized.split('/').some(p => p === '..')) return null;
|
|
208
|
-
if (file.startsWith('/') || file.startsWith('\\')) return null;
|
|
209
|
-
if (normalized === '.' || normalized === '') return null;
|
|
210
|
-
const resolved = resolve(ROOT, normalized);
|
|
211
|
-
if (!resolved.startsWith(ROOT + sep) && resolved !== ROOT) return null;
|
|
212
|
-
return resolved;
|
|
213
|
-
}
|
|
214
|
-
|
|
200
|
+
'.claude/rules',
|
|
201
|
+
'Harness/scripts',
|
|
202
|
+
'Harness/research',
|
|
203
|
+
'Harness/workflows',
|
|
204
|
+
'Harness/domain',
|
|
205
|
+
'Harness/features',
|
|
206
|
+
'Harness/memory',
|
|
207
|
+
'Harness/tasks/_template',
|
|
208
|
+
'Harness/tasks/auto',
|
|
209
|
+
'Harness/tasks',
|
|
210
|
+
'Harness',
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/** Reject paths that escape ROOT (traversal, absolute, .., etc.). Sync with wf-update-check. */
|
|
216
|
+
function safePath(file) {
|
|
217
|
+
let normalized = file.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
218
|
+
if (normalized.includes('//')) return null; // double slash bypass
|
|
219
|
+
if (normalized.split('/').some(p => p === '..')) return null;
|
|
220
|
+
if (file.startsWith('/') || file.startsWith('\\')) return null;
|
|
221
|
+
if (normalized === '.' || normalized === '') return null;
|
|
222
|
+
const resolved = resolve(ROOT, normalized);
|
|
223
|
+
if (!resolved.startsWith(ROOT + sep) && resolved !== ROOT) return null;
|
|
224
|
+
return resolved;
|
|
225
|
+
}
|
|
226
|
+
|
|
215
227
|
function sha256File(path) {
|
|
216
228
|
if (!existsSync(path)) return null;
|
|
217
229
|
let content = readFileSync(path, 'utf-8');
|
|
@@ -288,34 +300,34 @@ function removePlannedFile(file) {
|
|
|
288
300
|
|
|
289
301
|
function removeEmptyDirs(startDir) {
|
|
290
302
|
if (!existsSync(startDir)) return;
|
|
291
|
-
try {
|
|
292
|
-
const entries = readdirSync(startDir);
|
|
293
|
-
for (const e of entries) {
|
|
294
|
-
const full = join(startDir, e);
|
|
295
|
-
if (lstatSync(full).isDirectory() && !lstatSync(full).isSymbolicLink()) removeEmptyDirs(full);
|
|
296
|
-
}
|
|
297
|
-
// After recursing, check if dir is now empty
|
|
298
|
-
const remaining = readdirSync(startDir);
|
|
299
|
-
if (remaining.length === 0) {
|
|
300
|
-
rmdirSync(startDir);
|
|
301
|
-
}
|
|
302
|
-
} catch (_) { /* permissions or already gone */ }
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async function askUser(question) {
|
|
306
|
-
if (!process.stdin.isTTY) {
|
|
307
|
-
console.log(' (non-interactive �?defaulting to KEEP)');
|
|
308
|
-
return 'k';
|
|
309
|
-
}
|
|
310
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
311
|
-
return new Promise(resolve => {
|
|
312
|
-
rl.question(question, answer => { rl.close(); resolve(answer.trim().toLowerCase()); });
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// ── Main ───────────────────────────────────────────────────────────
|
|
317
|
-
|
|
318
|
-
async function main() {
|
|
303
|
+
try {
|
|
304
|
+
const entries = readdirSync(startDir);
|
|
305
|
+
for (const e of entries) {
|
|
306
|
+
const full = join(startDir, e);
|
|
307
|
+
if (lstatSync(full).isDirectory() && !lstatSync(full).isSymbolicLink()) removeEmptyDirs(full);
|
|
308
|
+
}
|
|
309
|
+
// After recursing, check if dir is now empty
|
|
310
|
+
const remaining = readdirSync(startDir);
|
|
311
|
+
if (remaining.length === 0) {
|
|
312
|
+
rmdirSync(startDir);
|
|
313
|
+
}
|
|
314
|
+
} catch (_) { /* permissions or already gone */ }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function askUser(question) {
|
|
318
|
+
if (!process.stdin.isTTY) {
|
|
319
|
+
console.log(' (non-interactive �?defaulting to KEEP)');
|
|
320
|
+
return 'k';
|
|
321
|
+
}
|
|
322
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
323
|
+
return new Promise(resolve => {
|
|
324
|
+
rl.question(question, answer => { rl.close(); resolve(answer.trim().toLowerCase()); });
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ── Main ───────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
async function main() {
|
|
319
331
|
const args = process.argv.slice(2);
|
|
320
332
|
const apply = args.includes('--apply');
|
|
321
333
|
const yes = args.includes('--yes');
|
|
@@ -323,13 +335,13 @@ async function main() {
|
|
|
323
335
|
const purgeUserData = args.includes('--purge-user-data') || args.includes('--purge');
|
|
324
336
|
const keepTasks = args.includes('--keep-tasks');
|
|
325
337
|
const deleteModified = new Set(parseRepeatedFlag(args, '--delete-modified'));
|
|
326
|
-
|
|
327
|
-
// 1. Build file inventory
|
|
328
|
-
const localVersion = existsSync(VERSION_FILE)
|
|
329
|
-
? JSON.parse(readFileSync(VERSION_FILE, 'utf-8'))
|
|
330
|
-
: { checksums: {} };
|
|
331
|
-
const storedChecksums = localVersion.checksums || {};
|
|
332
|
-
|
|
338
|
+
|
|
339
|
+
// 1. Build file inventory
|
|
340
|
+
const localVersion = existsSync(VERSION_FILE)
|
|
341
|
+
? JSON.parse(readFileSync(VERSION_FILE, 'utf-8'))
|
|
342
|
+
: { checksums: {} };
|
|
343
|
+
const storedChecksums = localVersion.checksums || {};
|
|
344
|
+
|
|
333
345
|
// All files from checksums + known framework patterns
|
|
334
346
|
const allFiles = new Set(Object.keys(storedChecksums));
|
|
335
347
|
const scanIssues = [];
|
|
@@ -352,7 +364,7 @@ async function main() {
|
|
|
352
364
|
]) {
|
|
353
365
|
addFilesUnder(allFiles, dir, scanIssues);
|
|
354
366
|
}
|
|
355
|
-
|
|
367
|
+
|
|
356
368
|
// Add framework files that might not be in checksums (newer additions)
|
|
357
369
|
const extraPatterns = [
|
|
358
370
|
...KNOWN_FRAMEWORK_FILES,
|
|
@@ -372,27 +384,27 @@ async function main() {
|
|
|
372
384
|
'Harness/scripts/wf-update-check.mjs',
|
|
373
385
|
'Harness/scripts/wf-remove.mjs',
|
|
374
386
|
];
|
|
375
|
-
for (const f of extraPatterns) {
|
|
376
|
-
if (existsSync(resolve(ROOT, f))) allFiles.add(f);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// 2. Classify every file
|
|
380
|
-
const safe = []; // SAFE �?matches checksum, auto-remove
|
|
381
|
-
const modified = []; // MODIFIED �?user edited, must confirm
|
|
382
|
-
const user = []; // USER �?never remove
|
|
387
|
+
for (const f of extraPatterns) {
|
|
388
|
+
if (existsSync(resolve(ROOT, f))) allFiles.add(f);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 2. Classify every file
|
|
392
|
+
const safe = []; // SAFE �?matches checksum, auto-remove
|
|
393
|
+
const modified = []; // MODIFIED �?user edited, must confirm
|
|
394
|
+
const user = []; // USER �?never remove
|
|
383
395
|
const skipped = []; // File not on disk, traversal rejected, or framework-keep
|
|
384
396
|
const purge = []; // Explicitly requested Harness user-data removal
|
|
385
397
|
skipped.push(...scanIssues);
|
|
386
|
-
|
|
387
|
-
for (const file of [...allFiles].sort()) {
|
|
388
|
-
// Canonical normalization for classification
|
|
389
|
-
const canonical = file.replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
390
|
-
|
|
391
|
-
if (KEEP_FRAMEWORK.has(canonical)) {
|
|
392
|
-
skipped.push({ file, reason: 'framework keep' });
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
|
|
398
|
+
|
|
399
|
+
for (const file of [...allFiles].sort()) {
|
|
400
|
+
// Canonical normalization for classification
|
|
401
|
+
const canonical = file.replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
402
|
+
|
|
403
|
+
if (KEEP_FRAMEWORK.has(canonical)) {
|
|
404
|
+
skipped.push({ file, reason: 'framework keep' });
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
|
|
396
408
|
if (isUserData(canonical)) {
|
|
397
409
|
if (purgeUserData && isPurgeableHarnessData(canonical, { keepTasks })) {
|
|
398
410
|
purge.push({
|
|
@@ -401,64 +413,70 @@ async function main() {
|
|
|
401
413
|
});
|
|
402
414
|
continue;
|
|
403
415
|
}
|
|
404
|
-
user.push({ file, reason: 'user data �?NEVER removed' });
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Only allow deletion of files under harness-owned prefixes
|
|
409
|
-
if (!isHarnessOwned(canonical)) {
|
|
410
|
-
user.push({ file, reason: 'outside harness prefix �?NEVER removed' });
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const diskPath = safePath(file);
|
|
415
|
-
if (!diskPath) {
|
|
416
|
-
skipped.push({ file, reason: 'path traversal rejected' });
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
if (!existsSync(diskPath)) {
|
|
420
|
-
skipped.push({ file, reason: 'not on disk' });
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
416
|
+
user.push({ file, reason: 'user data �?NEVER removed' });
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Only allow deletion of files under harness-owned prefixes
|
|
421
|
+
if (!isHarnessOwned(canonical)) {
|
|
422
|
+
user.push({ file, reason: 'outside harness prefix �?NEVER removed' });
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const diskPath = safePath(file);
|
|
427
|
+
if (!diskPath) {
|
|
428
|
+
skipped.push({ file, reason: 'path traversal rejected' });
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (!existsSync(diskPath)) {
|
|
432
|
+
skipped.push({ file, reason: 'not on disk' });
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
423
435
|
|
|
424
436
|
const currentHash = sha256File(diskPath);
|
|
425
437
|
const storedHash = storedChecksums[file];
|
|
426
438
|
|
|
427
439
|
if (!storedHash) {
|
|
428
440
|
if (isKnownFrameworkFile(canonical)) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
storedHash:
|
|
433
|
-
|
|
434
|
-
|
|
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
|
+
}
|
|
435
453
|
} else {
|
|
436
454
|
modified.push({ file, currentHash, storedHash: 'none', reason: 'not in checksums' });
|
|
437
455
|
}
|
|
438
456
|
} else if (currentHash === storedHash) {
|
|
439
457
|
safe.push({ file, currentHash, storedHash });
|
|
440
458
|
} else {
|
|
441
|
-
modified.push({ file, currentHash, storedHash, reason: 'user modified' });
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
459
|
+
modified.push({ file, currentHash, storedHash, reason: 'user modified' });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
445
463
|
// Also check for .claude/settings.json (might have user permissions)
|
|
446
|
-
const settingsFile = resolve(ROOT, '.claude', 'settings.json');
|
|
447
|
-
if (existsSync(settingsFile)) {
|
|
448
|
-
const settingsHash = sha256File(settingsFile);
|
|
449
|
-
const storedSettingsHash = storedChecksums['.claude/settings.json'];
|
|
450
|
-
if (storedSettingsHash && settingsHash !== storedSettingsHash) {
|
|
451
|
-
// User modified settings �?move to modified
|
|
452
|
-
// Remove from safe if it was there
|
|
453
|
-
const idx = safe.findIndex(s => s.file === '.claude/settings.json');
|
|
454
|
-
if (idx >= 0) {
|
|
455
|
-
safe.splice(idx, 1);
|
|
464
|
+
const settingsFile = resolve(ROOT, '.claude', 'settings.json');
|
|
465
|
+
if (existsSync(settingsFile)) {
|
|
466
|
+
const settingsHash = sha256File(settingsFile);
|
|
467
|
+
const storedSettingsHash = storedChecksums['.claude/settings.json'];
|
|
468
|
+
if (storedSettingsHash && settingsHash !== storedSettingsHash) {
|
|
469
|
+
// User modified settings �?move to modified
|
|
470
|
+
// Remove from safe if it was there
|
|
471
|
+
const idx = safe.findIndex(s => s.file === '.claude/settings.json');
|
|
472
|
+
if (idx >= 0) {
|
|
473
|
+
safe.splice(idx, 1);
|
|
456
474
|
modified.push({ file: '.claude/settings.json', currentHash: settingsHash, storedHash: storedSettingsHash, reason: 'user modified settings (may contain permissions)' });
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// 3. Output plan
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 3. Output plan
|
|
462
480
|
if (jsonOut) {
|
|
463
481
|
console.log(JSON.stringify({
|
|
464
482
|
safe: safe.map(s => s.file),
|
|
@@ -491,29 +509,29 @@ async function main() {
|
|
|
491
509
|
}, null, 2));
|
|
492
510
|
return;
|
|
493
511
|
}
|
|
494
|
-
|
|
495
|
-
console.log('\n🧹 WF-REMOVE �?Harness framework removal plan\n');
|
|
496
|
-
|
|
497
|
-
if (safe.length > 0) {
|
|
498
|
-
console.log(`�?SAFE (${safe.length} files �?auto-remove, unmodified framework):`);
|
|
499
|
-
for (const s of safe) console.log(` �?${s.file}`);
|
|
500
|
-
console.log('');
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (modified.length > 0) {
|
|
504
|
-
console.log(`�?MODIFIED (${modified.length} files �?REQUIRE CONFIRMATION):`);
|
|
505
|
-
for (const m of modified) {
|
|
506
|
-
console.log(` ? ${m.file} [${m.reason}]`);
|
|
507
|
-
}
|
|
508
|
-
console.log('');
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (user.length > 0) {
|
|
512
|
-
console.log(`🔒 USER DATA (${user.length} files �?NEVER removed):`);
|
|
513
|
-
for (const u of user) console.log(` �?${u.file} [${u.reason}]`);
|
|
514
|
-
console.log('');
|
|
515
|
-
}
|
|
516
|
-
|
|
512
|
+
|
|
513
|
+
console.log('\n🧹 WF-REMOVE �?Harness framework removal plan\n');
|
|
514
|
+
|
|
515
|
+
if (safe.length > 0) {
|
|
516
|
+
console.log(`�?SAFE (${safe.length} files �?auto-remove, unmodified framework):`);
|
|
517
|
+
for (const s of safe) console.log(` �?${s.file}`);
|
|
518
|
+
console.log('');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (modified.length > 0) {
|
|
522
|
+
console.log(`�?MODIFIED (${modified.length} files �?REQUIRE CONFIRMATION):`);
|
|
523
|
+
for (const m of modified) {
|
|
524
|
+
console.log(` ? ${m.file} [${m.reason}]`);
|
|
525
|
+
}
|
|
526
|
+
console.log('');
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (user.length > 0) {
|
|
530
|
+
console.log(`🔒 USER DATA (${user.length} files �?NEVER removed):`);
|
|
531
|
+
for (const u of user) console.log(` �?${u.file} [${u.reason}]`);
|
|
532
|
+
console.log('');
|
|
533
|
+
}
|
|
534
|
+
|
|
517
535
|
if (purge.length > 0) {
|
|
518
536
|
console.log(`PURGE (${purge.length} files - explicit user-data cleanup):`);
|
|
519
537
|
for (const p of purge) console.log(` x ${p.file} [${p.reason}]`);
|
|
@@ -521,41 +539,41 @@ async function main() {
|
|
|
521
539
|
}
|
|
522
540
|
|
|
523
541
|
console.log(`Summary: ${safe.length} safe, ${modified.length} need confirm, ${purge.length} purge, ${user.length} preserved, ${skipped.length} skipped\n`);
|
|
524
|
-
|
|
525
|
-
if (!apply) {
|
|
526
|
-
console.log('DRY-RUN. Use --apply to execute the removal.\n');
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// 4. Apply �?remove SAFE files automatically (rehash before unlink)
|
|
542
|
+
|
|
543
|
+
if (!apply) {
|
|
544
|
+
console.log('DRY-RUN. Use --apply to execute the removal.\n');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 4. Apply �?remove SAFE files automatically (rehash before unlink)
|
|
531
549
|
let safeRemoved = 0;
|
|
532
550
|
let safeFailed = 0;
|
|
533
|
-
for (const s of safe) {
|
|
534
|
-
const diskPath = safePath(s.file);
|
|
535
|
-
if (!diskPath) { console.error(` �?Traversal rejected: ${s.file}`); safeFailed++; continue; }
|
|
551
|
+
for (const s of safe) {
|
|
552
|
+
const diskPath = safePath(s.file);
|
|
553
|
+
if (!diskPath) { console.error(` �?Traversal rejected: ${s.file}`); safeFailed++; continue; }
|
|
536
554
|
if (!existsSync(diskPath)) continue;
|
|
537
|
-
// Re-verify hash hasn't changed since classification
|
|
538
|
-
const currentHash = sha256File(diskPath);
|
|
539
|
-
if (currentHash !== s.storedHash) {
|
|
540
|
-
console.log(` �?Skipped (modified since classification): ${s.file}`);
|
|
555
|
+
// Re-verify hash hasn't changed since classification
|
|
556
|
+
const currentHash = sha256File(diskPath);
|
|
557
|
+
if (currentHash !== s.storedHash) {
|
|
558
|
+
console.log(` �?Skipped (modified since classification): ${s.file}`);
|
|
541
559
|
safeFailed++;
|
|
542
560
|
continue;
|
|
543
|
-
}
|
|
544
|
-
try {
|
|
545
|
-
unlinkSync(diskPath);
|
|
546
|
-
safeRemoved++;
|
|
547
|
-
} catch (e) {
|
|
548
|
-
console.error(` �?Failed to remove: ${s.file} �?${e.message}`);
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
unlinkSync(diskPath);
|
|
564
|
+
safeRemoved++;
|
|
565
|
+
} catch (e) {
|
|
566
|
+
console.error(` �?Failed to remove: ${s.file} �?${e.message}`);
|
|
549
567
|
safeFailed++;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
console.log(`�?Removed ${safeRemoved} safe files.`);
|
|
553
|
-
|
|
554
|
-
// 5. Handle MODIFIED files �?prompt user
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
console.log(`�?Removed ${safeRemoved} safe files.`);
|
|
571
|
+
|
|
572
|
+
// 5. Handle MODIFIED files �?prompt user
|
|
555
573
|
let modifiedRemoved = 0;
|
|
556
574
|
let modifiedKept = 0;
|
|
557
575
|
let modifiedFailed = 0;
|
|
558
|
-
|
|
576
|
+
|
|
559
577
|
for (const m of modified) {
|
|
560
578
|
const canonicalModified = m.file.replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
561
579
|
if (deleteModified.has(canonicalModified)) {
|
|
@@ -569,32 +587,32 @@ async function main() {
|
|
|
569
587
|
}
|
|
570
588
|
continue;
|
|
571
589
|
}
|
|
572
|
-
if (yes) {
|
|
573
|
-
// Non-interactive mode: skip all modified
|
|
574
|
-
console.log(` �?Skipped (modified): ${m.file}`);
|
|
575
|
-
modifiedKept++;
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
console.log(`\n─── ${m.file} ───`);
|
|
580
|
-
console.log(` Reason: ${m.reason}`);
|
|
581
|
-
console.log(` [D]elete [K]eep (default)`);
|
|
582
|
-
|
|
583
|
-
const answer = await askUser(' Choose [d/k]: ');
|
|
584
|
-
if (answer === 'd' || answer === 'delete') {
|
|
590
|
+
if (yes) {
|
|
591
|
+
// Non-interactive mode: skip all modified
|
|
592
|
+
console.log(` �?Skipped (modified): ${m.file}`);
|
|
593
|
+
modifiedKept++;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
console.log(`\n─── ${m.file} ───`);
|
|
598
|
+
console.log(` Reason: ${m.reason}`);
|
|
599
|
+
console.log(` [D]elete [K]eep (default)`);
|
|
600
|
+
|
|
601
|
+
const answer = await askUser(' Choose [d/k]: ');
|
|
602
|
+
if (answer === 'd' || answer === 'delete') {
|
|
585
603
|
const diskPath = safePath(m.file);
|
|
586
|
-
if (!diskPath) { console.error(` �?Traversal rejected: ${m.file}`); modifiedFailed++; continue; }
|
|
604
|
+
if (!diskPath) { console.error(` �?Traversal rejected: ${m.file}`); modifiedFailed++; continue; }
|
|
587
605
|
try {
|
|
588
606
|
unlinkSync(diskPath);
|
|
589
|
-
console.log(` �?Deleted: ${m.file}`);
|
|
607
|
+
console.log(` �?Deleted: ${m.file}`);
|
|
590
608
|
modifiedRemoved++;
|
|
591
609
|
} catch (e) {
|
|
592
|
-
console.error(` �?Failed: ${m.file} �?${e.message}`);
|
|
610
|
+
console.error(` �?Failed: ${m.file} �?${e.message}`);
|
|
593
611
|
modifiedFailed++;
|
|
594
612
|
}
|
|
595
|
-
} else {
|
|
596
|
-
console.log(` �?Kept: ${m.file}`);
|
|
597
|
-
modifiedKept++;
|
|
613
|
+
} else {
|
|
614
|
+
console.log(` �?Kept: ${m.file}`);
|
|
615
|
+
modifiedKept++;
|
|
598
616
|
}
|
|
599
617
|
}
|
|
600
618
|
|
|
@@ -629,14 +647,14 @@ async function main() {
|
|
|
629
647
|
}
|
|
630
648
|
|
|
631
649
|
// 6. Cleanup empty directories
|
|
632
|
-
for (const dir of CLEANUP_DIRS) {
|
|
633
|
-
removeEmptyDirs(resolve(ROOT, dir));
|
|
634
|
-
}
|
|
635
|
-
// Remove root Harness/ if empty
|
|
636
|
-
removeEmptyDirs(resolve(ROOT, 'Harness'));
|
|
637
|
-
// Remove root .claude/ if empty (but this is rare)
|
|
638
|
-
removeEmptyDirs(resolve(ROOT, '.claude'));
|
|
639
|
-
|
|
650
|
+
for (const dir of CLEANUP_DIRS) {
|
|
651
|
+
removeEmptyDirs(resolve(ROOT, dir));
|
|
652
|
+
}
|
|
653
|
+
// Remove root Harness/ if empty
|
|
654
|
+
removeEmptyDirs(resolve(ROOT, 'Harness'));
|
|
655
|
+
// Remove root .claude/ if empty (but this is rare)
|
|
656
|
+
removeEmptyDirs(resolve(ROOT, '.claude'));
|
|
657
|
+
|
|
640
658
|
// 7. Remove version file if everything is gone
|
|
641
659
|
if (existsSync(VERSION_FILE)) {
|
|
642
660
|
const harPath = resolve(ROOT, 'Harness');
|
|
@@ -654,46 +672,46 @@ async function main() {
|
|
|
654
672
|
}
|
|
655
673
|
} else if (!existsSync(harPath) || (existsSync(harPath) && readdirSync(harPath).filter(f => f !== '.harness-version').length === 0)) {
|
|
656
674
|
unlinkSync(VERSION_FILE);
|
|
657
|
-
console.log('�?Removed .harness-version (Harness directory empty).');
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// 7.5 Prune stale checksums for files that no longer exist on disk
|
|
662
|
-
if (existsSync(VERSION_FILE)) {
|
|
663
|
-
const versionData = JSON.parse(readFileSync(VERSION_FILE, 'utf-8'));
|
|
664
|
-
const checksums = versionData.checksums || {};
|
|
665
|
-
let pruned = 0;
|
|
666
|
-
for (const file of Object.keys(checksums)) {
|
|
667
|
-
const diskPath = safePath(file);
|
|
668
|
-
if (!diskPath || !existsSync(diskPath)) {
|
|
669
|
-
delete checksums[file];
|
|
670
|
-
pruned++;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
if (pruned > 0) {
|
|
674
|
-
versionData.checksums = checksums;
|
|
675
|
-
writeFileSync(VERSION_FILE, JSON.stringify(versionData, null, 2) + '\n', 'utf-8');
|
|
676
|
-
console.log(`�?Pruned ${pruned} stale checksum(s) from .harness-version.`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// 8. Remove CLAUDE.md harness section if it only has harness binding
|
|
681
|
-
const claudePath = resolve(ROOT, 'CLAUDE.md');
|
|
682
|
-
if (existsSync(claudePath)) {
|
|
683
|
-
const content = readFileSync(claudePath, 'utf-8');
|
|
684
|
-
if (content.includes('Harness contract') || content.includes('Harness Binding')) {
|
|
685
|
-
const answer = yes ? 'k' : await askUser('\nCLAUDE.md contains Harness binding section. Remove it? [D]elete harness section / [K]eep as-is (default): ');
|
|
686
|
-
if (answer === 'd' || answer === 'delete') {
|
|
687
|
-
// Strip the harness section: everything from "## 1. Harness Binding" to the next "## "
|
|
688
|
-
const cleaned = content.replace(/## 1\. Harness Binding[\s\S]*?(?=## 2\.)/, '');
|
|
689
|
-
writeFileSync(claudePath, cleaned, 'utf-8');
|
|
690
|
-
console.log('�?Stripped Harness binding section from CLAUDE.md.');
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
console.log(`\n🏁 Done. Safe: ${safeRemoved} removed. Modified: ${modifiedRemoved} removed, ${modifiedKept} kept.`);
|
|
696
|
-
console.log(' Run `git status` to review changes.\n');
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
main().catch(e => { console.error(e); process.exit(1); });
|
|
675
|
+
console.log('�?Removed .harness-version (Harness directory empty).');
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// 7.5 Prune stale checksums for files that no longer exist on disk
|
|
680
|
+
if (existsSync(VERSION_FILE)) {
|
|
681
|
+
const versionData = JSON.parse(readFileSync(VERSION_FILE, 'utf-8'));
|
|
682
|
+
const checksums = versionData.checksums || {};
|
|
683
|
+
let pruned = 0;
|
|
684
|
+
for (const file of Object.keys(checksums)) {
|
|
685
|
+
const diskPath = safePath(file);
|
|
686
|
+
if (!diskPath || !existsSync(diskPath)) {
|
|
687
|
+
delete checksums[file];
|
|
688
|
+
pruned++;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (pruned > 0) {
|
|
692
|
+
versionData.checksums = checksums;
|
|
693
|
+
writeFileSync(VERSION_FILE, JSON.stringify(versionData, null, 2) + '\n', 'utf-8');
|
|
694
|
+
console.log(`�?Pruned ${pruned} stale checksum(s) from .harness-version.`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// 8. Remove CLAUDE.md harness section if it only has harness binding
|
|
699
|
+
const claudePath = resolve(ROOT, 'CLAUDE.md');
|
|
700
|
+
if (existsSync(claudePath)) {
|
|
701
|
+
const content = readFileSync(claudePath, 'utf-8');
|
|
702
|
+
if (content.includes('Harness contract') || content.includes('Harness Binding')) {
|
|
703
|
+
const answer = yes ? 'k' : await askUser('\nCLAUDE.md contains Harness binding section. Remove it? [D]elete harness section / [K]eep as-is (default): ');
|
|
704
|
+
if (answer === 'd' || answer === 'delete') {
|
|
705
|
+
// Strip the harness section: everything from "## 1. Harness Binding" to the next "## "
|
|
706
|
+
const cleaned = content.replace(/## 1\. Harness Binding[\s\S]*?(?=## 2\.)/, '');
|
|
707
|
+
writeFileSync(claudePath, cleaned, 'utf-8');
|
|
708
|
+
console.log('�?Stripped Harness binding section from CLAUDE.md.');
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
console.log(`\n🏁 Done. Safe: ${safeRemoved} removed. Modified: ${modifiedRemoved} removed, ${modifiedKept} kept.`);
|
|
714
|
+
console.log(' Run `git status` to review changes.\n');
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
main().catch(e => { console.error(e); process.exit(1); });
|