@markstanden/opencode-docs-plugin 0.8.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 +17 -0
- package/README.md +29 -9
- package/package.json +1 -1
- package/src/SKILL.md +9 -6
- package/src/index.ts +214 -71
package/CONVENTION.md
CHANGED
|
@@ -36,6 +36,23 @@ are RepoContext. A repo adds other local roots in
|
|
|
36
36
|
`.opencode/docs-lakes.json`; a personal or managed base file lists the
|
|
37
37
|
machine, global and wiki lakes.
|
|
38
38
|
|
|
39
|
+
Lakes are named, and several per class are supported. A root nested inside
|
|
40
|
+
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 there,
|
|
42
|
+
and `add` writes to the deepest writable knowledge lake. Nesting is decided on
|
|
43
|
+
real paths, so reaching one root through a symlink cannot defeat containment.
|
|
44
|
+
Two lakes that do not nest holding the same relative path stay a refusal —
|
|
45
|
+
pass `lake`. Near-twin checks apply inside one lake and never pair across the
|
|
46
|
+
nesting boundary.
|
|
47
|
+
|
|
48
|
+
Lake notes — a missing root, or a root outside version control — are computed
|
|
49
|
+
per call, not captured at startup: create, remove or version a root and the
|
|
50
|
+
next `check`, `search` and help reflect it without a restart.
|
|
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
|
+
|
|
39
56
|
The excluded areas — never scanned, never validated in a knowledge lake — are
|
|
40
57
|
`plans/` (scratch, gitignored, deleted when the work lands), `requirements/`
|
|
41
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.
|
|
@@ -181,10 +187,17 @@ writable repo lake may sit inside a larger read-only wiki lake, and search,
|
|
|
181
187
|
only decides its own profile and defaults; `repo`, `machine`, `global` and
|
|
182
188
|
`wiki` all skip a nested root the same way.
|
|
183
189
|
|
|
184
|
-
The skip
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
The skip is decided on real paths: every configured root is resolved through
|
|
191
|
+
symlinks, junctions and mapped drives before the comparison, so the two
|
|
192
|
+
declarations may reach the tree by different routes and the nested doc is
|
|
193
|
+
still indexed once.
|
|
194
|
+
|
|
195
|
+
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; `add` writes
|
|
197
|
+
to the deepest writable knowledge lake, so an outer read-only wrapper never
|
|
198
|
+
hijacks the write. Two lakes that do not nest holding the same relative path
|
|
199
|
+
stay a refusal — pass `lake`. Near-twin checks apply inside one lake and never
|
|
200
|
+
pair across the nesting boundary.
|
|
188
201
|
|
|
189
202
|
```jsonc
|
|
190
203
|
{
|
|
@@ -220,6 +233,10 @@ it `— missing`, and `add` refuses with the path rather than bootstrapping a
|
|
|
220
233
|
tree. Create the directory deliberately. Area folders beneath an existing root
|
|
221
234
|
are still created on write.
|
|
222
235
|
|
|
236
|
+
Root and git notes are computed per call, not captured at startup: a root that
|
|
237
|
+
appears, disappears or gains a git checkout is reflected by the next `check`,
|
|
238
|
+
`search` and help without a restart.
|
|
239
|
+
|
|
223
240
|
`maxDocLines` (default 200, `0` disables) is a **soft** note on a long doc, not a
|
|
224
241
|
refusal. `perLakeQuota` (default 3, `0` disables) is the per-source result
|
|
225
242
|
reservation.
|
|
@@ -235,12 +252,15 @@ wiki's owners decide.
|
|
|
235
252
|
|
|
236
253
|
## Check
|
|
237
254
|
|
|
238
|
-
`check` validates the knowledge lakes: required front-matter, an H1,
|
|
239
|
-
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`,
|
|
240
257
|
`Open questions`), no hand-maintained `Date`, a distinctive H1+Keywords
|
|
241
|
-
identity, same-area near-twins, and broken relative `.md` links.
|
|
242
|
-
|
|
243
|
-
|
|
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`.
|
|
244
264
|
|
|
245
265
|
There is no lifecycle field. A doc is current or deleted; git is the archive.
|
|
246
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. */
|
|
@@ -2227,8 +2245,12 @@ export interface Lake extends LakeSpec {
|
|
|
2227
2245
|
* existing root; throws when the root is absent rather than creating it.
|
|
2228
2246
|
*/
|
|
2229
2247
|
write: (path: string, contents: string) => void;
|
|
2230
|
-
/**
|
|
2231
|
-
|
|
2248
|
+
/**
|
|
2249
|
+
* Legacy seam notes only. Real lakes compute their environment notes at
|
|
2250
|
+
* call time — a root that appears, disappears or gains a git checkout is
|
|
2251
|
+
* re-probed on the next `check`, `search` and help, never captured here.
|
|
2252
|
+
*/
|
|
2253
|
+
notes?: readonly string[];
|
|
2232
2254
|
/** Whether `check` should verify relative links; false for the legacy seam. */
|
|
2233
2255
|
linkCheck: boolean;
|
|
2234
2256
|
}
|
|
@@ -2314,7 +2336,6 @@ export function createLake(
|
|
|
2314
2336
|
};
|
|
2315
2337
|
return {
|
|
2316
2338
|
...spec,
|
|
2317
|
-
notes: unversionedNote(spec.root, spec.name),
|
|
2318
2339
|
linkCheck: true,
|
|
2319
2340
|
collect: () =>
|
|
2320
2341
|
collectDocs(spec.root, (paths) => fallbackDates(spec.root, paths), {
|
|
@@ -2346,18 +2367,27 @@ export function createLake(
|
|
|
2346
2367
|
|
|
2347
2368
|
/**
|
|
2348
2369
|
* Build every configured lake, each aware of the others so a nested root is
|
|
2349
|
-
* collected once.
|
|
2370
|
+
* collected once. Nesting is decided on real paths, so a symlinked root cannot
|
|
2371
|
+
* defeat containment: the skip paths handed to the walker are re-anchored
|
|
2372
|
+
* under the collecting lake's configured root.
|
|
2350
2373
|
* @param specs The configured lakes.
|
|
2351
2374
|
* @returns The runtime lakes.
|
|
2352
2375
|
*/
|
|
2353
2376
|
export function createLakes(specs: readonly LakeSpec[]): Lake[] {
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2377
|
+
const realRoots = specs.map((spec) => realPathOf(spec.root));
|
|
2378
|
+
return specs.map((spec, index) => {
|
|
2379
|
+
const own = realRoots[index] ?? spec.root;
|
|
2380
|
+
const skip = specs.flatMap((other, otherIndex) => {
|
|
2381
|
+
if (otherIndex === index) {
|
|
2382
|
+
return [];
|
|
2383
|
+
}
|
|
2384
|
+
const otherRoot = realRoots[otherIndex] ?? other.root;
|
|
2385
|
+
return otherRoot !== own && isUnder(otherRoot, own)
|
|
2386
|
+
? [join(spec.root, relative(own, otherRoot))]
|
|
2387
|
+
: [];
|
|
2388
|
+
});
|
|
2389
|
+
return createLake(spec, { skip });
|
|
2390
|
+
});
|
|
2361
2391
|
}
|
|
2362
2392
|
|
|
2363
2393
|
/** The lake classes the configuration accepts. */
|
|
@@ -2819,6 +2849,44 @@ function findLake(lakes: readonly Lake[], name: string): Lake | undefined {
|
|
|
2819
2849
|
return lakes.find((lake) => lake.name === name);
|
|
2820
2850
|
}
|
|
2821
2851
|
|
|
2852
|
+
/**
|
|
2853
|
+
* The environment notes for one lake, probed at call time. Legacy seams keep
|
|
2854
|
+
* their captured notes; every real lake re-probes, so a root that appears,
|
|
2855
|
+
* disappears or gains a git checkout is reported by the next surface that
|
|
2856
|
+
* looks — `check`, `search` and help never disagree with a stale capture.
|
|
2857
|
+
* @param lake The lake to describe.
|
|
2858
|
+
* @returns The note lines.
|
|
2859
|
+
*/
|
|
2860
|
+
function lakeNotes(lake: Lake): readonly string[] {
|
|
2861
|
+
return lake.root === ""
|
|
2862
|
+
? (lake.notes ?? [])
|
|
2863
|
+
: unversionedNote(lake.root, lake.name);
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
/** Whether a lake root is absent, probed at call time. */
|
|
2867
|
+
function lakeMissing(lake: Lake): boolean {
|
|
2868
|
+
return lake.root !== "" && !existsSync(lake.root);
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
/**
|
|
2872
|
+
* How many knowledge lakes enclose a lake — the nesting depth used to pick the
|
|
2873
|
+
* narrowest owner of a path and the deepest writable target for `add`. Wiki
|
|
2874
|
+
* lakes never enclose anything.
|
|
2875
|
+
* @param lake The lake to measure.
|
|
2876
|
+
* @param lakes The configured lakes.
|
|
2877
|
+
* @returns The number of enclosing knowledge lakes.
|
|
2878
|
+
*/
|
|
2879
|
+
function nestingDepth(lake: Lake, lakes: readonly Lake[]): number {
|
|
2880
|
+
const root = realPathOf(lake.root);
|
|
2881
|
+
return lakes.filter((other) => {
|
|
2882
|
+
if (other === lake || other.profile === "wiki") {
|
|
2883
|
+
return false;
|
|
2884
|
+
}
|
|
2885
|
+
const otherRoot = realPathOf(other.root);
|
|
2886
|
+
return otherRoot !== root && isUnder(root, otherRoot);
|
|
2887
|
+
}).length;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2822
2890
|
/** The lake `add` writes to, or the refusal reason. */
|
|
2823
2891
|
function writableLake(
|
|
2824
2892
|
lakes: readonly Lake[],
|
|
@@ -2836,14 +2904,34 @@ function writableLake(
|
|
|
2836
2904
|
}
|
|
2837
2905
|
return lake;
|
|
2838
2906
|
}
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2907
|
+
// The narrowest writable knowledge lake wins: with nested roots the inner
|
|
2908
|
+
// lake owns the file, so an outer read-only lake must not hijack the write.
|
|
2909
|
+
const repos = lakes.filter(
|
|
2910
|
+
(lake) => lake.class === "repo" && lake.writable,
|
|
2911
|
+
);
|
|
2912
|
+
const pool =
|
|
2913
|
+
repos.length > 0
|
|
2914
|
+
? repos
|
|
2915
|
+
: lakes.filter(
|
|
2916
|
+
(lake) => lake.profile === "knowledge" && lake.writable,
|
|
2917
|
+
);
|
|
2918
|
+
if (pool.length === 0) {
|
|
2919
|
+
return {
|
|
2842
2920
|
reason:
|
|
2843
2921
|
`${REFUSE} no writable knowledge lake is configured; ` +
|
|
2844
2922
|
"add a repo, machine, or global lake.",
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
let target = pool[0] as Lake;
|
|
2926
|
+
let depth = nestingDepth(target, lakes);
|
|
2927
|
+
for (const candidate of pool.slice(1)) {
|
|
2928
|
+
const candidateDepth = nestingDepth(candidate, lakes);
|
|
2929
|
+
if (candidateDepth > depth) {
|
|
2930
|
+
target = candidate;
|
|
2931
|
+
depth = candidateDepth;
|
|
2845
2932
|
}
|
|
2846
|
-
|
|
2933
|
+
}
|
|
2934
|
+
return target;
|
|
2847
2935
|
}
|
|
2848
2936
|
|
|
2849
2937
|
/** Qualify a path with its lake when more than one lake is configured. */
|
|
@@ -2887,9 +2975,8 @@ function searchDocs(
|
|
|
2887
2975
|
const missing = runtime.lakes
|
|
2888
2976
|
.filter(
|
|
2889
2977
|
(lake) =>
|
|
2890
|
-
lake.root !== "" &&
|
|
2891
2978
|
(lakeName === undefined || lakeName === lake.name) &&
|
|
2892
|
-
|
|
2979
|
+
lakeMissing(lake),
|
|
2893
2980
|
)
|
|
2894
2981
|
.map((lake) => missingRootNote(lake.root, lake.name));
|
|
2895
2982
|
return {
|
|
@@ -2902,6 +2989,80 @@ function searchDocs(
|
|
|
2902
2989
|
};
|
|
2903
2990
|
}
|
|
2904
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
|
+
|
|
2905
3066
|
/** Check the knowledge lakes, optionally limited to one. */
|
|
2906
3067
|
function checkLakes(
|
|
2907
3068
|
runtime: Runtime,
|
|
@@ -2912,54 +3073,24 @@ function checkLakes(
|
|
|
2912
3073
|
if (lake === undefined) {
|
|
2913
3074
|
return { content: `No lake named '${lakeName}'.`, checked: 0 };
|
|
2914
3075
|
}
|
|
2915
|
-
if (lake
|
|
2916
|
-
return {
|
|
2917
|
-
content:
|
|
2918
|
-
`Lake '${lake.name}' is a wiki lake — read-only reference, ` +
|
|
2919
|
-
"not validated.",
|
|
2920
|
-
checked: 0,
|
|
2921
|
-
};
|
|
3076
|
+
if (!lakeValidated(lake)) {
|
|
3077
|
+
return { content: skippedLakeLine(lake), checked: 0 };
|
|
2922
3078
|
}
|
|
2923
|
-
const
|
|
2924
|
-
return {
|
|
2925
|
-
content: checkDocs(entries, lake.read, lake.notes, {
|
|
2926
|
-
maxDocLines: runtime.maxDocLines,
|
|
2927
|
-
...(lake.linkCheck ? { exists: lake.exists } : {}),
|
|
2928
|
-
}),
|
|
2929
|
-
checked: entries.length,
|
|
2930
|
-
};
|
|
3079
|
+
const { report, checked } = lakeReport(runtime, lake);
|
|
3080
|
+
return { content: cappedCheck(report), checked };
|
|
2931
3081
|
}
|
|
2932
|
-
const knowledge = runtime.lakes.filter(
|
|
2933
|
-
(lake) => lake.profile === "knowledge",
|
|
2934
|
-
);
|
|
2935
3082
|
let checked = 0;
|
|
2936
|
-
const reports =
|
|
2937
|
-
const
|
|
2938
|
-
checked +=
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
});
|
|
2943
|
-
return knowledge.length > 1 || runtime.lakes.length > 1
|
|
2944
|
-
? `${lake.name}:\n${report}`
|
|
2945
|
-
: 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;
|
|
2946
3089
|
});
|
|
2947
|
-
const
|
|
2948
|
-
.filter((lake) => lake
|
|
2949
|
-
.map(
|
|
2950
|
-
|
|
2951
|
-
reports.push(
|
|
2952
|
-
`note: wiki lakes are read-only reference and are not validated: ` +
|
|
2953
|
-
`${wiki.join(", ")}.`,
|
|
2954
|
-
);
|
|
2955
|
-
}
|
|
2956
|
-
return {
|
|
2957
|
-
content:
|
|
2958
|
-
reports.length > 0
|
|
2959
|
-
? reports.join("\n\n")
|
|
2960
|
-
: "No knowledge lakes configured.",
|
|
2961
|
-
checked,
|
|
2962
|
-
};
|
|
3090
|
+
const skipped = runtime.lakes
|
|
3091
|
+
.filter((lake) => !lakeValidated(lake))
|
|
3092
|
+
.map(skippedLakeLine);
|
|
3093
|
+
return { content: assembleCheck(reports, skipped), checked };
|
|
2963
3094
|
}
|
|
2964
3095
|
|
|
2965
3096
|
/** Add a doc to the target lake. */
|
|
@@ -3064,14 +3195,26 @@ function uniqueHolder(
|
|
|
3064
3195
|
if (holders.length === 0) {
|
|
3065
3196
|
return { reason: `No doc at ${path}.` };
|
|
3066
3197
|
}
|
|
3067
|
-
if (holders.length
|
|
3068
|
-
return
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3198
|
+
if (holders.length === 1) {
|
|
3199
|
+
return holders[0] as Lake;
|
|
3200
|
+
}
|
|
3201
|
+
// Nested lakes both hold an inner path: the narrowest enclosing lake owns
|
|
3202
|
+
// it. Non-nested holders tie on depth, which stays a documented refusal —
|
|
3203
|
+
// never a silent pick by collection order.
|
|
3204
|
+
const deepest = holders.map((lake) => ({
|
|
3205
|
+
lake,
|
|
3206
|
+
depth: nestingDepth(lake, holders),
|
|
3207
|
+
}));
|
|
3208
|
+
const max = Math.max(...deepest.map((item) => item.depth));
|
|
3209
|
+
const winners = deepest.filter((item) => item.depth === max);
|
|
3210
|
+
if (winners.length === 1) {
|
|
3211
|
+
return winners[0]?.lake as Lake;
|
|
3073
3212
|
}
|
|
3074
|
-
return
|
|
3213
|
+
return {
|
|
3214
|
+
reason:
|
|
3215
|
+
`${REFUSE} ${path} exists in multiple lakes ` +
|
|
3216
|
+
`(${holders.map((lake) => lake.name).join(", ")}); pass lake.`,
|
|
3217
|
+
};
|
|
3075
3218
|
}
|
|
3076
3219
|
|
|
3077
3220
|
/** Read one doc, resolving a `lake:path` prefix or the unique match. */
|
|
@@ -3121,8 +3264,8 @@ function lakeInfo(lakes: readonly Lake[]): LakeInfo[] {
|
|
|
3121
3264
|
class: lake.class,
|
|
3122
3265
|
root: lake.root,
|
|
3123
3266
|
writable: lake.writable,
|
|
3124
|
-
validated: lake
|
|
3125
|
-
missing:
|
|
3267
|
+
validated: lakeValidated(lake),
|
|
3268
|
+
missing: lakeMissing(lake),
|
|
3126
3269
|
}));
|
|
3127
3270
|
}
|
|
3128
3271
|
|