create-harness-vibe-coding 0.8.5 → 0.8.7
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.md +20 -4
- package/package.json +1 -1
- package/src/generator.js +30 -11
- package/src/index.js +129 -5
- package/templates/common/.claude/agents/reflector.md +35 -0
- package/templates/common/.claude/agents/verifier.md +5 -3
- package/templates/common/.claude/commands/wf-help.md +1 -2
- 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 +59 -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-update/SKILL.md +9 -4
- package/templates/common/.codex/config.toml +5 -0
- package/templates/common/.harness-version +36 -34
- package/templates/common/AGENTS.md +26 -25
- package/templates/common/CLAUDE.md +10 -9
- package/templates/common/Harness/ACCEPTANCE_PROTOCOL.md +12 -4
- package/templates/common/Harness/README.md +12 -13
- package/templates/common/Harness/WF-AUTO-SPARK.md +18 -1
- package/templates/common/Harness/WF-AUTO.md +518 -492
- 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/dispatch.md +96 -95
- package/templates/common/Harness/extension.md +1 -1
- package/templates/common/Harness/subagents.md +78 -56
- package/templates/common/Harness/tasks/_template/ARTIFACTS.md +1 -1
- package/templates/common/Harness/tasks/_template/NOTES.md +1 -1
- 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 +26 -29
- package/templates/common/SETUP.md +29 -22
- package/templates/common/scripts/scan-clean.mjs +80 -41
- package/templates/common/scripts/validate-harness.mjs +101 -31
- package/templates/common/scripts/wf-remove.mjs +279 -278
- package/templates/common/scripts/wf-update-check.mjs +395 -195
- package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +1 -1
- package/templates/optional/skills/browser-e2e/Harness/workflows/browser-e2e.md +57 -21
|
@@ -1,64 +1,64 @@
|
|
|
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
41
|
'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. */
|
|
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. */
|
|
49
49
|
const USER_DATA_PATTERNS = [
|
|
50
50
|
/^Harness\/PROGRESS\.md$/,
|
|
51
51
|
/^Harness\/tasks\//,
|
|
52
52
|
/^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$/,
|
|
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$/,
|
|
62
62
|
/^package-lock\.json$/,
|
|
63
63
|
];
|
|
64
64
|
|
|
@@ -114,6 +114,7 @@ const BUILT_IN_AGENT_NAMES = [
|
|
|
114
114
|
'implementer',
|
|
115
115
|
'memory-master',
|
|
116
116
|
'planner',
|
|
117
|
+
'reflector',
|
|
117
118
|
'researcher',
|
|
118
119
|
'review-manager',
|
|
119
120
|
'reviewer',
|
|
@@ -158,16 +159,16 @@ function isKnownFrameworkFile(file) {
|
|
|
158
159
|
|
|
159
160
|
/** Directories to clean up if empty after file removal. */
|
|
160
161
|
const CLEANUP_DIRS = [
|
|
161
|
-
'.claude/agents',
|
|
162
|
+
'.claude/agents',
|
|
162
163
|
'.claude/skills/wf-auto',
|
|
163
164
|
'.claude/skills/wf-browser',
|
|
164
165
|
'.claude/skills/tdd',
|
|
165
166
|
'.claude/skills/wf',
|
|
166
167
|
'.claude/skills/wf-learn',
|
|
167
|
-
'.claude/skills/wf-max',
|
|
168
|
-
'.claude/skills/wf-readme',
|
|
169
|
-
'.claude/skills/wf-review',
|
|
170
|
-
'.claude/skills/wf-update',
|
|
168
|
+
'.claude/skills/wf-max',
|
|
169
|
+
'.claude/skills/wf-readme',
|
|
170
|
+
'.claude/skills/wf-review',
|
|
171
|
+
'.claude/skills/wf-update',
|
|
171
172
|
'.claude/skills/wf-remove',
|
|
172
173
|
'.claude/skills/subagent-orchestrator',
|
|
173
174
|
'.agents/skills/wf-auto',
|
|
@@ -185,33 +186,33 @@ const CLEANUP_DIRS = [
|
|
|
185
186
|
'.agents',
|
|
186
187
|
'.codex',
|
|
187
188
|
'.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
|
-
|
|
189
|
+
'.claude/rules',
|
|
190
|
+
'Harness/scripts',
|
|
191
|
+
'Harness/research',
|
|
192
|
+
'Harness/workflows',
|
|
193
|
+
'Harness/domain',
|
|
194
|
+
'Harness/features',
|
|
195
|
+
'Harness/memory',
|
|
196
|
+
'Harness/tasks/_template',
|
|
197
|
+
'Harness/tasks/auto',
|
|
198
|
+
'Harness/tasks',
|
|
199
|
+
'Harness',
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
/** Reject paths that escape ROOT (traversal, absolute, .., etc.). Sync with wf-update-check. */
|
|
205
|
+
function safePath(file) {
|
|
206
|
+
let normalized = file.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
207
|
+
if (normalized.includes('//')) return null; // double slash bypass
|
|
208
|
+
if (normalized.split('/').some(p => p === '..')) return null;
|
|
209
|
+
if (file.startsWith('/') || file.startsWith('\\')) return null;
|
|
210
|
+
if (normalized === '.' || normalized === '') return null;
|
|
211
|
+
const resolved = resolve(ROOT, normalized);
|
|
212
|
+
if (!resolved.startsWith(ROOT + sep) && resolved !== ROOT) return null;
|
|
213
|
+
return resolved;
|
|
214
|
+
}
|
|
215
|
+
|
|
215
216
|
function sha256File(path) {
|
|
216
217
|
if (!existsSync(path)) return null;
|
|
217
218
|
let content = readFileSync(path, 'utf-8');
|
|
@@ -288,34 +289,34 @@ function removePlannedFile(file) {
|
|
|
288
289
|
|
|
289
290
|
function removeEmptyDirs(startDir) {
|
|
290
291
|
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() {
|
|
292
|
+
try {
|
|
293
|
+
const entries = readdirSync(startDir);
|
|
294
|
+
for (const e of entries) {
|
|
295
|
+
const full = join(startDir, e);
|
|
296
|
+
if (lstatSync(full).isDirectory() && !lstatSync(full).isSymbolicLink()) removeEmptyDirs(full);
|
|
297
|
+
}
|
|
298
|
+
// After recursing, check if dir is now empty
|
|
299
|
+
const remaining = readdirSync(startDir);
|
|
300
|
+
if (remaining.length === 0) {
|
|
301
|
+
rmdirSync(startDir);
|
|
302
|
+
}
|
|
303
|
+
} catch (_) { /* permissions or already gone */ }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function askUser(question) {
|
|
307
|
+
if (!process.stdin.isTTY) {
|
|
308
|
+
console.log(' (non-interactive �?defaulting to KEEP)');
|
|
309
|
+
return 'k';
|
|
310
|
+
}
|
|
311
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
312
|
+
return new Promise(resolve => {
|
|
313
|
+
rl.question(question, answer => { rl.close(); resolve(answer.trim().toLowerCase()); });
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ── Main ───────────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
async function main() {
|
|
319
320
|
const args = process.argv.slice(2);
|
|
320
321
|
const apply = args.includes('--apply');
|
|
321
322
|
const yes = args.includes('--yes');
|
|
@@ -323,13 +324,13 @@ async function main() {
|
|
|
323
324
|
const purgeUserData = args.includes('--purge-user-data') || args.includes('--purge');
|
|
324
325
|
const keepTasks = args.includes('--keep-tasks');
|
|
325
326
|
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
|
-
|
|
327
|
+
|
|
328
|
+
// 1. Build file inventory
|
|
329
|
+
const localVersion = existsSync(VERSION_FILE)
|
|
330
|
+
? JSON.parse(readFileSync(VERSION_FILE, 'utf-8'))
|
|
331
|
+
: { checksums: {} };
|
|
332
|
+
const storedChecksums = localVersion.checksums || {};
|
|
333
|
+
|
|
333
334
|
// All files from checksums + known framework patterns
|
|
334
335
|
const allFiles = new Set(Object.keys(storedChecksums));
|
|
335
336
|
const scanIssues = [];
|
|
@@ -352,7 +353,7 @@ async function main() {
|
|
|
352
353
|
]) {
|
|
353
354
|
addFilesUnder(allFiles, dir, scanIssues);
|
|
354
355
|
}
|
|
355
|
-
|
|
356
|
+
|
|
356
357
|
// Add framework files that might not be in checksums (newer additions)
|
|
357
358
|
const extraPatterns = [
|
|
358
359
|
...KNOWN_FRAMEWORK_FILES,
|
|
@@ -372,27 +373,27 @@ async function main() {
|
|
|
372
373
|
'Harness/scripts/wf-update-check.mjs',
|
|
373
374
|
'Harness/scripts/wf-remove.mjs',
|
|
374
375
|
];
|
|
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
|
|
376
|
+
for (const f of extraPatterns) {
|
|
377
|
+
if (existsSync(resolve(ROOT, f))) allFiles.add(f);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// 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
|
|
383
384
|
const skipped = []; // File not on disk, traversal rejected, or framework-keep
|
|
384
385
|
const purge = []; // Explicitly requested Harness user-data removal
|
|
385
386
|
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
|
-
|
|
387
|
+
|
|
388
|
+
for (const file of [...allFiles].sort()) {
|
|
389
|
+
// Canonical normalization for classification
|
|
390
|
+
const canonical = file.replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
391
|
+
|
|
392
|
+
if (KEEP_FRAMEWORK.has(canonical)) {
|
|
393
|
+
skipped.push({ file, reason: 'framework keep' });
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
|
|
396
397
|
if (isUserData(canonical)) {
|
|
397
398
|
if (purgeUserData && isPurgeableHarnessData(canonical, { keepTasks })) {
|
|
398
399
|
purge.push({
|
|
@@ -401,25 +402,25 @@ async function main() {
|
|
|
401
402
|
});
|
|
402
403
|
continue;
|
|
403
404
|
}
|
|
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
|
-
}
|
|
405
|
+
user.push({ file, reason: 'user data �?NEVER removed' });
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Only allow deletion of files under harness-owned prefixes
|
|
410
|
+
if (!isHarnessOwned(canonical)) {
|
|
411
|
+
user.push({ file, reason: 'outside harness prefix �?NEVER removed' });
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const diskPath = safePath(file);
|
|
416
|
+
if (!diskPath) {
|
|
417
|
+
skipped.push({ file, reason: 'path traversal rejected' });
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (!existsSync(diskPath)) {
|
|
421
|
+
skipped.push({ file, reason: 'not on disk' });
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
423
424
|
|
|
424
425
|
const currentHash = sha256File(diskPath);
|
|
425
426
|
const storedHash = storedChecksums[file];
|
|
@@ -438,27 +439,27 @@ async function main() {
|
|
|
438
439
|
} else if (currentHash === storedHash) {
|
|
439
440
|
safe.push({ file, currentHash, storedHash });
|
|
440
441
|
} else {
|
|
441
|
-
modified.push({ file, currentHash, storedHash, reason: 'user modified' });
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
442
|
+
modified.push({ file, currentHash, storedHash, reason: 'user modified' });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
445
446
|
// 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);
|
|
447
|
+
const settingsFile = resolve(ROOT, '.claude', 'settings.json');
|
|
448
|
+
if (existsSync(settingsFile)) {
|
|
449
|
+
const settingsHash = sha256File(settingsFile);
|
|
450
|
+
const storedSettingsHash = storedChecksums['.claude/settings.json'];
|
|
451
|
+
if (storedSettingsHash && settingsHash !== storedSettingsHash) {
|
|
452
|
+
// User modified settings �?move to modified
|
|
453
|
+
// Remove from safe if it was there
|
|
454
|
+
const idx = safe.findIndex(s => s.file === '.claude/settings.json');
|
|
455
|
+
if (idx >= 0) {
|
|
456
|
+
safe.splice(idx, 1);
|
|
456
457
|
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
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 3. Output plan
|
|
462
463
|
if (jsonOut) {
|
|
463
464
|
console.log(JSON.stringify({
|
|
464
465
|
safe: safe.map(s => s.file),
|
|
@@ -491,29 +492,29 @@ async function main() {
|
|
|
491
492
|
}, null, 2));
|
|
492
493
|
return;
|
|
493
494
|
}
|
|
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
|
-
|
|
495
|
+
|
|
496
|
+
console.log('\n🧹 WF-REMOVE �?Harness framework removal plan\n');
|
|
497
|
+
|
|
498
|
+
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}`);
|
|
501
|
+
console.log('');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (modified.length > 0) {
|
|
505
|
+
console.log(`�?MODIFIED (${modified.length} files �?REQUIRE CONFIRMATION):`);
|
|
506
|
+
for (const m of modified) {
|
|
507
|
+
console.log(` ? ${m.file} [${m.reason}]`);
|
|
508
|
+
}
|
|
509
|
+
console.log('');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
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}]`);
|
|
515
|
+
console.log('');
|
|
516
|
+
}
|
|
517
|
+
|
|
517
518
|
if (purge.length > 0) {
|
|
518
519
|
console.log(`PURGE (${purge.length} files - explicit user-data cleanup):`);
|
|
519
520
|
for (const p of purge) console.log(` x ${p.file} [${p.reason}]`);
|
|
@@ -521,41 +522,41 @@ async function main() {
|
|
|
521
522
|
}
|
|
522
523
|
|
|
523
524
|
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)
|
|
525
|
+
|
|
526
|
+
if (!apply) {
|
|
527
|
+
console.log('DRY-RUN. Use --apply to execute the removal.\n');
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// 4. Apply �?remove SAFE files automatically (rehash before unlink)
|
|
531
532
|
let safeRemoved = 0;
|
|
532
533
|
let safeFailed = 0;
|
|
533
|
-
for (const s of safe) {
|
|
534
|
-
const diskPath = safePath(s.file);
|
|
534
|
+
for (const s of safe) {
|
|
535
|
+
const diskPath = safePath(s.file);
|
|
535
536
|
if (!diskPath) { console.error(` �?Traversal rejected: ${s.file}`); safeFailed++; continue; }
|
|
536
537
|
if (!existsSync(diskPath)) continue;
|
|
537
|
-
// Re-verify hash hasn't changed since classification
|
|
538
|
-
const currentHash = sha256File(diskPath);
|
|
539
|
-
if (currentHash !== s.storedHash) {
|
|
538
|
+
// Re-verify hash hasn't changed since classification
|
|
539
|
+
const currentHash = sha256File(diskPath);
|
|
540
|
+
if (currentHash !== s.storedHash) {
|
|
540
541
|
console.log(` �?Skipped (modified since classification): ${s.file}`);
|
|
541
542
|
safeFailed++;
|
|
542
543
|
continue;
|
|
543
|
-
}
|
|
544
|
-
try {
|
|
545
|
-
unlinkSync(diskPath);
|
|
546
|
-
safeRemoved++;
|
|
547
|
-
} catch (e) {
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
unlinkSync(diskPath);
|
|
547
|
+
safeRemoved++;
|
|
548
|
+
} catch (e) {
|
|
548
549
|
console.error(` �?Failed to remove: ${s.file} �?${e.message}`);
|
|
549
550
|
safeFailed++;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
console.log(`�?Removed ${safeRemoved} safe files.`);
|
|
553
|
-
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
console.log(`�?Removed ${safeRemoved} safe files.`);
|
|
554
|
+
|
|
554
555
|
// 5. Handle MODIFIED files �?prompt user
|
|
555
556
|
let modifiedRemoved = 0;
|
|
556
557
|
let modifiedKept = 0;
|
|
557
558
|
let modifiedFailed = 0;
|
|
558
|
-
|
|
559
|
+
|
|
559
560
|
for (const m of modified) {
|
|
560
561
|
const canonicalModified = m.file.replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
561
562
|
if (deleteModified.has(canonicalModified)) {
|
|
@@ -569,19 +570,19 @@ async function main() {
|
|
|
569
570
|
}
|
|
570
571
|
continue;
|
|
571
572
|
}
|
|
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') {
|
|
573
|
+
if (yes) {
|
|
574
|
+
// Non-interactive mode: skip all modified
|
|
575
|
+
console.log(` �?Skipped (modified): ${m.file}`);
|
|
576
|
+
modifiedKept++;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
console.log(`\n─── ${m.file} ───`);
|
|
581
|
+
console.log(` Reason: ${m.reason}`);
|
|
582
|
+
console.log(` [D]elete [K]eep (default)`);
|
|
583
|
+
|
|
584
|
+
const answer = await askUser(' Choose [d/k]: ');
|
|
585
|
+
if (answer === 'd' || answer === 'delete') {
|
|
585
586
|
const diskPath = safePath(m.file);
|
|
586
587
|
if (!diskPath) { console.error(` �?Traversal rejected: ${m.file}`); modifiedFailed++; continue; }
|
|
587
588
|
try {
|
|
@@ -592,9 +593,9 @@ async function main() {
|
|
|
592
593
|
console.error(` �?Failed: ${m.file} �?${e.message}`);
|
|
593
594
|
modifiedFailed++;
|
|
594
595
|
}
|
|
595
|
-
} else {
|
|
596
|
-
console.log(` �?Kept: ${m.file}`);
|
|
597
|
-
modifiedKept++;
|
|
596
|
+
} else {
|
|
597
|
+
console.log(` �?Kept: ${m.file}`);
|
|
598
|
+
modifiedKept++;
|
|
598
599
|
}
|
|
599
600
|
}
|
|
600
601
|
|
|
@@ -629,14 +630,14 @@ async function main() {
|
|
|
629
630
|
}
|
|
630
631
|
|
|
631
632
|
// 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
|
-
|
|
633
|
+
for (const dir of CLEANUP_DIRS) {
|
|
634
|
+
removeEmptyDirs(resolve(ROOT, dir));
|
|
635
|
+
}
|
|
636
|
+
// Remove root Harness/ if empty
|
|
637
|
+
removeEmptyDirs(resolve(ROOT, 'Harness'));
|
|
638
|
+
// Remove root .claude/ if empty (but this is rare)
|
|
639
|
+
removeEmptyDirs(resolve(ROOT, '.claude'));
|
|
640
|
+
|
|
640
641
|
// 7. Remove version file if everything is gone
|
|
641
642
|
if (existsSync(VERSION_FILE)) {
|
|
642
643
|
const harPath = resolve(ROOT, 'Harness');
|
|
@@ -655,45 +656,45 @@ async function main() {
|
|
|
655
656
|
} else if (!existsSync(harPath) || (existsSync(harPath) && readdirSync(harPath).filter(f => f !== '.harness-version').length === 0)) {
|
|
656
657
|
unlinkSync(VERSION_FILE);
|
|
657
658
|
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); });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// 7.5 Prune stale checksums for files that no longer exist on disk
|
|
663
|
+
if (existsSync(VERSION_FILE)) {
|
|
664
|
+
const versionData = JSON.parse(readFileSync(VERSION_FILE, 'utf-8'));
|
|
665
|
+
const checksums = versionData.checksums || {};
|
|
666
|
+
let pruned = 0;
|
|
667
|
+
for (const file of Object.keys(checksums)) {
|
|
668
|
+
const diskPath = safePath(file);
|
|
669
|
+
if (!diskPath || !existsSync(diskPath)) {
|
|
670
|
+
delete checksums[file];
|
|
671
|
+
pruned++;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (pruned > 0) {
|
|
675
|
+
versionData.checksums = checksums;
|
|
676
|
+
writeFileSync(VERSION_FILE, JSON.stringify(versionData, null, 2) + '\n', 'utf-8');
|
|
677
|
+
console.log(`�?Pruned ${pruned} stale checksum(s) from .harness-version.`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// 8. Remove CLAUDE.md harness section if it only has harness binding
|
|
682
|
+
const claudePath = resolve(ROOT, 'CLAUDE.md');
|
|
683
|
+
if (existsSync(claudePath)) {
|
|
684
|
+
const content = readFileSync(claudePath, 'utf-8');
|
|
685
|
+
if (content.includes('Harness contract') || content.includes('Harness Binding')) {
|
|
686
|
+
const answer = yes ? 'k' : await askUser('\nCLAUDE.md contains Harness binding section. Remove it? [D]elete harness section / [K]eep as-is (default): ');
|
|
687
|
+
if (answer === 'd' || answer === 'delete') {
|
|
688
|
+
// Strip the harness section: everything from "## 1. Harness Binding" to the next "## "
|
|
689
|
+
const cleaned = content.replace(/## 1\. Harness Binding[\s\S]*?(?=## 2\.)/, '');
|
|
690
|
+
writeFileSync(claudePath, cleaned, 'utf-8');
|
|
691
|
+
console.log('�?Stripped Harness binding section from CLAUDE.md.');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
console.log(`\n🏁 Done. Safe: ${safeRemoved} removed. Modified: ${modifiedRemoved} removed, ${modifiedKept} kept.`);
|
|
697
|
+
console.log(' Run `git status` to review changes.\n');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
main().catch(e => { console.error(e); process.exit(1); });
|