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