reviewer-harness 1.1.0 → 1.3.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/README.md CHANGED
@@ -176,7 +176,11 @@ Reusable playbooks, enabled per project:
176
176
  - `skills add <name>` — install into the project and enable it in the config
177
177
  - `skills remove <name>` — disable it
178
178
 
179
- Bundled: `safe-editing`, `no-hallucination`, `debug-first`, `explain-bisaya`. Enabled skills render below the rules in every instruction file.
179
+ Bundled: `safe-editing`, `no-hallucination`, `debug-first`, `explain-bisaya`, `clone`. Enabled skills render below the rules in every instruction file — and sync also generates a **slash command** for each one (`.opencode/commands/<name>.md` and `.claude/commands/<name>.md`), so every enabled skill appears in the `/` menu of OpenCode and Claude, ready to invoke on a specific task: `/safe-editing`, `/no-hallucination`, and so on.
180
+
181
+ **Every project, instantly:** want the skills in the `/` menu of *every* project, with no per-project setup? Run `reviewer-harness global` once — it installs all bundled skills as slash commands into the global OpenCode (`~/.config/opencode/commands`) and Claude (`~/.claude/commands`) folders. From then on, they are in the `/` menu of any project you open. (Project rules still come from `sync` inside a project.)
182
+
183
+ **A built-in pairing:** the `/safe-editing` command always carries the `no-hallucination` playbook inside it as a companion section — one call, both disciplines, so the AI that edits can never invent the code it edits with.
180
184
 
181
185
  ## Sub-agents and Slash Commands
182
186
 
@@ -190,20 +194,37 @@ One master definition in the harness, generated into each runtime's format:
190
194
 
191
195
  ## Command reference
192
196
 
193
- Requires Node.js 18+ (no dependencies). Every command accepts `--target <dir>` (default: the current directory).
197
+ Requires Node.js 18+ (no dependencies). Run these inside your project — every command works on the current directory unless you add `--target <dir>`.
198
+
199
+ **Start & everyday**
200
+
201
+ ```bash
202
+ reviewer-harness init # one-time: create the master config
203
+ reviewer-harness validate # check the config (quality + secret scan)
204
+ reviewer-harness sync --dry-run # preview the changes, writes nothing
205
+ reviewer-harness sync # apply the rules to every runtime
206
+ reviewer-harness status # show the config + target files
207
+ ```
208
+
209
+ **Skills, agents, commands**
210
+
211
+ ```bash
212
+ reviewer-harness skills list
213
+ reviewer-harness skills add safe-editing
214
+ reviewer-harness skills remove safe-editing
215
+ reviewer-harness agents add reviewer
216
+ reviewer-harness commands add review
217
+ ```
218
+
219
+ **Local memory**
194
220
 
195
221
  ```bash
196
- node bin/cli.js init --target ./my-project
197
- node bin/cli.js validate --target ./my-project
198
- node bin/cli.js sync --dry-run --target ./my-project
199
- node bin/cli.js sync --target ./my-project
200
- node bin/cli.js memory init --target ./my-project
201
- node bin/cli.js skills add safe-editing --target ./my-project
202
- node bin/cli.js agents add reviewer --target ./my-project
203
- node bin/cli.js commands add review --target ./my-project
222
+ reviewer-harness memory init # start the memory/ vault
223
+ reviewer-harness memory list # list notes + their links
224
+ reviewer-harness memory index # rebuild INDEX.md + graph.json
204
225
  ```
205
226
 
206
- Installed globally or via npx (see Quickstart), the same commands run as `reviewer-harness <command>`.
227
+ Running from a source checkout instead? The same commands work as `node bin/cli.js <command>`.
207
228
 
208
229
  ## File map
209
230
 
package/bin/cli.js CHANGED
@@ -1,18 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  // reviewer-harness — CLI: sync engine, guardrail validation, local memory,
3
3
  // skills, sub-agents, and slash commands.
4
- // Commands: status | sync | init | validate | memory | skills | agents | commands
4
+ // Commands: status | sync | init | validate | memory | skills | agents | commands | global
5
5
 
6
6
  import { parseArgs } from "node:util";
7
7
  import { existsSync } from "node:fs";
8
8
  import { join } from "node:path";
9
+ import { homedir } from "node:os";
9
10
  import { loadConfig } from "../lib/config.js";
10
11
  import { adapters, supportedRuntimes } from "../lib/adapters/index.js";
11
- import { sync } from "../lib/sync.js";
12
+ import { sync, writeSafe } from "../lib/sync.js";
12
13
  import { initProject } from "../lib/init.js";
13
14
  import { initMemory, buildIndex, readNotes, memoryDir } from "../lib/memory.js";
14
15
  import { validateConfig } from "../lib/validate.js";
15
- import { listBundledSkills, addSkill, removeSkill } from "../lib/skills.js";
16
+ import { listBundledSkills, addSkill, removeSkill, loadBundledSkills, skillCommandContent } from "../lib/skills.js";
16
17
  import { listBundledAgents, addAgent } from "../lib/agents.js";
17
18
  import { listBundledCommands, addCommand } from "../lib/commands.js";
18
19
 
@@ -30,21 +31,27 @@ const dryRun = values["dry-run"];
30
31
  function help() {
31
32
  console.log(`reviewer-harness — your own AI harness
32
33
 
33
- Usage:
34
- node bin/cli.js status [--target <dir>] Show the config + target files
35
- node bin/cli.js sync [--dry-run] [--target <dir>] Sync the rules to every runtime
36
- node bin/cli.js init [--target <dir>] Create harness.config.json in a project
37
- node bin/cli.js validate [--target <dir>] Check the config: quality + secret scanning
38
- node bin/cli.js memory init [--target <dir>] Start the local memory (memory/ folder)
39
- node bin/cli.js memory index [--target <dir>] Rebuild the memory INDEX.md + graph.json
40
- node bin/cli.js memory list [--target <dir>] List memory notes + their links
41
- node bin/cli.js skills list [--target <dir>] List bundled skills (+ which are enabled)
42
- node bin/cli.js skills add <name> [--target <dir>] Install + enable a skill in the project
43
- node bin/cli.js skills remove <name> [--target <dir>] Disable + remove a skill
44
- node bin/cli.js agents list List bundled sub-agents
45
- node bin/cli.js agents add <name> [--target <dir>] Install a sub-agent (OpenCode + Claude)
46
- node bin/cli.js commands list List bundled slash commands
47
- node bin/cli.js commands add <name> [--target <dir>] Install a slash command (OpenCode + Claude)
34
+ Usage (run inside your project):
35
+ reviewer-harness init Create harness.config.json (one-time setup)
36
+ reviewer-harness validate Check the config: quality + secret scanning
37
+ reviewer-harness sync [--dry-run] Sync the rules to every runtime (--dry-run = preview only)
38
+ reviewer-harness status Show the config + target files
39
+
40
+ reviewer-harness skills list List bundled skills (+ which are enabled)
41
+ reviewer-harness skills add <name> Install + enable a skill in the project
42
+ reviewer-harness skills remove <name> Disable + remove a skill
43
+ reviewer-harness agents list List bundled sub-agents
44
+ reviewer-harness agents add <name> Install a sub-agent (OpenCode + Claude)
45
+ reviewer-harness commands list List bundled slash commands
46
+ reviewer-harness commands add <name> Install a slash command (OpenCode + Claude)
47
+
48
+ reviewer-harness global [--dry-run] Install EVERY bundled skill as a slash command for ALL projects (global OpenCode + Claude "/" menus)
49
+
50
+ reviewer-harness memory init Start the local memory (memory/ folder)
51
+ reviewer-harness memory index Rebuild the memory INDEX.md + graph.json
52
+ reviewer-harness memory list List memory notes + their links
53
+
54
+ Every command accepts --target <dir> (default: the current directory).
48
55
  `);
49
56
  }
50
57
 
@@ -64,7 +71,16 @@ function cmdSync() {
64
71
  const results = sync({ targetDir, dryRun });
65
72
  if (dryRun) console.log("(dry-run: nothing was written)\n");
66
73
  for (const r of results) {
67
- if (r.skipped) {
74
+ if (r.skillCommands) {
75
+ const written = r.skillCommands.filter((f) => f.written).length;
76
+ const pending = r.skillCommands.filter((f) => f.dryRun && f.changed).length;
77
+ const unchanged = r.skillCommands.filter((f) => f.unchanged || (f.dryRun && !f.changed)).length;
78
+ if (dryRun) {
79
+ console.log(`- skill commands: ${pending} would be written/updated, ${unchanged} unchanged (they appear in the "/" menu of OpenCode and Claude)`);
80
+ } else {
81
+ console.log(`- skill commands: ${written} written ✓, ${unchanged} unchanged (one per skill, in the "/" menu of OpenCode and Claude)`);
82
+ }
83
+ } else if (r.skipped) {
68
84
  console.log(`- ${r.runtime}: SKIP — ${r.skipped}`);
69
85
  } else if (r.dryRun) {
70
86
  console.log(`- ${r.runtime} → ${r.file}: ${r.changed ? "WOULD WRITE (changes pending)" : "no changes"}`);
@@ -192,6 +208,30 @@ function cmdCommands() {
192
208
  }
193
209
  }
194
210
 
211
+ function cmdGlobal() {
212
+ const skills = loadBundledSkills();
213
+ const targets = [
214
+ { runtime: "OpenCode", dir: join(homedir(), ".config", "opencode", "commands") },
215
+ { runtime: "Claude", dir: join(homedir(), ".claude", "commands") },
216
+ ];
217
+ if (dryRun) console.log("(dry-run: nothing was written)\n");
218
+ for (const { runtime, dir } of targets) {
219
+ let written = 0, unchanged = 0, pending = 0;
220
+ for (const skill of skills) {
221
+ const outcome = writeSafe(join(dir, `${skill.name}.md`), skillCommandContent(skill, skills), dryRun);
222
+ if (outcome.written) written++;
223
+ else if (outcome.dryRun && outcome.changed) pending++;
224
+ else unchanged++;
225
+ }
226
+ if (dryRun) console.log(`- ${runtime} (global): ${pending} would be written/updated, ${unchanged} unchanged → ${dir}`);
227
+ else console.log(`- ${runtime} (global): ${written} written ✓, ${unchanged} unchanged → ${dir}`);
228
+ }
229
+ console.log(
230
+ `\nDone: ${skills.map((s) => "/" + s.name).join(", ")} are now in the "/" menu of EVERY project.` +
231
+ `\n(Per-project rules still come from "sync" inside a project.)`
232
+ );
233
+ }
234
+
195
235
  async function main() {
196
236
  if (!command || command === "status") cmdStatus();
197
237
  else if (command === "sync") cmdSync();
@@ -201,6 +241,7 @@ async function main() {
201
241
  else if (command === "skills") cmdSkills();
202
242
  else if (command === "agents") cmdAgents();
203
243
  else if (command === "commands") cmdCommands();
244
+ else if (command === "global") cmdGlobal();
204
245
  else help();
205
246
  }
206
247
 
package/lib/skills.js CHANGED
@@ -1,8 +1,7 @@
1
1
  // lib/skills.js — LESSON 4: SKILLS SYSTEM.
2
2
  //
3
- // Rules are short laws. SKILLS are deeper playbooks for specific work
4
- // (like sauron's `add`): reusable instruction bundles you enable
5
- // per project.
3
+ // Rules are short laws. SKILLS are deeper playbooks for specific work:
4
+ // reusable instruction bundles you enable per project.
6
5
  //
7
6
  // skills/<name>.md — bundled in the harness (the "store")
8
7
  // <project>/skills/ — the installed copy in your project
@@ -27,6 +26,26 @@ import { parseFrontmatter } from "./frontmatter.js";
27
26
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
28
27
  const BUNDLED_DIR = join(ROOT, "skills");
29
28
 
29
+ // The bundled skills in full (name + description + body) — used by
30
+ // the "global" command, which installs them as slash commands for
31
+ // every project on the machine.
32
+ export function loadBundledSkills() {
33
+ if (!existsSync(BUNDLED_DIR)) return [];
34
+ return readdirSync(BUNDLED_DIR)
35
+ .filter((f) => f.endsWith(".md"))
36
+ .sort()
37
+ .map((file) => {
38
+ const { data, body } = parseFrontmatter(
39
+ readFileSync(join(BUNDLED_DIR, file), "utf8")
40
+ );
41
+ return {
42
+ name: file.replace(/\.md$/, ""),
43
+ description: data.description ?? "",
44
+ body: body.trim(),
45
+ };
46
+ });
47
+ }
48
+
30
49
  export function listBundledSkills() {
31
50
  if (!existsSync(BUNDLED_DIR)) return [];
32
51
  return readdirSync(BUNDLED_DIR)
@@ -101,19 +120,51 @@ export function removeSkill(name, targetDir) {
101
120
  return { wasEnabled, removedFile };
102
121
  }
103
122
 
104
- // Read the enabled skills' contents so sync can render them.
105
- // Enabled but no file? That's a clear error, not a silent skip:
106
- // a strict harness never guesses.
107
- export function loadSkillSections(targetDir, config) {
108
- const sections = [];
123
+ // Read the enabled skills in full (name + description + body) so sync
124
+ // can render them into the instruction files AND generate a slash
125
+ // command for each one.
126
+ export function loadSkills(targetDir, config) {
127
+ const skills = [];
109
128
  for (const name of config.skills ?? []) {
110
129
  const file = join(targetDir, "skills", `${name}.md`);
111
130
  if (!existsSync(file))
112
131
  throw new Error(
113
132
  `Skill "${name}" is enabled in the config but has no file in skills/ — run "skills add ${name}".`
114
133
  );
115
- const { body } = parseFrontmatter(readFileSync(file, "utf8"));
116
- sections.push(body.trim());
134
+ const { data, body } = parseFrontmatter(readFileSync(file, "utf8"));
135
+ skills.push({ name, description: data.description ?? "", body: body.trim() });
136
+ }
137
+ return skills;
138
+ }
139
+
140
+ // Enabled but no file? loadSkills throws a clear error, never a
141
+ // silent skip: a strict harness never guesses.
142
+ export function loadSkillSections(targetDir, config) {
143
+ return loadSkills(targetDir, config).map((s) => s.body);
144
+ }
145
+
146
+ // The slash-command file generated for a skill, so the skill shows up
147
+ // in the runtime's "/" menu (OpenCode and Claude list the files in
148
+ // their commands folders). Invoking /<skill> hands the playbook to
149
+ // the AI together with whatever the user types after it.
150
+ // Companion pairings: when one of these skills is rendered as a slash
151
+ // command, the companion's playbook is embedded in the same file —
152
+ // so a single call always carries both disciplines. safe-editing
153
+ // pairs with no-hallucination: the AI that edits must never invent
154
+ // the code it edits with.
155
+ const COMPANIONS = { "safe-editing": ["no-hallucination"] };
156
+
157
+ export function skillCommandContent(skill, allSkills = []) {
158
+ let content =
159
+ `---\ndescription: ${skill.description}\n---\n\n` + `${skill.body}\n`;
160
+ for (const companionName of COMPANIONS[skill.name] ?? []) {
161
+ const companion = allSkills.find((s) => s.name === companionName);
162
+ if (companion) {
163
+ content +=
164
+ `\n---\n\n## Companion playbook: ${companion.name} ` +
165
+ `(always paired with ${skill.name})\n\n${companion.body}\n`;
166
+ }
117
167
  }
118
- return sections;
168
+ content += `\nApply this playbook to the following: $ARGUMENTS\n`;
169
+ return content;
119
170
  }
package/lib/sync.js CHANGED
@@ -8,6 +8,10 @@
8
8
  // 3. BACKUP — before overwriting an existing file, make a .bak copy
9
9
  // 4. SKIP — if nothing changed, touch nothing (idempotent)
10
10
  //
11
+ // Sync also generates one SLASH COMMAND per enabled skill (into the
12
+ // OpenCode and Claude commands folders), so every skill appears in
13
+ // the runtime's "/" menu and can be invoked on demand.
14
+ //
11
15
  // This is the harness's first guardrail: the harness itself cannot
12
16
  // destroy your files — the same protection it gives you against an
13
17
  // AI breaking your code.
@@ -23,7 +27,33 @@ import { join, dirname } from "node:path";
23
27
  import { loadConfig } from "./config.js";
24
28
  import { adapters, supportedRuntimes } from "./adapters/index.js";
25
29
  import { validateConfig } from "./validate.js";
26
- import { loadSkillSections } from "./skills.js";
30
+ import { loadSkills, skillCommandContent } from "./skills.js";
31
+
32
+ // Write one file the safe way. Returns what happened:
33
+ // dry-run → { dryRun: true, changed } · no change → { unchanged: true }
34
+ // changed → { written: true, backup } (with a .bak copy when the
35
+ // file already existed). Every real write is verified by reading
36
+ // the file back.
37
+ export function writeSafe(abs, content, dryRun) {
38
+ if (dryRun) {
39
+ const existing = existsSync(abs) ? readFileSync(abs, "utf8") : null;
40
+ return { dryRun: true, changed: existing !== content };
41
+ }
42
+ mkdirSync(dirname(abs), { recursive: true });
43
+ if (existsSync(abs)) {
44
+ const old = readFileSync(abs, "utf8");
45
+ if (old === content) return { unchanged: true };
46
+ copyFileSync(abs, abs + ".bak");
47
+ writeFileSync(abs, content);
48
+ if (readFileSync(abs, "utf8") !== content)
49
+ throw new Error(`Failed to write ${abs}`);
50
+ return { written: true, backup: true };
51
+ }
52
+ writeFileSync(abs, content);
53
+ if (readFileSync(abs, "utf8") !== content)
54
+ throw new Error(`Failed to write ${abs}`);
55
+ return { written: true, backup: false };
56
+ }
27
57
 
28
58
  export function sync({ targetDir, dryRun = false }) {
29
59
  const config = loadConfig(targetDir);
@@ -39,8 +69,11 @@ export function sync({ targetDir, dryRun = false }) {
39
69
  );
40
70
  }
41
71
 
42
- // LESSON 4: attach the enabled skills — the adapters render them.
43
- config.skillSections = loadSkillSections(targetDir, config);
72
+ // LESSON 4: attach the enabled skills — the adapters render them
73
+ // into the instruction files, and each one also becomes a slash
74
+ // command below.
75
+ const skills = loadSkills(targetDir, config);
76
+ config.skillSections = skills.map((s) => s.body);
44
77
 
45
78
  const results = [];
46
79
 
@@ -54,38 +87,22 @@ export function sync({ targetDir, dryRun = false }) {
54
87
  }
55
88
 
56
89
  const rel = adapter.targetFile();
57
- const abs = join(targetDir, rel);
58
- const content = adapter.renderInstructions(config);
59
-
60
- // DRY-RUN: read only, never write. "Look before you agree."
61
- if (dryRun) {
62
- const existing = existsSync(abs) ? readFileSync(abs, "utf8") : null;
63
- results.push({ runtime: name, file: rel, dryRun: true, changed: existing !== content });
64
- continue;
65
- }
66
-
67
- mkdirSync(dirname(abs), { recursive: true });
90
+ const outcome = writeSafe(join(targetDir, rel), adapter.renderInstructions(config), dryRun);
91
+ results.push({ runtime: name, file: rel, ...outcome });
92
+ }
68
93
 
69
- if (existsSync(abs)) {
70
- const old = readFileSync(abs, "utf8");
71
- // Nothing changed? Touch nothing — sync is idempotent.
72
- if (old === content) {
73
- results.push({ runtime: name, file: rel, unchanged: true });
74
- continue;
94
+ // SLASH COMMANDS: one per enabled skill, for the two runtimes whose
95
+ // "/" menu lists command files (OpenCode and Claude).
96
+ if (skills.length) {
97
+ const files = [];
98
+ for (const skill of skills) {
99
+ const content = skillCommandContent(skill, skills);
100
+ for (const dir of [".opencode/commands", ".claude/commands"]) {
101
+ const rel = `${dir}/${skill.name}.md`;
102
+ files.push({ file: rel, ...writeSafe(join(targetDir, rel), content, dryRun) });
75
103
  }
76
- // About to overwrite? BACK UP first — protection against loss.
77
- copyFileSync(abs, abs + ".bak");
78
- writeFileSync(abs, content);
79
- results.push({ runtime: name, file: rel, written: true, backup: true });
80
- } else {
81
- writeFileSync(abs, content);
82
- results.push({ runtime: name, file: rel, written: true, backup: false });
83
- }
84
-
85
- // Verify the write actually landed (read it back).
86
- if (!dryRun && readFileSync(abs, "utf8") !== content) {
87
- throw new Error(`Failed to write ${abs}`);
88
104
  }
105
+ results.push({ skillCommands: files });
89
106
  }
90
107
 
91
108
  return results;
package/package.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "url": "https://github.com/xcell321/reviewer-harness.git"
24
24
  },
25
25
  "type": "module",
26
- "version": "1.1.0",
26
+ "version": "1.3.0",
27
27
  "files": [
28
28
  "bin",
29
29
  "lib",
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: clone
3
+ description: Fully clone a website's design — layout, components, animations, borders — while replacing all text and assets with your own.
4
+ ---
5
+
6
+ ## Skill: clone
7
+
8
+ When to use: the user asks to clone a website or design, and gives a URL or reference to copy.
9
+
10
+ - Clone the DESIGN, replace the CONTENT. That is the whole rule: everything visual may be reproduced, everything owned must be replaced.
11
+ - First, inventory the target: sections, components, layout grid, colors, fonts, spacing, borders, and every animation (type, duration, easing, trigger). Write the inventory down before writing any code.
12
+ - Rebuild from the inventory with original code: reproduce the look and motion, never paste the target's source code.
13
+ - Replace every owned thing: all text (headings, skills, descriptions, projects), all image files, logos, and brand names become the user's own content or placeholders. Image placement and sizes may be copied; image files may not.
14
+ - Front-end only: a clone reproduces the look, never the backend — no login, database, or dynamic data.
15
+ - If the target is the user's own site, their own assets may be reused.
16
+ - Afterwards, validate: compare screenshots side by side, check responsive breakpoints, and audit that no third-party image, text, or brand remains. Report what was cloned and what was replaced.