@vasilvestre/opencode-plain-style 1.0.0 → 1.1.1

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.
@@ -0,0 +1,54 @@
1
+ // plain-style — OpenCode plugin, supports opencode v1.x and v2.x.
2
+ //
3
+ // Injects the plain-language rulebook (plain-style.md, beside this file) into
4
+ // every chat's system prompt on every agent-loop LLM request, primary agent
5
+ // and subagents alike.
6
+ //
7
+ // The default export is one object with both entrypoints (v2 migration guide:
8
+ // https://opencode.ai/v2/docs/build/plugins/migrate-v1):
9
+ // - v2 calls setup() and registers a "context" session hook; system parts are
10
+ // objects ({ type: "text", text }) there.
11
+ // - v1 (object entrypoints need opencode >= 1.18.29) calls server() and gets
12
+ // the experimental.chat.system.transform hook; system entries there are
13
+ // plain strings. The hook must mutate output.system in place (push), because v1
14
+ // ignores a reassignment of output.system.
15
+ //
16
+ // Auto-discovered from .opencode/plugins/ in both versions, so no
17
+ // opencode.json entry is needed. To register it by hand instead:
18
+ // { "plugin": ["./.opencode/plugins/plain-style.js"] } (opencode v1)
19
+ // { "plugins": ["./.opencode/plugins/plain-style.js"] } (opencode v2)
20
+
21
+ import fs from 'fs';
22
+ import path from 'path';
23
+ import { fileURLToPath } from 'url';
24
+
25
+ // Read once at load; a missing file throws here so startup fails loud instead
26
+ // of silently injecting nothing.
27
+ const dir = path.dirname(fileURLToPath(import.meta.url));
28
+ const rulebook = fs.readFileSync(
29
+ path.join(dir, 'plain-style.md'),
30
+ 'utf8',
31
+ );
32
+
33
+ // opencode v2 rejects duplicate plugin ids at startup and drops every plugin
34
+ // when two instances collide. The npm install and a local repo copy share one
35
+ // file path shape, so each instance gets a unique id keyed by its own
36
+ // directory.
37
+ const location = dir.replaceAll(/[^\w]/g, '');
38
+ const hash = [...location].reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 7).toString(16);
39
+
40
+ export default {
41
+ id: `vasilvestre.opencode-plain-style-${hash}`,
42
+ async setup(ctx) {
43
+ await ctx.session.hook('context', (event) => {
44
+ event.system.push({ type: 'text', text: rulebook });
45
+ });
46
+ },
47
+ async server() {
48
+ return {
49
+ 'experimental.chat.system.transform': async (_input, output) => {
50
+ output.system.push(rulebook);
51
+ },
52
+ };
53
+ },
54
+ };
package/README.md CHANGED
@@ -90,7 +90,13 @@ Rules state what to do, not what to avoid by name.
90
90
  ### Steps
91
91
 
92
92
  1. Open `~/.config/opencode/opencode.json`.
93
- 2. Add the package name to the `plugin` array:
93
+ 2. Add the package name to the plugin array. opencode v2 uses the `plugins` key, opencode v1 uses `plugin`:
94
+
95
+ ```json
96
+ {
97
+ "plugins": ["@vasilvestre/opencode-plain-style"]
98
+ }
99
+ ```
94
100
 
95
101
  ```json
96
102
  {
@@ -119,28 +125,32 @@ Use this to test changes before publishing, or to run a fork.
119
125
 
120
126
  ```json
121
127
  {
122
- "plugin": ["file:///absolute/path/to/opencode-plain-style/.opencode/plugins/plain-style.mjs"]
128
+ "plugins": ["file:///absolute/path/to/opencode-plain-style/.opencode/plugins/plain-style.js"]
123
129
  }
124
130
  ```
125
131
 
126
- The path must be absolute and must point at the `.mjs` file, not the directory.
132
+ opencode v1 uses the same URL under the `plugin` key. The path must be absolute and must point at the `.js` file, not the directory.
127
133
 
128
134
  3. Restart opencode.
129
135
 
130
136
  ### Uninstall
131
137
 
132
- 1. Remove the entry from the `plugin` array in `~/.config/opencode/opencode.json`.
138
+ 1. Remove the entry from the plugin array (`plugins` in opencode v2, `plugin` in v1) in `~/.config/opencode/opencode.json`.
133
139
  2. Restart opencode.
134
140
 
135
141
  ## How it works
136
142
 
137
- The plugin registers one hook: `experimental.chat.system.transform`.
138
- On each request it pushes the rulebook text into the system prompt array.
143
+ The plugin exports one object with two entrypoints; opencode picks the one matching its version:
144
+
145
+ - opencode v1 calls `server()`. It hooks `experimental.chat.system.transform` and pushes the rulebook text (a string) into the system prompt array on each request.
146
+ - opencode v2 calls `setup()`. It registers a `context` session hook and appends a `{ type: "text" }` part to the system prompt.
147
+
139
148
  The rulebook is read from disk once at module load, so each request costs one array push and zero I/O.
140
- Total plugin code: about 25 lines, no dependencies.
149
+ The plugin has no dependencies.
141
150
 
142
- Note: the hook is marked experimental in opencode.
143
- If opencode renames it, the plugin fails loudly at startup rather than injecting nothing.
151
+ In opencode v1 the hook must mutate `output.system` in place: a reassignment is silently ignored.
152
+
153
+ Note: v1 support needs opencode 1.18.29 or later (object-style plugin entrypoints).
144
154
 
145
155
  ## Example output
146
156
 
@@ -205,10 +215,12 @@ Fork the repo, edit the file, and point your config at the local copy:
205
215
 
206
216
  ```json
207
217
  {
208
- "plugin": ["file:///path/to/your/fork/.opencode/plugins/plain-style.mjs"]
218
+ "plugins": ["file:///path/to/your/fork/.opencode/plugins/plain-style.js"]
209
219
  }
210
220
  ```
211
221
 
222
+ opencode v1 uses the `plugin` key instead.
223
+
212
224
  ## Development
213
225
 
214
226
  The `bench/` directory holds the full benchmark suite: harness, scorer, aggregator, raw results.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vasilvestre/opencode-plain-style",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Plain-language writing rules for opencode agents, injected into every system prompt. Enforces ISO 24495-1 principles on commits, comments, docs, and chat prose.",
5
5
  "keywords": [
6
6
  "opencode-plugin",
@@ -23,10 +23,10 @@
23
23
  "url": "https://github.com/vasilvestre/opencode-plain-style/issues"
24
24
  },
25
25
  "type": "module",
26
- "main": "./.opencode/plugins/plain-style.mjs",
26
+ "main": "./.opencode/plugins/plain-style.js",
27
27
  "exports": {
28
- ".": "./.opencode/plugins/plain-style.mjs",
29
- "./plugin": "./.opencode/plugins/plain-style.mjs"
28
+ ".": "./.opencode/plugins/plain-style.js",
29
+ "./plugin": "./.opencode/plugins/plain-style.js"
30
30
  },
31
31
  "scripts": {
32
32
  "test": "node scripts/check-plugin.mjs"
@@ -1,25 +0,0 @@
1
- // plain-style — OpenCode plugin.
2
- //
3
- // Injects the plain-language rulebook (plain-style.md, beside this file) into
4
- // every chat's system prompt on every LLM request — primary agent and
5
- // subagents alike — so the rules survive context compaction. Pattern cloned
6
- // from the ponytail plugin (experimental.chat.system.transform).
7
- //
8
- // Registered in opencode.jsonc: { "plugin": [..., "./plugins/plain-style.mjs"] }
9
-
10
- import fs from 'fs';
11
- import path from 'path';
12
- import { fileURLToPath } from 'url';
13
-
14
- // Read once at load; a missing file throws here so startup fails loud instead
15
- // of silently injecting nothing.
16
- const rulebook = fs.readFileSync(
17
- path.join(path.dirname(fileURLToPath(import.meta.url)), 'plain-style.md'),
18
- 'utf8',
19
- );
20
-
21
- export default async () => ({
22
- 'experimental.chat.system.transform': async (_input, output) => {
23
- output.system.push(rulebook);
24
- },
25
- });