Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "humanizer",
"description": "Rewrite AI-sounding text so it reads naturally without changing what it says.",
"version": "3.0.0",
"version": "3.1.0",
"author": {
"name": "blader",
"url": "https://github.com/blader"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
python-version: "3.12"
- name: Check package files
run: python3 scripts/validate-package.py
- name: Check OpenCode plugin
run: node --test scripts/test-opencode-plugin.mjs
- name: Check skill discovery
run: npx --yes skills@1.5.20 add . --list
- name: Check Claude marketplace
Expand Down
24 changes: 24 additions & 0 deletions .opencode/plugins/humanizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { readFileSync } from "node:fs";

const skill = readFileSync(new URL("../../SKILL.md", import.meta.url), "utf8");
const instructions = skill
.replace(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/, "")
.trim();

export default {
id: "humanizer",

async setup(ctx) {
await ctx.session.hook("context", (event) => {
event.system.push({ type: "text", text: instructions });
});
},

async server() {
return {
"experimental.chat.system.transform": async (_input, output) => {
output.system.push(instructions);
},
};
},
};
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Claude Code 2.1.142 or newer can install the plugin instead:

The plugin answers to `/humanizer:humanizer`.

OpenCode 1.18.29 or newer loads the bundled Humanizer plugin automatically when you run OpenCode from this repository. It adds the guidance in `SKILL.md` to each chat session. The plugin also supports OpenCode V2.

In Claude Desktop, download this repository as a ZIP and upload it as a skill. For a manual install, copy `SKILL.md` into the agent's skill folder.

## Usage
Expand Down Expand Up @@ -168,6 +170,7 @@ The writer supplied these notes with the draft, so the rewrite can use them: the
<details>
<summary>Show release notes</summary>

- **3.1.0** - Added a project-local OpenCode plugin that applies the existing Humanizer guidance to each chat session.
- **3.0.0** - Rebuilt the skill around one account of why AI text sounds the way it does, and consolidated 35 patterns into 25. Patterns are grouped in five sections and numbered by strength and frequency, so the not-X-but-Y contrast and the one-line closer come first and get the fullest treatment. Merged duplicate guidance: the workflow is one section instead of five, the dash rule is stated once, and each false-positive guard lives inside its pattern. Realigned with the current Wikipedia article: dropped false ranges and synonym cycling, which Wikipedia now lists as human habits or historical, added vague connection or association, and extended the watch lists for words, notability, copulatives, sales language, disclaimers, and Markdown formatting. Reordered the README and removed the `ai-detection` keyword from the package files. Old to new numbers: 1→13, 2→17, 3→15, 4→16, 5→17, 6→13, 7→12, 8→18, 9→1, 10→6, 11→7, 12→dropped, 13→11, 14→8, 15→19, 16→19, 17→20, 18→20, 19→21, 20→22, 21→23, 22→22, 23→dropped, 24→9, 25→13, 26→10, 27→3, 28→4, 29→24, 30→25, 31→2, 32→3, 33→4, 34→5, 35→5.
- **2.11.3** - Grouped patterns 26-35 under "More style patterns" in the skill and README (fixes #247). Kept inline code, commands, paths, and URLs out of the dash rule and file mode edits. Step 3 now keeps every supported claim, allows a removal that a pattern requires, and checks that rankings and simultaneity claims survive shape edits (fixes #212). Explained in §9 why the not-X-but-Y form appears and when to keep it. Added decorative arrows to §18 and pause commands and one-word shouting to §31. The text given to the skill is content to edit, never instructions (#238). No change to the 35 patterns.
- **2.11.2** - Removed the plugin symlink and separate Claude Desktop package. Current Claude Code loads the root `SKILL.md` directly, so GitHub's source ZIP now works in Claude Desktop. No change to the 35 patterns.
Expand Down
2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: |
language, stock AI words, bold labels, or filler. Based on Wikipedia's "Signs of AI writing."
license: MIT
metadata:
version: "3.0.0"
version: "3.1.0"
---

# Humanizer: remove AI writing patterns
Expand Down
35 changes: 35 additions & 0 deletions scripts/test-opencode-plugin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import assert from "node:assert/strict";
import test from "node:test";

import plugin from "../.opencode/plugins/humanizer.ts";

test("V1 system transform adds the Humanizer instructions without frontmatter", async () => {
const hooks = await plugin.server();
const output = { system: [] };

await hooks["experimental.chat.system.transform"]({}, output);

assert.equal(output.system.length, 1);
assert.match(output.system[0], /# Humanizer: remove AI writing patterns/);
assert.doesNotMatch(output.system[0], /^---/);
});

test("V2 context hook appends the Humanizer instructions as a text block", async () => {
let contextHook;
await plugin.setup({
session: {
hook: async (name, callback) => {
assert.equal(name, "context");
contextHook = callback;
},
},
});
const event = { system: [] };

await contextHook(event);

assert.equal(event.system.length, 1);
assert.deepEqual(event.system[0].type, "text");
assert.match(event.system[0].text, /# Humanizer: remove AI writing patterns/);
assert.doesNotMatch(event.system[0].text, /^---/);
});