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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Raphael Valdetaro Christi Cordeiro
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
- * Interactive setup with runtime selection, global/local choice.
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 installForRuntime(runtimeDir, scope) {
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
- const commandsDir = path.join(baseDir, "commands", "harness-evolver");
87
- const agentsDir = path.join(baseDir, "agents");
88
-
89
- // Skills
90
- const skillsSource = path.join(PLUGIN_ROOT, "skills");
91
- if (fs.existsSync(skillsSource)) {
92
- for (const skill of fs.readdirSync(skillsSource, { withFileTypes: true })) {
93
- if (skill.isDirectory()) {
94
- copyDir(path.join(skillsSource, skill.name), path.join(commandsDir, skill.name));
95
- console.log(` ${GREEN}✓${RESET} Installed skill: ${skill.name}`);
96
- }
97
- }
98
- }
99
-
100
- // Agents
101
- const agentsSource = path.join(PLUGIN_ROOT, "agents");
102
- if (fs.existsSync(agentsSource)) {
103
- fs.mkdirSync(agentsDir, { recursive: true });
104
- for (const agent of fs.readdirSync(agentsSource)) {
105
- copyFile(path.join(agentsSource, agent), path.join(agentsDir, agent));
106
- console.log(` ${GREEN}✓${RESET} Installed agent: ${agent}`);
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 installTools() {
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
- copyFile(path.join(toolsSource, tool), path.join(toolsDir, tool));
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} Installed examples: classifier`);
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 available = RUNTIMES.filter((r) => r.detected());
149
- if (available.length === 0) {
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
- available.forEach((r, i) => {
160
- console.log(` ${i + 1}) ${r.name.padEnd(14)} (~/${r.dir})`);
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 defaultChoice = "1";
168
- const runtimeAnswer = await ask(rl, `\n ${YELLOW}Choice [${defaultChoice}]:${RESET} `);
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 selectedRuntimes;
172
- if (runtimeInput === String(available.length + 1)) {
173
- selectedRuntimes = available;
194
+ let selected;
195
+ if (runtimeInput === String(RUNTIMES.length + 1)) {
196
+ selected = RUNTIMES;
174
197
  } else {
175
- const indices = runtimeInput.split(/[,\s]+/).map((s) => parseInt(s, 10) - 1);
176
- selectedRuntimes = indices
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 (~/${selectedRuntimes[0].dir}) - available in all projects`);
188
- console.log(` 2) Local (./${selectedRuntimes[0].dir}) - this project only`);
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 for each selected runtime
196
- for (const runtime of selectedRuntimes) {
197
- const target = scope === "local" ? `./${runtime.dir}` : `~/${runtime.dir}`;
198
- console.log(` Installing for ${BRIGHT_MAGENTA}${runtime.name}${RESET} to ${target}`);
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
- // Tools and examples are always global
204
- installTools();
220
+ installToolsGlobal();
205
221
  installExamples();
206
222
 
207
- // Write version file
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} Wrote VERSION (${VERSION})`);
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((err) => {
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.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Meta-Harness-style autonomous harness optimization for Claude Code",
5
- "author": "Raphael Valdetaro Christi Cordeiro",
5
+ "author": "Raphael Valdetaro",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
@@ -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]
@@ -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]
@@ -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]
@@ -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]
@@ -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]
@@ -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
  ---