harness-evolver 0.5.1 → 0.6.0
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/LICENSE +1 -1
- package/bin/install.js +93 -79
- package/package.json +2 -2
- package/skills/compare/SKILL.md +1 -1
- package/skills/deploy/SKILL.md +1 -1
- package/skills/diagnose/SKILL.md +1 -1
- package/skills/evolve/SKILL.md +1 -1
- package/skills/init/SKILL.md +1 -1
- package/skills/status/SKILL.md +1 -1
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 Raphael Valdetaro
|
|
3
|
+
Copyright (c) 2026 Raphael Valdetaro
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/bin/install.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Harness Evolver installer.
|
|
4
|
-
*
|
|
4
|
+
* Copies plugin to Claude Code plugin cache and registers it.
|
|
5
5
|
*
|
|
6
6
|
* Usage: npx harness-evolver@latest
|
|
7
7
|
*/
|
|
@@ -15,7 +15,6 @@ const VERSION = require("../package.json").version;
|
|
|
15
15
|
const PLUGIN_ROOT = path.resolve(__dirname, "..");
|
|
16
16
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
17
17
|
|
|
18
|
-
// ANSI colors
|
|
19
18
|
const MAGENTA = "\x1b[35m";
|
|
20
19
|
const BRIGHT_MAGENTA = "\x1b[95m";
|
|
21
20
|
const GREEN = "\x1b[32m";
|
|
@@ -40,13 +39,6 @@ ${BRIGHT_MAGENTA} ██╗ ██╗ █████╗ ██████╗
|
|
|
40
39
|
╚══════╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝${RESET}
|
|
41
40
|
`;
|
|
42
41
|
|
|
43
|
-
const RUNTIMES = [
|
|
44
|
-
{ name: "Claude Code", dir: ".claude", detected: () => fs.existsSync(path.join(HOME, ".claude")) },
|
|
45
|
-
{ name: "Cursor", dir: ".cursor", detected: () => fs.existsSync(path.join(HOME, ".cursor")) },
|
|
46
|
-
{ name: "Codex", dir: ".codex", detected: () => fs.existsSync(path.join(HOME, ".codex")) },
|
|
47
|
-
{ name: "Windsurf", dir: ".windsurf", detected: () => fs.existsSync(path.join(HOME, ".windsurf")) },
|
|
48
|
-
];
|
|
49
|
-
|
|
50
42
|
function ask(rl, question) {
|
|
51
43
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
52
44
|
}
|
|
@@ -57,6 +49,7 @@ function copyDir(src, dest) {
|
|
|
57
49
|
const srcPath = path.join(src, entry.name);
|
|
58
50
|
const destPath = path.join(dest, entry.name);
|
|
59
51
|
if (entry.isDirectory()) {
|
|
52
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "__pycache__" || entry.name === "tests" || entry.name === "docs") continue;
|
|
60
53
|
copyDir(srcPath, destPath);
|
|
61
54
|
} else {
|
|
62
55
|
fs.copyFileSync(srcPath, destPath);
|
|
@@ -64,11 +57,6 @@ function copyDir(src, dest) {
|
|
|
64
57
|
}
|
|
65
58
|
}
|
|
66
59
|
|
|
67
|
-
function copyFile(src, dest) {
|
|
68
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
69
|
-
fs.copyFileSync(src, dest);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
60
|
function checkPython() {
|
|
73
61
|
try {
|
|
74
62
|
execSync("python3 --version", { stdio: "pipe" });
|
|
@@ -78,47 +66,80 @@ function checkPython() {
|
|
|
78
66
|
}
|
|
79
67
|
}
|
|
80
68
|
|
|
81
|
-
function
|
|
69
|
+
function readJSON(filepath) {
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(fs.readFileSync(filepath, "utf8"));
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function writeJSON(filepath, data) {
|
|
78
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
79
|
+
fs.writeFileSync(filepath, JSON.stringify(data, null, 2) + "\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function installPlugin(runtimeDir, scope) {
|
|
82
83
|
const baseDir = scope === "local"
|
|
83
84
|
? path.join(process.cwd(), runtimeDir)
|
|
84
85
|
: path.join(HOME, runtimeDir);
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
87
|
+
// 1. Copy plugin to cache
|
|
88
|
+
const cacheDir = path.join(baseDir, "plugins", "cache", "local", "harness-evolver", VERSION);
|
|
89
|
+
console.log(` Copying plugin to ${scope === "local" ? "." : "~"}/${runtimeDir}/plugins/cache/...`);
|
|
90
|
+
copyDir(PLUGIN_ROOT, cacheDir);
|
|
91
|
+
console.log(` ${GREEN}✓${RESET} Plugin files copied`);
|
|
92
|
+
|
|
93
|
+
// 2. Register in installed_plugins.json
|
|
94
|
+
const installedPath = path.join(baseDir, "plugins", "installed_plugins.json");
|
|
95
|
+
let installed = readJSON(installedPath) || { version: 2, plugins: {} };
|
|
96
|
+
if (!installed.plugins) installed.plugins = {};
|
|
97
|
+
|
|
98
|
+
installed.plugins["harness-evolver@local"] = [{
|
|
99
|
+
scope: "user",
|
|
100
|
+
installPath: cacheDir,
|
|
101
|
+
version: VERSION,
|
|
102
|
+
installedAt: new Date().toISOString(),
|
|
103
|
+
lastUpdated: new Date().toISOString(),
|
|
104
|
+
}];
|
|
105
|
+
writeJSON(installedPath, installed);
|
|
106
|
+
console.log(` ${GREEN}✓${RESET} Registered in installed_plugins.json`);
|
|
107
|
+
|
|
108
|
+
// 3. Enable in settings.json
|
|
109
|
+
const settingsPath = path.join(baseDir, "settings.json");
|
|
110
|
+
let settings = readJSON(settingsPath) || {};
|
|
111
|
+
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
112
|
+
settings.enabledPlugins["harness-evolver@local"] = true;
|
|
113
|
+
writeJSON(settingsPath, settings);
|
|
114
|
+
console.log(` ${GREEN}✓${RESET} Enabled in settings.json`);
|
|
115
|
+
|
|
116
|
+
// Count installed items
|
|
117
|
+
const skillCount = fs.existsSync(path.join(cacheDir, "skills"))
|
|
118
|
+
? fs.readdirSync(path.join(cacheDir, "skills")).filter(f =>
|
|
119
|
+
fs.statSync(path.join(cacheDir, "skills", f)).isDirectory()
|
|
120
|
+
).length
|
|
121
|
+
: 0;
|
|
122
|
+
const agentCount = fs.existsSync(path.join(cacheDir, "agents"))
|
|
123
|
+
? fs.readdirSync(path.join(cacheDir, "agents")).length
|
|
124
|
+
: 0;
|
|
125
|
+
const toolCount = fs.existsSync(path.join(cacheDir, "tools"))
|
|
126
|
+
? fs.readdirSync(path.join(cacheDir, "tools")).filter(f => f.endsWith(".py")).length
|
|
127
|
+
: 0;
|
|
128
|
+
|
|
129
|
+
console.log(` ${GREEN}✓${RESET} ${skillCount} skills, ${agentCount} agent, ${toolCount} tools`);
|
|
109
130
|
}
|
|
110
131
|
|
|
111
|
-
function
|
|
132
|
+
function installToolsGlobal() {
|
|
112
133
|
const toolsDir = path.join(HOME, ".harness-evolver", "tools");
|
|
113
134
|
const toolsSource = path.join(PLUGIN_ROOT, "tools");
|
|
114
135
|
if (fs.existsSync(toolsSource)) {
|
|
115
136
|
fs.mkdirSync(toolsDir, { recursive: true });
|
|
116
137
|
for (const tool of fs.readdirSync(toolsSource)) {
|
|
117
138
|
if (tool.endsWith(".py")) {
|
|
118
|
-
|
|
119
|
-
console.log(` ${GREEN}✓${RESET} Installed tool: ${tool}`);
|
|
139
|
+
fs.copyFileSync(path.join(toolsSource, tool), path.join(toolsDir, tool));
|
|
120
140
|
}
|
|
121
141
|
}
|
|
142
|
+
console.log(` ${GREEN}✓${RESET} Tools copied to ~/.harness-evolver/tools/`);
|
|
122
143
|
}
|
|
123
144
|
}
|
|
124
145
|
|
|
@@ -127,7 +148,7 @@ function installExamples() {
|
|
|
127
148
|
const examplesSource = path.join(PLUGIN_ROOT, "examples");
|
|
128
149
|
if (fs.existsSync(examplesSource)) {
|
|
129
150
|
copyDir(examplesSource, examplesDir);
|
|
130
|
-
console.log(` ${GREEN}✓${RESET}
|
|
151
|
+
console.log(` ${GREEN}✓${RESET} Examples copied to ~/.harness-evolver/examples/`);
|
|
131
152
|
}
|
|
132
153
|
}
|
|
133
154
|
|
|
@@ -137,7 +158,6 @@ async function main() {
|
|
|
137
158
|
console.log(` ${DIM}Meta-Harness-style autonomous harness optimization${RESET}`);
|
|
138
159
|
console.log();
|
|
139
160
|
|
|
140
|
-
// Check python
|
|
141
161
|
if (!checkPython()) {
|
|
142
162
|
console.error(` ${RED}ERROR:${RESET} python3 not found in PATH. Install Python 3.8+ first.`);
|
|
143
163
|
process.exit(1);
|
|
@@ -145,8 +165,14 @@ async function main() {
|
|
|
145
165
|
console.log(` ${GREEN}✓${RESET} python3 found`);
|
|
146
166
|
|
|
147
167
|
// Detect runtimes
|
|
148
|
-
const
|
|
149
|
-
|
|
168
|
+
const RUNTIMES = [
|
|
169
|
+
{ name: "Claude Code", dir: ".claude" },
|
|
170
|
+
{ name: "Cursor", dir: ".cursor" },
|
|
171
|
+
{ name: "Codex", dir: ".codex" },
|
|
172
|
+
{ name: "Windsurf", dir: ".windsurf" },
|
|
173
|
+
].filter(r => fs.existsSync(path.join(HOME, r.dir)));
|
|
174
|
+
|
|
175
|
+
if (RUNTIMES.length === 0) {
|
|
150
176
|
console.error(`\n ${RED}ERROR:${RESET} No supported runtime detected.`);
|
|
151
177
|
console.error(` Install Claude Code, Cursor, Codex, or Windsurf first.`);
|
|
152
178
|
process.exit(1);
|
|
@@ -156,59 +182,49 @@ async function main() {
|
|
|
156
182
|
|
|
157
183
|
// Runtime selection
|
|
158
184
|
console.log(`\n ${YELLOW}Which runtime(s) would you like to install for?${RESET}\n`);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
162
|
-
if (available.length > 1) {
|
|
163
|
-
console.log(` ${available.length + 1}) All`);
|
|
185
|
+
RUNTIMES.forEach((r, i) => console.log(` ${i + 1}) ${r.name.padEnd(14)} (~/${r.dir})`));
|
|
186
|
+
if (RUNTIMES.length > 1) {
|
|
187
|
+
console.log(` ${RUNTIMES.length + 1}) All`);
|
|
164
188
|
console.log(`\n ${DIM}Select multiple: 1,2 or 1 2${RESET}`);
|
|
165
189
|
}
|
|
166
190
|
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const runtimeInput = (runtimeAnswer.trim() || defaultChoice);
|
|
191
|
+
const runtimeAnswer = await ask(rl, `\n ${YELLOW}Choice [1]:${RESET} `);
|
|
192
|
+
const runtimeInput = (runtimeAnswer.trim() || "1");
|
|
170
193
|
|
|
171
|
-
let
|
|
172
|
-
if (runtimeInput === String(
|
|
173
|
-
|
|
194
|
+
let selected;
|
|
195
|
+
if (runtimeInput === String(RUNTIMES.length + 1)) {
|
|
196
|
+
selected = RUNTIMES;
|
|
174
197
|
} else {
|
|
175
|
-
const indices = runtimeInput.split(/[,\s]+/).map(
|
|
176
|
-
|
|
177
|
-
.filter((i) => i >= 0 && i < available.length)
|
|
178
|
-
.map((i) => available[i]);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (selectedRuntimes.length === 0) {
|
|
182
|
-
selectedRuntimes = [available[0]];
|
|
198
|
+
const indices = runtimeInput.split(/[,\s]+/).map(s => parseInt(s, 10) - 1);
|
|
199
|
+
selected = indices.filter(i => i >= 0 && i < RUNTIMES.length).map(i => RUNTIMES[i]);
|
|
183
200
|
}
|
|
201
|
+
if (selected.length === 0) selected = [RUNTIMES[0]];
|
|
184
202
|
|
|
185
203
|
// Scope selection
|
|
186
204
|
console.log(`\n ${YELLOW}Where would you like to install?${RESET}\n`);
|
|
187
|
-
console.log(` 1) Global (~/${
|
|
188
|
-
console.log(` 2) Local (./${
|
|
205
|
+
console.log(` 1) Global (~/${selected[0].dir}) - available in all projects`);
|
|
206
|
+
console.log(` 2) Local (./${selected[0].dir}) - this project only`);
|
|
189
207
|
|
|
190
208
|
const scopeAnswer = await ask(rl, `\n ${YELLOW}Choice [1]:${RESET} `);
|
|
191
209
|
const scope = (scopeAnswer.trim() === "2") ? "local" : "global";
|
|
192
210
|
|
|
193
211
|
console.log();
|
|
194
212
|
|
|
195
|
-
// Install
|
|
196
|
-
for (const runtime of
|
|
197
|
-
|
|
198
|
-
|
|
213
|
+
// Install
|
|
214
|
+
for (const runtime of selected) {
|
|
215
|
+
console.log(` Installing for ${BRIGHT_MAGENTA}${runtime.name}${RESET}\n`);
|
|
216
|
+
installPlugin(runtime.dir, scope);
|
|
199
217
|
console.log();
|
|
200
|
-
installForRuntime(runtime.dir, scope);
|
|
201
218
|
}
|
|
202
219
|
|
|
203
|
-
|
|
204
|
-
installTools();
|
|
220
|
+
installToolsGlobal();
|
|
205
221
|
installExamples();
|
|
206
222
|
|
|
207
|
-
//
|
|
223
|
+
// Version marker
|
|
208
224
|
const versionPath = path.join(HOME, ".harness-evolver", "VERSION");
|
|
209
225
|
fs.mkdirSync(path.dirname(versionPath), { recursive: true });
|
|
210
226
|
fs.writeFileSync(versionPath, VERSION);
|
|
211
|
-
console.log(` ${GREEN}✓${RESET}
|
|
227
|
+
console.log(` ${GREEN}✓${RESET} VERSION ${VERSION}`);
|
|
212
228
|
|
|
213
229
|
console.log(`\n ${GREEN}Done!${RESET} Open a project in Claude Code and run ${BRIGHT_MAGENTA}/harness-evolver:init${RESET}`);
|
|
214
230
|
console.log(`\n ${DIM}Quick start with example:${RESET}`);
|
|
@@ -216,14 +232,12 @@ async function main() {
|
|
|
216
232
|
console.log(` cd my-project && claude`);
|
|
217
233
|
console.log(` /harness-evolver:init`);
|
|
218
234
|
console.log(` /harness-evolver:evolve`);
|
|
219
|
-
|
|
220
|
-
console.log(`\n ${DIM}GitHub: https://github.com/raphaelchristi/harness-evolver${RESET}`);
|
|
221
|
-
console.log();
|
|
235
|
+
console.log(`\n ${DIM}GitHub: https://github.com/raphaelchristi/harness-evolver${RESET}\n`);
|
|
222
236
|
|
|
223
237
|
rl.close();
|
|
224
238
|
}
|
|
225
239
|
|
|
226
|
-
main().catch(
|
|
240
|
+
main().catch(err => {
|
|
227
241
|
console.error(` ${RED}ERROR:${RESET} ${err.message}`);
|
|
228
242
|
process.exit(1);
|
|
229
243
|
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-evolver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Meta-Harness-style autonomous harness optimization for Claude Code",
|
|
5
|
-
"author": "Raphael Valdetaro
|
|
5
|
+
"author": "Raphael Valdetaro",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
package/skills/compare/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: compare
|
|
2
|
+
name: harness-evolver:compare
|
|
3
3
|
description: "Use when the user wants to compare two harness versions, understand what changed between iterations, see why one version scored better than another, or debug a regression."
|
|
4
4
|
argument-hint: "<vA> <vB>"
|
|
5
5
|
allowed-tools: [Read, Bash, Glob, Grep]
|
package/skills/deploy/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: deploy
|
|
2
|
+
name: harness-evolver:deploy
|
|
3
3
|
description: "Use when the user wants to use the best evolved harness in their project, promote a version to production, copy the winning harness back, or is done evolving and wants to apply the result."
|
|
4
4
|
argument-hint: "[version]"
|
|
5
5
|
allowed-tools: [Read, Write, Bash, Glob]
|
package/skills/diagnose/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: diagnose
|
|
2
|
+
name: harness-evolver:diagnose
|
|
3
3
|
description: "Use when the user wants to understand why a specific harness version failed, investigate a regression, analyze trace data, or debug a low score. Also use when the user says 'why did v003 fail' or 'what went wrong'."
|
|
4
4
|
argument-hint: "[version]"
|
|
5
5
|
allowed-tools: [Read, Bash, Glob, Grep]
|
package/skills/evolve/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: evolve
|
|
2
|
+
name: harness-evolver:evolve
|
|
3
3
|
description: "Use when the user wants to run the optimization loop, improve harness performance, evolve the harness, or iterate on harness quality. Requires .harness-evolver/ to exist (run harness-evolver:init first)."
|
|
4
4
|
argument-hint: "[--iterations N]"
|
|
5
5
|
allowed-tools: [Read, Write, Edit, Bash, Glob, Grep, Agent]
|
package/skills/init/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: init
|
|
2
|
+
name: harness-evolver:init
|
|
3
3
|
description: "Use when the user wants to set up harness optimization in their project, optimize an LLM agent, improve a harness, or mentions harness-evolver for the first time in a project without .harness-evolver/ directory."
|
|
4
4
|
argument-hint: "[directory]"
|
|
5
5
|
allowed-tools: [Read, Write, Edit, Bash, Glob, Grep, Agent]
|
package/skills/status/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: status
|
|
2
|
+
name: harness-evolver:status
|
|
3
3
|
description: "Use when the user asks about evolution progress, current scores, best harness version, how many iterations ran, or whether the loop is stagnating. Also use when the user says 'status', 'progress', or 'how is it going'."
|
|
4
4
|
allowed-tools: [Read, Bash]
|
|
5
5
|
---
|