@markstanden/opencode-docs-plugin 0.9.0 → 0.10.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 +4 -0
- package/README.md +14 -5
- package/package.json +1 -1
- package/src/SKILL.md +9 -6
- package/src/index.ts +108 -46
package/CONVENTION.md
CHANGED
|
@@ -49,6 +49,10 @@ Lake notes — a missing root, or a root outside version control — are compute
|
|
|
49
49
|
per call, not captured at startup: create, remove or version a root and the
|
|
50
50
|
next `check`, `search` and help reflect it without a restart.
|
|
51
51
|
|
|
52
|
+
Validation follows writability: `check` validates only writable knowledge lakes.
|
|
53
|
+
A read-only lake is indexed for search and never validated, and the report names
|
|
54
|
+
it as skipped.
|
|
55
|
+
|
|
52
56
|
The excluded areas — never scanned, never validated in a knowledge lake — are
|
|
53
57
|
`plans/` (scratch, gitignored, deleted when the work lands), `requirements/`
|
|
54
58
|
and `wiki/` (vendored mirrors). The wiki profile has no such exclusions; only
|
package/README.md
CHANGED
|
@@ -169,6 +169,12 @@ Lake entries:
|
|
|
169
169
|
| `name` | Lower-case dashes; defaults to the root folder name. `instructions` is reserved. |
|
|
170
170
|
| `writable` | Defaults true for knowledge classes, fixed false for `wiki`. |
|
|
171
171
|
|
|
172
|
+
Validation follows writability: `check` validates a writable knowledge lake
|
|
173
|
+
only. A read-only knowledge lake, and every `wiki` lake, is indexed for search
|
|
174
|
+
and never validated — the plugin cannot write there, so findings could not be
|
|
175
|
+
fixed through it. Each skipped lake is named in the report, so "no findings" is
|
|
176
|
+
never mistaken for compliance.
|
|
177
|
+
|
|
172
178
|
Lake paths never follow symlinks: a symlinked doc is skipped by search and
|
|
173
179
|
`check`, refused by `read`, and refused by `add`. The configured `root` itself
|
|
174
180
|
may be a symlink; the content beneath it must be real.
|
|
@@ -246,12 +252,15 @@ wiki's owners decide.
|
|
|
246
252
|
|
|
247
253
|
## Check
|
|
248
254
|
|
|
249
|
-
`check` validates the knowledge lakes: required front-matter, an H1,
|
|
250
|
-
one claim section (`Facts`, `Inferred`, `Tested`, `Failed approaches`,
|
|
255
|
+
`check` validates the **writable** knowledge lakes: required front-matter, an H1,
|
|
256
|
+
at least one claim section (`Facts`, `Inferred`, `Tested`, `Failed approaches`,
|
|
251
257
|
`Open questions`), no hand-maintained `Date`, a distinctive H1+Keywords
|
|
252
|
-
identity, same-area near-twins, and broken relative `.md` links.
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
identity, same-area near-twins, and broken relative `.md` links. Read-only
|
|
259
|
+
knowledge lakes and wiki lakes are searched in full but never validated; the
|
|
260
|
+
report names each skipped lake and why. Obsolete `Status`/`Supersedes` fields,
|
|
261
|
+
long docs, and unversioned or missing roots are **notes**, not findings. An
|
|
262
|
+
empty findings list means compliant. A very large report is capped with an
|
|
263
|
+
`and N more lines not shown` tail; narrow it with `lake`.
|
|
255
264
|
|
|
256
265
|
There is no lifecycle field. A doc is current or deleted; git is the archive.
|
|
257
266
|
Correct a wrong doc by replacing it in place. A failed attempt is valid
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markstanden/opencode-docs-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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": {
|
package/src/SKILL.md
CHANGED
|
@@ -64,12 +64,15 @@ variable, never the value — and no personal data in GlobalContext or Wiki.
|
|
|
64
64
|
|
|
65
65
|
## Check it
|
|
66
66
|
|
|
67
|
-
`check` audits the lakes: front-matter, an `H1`, a claim
|
|
68
|
-
hand-maintained `Date`, a distinctive identity, near-twins and broken
|
|
69
|
-
links. Obsolete fields, long docs and missing roots are **notes**, not
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
`check` audits the **writable** knowledge lakes: front-matter, an `H1`, a claim
|
|
68
|
+
section, no hand-maintained `Date`, a distinctive identity, near-twins and broken
|
|
69
|
+
relative links. Obsolete fields, long docs and missing roots are **notes**, not
|
|
70
|
+
findings. Validation follows writability — read-only lakes, wiki lakes and
|
|
71
|
+
READMEs are not validated, and skipped lakes are named in the report. A very
|
|
72
|
+
large report is capped with an `and N more lines not shown` tail; narrow it with
|
|
73
|
+
`lake`. `add` enforces the structural rules at the write boundary;
|
|
74
|
+
cross-document audits stay with `check`. `maxDocLines` (default 200, `0`
|
|
75
|
+
disables) is a soft size signal, not a refusal.
|
|
73
76
|
|
|
74
77
|
Run the project's tests and typecheck before proposing a commit, fixing
|
|
75
78
|
failures rather than papering over them. Prepare the commit and **wait for
|
package/src/index.ts
CHANGED
|
@@ -126,7 +126,7 @@ export const DOCS_INPUT = Schema.Struct({
|
|
|
126
126
|
check: Schema.optional(
|
|
127
127
|
Schema.Boolean.annotate({
|
|
128
128
|
description:
|
|
129
|
-
"Validate every knowledge doc: front-matter, an H1, a claim section, no hand-maintained Date, a distinctive H1+Keywords identity, near-twins, broken relative links, a soft length note.
|
|
129
|
+
"Validate every knowledge doc: front-matter, an H1, a claim section, no hand-maintained Date, a distinctive H1+Keywords identity, near-twins, broken relative links, a soft length note. Only writable knowledge lakes are validated; read-only lakes, wiki lakes and READMEs are not, and skipped lakes are named.",
|
|
130
130
|
}),
|
|
131
131
|
),
|
|
132
132
|
read: Schema.optional(
|
|
@@ -541,6 +541,13 @@ export const MIN_TERM_LENGTH = 3;
|
|
|
541
541
|
*/
|
|
542
542
|
export const MAX_RESULTS = 10;
|
|
543
543
|
|
|
544
|
+
/**
|
|
545
|
+
* The most characters a `check` report returns before it is truncated with an
|
|
546
|
+
* `and N more` tail. A finding-heavy run once returned 39,871 characters in one
|
|
547
|
+
* tool result; the cap keeps a report readable. The single knob for the cap.
|
|
548
|
+
*/
|
|
549
|
+
export const MAX_CHECK_CHARS = 8000;
|
|
550
|
+
|
|
544
551
|
/** Split a raw query into lower-cased, non-empty terms, dropping filler. */
|
|
545
552
|
function termsOf(query: string): string[] {
|
|
546
553
|
const terms = query
|
|
@@ -2214,6 +2221,17 @@ export interface LakeSpec {
|
|
|
2214
2221
|
writable: boolean;
|
|
2215
2222
|
}
|
|
2216
2223
|
|
|
2224
|
+
/**
|
|
2225
|
+
* Whether `check` validates a lake: validation follows writability, because a
|
|
2226
|
+
* lake the plugin cannot write is out of its control. A wiki lake is never
|
|
2227
|
+
* validated. One rule, no per-lake override.
|
|
2228
|
+
* @param lake The configured lake.
|
|
2229
|
+
* @returns True when the lake's docs are validated.
|
|
2230
|
+
*/
|
|
2231
|
+
function lakeValidated(lake: LakeSpec): boolean {
|
|
2232
|
+
return lake.profile === "knowledge" && lake.writable;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2217
2235
|
/** A lake with its runtime seams. */
|
|
2218
2236
|
export interface Lake extends LakeSpec {
|
|
2219
2237
|
/** Collects this lake's docs. */
|
|
@@ -2971,6 +2989,80 @@ function searchDocs(
|
|
|
2971
2989
|
};
|
|
2972
2990
|
}
|
|
2973
2991
|
|
|
2992
|
+
/** Why a lake is skipped by `check`, in one short line. */
|
|
2993
|
+
function skippedLakeLine(lake: LakeSpec): string {
|
|
2994
|
+
const why = lake.profile === "wiki" ? "wiki class, read-only" : "read-only";
|
|
2995
|
+
return `${lake.name}: ${why}, not validated.`;
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
/**
|
|
2999
|
+
* Truncate a check report at a line boundary, counting the lines cut and
|
|
3000
|
+
* pointing at the narrower report. A finding-heavy lake otherwise floods a
|
|
3001
|
+
* tool result.
|
|
3002
|
+
* @param content The full report text.
|
|
3003
|
+
* @returns The report, within {@link MAX_CHECK_CHARS}, or its capped form.
|
|
3004
|
+
*/
|
|
3005
|
+
function cappedCheck(content: string): string {
|
|
3006
|
+
if (content.length <= MAX_CHECK_CHARS) {
|
|
3007
|
+
return content;
|
|
3008
|
+
}
|
|
3009
|
+
const lines = content.split("\n");
|
|
3010
|
+
const kept: string[] = [];
|
|
3011
|
+
let length = 0;
|
|
3012
|
+
for (const line of lines) {
|
|
3013
|
+
const next = length + (kept.length > 0 ? 1 : 0) + line.length;
|
|
3014
|
+
if (next > MAX_CHECK_CHARS) {
|
|
3015
|
+
break;
|
|
3016
|
+
}
|
|
3017
|
+
kept.push(line);
|
|
3018
|
+
length = next;
|
|
3019
|
+
}
|
|
3020
|
+
const omitted = lines.length - kept.length;
|
|
3021
|
+
const head = kept.length > 0 ? `${kept.join("\n")}\n\n` : "";
|
|
3022
|
+
return (
|
|
3023
|
+
`${head}… and ${omitted} more lines not shown. ` +
|
|
3024
|
+
"Narrow it per lake with the `lake` selector."
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
/** Run one lake's check report and count the docs looked at. */
|
|
3029
|
+
function lakeReport(
|
|
3030
|
+
runtime: Runtime,
|
|
3031
|
+
lake: Lake,
|
|
3032
|
+
): { report: string; checked: number } {
|
|
3033
|
+
const entries = lake.collect();
|
|
3034
|
+
return {
|
|
3035
|
+
report: checkDocs(entries, lake.read, lakeNotes(lake), {
|
|
3036
|
+
maxDocLines: runtime.maxDocLines,
|
|
3037
|
+
...(lake.linkCheck ? { exists: lake.exists } : {}),
|
|
3038
|
+
}),
|
|
3039
|
+
checked: entries.length,
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
/**
|
|
3044
|
+
* Join the validated lakes' reports and the skipped-lake lines. Only the
|
|
3045
|
+
* reports are capped: "no findings" must never read as "compliant" when a lake
|
|
3046
|
+
* was simply not looked at.
|
|
3047
|
+
* @param reports The validated lakes' reports, in configuration order.
|
|
3048
|
+
* @param skipped One-line reasons for each lake `check` did not validate.
|
|
3049
|
+
* @returns The check result text.
|
|
3050
|
+
*/
|
|
3051
|
+
function assembleCheck(
|
|
3052
|
+
reports: readonly string[],
|
|
3053
|
+
skipped: readonly string[],
|
|
3054
|
+
): string {
|
|
3055
|
+
const report = reports.length > 0 ? cappedCheck(reports.join("\n\n")) : "";
|
|
3056
|
+
const tail = skipped.join("\n");
|
|
3057
|
+
if (report === "" && tail === "") {
|
|
3058
|
+
return "No knowledge lakes configured.";
|
|
3059
|
+
}
|
|
3060
|
+
if (report === "" || tail === "") {
|
|
3061
|
+
return report || tail;
|
|
3062
|
+
}
|
|
3063
|
+
return `${report}\n\n${tail}`;
|
|
3064
|
+
}
|
|
3065
|
+
|
|
2974
3066
|
/** Check the knowledge lakes, optionally limited to one. */
|
|
2975
3067
|
function checkLakes(
|
|
2976
3068
|
runtime: Runtime,
|
|
@@ -2981,54 +3073,24 @@ function checkLakes(
|
|
|
2981
3073
|
if (lake === undefined) {
|
|
2982
3074
|
return { content: `No lake named '${lakeName}'.`, checked: 0 };
|
|
2983
3075
|
}
|
|
2984
|
-
if (lake
|
|
2985
|
-
return {
|
|
2986
|
-
content:
|
|
2987
|
-
`Lake '${lake.name}' is a wiki lake — read-only reference, ` +
|
|
2988
|
-
"not validated.",
|
|
2989
|
-
checked: 0,
|
|
2990
|
-
};
|
|
3076
|
+
if (!lakeValidated(lake)) {
|
|
3077
|
+
return { content: skippedLakeLine(lake), checked: 0 };
|
|
2991
3078
|
}
|
|
2992
|
-
const
|
|
2993
|
-
return {
|
|
2994
|
-
content: checkDocs(entries, lake.read, lakeNotes(lake), {
|
|
2995
|
-
maxDocLines: runtime.maxDocLines,
|
|
2996
|
-
...(lake.linkCheck ? { exists: lake.exists } : {}),
|
|
2997
|
-
}),
|
|
2998
|
-
checked: entries.length,
|
|
2999
|
-
};
|
|
3079
|
+
const { report, checked } = lakeReport(runtime, lake);
|
|
3080
|
+
return { content: cappedCheck(report), checked };
|
|
3000
3081
|
}
|
|
3001
|
-
const knowledge = runtime.lakes.filter(
|
|
3002
|
-
(lake) => lake.profile === "knowledge",
|
|
3003
|
-
);
|
|
3004
3082
|
let checked = 0;
|
|
3005
|
-
const reports =
|
|
3006
|
-
const
|
|
3007
|
-
checked +=
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
});
|
|
3012
|
-
return knowledge.length > 1 || runtime.lakes.length > 1
|
|
3013
|
-
? `${lake.name}:\n${report}`
|
|
3014
|
-
: report;
|
|
3083
|
+
const reports = runtime.lakes.filter(lakeValidated).map((lake) => {
|
|
3084
|
+
const outcome = lakeReport(runtime, lake);
|
|
3085
|
+
checked += outcome.checked;
|
|
3086
|
+
return runtime.lakes.length > 1
|
|
3087
|
+
? `${lake.name}:\n${outcome.report}`
|
|
3088
|
+
: outcome.report;
|
|
3015
3089
|
});
|
|
3016
|
-
const
|
|
3017
|
-
.filter((lake) => lake
|
|
3018
|
-
.map(
|
|
3019
|
-
|
|
3020
|
-
reports.push(
|
|
3021
|
-
`note: wiki lakes are read-only reference and are not validated: ` +
|
|
3022
|
-
`${wiki.join(", ")}.`,
|
|
3023
|
-
);
|
|
3024
|
-
}
|
|
3025
|
-
return {
|
|
3026
|
-
content:
|
|
3027
|
-
reports.length > 0
|
|
3028
|
-
? reports.join("\n\n")
|
|
3029
|
-
: "No knowledge lakes configured.",
|
|
3030
|
-
checked,
|
|
3031
|
-
};
|
|
3090
|
+
const skipped = runtime.lakes
|
|
3091
|
+
.filter((lake) => !lakeValidated(lake))
|
|
3092
|
+
.map(skippedLakeLine);
|
|
3093
|
+
return { content: assembleCheck(reports, skipped), checked };
|
|
3032
3094
|
}
|
|
3033
3095
|
|
|
3034
3096
|
/** Add a doc to the target lake. */
|
|
@@ -3202,7 +3264,7 @@ function lakeInfo(lakes: readonly Lake[]): LakeInfo[] {
|
|
|
3202
3264
|
class: lake.class,
|
|
3203
3265
|
root: lake.root,
|
|
3204
3266
|
writable: lake.writable,
|
|
3205
|
-
validated: lake
|
|
3267
|
+
validated: lakeValidated(lake),
|
|
3206
3268
|
missing: lakeMissing(lake),
|
|
3207
3269
|
}));
|
|
3208
3270
|
}
|