@markstanden/opencode-docs-plugin 0.10.0 → 0.12.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/CONVENTION.md +72 -42
- package/README.md +173 -33
- package/package.json +2 -2
- package/src/SKILL.md +58 -58
- package/src/index.ts +1444 -125
package/CONVENTION.md
CHANGED
|
@@ -4,46 +4,72 @@ This plugin owns this convention: it is what the `docs` tool's `add` writes,
|
|
|
4
4
|
what `check` validates, and what the registered `docs` skill teaches. A
|
|
5
5
|
repository that adopts the plugin takes the convention with it.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
Every doc belongs to exactly one
|
|
10
|
-
in, so a doc cannot mix scopes.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
7
|
+
## Scopes and classes
|
|
8
|
+
|
|
9
|
+
Every doc belongs to exactly one scope. The scope is fixed by the lake it
|
|
10
|
+
lives in, so a doc cannot mix scopes. The config values below never change;
|
|
11
|
+
the labels are how the tool presents them.
|
|
12
|
+
|
|
13
|
+
| Scope | Config value | Where | Reader | Meaning | Writes |
|
|
14
|
+
| ------------ | ------------------ | ----------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
15
|
+
| Repository | `repo` | `<repo>/docs` by default | this repo | understanding or maintaining this particular repository | yes |
|
|
16
|
+
| Personal | `machine` | a personal lake such as `~/doclake` | one person, any machine | tied to a person's devices, accounts, preferences or individual setup — one named host counts | yes |
|
|
17
|
+
| Organisation | `global` | an organisation lake | the owning teams | established shared standards, services or integrations, with their boundary stated | yes, lock down with `writable: false` |
|
|
18
|
+
| Instructions | nested `README.md` | the project tree | code-local how-to | build, run, test, deploy this code | never by the tool |
|
|
19
|
+
| Wiki | `wiki` | a human-readable wiki checkout | humans first | human-facing reference and narrative | no |
|
|
20
|
+
|
|
21
|
+
Place at the **narrowest justified scope**:
|
|
22
|
+
|
|
23
|
+
1. Identify the claim, what it depends on and its evidence. Where the session
|
|
24
|
+
happened is not itself a destination.
|
|
25
|
+
2. Depends on this repository's implementation, architecture or decisions →
|
|
26
|
+
Repository.
|
|
27
|
+
3. Depends on a person's device, account or individual setup → Personal.
|
|
28
|
+
4. An established shared rule or integration → Organisation, recording its
|
|
29
|
+
actual team/service boundary. It need not apply to every team to belong
|
|
30
|
+
there.
|
|
31
|
+
5. Reusability alone is not organisational ownership: a possible
|
|
32
|
+
generalisation from one repo stays with the repo that observed it, and its
|
|
33
|
+
conditions, until broader applicability or ownership is established.
|
|
34
|
+
6. Operational procedures belong to the README editing workflow — `add`
|
|
35
|
+
cannot write them. A knowledge doc may keep the minimal diagnostic
|
|
36
|
+
commands that substantiate its claim and link to the maintained procedure;
|
|
37
|
+
commands are not banned from knowledge docs.
|
|
38
|
+
7. Human narrative follows the wiki's owner workflow. The wiki being
|
|
39
|
+
read-only to the tool is not a reason to write a knowledge lake instead.
|
|
40
|
+
8. Split only when there are independently useful claims with different
|
|
41
|
+
owners or topics. A shared rule plus a genuine repo exception may need two
|
|
42
|
+
linked docs; an uncertain owner does not make a doc unfocused.
|
|
43
|
+
9. If ownership cannot be established, keep an explicitly unresolved proposal
|
|
44
|
+
in the handover and ask a focused question. Never guess a broader scope or
|
|
45
|
+
silently write to a substitute lake.
|
|
46
|
+
|
|
47
|
+
Two answers with different owners means two focused docs, one per scope.
|
|
48
|
+
Configured lakes may carry a `description` saying what belongs in that
|
|
49
|
+
particular destination; when several writable lakes exist, `add` requires an
|
|
50
|
+
explicit `lake` choice.
|
|
30
51
|
|
|
31
52
|
## Where knowledge lives
|
|
32
53
|
|
|
33
54
|
Durable knowledge is one Markdown file per topic under a lake root, grouped by
|
|
34
55
|
area: `<area>/<name>.md`. The default repo lake is `<location>/docs`; its docs
|
|
35
|
-
are
|
|
56
|
+
are Repository scope. A repo adds other local roots in
|
|
36
57
|
`.opencode/docs-lakes.json`; a personal or managed base file lists the
|
|
37
|
-
|
|
58
|
+
personal, organisation and wiki lakes.
|
|
38
59
|
|
|
39
60
|
Lakes are named, and several per class are supported. A root nested inside
|
|
40
61
|
another lake's root belongs to the **narrowest enclosing lake**: the outer lake
|
|
41
|
-
skips it, search returns the inner lake's entry, a bare `read` resolves
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
skips it, search returns the inner lake's entry, and a bare `read` resolves
|
|
63
|
+
there. Nesting resolves the physical owner of an already identified target; it
|
|
64
|
+
never infers the semantic owner of a new claim — `add` without a `lake`
|
|
65
|
+
selector requires exactly one configured writable knowledge lake, and any
|
|
66
|
+
other number refuses with the candidate list. Nesting is decided on
|
|
67
|
+
real paths, so reaching one root through a symlink cannot defeat containment,
|
|
68
|
+
and a write addressed through an outer route that physically resolves inside
|
|
69
|
+
a nested lake's tree refuses and names its physical owner. Two lakes that do
|
|
70
|
+
not nest holding the same relative path stay a refusal — pass `lake`.
|
|
71
|
+
Near-twin checks apply inside one lake and never pair across the nesting
|
|
72
|
+
boundary.
|
|
47
73
|
|
|
48
74
|
Lake notes — a missing root, or a root outside version control — are computed
|
|
49
75
|
per call, not captured at startup: create, remove or version a root and the
|
|
@@ -115,11 +141,12 @@ git. Optional invisible metadata may be added later; it is not required.
|
|
|
115
141
|
`add` refuses a near-twin write and returns the candidates to reconcile
|
|
116
142
|
against — edit before add, merge near-twins, split a bloated doc into
|
|
117
143
|
per-topic files.
|
|
118
|
-
- The reconcile applies to
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
- The reconcile applies to every **create**, derived and explicit paths alike:
|
|
145
|
+
`add` refuses a near-twin and returns the candidates to reconcile against.
|
|
146
|
+
A non-blank `allowNearTwinReason` overrides that guard alone, for a
|
|
147
|
+
deliberate distinct doc; it never bypasses target existence or the
|
|
148
|
+
structural checks. Replacement is `mode: "replace"` with the existing
|
|
149
|
+
doc's path — wholesale, and never an implicit create.
|
|
123
150
|
- Near-twins are per lake. The same topic at repo and global scope is
|
|
124
151
|
legitimate; the narrower doc states its delta and links up.
|
|
125
152
|
|
|
@@ -127,10 +154,10 @@ git. Optional invisible metadata may be added later; it is not required.
|
|
|
127
154
|
|
|
128
155
|
- One question per doc. One topic per doc.
|
|
129
156
|
- More than one H2 aimed at different topics or audiences means split.
|
|
130
|
-
- Never write an omnibus notes doc to avoid choosing a
|
|
131
|
-
- The narrowest
|
|
132
|
-
rule; the narrower doc states only its delta and links up.
|
|
133
|
-
- Do not copy between knowledge
|
|
157
|
+
- Never write an omnibus notes doc to avoid choosing a scope.
|
|
158
|
+
- The narrowest justified scope owns the claim. A broader scope owns the
|
|
159
|
+
general rule; the narrower doc states only its delta and links up.
|
|
160
|
+
- Do not copy between knowledge scopes. Move or link.
|
|
134
161
|
- Instructions own commands. Knowledge docs own reasons and link to the
|
|
135
162
|
command. Knowledge docs link to the wiki page for human narrative.
|
|
136
163
|
- Wiki duplication is expected, not a defect. If a wiki page drifts from a
|
|
@@ -145,13 +172,16 @@ git. Optional invisible metadata may be added later; it is not required.
|
|
|
145
172
|
|
|
146
173
|
- No secrets, credentials, tokens, or customer data. Name the vault or variable,
|
|
147
174
|
never the value.
|
|
148
|
-
- No personal data in
|
|
175
|
+
- No personal data in Organisation or Wiki.
|
|
149
176
|
- The wiki profile is read-only to the tool. Its own owners review changes.
|
|
150
177
|
|
|
151
178
|
## Lifecycle
|
|
152
179
|
|
|
153
180
|
A doc is current or deleted. Correct a wrong doc by replacing it in place with
|
|
154
|
-
`
|
|
181
|
+
`mode: "replace"`, the doc's path, and the revision token your last read or
|
|
182
|
+
write returned in its result text — a stale token refuses rather than
|
|
183
|
+
overwriting a newer edit. Delete
|
|
184
|
+
an obsolete doc through the shell with
|
|
155
185
|
explicit approval; there is no `delete` verb. A failed attempt is still valid
|
|
156
186
|
knowledge when it records the outcome and the current truth.
|
|
157
187
|
|
package/README.md
CHANGED
|
@@ -5,20 +5,25 @@ knowledge **lakes** it can actually use: a `docs` tool and a `/docs` command
|
|
|
5
5
|
over configured Markdown trees, nested `README.md` files in the project, and the
|
|
6
6
|
writing convention those docs follow.
|
|
7
7
|
|
|
8
|
-
Docs are grouped by **
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
| **
|
|
14
|
-
| **
|
|
15
|
-
| **
|
|
16
|
-
| **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
Docs are grouped by **scope** (config values never change; labels are how the
|
|
9
|
+
tool presents them):
|
|
10
|
+
|
|
11
|
+
| Scope | Config value | Where | Use it for | Writes |
|
|
12
|
+
| ---------------- | --------------------------- | ------------------------------- | ----------------------------------------------- | ------------- |
|
|
13
|
+
| **Repository** | `repo` / nested `README.md` | `<repo>/docs` by default | this repo's traps, decisions, landmines | yes |
|
|
14
|
+
| **Personal** | `machine` | personal lake, e.g. `~/doclake` | a person's devices, accounts and setup | yes |
|
|
15
|
+
| **Organisation** | `global` | organisation lake | standards and integrations, with their boundary | yes, reviewed |
|
|
16
|
+
| **Instructions** | implicit | nested `README.md` | build, run, test, deploy this code | never |
|
|
17
|
+
| **Wiki** | `wiki` | human-readable wiki checkout | narrative for humans | read-only |
|
|
18
|
+
|
|
19
|
+
Place knowledge at the **narrowest justified scope**. Identify the claim and
|
|
20
|
+
what it depends on: this repo → Repository; a person's device, account or
|
|
21
|
+
setup → Personal; an established shared rule → Organisation, stating its
|
|
22
|
+
actual team boundary; operational steps → the README workflow; human
|
|
23
|
+
narrative → the wiki's owners. Reusability alone is not organisational
|
|
24
|
+
ownership, and unresolved ownership stays an explicit question — never a
|
|
25
|
+
guessed destination. The full rulebook is [CONVENTION.md](CONVENTION.md); the
|
|
26
|
+
registered `docs` skill carries the working version.
|
|
22
27
|
|
|
23
28
|
## The tool and command
|
|
24
29
|
|
|
@@ -26,7 +31,8 @@ version.
|
|
|
26
31
|
(`path — Area — date — Summary`); a search ranks docs over their front-matter,
|
|
27
32
|
path and body identifiers (plus wiki prose), falling back to the closest
|
|
28
33
|
matches rather than a dead end; `read` returns a doc verbatim and accepts a
|
|
29
|
-
`lake` or `lake:path`; `add` writes knowledge through a typed DTO
|
|
34
|
+
`lake` or `lake:path`; `add` writes knowledge through a typed DTO with an
|
|
35
|
+
explicit `mode`; `check`
|
|
30
36
|
validates the knowledge lakes. Nested project `README.md` files are indexed
|
|
31
37
|
too — `.gitignore` rules decide what is skipped — but only for search: `add`
|
|
32
38
|
and `check` never touch them, and their results are prefixed `instructions:`
|
|
@@ -89,21 +95,27 @@ The simplest setup uses `root` — one repo lake:
|
|
|
89
95
|
|
|
90
96
|
`root` expands `~`, resolves a relative path against the location, and defaults
|
|
91
97
|
to `<location>/docs`. A blank value counts as unset. `root` declares a **repo**
|
|
92
|
-
lake; for a personal or
|
|
93
|
-
name follows the root folder unless you set the `name` option beside it
|
|
98
|
+
lake; for a personal or organisation lake, use `lakes` with the class instead.
|
|
99
|
+
Its name follows the root folder unless you set the `name` option beside it,
|
|
100
|
+
and `description` documents what belongs there the same way:
|
|
94
101
|
|
|
95
102
|
```jsonc
|
|
96
103
|
{
|
|
97
104
|
"plugins": [
|
|
98
105
|
{
|
|
99
106
|
"package": "@markstanden/opencode-docs-plugin",
|
|
100
|
-
"options": {
|
|
107
|
+
"options": {
|
|
108
|
+
"root": "documentation",
|
|
109
|
+
"name": "doclake",
|
|
110
|
+
"description": "This repository's decisions and traps.",
|
|
111
|
+
},
|
|
101
112
|
},
|
|
102
113
|
],
|
|
103
114
|
}
|
|
104
115
|
```
|
|
105
116
|
|
|
106
|
-
`name` only
|
|
117
|
+
`name` and `description` only apply to `root`; inside `lakes`, set them on the
|
|
118
|
+
entry.
|
|
107
119
|
|
|
108
120
|
For several lakes, list them inline, in a base file, or both. The base file is
|
|
109
121
|
usually a personal or managed file; the repo file adds local lakes:
|
|
@@ -129,7 +141,12 @@ usually a personal or managed file; the repo file adds local lakes:
|
|
|
129
141
|
```jsonc
|
|
130
142
|
{
|
|
131
143
|
"lakes": [
|
|
132
|
-
{
|
|
144
|
+
{
|
|
145
|
+
"class": "machine",
|
|
146
|
+
"root": "~/doclake",
|
|
147
|
+
"name": "doclake",
|
|
148
|
+
"description": "Personal devices, workstation configuration and individual tooling preferences.",
|
|
149
|
+
},
|
|
133
150
|
{ "class": "global", "root": "~/org-docs", "name": "global" },
|
|
134
151
|
{ "class": "wiki", "root": "~/org-wiki", "name": "wiki" },
|
|
135
152
|
],
|
|
@@ -162,12 +179,13 @@ docs live elsewhere.
|
|
|
162
179
|
|
|
163
180
|
Lake entries:
|
|
164
181
|
|
|
165
|
-
| Field
|
|
166
|
-
|
|
|
167
|
-
| `class`
|
|
168
|
-
| `root`
|
|
169
|
-
| `name`
|
|
170
|
-
| `
|
|
182
|
+
| Field | Meaning |
|
|
183
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
184
|
+
| `class` | `repo`, `machine`, `global` or `wiki`; default `repo`. Presented as Repository, Personal, Organisation, Wiki. |
|
|
185
|
+
| `root` | The lake root. Required for `machine`/`global`/`wiki`; defaults to `<location>/docs` for `repo`. |
|
|
186
|
+
| `name` | Lower-case dashes; defaults to the root folder name. `instructions` is reserved. |
|
|
187
|
+
| `description` | Optional. What belongs in this particular destination; shown in `help` and refusal candidate lists. Trimmed, blank counts as absent, non-string fails at startup. Descriptive data only — never indexed for search. |
|
|
188
|
+
| `writable` | Defaults true for knowledge classes, fixed false for `wiki`. |
|
|
171
189
|
|
|
172
190
|
Validation follows writability: `check` validates a writable knowledge lake
|
|
173
191
|
only. A read-only knowledge lake, and every `wiki` lake, is indexed for search
|
|
@@ -193,11 +211,13 @@ declarations may reach the tree by different routes and the nested doc is
|
|
|
193
211
|
still indexed once.
|
|
194
212
|
|
|
195
213
|
A doc belongs to the **narrowest enclosing lake**. With nested roots, `search`
|
|
196
|
-
returns the inner lake's entry and a bare `read` resolves to it
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
214
|
+
returns the inner lake's entry and a bare `read` resolves to it. `add` never
|
|
215
|
+
infers a destination from nesting: an omitted `lake` needs exactly one
|
|
216
|
+
configured writable knowledge lake (see below), and a write addressed through
|
|
217
|
+
an outer route that physically resolves inside a nested lake's tree refuses,
|
|
218
|
+
naming its physical owner. Two lakes that do not nest holding the same
|
|
219
|
+
relative path stay a refusal — pass `lake`. Near-twin checks apply inside one
|
|
220
|
+
lake and never pair across the nesting boundary.
|
|
201
221
|
|
|
202
222
|
```jsonc
|
|
203
223
|
{
|
|
@@ -233,6 +253,26 @@ it `— missing`, and `add` refuses with the path rather than bootstrapping a
|
|
|
233
253
|
tree. Create the directory deliberately. Area folders beneath an existing root
|
|
234
254
|
are still created on write.
|
|
235
255
|
|
|
256
|
+
### Choosing a write destination
|
|
257
|
+
|
|
258
|
+
`add` writes through the `lake` selector deliberately; it never guesses.
|
|
259
|
+
|
|
260
|
+
- An explicit `lake` selects exactly that lake: an unknown, read-only, or
|
|
261
|
+
missing-root lake refuses — it never falls back to another.
|
|
262
|
+
- With exactly one configured writable knowledge lake, an omitted `lake` uses
|
|
263
|
+
it. That is a convenience, not a classification: check the destination fits
|
|
264
|
+
before writing.
|
|
265
|
+
- With several writable knowledge lakes (two `machine` lakes included), an
|
|
266
|
+
omitted `lake` refuses before writing and lists **every** writable
|
|
267
|
+
candidate — name, scope label and config value, root, availability,
|
|
268
|
+
description. Nesting, class, configuration order and missing roots change
|
|
269
|
+
nothing: folder depth cannot infer the semantic owner of a claim, and no
|
|
270
|
+
candidate is auto-selected for being the deepest.
|
|
271
|
+
- With no writable knowledge lake, `add` refuses; wiki and read-only lakes are
|
|
272
|
+
never written. A temporarily missing root still counts as a candidate, so
|
|
273
|
+
dormancy cannot silently change the destination.
|
|
274
|
+
- Searching and checking never need a destination; they run across all lakes.
|
|
275
|
+
|
|
236
276
|
Root and git notes are computed per call, not captured at startup: a root that
|
|
237
277
|
appears, disappears or gains a git checkout is reflected by the next `check`,
|
|
238
278
|
`search` and help without a restart.
|
|
@@ -263,8 +303,97 @@ empty findings list means compliant. A very large report is capped with an
|
|
|
263
303
|
`and N more lines not shown` tail; narrow it with `lake`.
|
|
264
304
|
|
|
265
305
|
There is no lifecycle field. A doc is current or deleted; git is the archive.
|
|
266
|
-
Correct a wrong doc by replacing it in place
|
|
267
|
-
knowledge when it records the outcome and what is true now.
|
|
306
|
+
Correct a wrong doc by replacing it in place with `mode: "replace"`. A failed
|
|
307
|
+
attempt is valid knowledge when it records the outcome and what is true now.
|
|
308
|
+
|
|
309
|
+
## Creating and replacing
|
|
310
|
+
|
|
311
|
+
`add` carries an explicit mutation `mode`; the implicit path-based update of
|
|
312
|
+
earlier versions is intentionally unsupported.
|
|
313
|
+
|
|
314
|
+
```jsonc
|
|
315
|
+
// Create: path is optional — omitted derives <Area>/<slug>.md from the H1.
|
|
316
|
+
{
|
|
317
|
+
"add": {
|
|
318
|
+
"mode": "create",
|
|
319
|
+
"metadata": { "Area": "decisions", "Keywords": ["queue"], "Summary": "Why Kafka." },
|
|
320
|
+
"body": "# Queue choice\n\n### Facts\n\n- Kafka, for the replay window.\n"
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Replace: path is required and must name an existing doc, and
|
|
325
|
+
// expectedRevision must match the doc's current bytes — the token your last
|
|
326
|
+
// read (or write) returned. The body is wholesale: omitted content is lost.
|
|
327
|
+
// A stale token refuses; read again, reconcile, resubmit.
|
|
328
|
+
{
|
|
329
|
+
"add": {
|
|
330
|
+
"mode": "replace",
|
|
331
|
+
"path": "decisions/queue-choice.md",
|
|
332
|
+
"expectedRevision": "<sha256 from the read>",
|
|
333
|
+
"metadata": { "Area": "decisions", "Keywords": ["queue"], "Summary": "Why Kafka." },
|
|
334
|
+
"body": "# Queue choice\n\n### Facts\n\n- Kafka, for the replay window.\n"
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
A `read` of a knowledge lake doc carries the token in its metadata alongside
|
|
340
|
+
the resolved target — `{ read, lake, path, revision }` — and a successful
|
|
341
|
+
create or replace returns the new content's revision the same way. The result
|
|
342
|
+
text also ends with a `docs: revision …` line, so the token is visible to a
|
|
343
|
+
model call and to Code Mode JavaScript, not only in the stored metadata a
|
|
344
|
+
host may treat as UI-only. Wiki reads carry no revision: they promise no
|
|
345
|
+
write-capable workflow.
|
|
346
|
+
|
|
347
|
+
- **Create never overwrites.** A target that already exists refuses, even with
|
|
348
|
+
an `allowNearTwinReason`; pass `mode: "replace"` deliberately.
|
|
349
|
+
- **Every create reconciles.** An explicit path gets the same structural and
|
|
350
|
+
same-area near-twin checks as a derived path. A genuine near-twin refusal is
|
|
351
|
+
answered by `mode: "replace"` — or, for a deliberate distinct doc, a
|
|
352
|
+
non-blank `allowNearTwinReason`, which bypasses only that semantic guard,
|
|
353
|
+
never target existence, path safety, metadata validation, writability or
|
|
354
|
+
destination ambiguity.
|
|
355
|
+
- **Replace never creates.** A missing target refuses; it is never an
|
|
356
|
+
implicit create.
|
|
357
|
+
- **Revisions are content, not history.** A revision is the SHA-256 of the
|
|
358
|
+
doc's exact stored bytes; if the same bytes return later, the token matches
|
|
359
|
+
again. Create takes no `expectedRevision` — its precondition is that the
|
|
360
|
+
target does not exist.
|
|
361
|
+
|
|
362
|
+
## Concurrency limits
|
|
363
|
+
|
|
364
|
+
Publications of one target are serialised by a per-target lock file in a
|
|
365
|
+
hidden `.docs-lock/` directory **beside the target's directory on the real
|
|
366
|
+
filesystem**, keyed on the target's canonical physical path: two
|
|
367
|
+
configurations reaching the same tree by different routes — including a
|
|
368
|
+
permitted symlinked configured root — resolve the same lock location, however
|
|
369
|
+
each runtime's lakes are configured. The lock directory is only ever created
|
|
370
|
+
inside an already-existing ancestor, existing symlinked or non-directory
|
|
371
|
+
objects refuse without being touched, and abandoned locks are never stolen
|
|
372
|
+
automatically (confirm the owner is gone, then remove the named file). Older
|
|
373
|
+
plugin versions lock at the configured root instead: quiesce or restart them
|
|
374
|
+
before relying on this protocol — no automatic migration, and any unknown
|
|
375
|
+
existing locks are left alone.
|
|
376
|
+
|
|
377
|
+
A replacement re-reads and re-checks the revision under the lock and publishes
|
|
378
|
+
through a **temp file this operation exclusively creates** (an ordinary
|
|
379
|
+
pathname create that never follows a pre-existing symlink or truncates a
|
|
380
|
+
foreign file) and an atomic whole-file rename — the all-or-nothing swap is a
|
|
381
|
+
**replacement-only guarantee**. A create opens the target exclusively and then
|
|
382
|
+
writes its bytes, so a reader inside that window can observe the new file
|
|
383
|
+
before every byte is stored; a create's promise is exclusive naming plus
|
|
384
|
+
eventually-complete bytes, not atomic first visibility. A physically
|
|
385
|
+
addressed write that reaches a nested lake's tree through an outer route is
|
|
386
|
+
refused with that lake named, so a writable outer lake cannot make read-only
|
|
387
|
+
nested content writable.
|
|
388
|
+
|
|
389
|
+
The guarantee covers **cooperating plugin writers** and pre-existing
|
|
390
|
+
operational-path symlinks — the native tool, the Code Mode twin and `/docs`
|
|
391
|
+
all use the same guarded route. It is not armour against an arbitrary local
|
|
392
|
+
attacker who can swap directories between individual syscalls, and a manual
|
|
393
|
+
editor or unrelated program that ignores the protocol can still race a
|
|
394
|
+
publication; changes visible before the guarded commit are detected, but this
|
|
395
|
+
is not a filesystem-wide compare-and-swap. A busy lock refuses with its
|
|
396
|
+
recorded owner instead of waiting indefinitely.
|
|
268
397
|
|
|
269
398
|
## Status
|
|
270
399
|
|
|
@@ -285,6 +414,17 @@ Tests run the plugin source directly under Node's type-stripping (`noEmit`,
|
|
|
285
414
|
ranking harness pins behaviour against — edit it directly. It holds invented
|
|
286
415
|
paths and summaries only; never snapshot a real docs tree into it.
|
|
287
416
|
|
|
417
|
+
`tests/fixtures/placement-cases.json` is a synthetic evaluation fixture for
|
|
418
|
+
the placement guidance (cases E01–E16, each with a claim, evidence,
|
|
419
|
+
destinations, expected scope or action, acceptable rationale and tempting
|
|
420
|
+
wrong answer). To evaluate the guidance behaviourally: run the cases through
|
|
421
|
+
a model with the docs tools mounted, holding model, settings and tool
|
|
422
|
+
configuration constant, and score first-choice accuracy, unnecessary
|
|
423
|
+
splitting, inappropriate fallback writes and the tool calls needed to correct
|
|
424
|
+
recording. No behavioural trials have been run with the current wording; a
|
|
425
|
+
fixture that merely asserts expected answers in Node is not evidence a model
|
|
426
|
+
follows the guidance.
|
|
427
|
+
|
|
288
428
|
## Licence
|
|
289
429
|
|
|
290
430
|
MIT — see [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markstanden/opencode-docs-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode V2 plugin: a `docs` tool and `/docs` command over configurable knowledge lakes (repo, machine, global and read-only wiki), nested README.md search, and the writing convention they share",
|
|
6
6
|
"repository": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "node --test 'tests/**/*.test.ts'",
|
|
24
|
-
"test:coverage": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info 'tests/**/*.test.ts'",
|
|
24
|
+
"test:coverage": "mkdir -p coverage && node --test --experimental-test-coverage --test-coverage-include='src/**/*.ts' --test-coverage-exclude='**/*.test.ts' --test-reporter=lcov --test-reporter-destination=coverage/lcov.info 'tests/**/*.test.ts'",
|
|
25
25
|
"test:watch": "node --test --watch 'tests/**/*.test.ts'",
|
|
26
26
|
"typecheck": "tsc --noEmit"
|
|
27
27
|
},
|
package/src/SKILL.md
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
1
|
# Docs
|
|
2
2
|
|
|
3
|
-
Durable knowledge lives in **lakes**: **find** it
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
| **
|
|
12
|
-
| **
|
|
13
|
-
| **
|
|
14
|
-
| **
|
|
15
|
-
| **Wiki**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
Durable knowledge lives in **lakes**: **find** it, or **record** one focused
|
|
4
|
+
doc at the narrowest justified scope. Call the `docs` tool; inside `execute`
|
|
5
|
+
it is `tools.docs.docs_search(…)`, same verbs.
|
|
6
|
+
|
|
7
|
+
## Scopes
|
|
8
|
+
|
|
9
|
+
| Scope | Config | Where | Use it for | Writes |
|
|
10
|
+
| ---------------- | --------- | --------------------------- | --------------------------------- | ------------- |
|
|
11
|
+
| **Repository** | `repo` | `<repo>/docs` | this repo's traps and decisions | yes |
|
|
12
|
+
| **Personal** | `machine` | personal lake (`~/doclake`) | a person's devices and setup | yes |
|
|
13
|
+
| **Organisation** | `global` | organisation lake | shared standards and integrations | yes, reviewed |
|
|
14
|
+
| **Instructions** | implicit | nested `README.md` | build, run, test, deploy | never |
|
|
15
|
+
| **Wiki** | `wiki` | human-readable wiki | narrative for humans | read-only |
|
|
16
|
+
|
|
17
|
+
Place by dependency: this repo's code or decisions → Repository; a person's
|
|
18
|
+
device, account or setup (a named host counts) → Personal; an established
|
|
19
|
+
shared rule → Organisation, boundary stated — reusability alone is not
|
|
20
|
+
ownership; operational steps → the README workflow; human narrative → the
|
|
21
|
+
wiki's owners. Split only for claims with different owners; unresolved
|
|
22
|
+
ownership stays an explicit proposal, never a guessed destination.
|
|
23
|
+
|
|
24
|
+
`help` lists each lake's scope and description; `add` writes the sole writable
|
|
25
|
+
lake without `lake`, else refuses with candidates — pass `lake`.
|
|
19
26
|
|
|
20
27
|
## Find it
|
|
21
28
|
|
|
22
|
-
A **blank `search`** lists recent docs
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
when longer terms exist; otherwise all query terms are kept.
|
|
26
|
-
lakes and ranking.
|
|
29
|
+
A **blank `search`** lists recent docs; a **`search`** ranks them, matching
|
|
30
|
+
terms as **substrings** (a typo finds nothing) and falling back to
|
|
31
|
+
`Closest matches`. Terms shorter than `MIN_TERM_LENGTH` (3) are dropped
|
|
32
|
+
when longer terms exist; otherwise all query terms are kept.
|
|
27
33
|
|
|
28
|
-
Open the match with `read
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
dot-directories and `node_modules`.
|
|
34
|
+
Open the match with `read` (root-relative, or `lake:path`); a README
|
|
35
|
+
result is `instructions:path` for the built-in `read`. Consult
|
|
36
|
+
matching docs before a broad grep; a tie ranks repo, instructions, global,
|
|
37
|
+
machine, wiki. Knowledge lakes exclude `plans/`, `requirements/` and `wiki/`
|
|
38
|
+
at the root; wiki lakes only dot-directories and `node_modules`.
|
|
34
39
|
|
|
35
40
|
## Record it
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
authoritative
|
|
39
|
-
|
|
40
|
-
1. **Read first, never duplicate.** Read the docs relevant to the task: the same
|
|
41
|
-
area, keyword hits on its entities, or links from touched docs.
|
|
42
|
-
2. **Choose one class** from the table — the narrowest that holds the claim.
|
|
43
|
-
3. **Reconcile, then add.** Edit before add; merge near-twins; split a bloated
|
|
44
|
-
doc per topic; after a split, merge or move, repoint inbound references and
|
|
45
|
-
delete the emptied doc — no superseded stubs.
|
|
42
|
+
Record to `<area>/<title>.md` under the right lake (`CONVENTION.md` is
|
|
43
|
+
authoritative):
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
1. **Read first, never duplicate:** the same area, keyword hits on its
|
|
46
|
+
entities, or links from touched docs.
|
|
47
|
+
2. **Choose one scope** from the table — the narrowest that holds the claim;
|
|
48
|
+
one artifact per topic and scope.
|
|
49
|
+
3. **Reconcile, then add:** edit before add; merge near-twins; split per topic,
|
|
50
|
+
repoint references, delete the emptied doc — no superseded stubs.
|
|
49
51
|
|
|
50
|
-
Record through the typed `add`: metadata (`Area`, `Keywords`, `Summary`)
|
|
51
|
-
body with an `H1` and a claim section — Facts, Inferred, Tested, Failed approaches, Open questions.
|
|
52
|
+
Record through the typed `add`: metadata (`Area`, `Keywords`, `Summary`) plus a
|
|
53
|
+
body with an `H1` and a claim section — Facts, Inferred, Tested, Failed approaches, Open questions. `mode:"create"` writes a new doc (never overwrites; near-twins refuse unless `allowNearTwinReason` explains the distinction). `mode:"replace"` wholesale-replaces the doc at `path` against your `expectedRevision` (omitted content is lost; a missing target or stale token refuses, never creates). Refused always: blank metadata, traversal, a non-`.md` or symlinked target, no claim section.
|
|
52
54
|
|
|
53
|
-
A doc is current or deleted;
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
A doc is current or deleted; a failed attempt is valid knowledge (what was
|
|
56
|
+
tried, why it failed, what is true now). Knowledge reads and writes end with a
|
|
57
|
+
`docs: revision` line: the replace token.
|
|
56
58
|
|
|
57
|
-
Lead with the actionable fact,
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
Lead with the actionable fact, its reason, limits and evidence; drop
|
|
60
|
+
chronology and empty sections. Keep the
|
|
61
|
+
service names, errors, paths and commands someone will search for; the
|
|
60
62
|
`Summary` decides whether to open the file.
|
|
61
63
|
|
|
62
64
|
Never record secrets, credentials, tokens or customer data — name the vault or
|
|
63
|
-
variable, never the value — and no personal data in
|
|
65
|
+
variable, never the value — and no personal data in Organisation or Wiki.
|
|
64
66
|
|
|
65
67
|
## Check it
|
|
66
68
|
|
|
67
69
|
`check` audits the **writable** knowledge lakes: front-matter, an `H1`, a claim
|
|
68
70
|
section, no hand-maintained `Date`, a distinctive identity, near-twins and broken
|
|
69
|
-
relative links. Obsolete fields, long docs
|
|
70
|
-
|
|
71
|
-
READMEs are not validated
|
|
72
|
-
large report is capped with an `and N more lines not shown` tail
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
failures rather than papering over them. Prepare the commit and **wait for
|
|
79
|
-
explicit approval** — recording findings is not commit approval. Never push.
|
|
71
|
+
relative links. Obsolete fields, long docs (`maxDocLines`, default 200, `0`
|
|
72
|
+
disables) and missing roots are **notes**, not findings. Read-only lakes, wiki
|
|
73
|
+
lakes and READMEs are searched but not validated; skipped lakes are named, a
|
|
74
|
+
very large report is capped with an `and N more lines not shown` tail. `add`
|
|
75
|
+
enforces the structural rules at the write boundary; cross-document audits
|
|
76
|
+
stay with `check`.
|
|
77
|
+
|
|
78
|
+
Run the project's tests and typecheck before proposing a commit; prepare it
|
|
79
|
+
and **wait for explicit approval** — recording is not approval. Never push.
|