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