@williamthorsen/kb 0.8.0 → 0.8.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.
- package/README.md +66 -82
- package/bin/kb.js +2 -4
- package/dist/esm/check/check.d.ts +9 -17
- package/dist/esm/check/enumerate.d.ts +12 -15
- package/dist/esm/check/glob-segments.d.ts +1 -1
- package/dist/esm/check/resolve-foreign-stores.d.ts +9 -8
- package/dist/esm/cli/commands/check.d.ts +4 -10
- package/dist/esm/cli/commands/check.js +2 -2
- package/dist/esm/cli/commands/create.d.ts +3 -7
- package/dist/esm/cli/commands/create.js +1 -1
- package/dist/esm/cli/commands/scaffold.d.ts +5 -9
- package/dist/esm/cli/commands/scaffold.js +5 -5
- package/dist/esm/cli/commands/set-default.d.ts +1 -5
- package/dist/esm/cli/commands/set-default.js +1 -1
- package/dist/esm/cli/commands/taxonomy.d.ts +5 -10
- package/dist/esm/cli/commands/taxonomy.js +10 -9
- package/dist/esm/cli/format.d.ts +8 -10
- package/dist/esm/cli/resolve-store.d.ts +8 -8
- package/dist/esm/cli/run.d.ts +4 -6
- package/dist/esm/cli/run.js +1 -1
- package/dist/esm/cli/select-kb-prompt.d.ts +6 -5
- package/dist/esm/cli/select-kb-prompt.js +1 -1
- package/dist/esm/cli/targeting/resolve-changed-paths.d.ts +3 -3
- package/dist/esm/cli/targeting/select-notes.d.ts +2 -2
- package/dist/esm/config/config-schema.d.ts +10 -9
- package/dist/esm/config/kb-loader-error.d.ts +3 -3
- package/dist/esm/config/load-config.d.ts +2 -2
- package/dist/esm/config/load-config.js +1 -1
- package/dist/esm/config/note-scope.d.ts +7 -7
- package/dist/esm/create/create.d.ts +8 -8
- package/dist/esm/discovery/kb-registry-schema.d.ts +0 -2
- package/dist/esm/discovery/load-registry.d.ts +7 -9
- package/dist/esm/discovery/load-registry.js +1 -1
- package/dist/esm/discovery/register-store.d.ts +3 -4
- package/dist/esm/discovery/register-store.js +2 -2
- package/dist/esm/discovery/registry-document.d.ts +2 -2
- package/dist/esm/discovery/set-default-kb.d.ts +5 -5
- package/dist/esm/discovery/set-default-kb.js +3 -3
- package/dist/esm/filesystem/exists.d.ts +4 -4
- package/dist/esm/frontmatter/parse-note.d.ts +3 -3
- package/dist/esm/git/list-git-scope.d.ts +6 -6
- package/dist/esm/git/run-git.d.ts +2 -2
- package/dist/esm/layout/store-layout.d.ts +5 -5
- package/dist/esm/lints/paths.d.ts +1 -1
- package/dist/esm/lints/paths.js +1 -1
- package/dist/esm/lints/tag-alias.d.ts +1 -1
- package/dist/esm/lints/tag-alias.js +1 -1
- package/dist/esm/lints/taxonomy.d.ts +9 -8
- package/dist/esm/lints/taxonomy.js +2 -2
- package/dist/esm/note-io/field-validators.d.ts +1 -1
- package/dist/esm/note-io/read-note.d.ts +2 -2
- package/dist/esm/note-io/write-note.d.ts +3 -6
- package/dist/esm/records/event.d.ts +3 -3
- package/dist/esm/scaffold/render-seeds.d.ts +3 -3
- package/dist/esm/scaffold/render-seeds.js +2 -2
- package/dist/esm/scaffold/scaffold.d.ts +7 -8
- package/dist/esm/tags/load-aliases.d.ts +3 -4
- package/dist/esm/taxonomy/domain-paths.d.ts +6 -6
- package/dist/esm/taxonomy/load-taxonomy.d.ts +3 -3
- package/dist/esm/taxonomy/load-taxonomy.js +2 -2
- package/dist/esm/taxonomy/taxonomy-schema.d.ts +5 -6
- package/dist/esm/taxonomy/write-taxonomy.d.ts +7 -8
- package/dist/esm/type-guards.d.ts +1 -1
- package/dist/esm/types.d.ts +6 -9
- package/dist/esm/vault-integrity/build-vault-index.d.ts +3 -4
- package/dist/esm/vault-integrity/check-vault-integrity.d.ts +13 -13
- package/dist/esm/vault-integrity/check-vault-integrity.js +1 -1
- package/dist/esm/vault-integrity/wikilink-parse.d.ts +21 -16
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -4,32 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
Foundation library for knowledge-base tooling.
|
|
6
6
|
Provides knowledge-base discovery, registry loading, frontmatter parsing and writing, tag canonicalization, and type-blind vault-integrity checks.
|
|
7
|
-
It underpins the knowledge-base skills
|
|
7
|
+
It underpins the planned `@williamthorsen/kb-mcp` server and the knowledge-base skills, among them `kb-retrieve` (assertion recall) and `kb-retrieve-events` (event recall), `kb-add`, `kb-curate`, `capture-event`, and `kb-update-events`.
|
|
8
8
|
|
|
9
9
|
<!-- section:release-notes -->
|
|
10
|
-
## Release notes — v0.8.
|
|
10
|
+
## Release notes — v0.8.1 (2026-09-21)
|
|
11
11
|
|
|
12
|
-
###
|
|
12
|
+
### 🐛 Bug fixes
|
|
13
13
|
|
|
14
|
-
-
|
|
14
|
+
- Fix writing-rule violations in skill data, partials, and skills a to l (#1711)
|
|
15
15
|
|
|
16
|
-
-
|
|
17
|
-
- Adds `visibility` to `.kb/config.yaml`, taking `shared` or `private` and defaulting to `private`, which sets the permitted direction: a link may point at a store as shareable as its own or more so.
|
|
18
|
-
- Adds three findings for a qualified link that does not resolve, `wikilinks.unknown-store` and `wikilinks.disallowed-store` as errors and `wikilinks.store-unavailable` as a warning, so that a correct link into a store that this machine has not cloned is reported as unverifiable rather than broken.
|
|
19
|
-
|
|
20
|
-
- Add a second-person rule to the writing preferences (#1634)
|
|
21
|
-
|
|
22
|
-
- Bans the use of the 2nd person in user- and developer-facing documentation prose.
|
|
23
|
-
- Adds `second-person` to the rule names that `revise-prose` and `prose-reviser` accept, alongside `plain-speech`, so a sweep repairs a site or records a rejection under it though no detector reports its sites.
|
|
24
|
-
- Moves the rulebook's `version` to `3`, so that a bound repository re-sweeps and re-adjudicates the rejections recorded under the old version.
|
|
25
|
-
|
|
26
|
-
### 🪦 Removed
|
|
27
|
-
|
|
28
|
-
- 🚨 **Breaking:** Remove the wikilink-parse primitives from the vault-integrity subpath (#1680)
|
|
29
|
-
|
|
30
|
-
- Removes `countNewlines`, `extractTarget`, `hasNonMarkdownExtension`, `maskFencedCode`, `maskInlineCode`, and `WIKILINK` from `@williamthorsen/kb/vault-integrity`.
|
|
31
|
-
|
|
32
|
-
Migration: Call `scanWikilinks` to find the wikilinks in a note body and their targets, in place of a scan assembled from the removed primitives.
|
|
16
|
+
- Fixes writing-rule violations in shared skill data, skill partials, and a range of skills.
|
|
33
17
|
<!-- /section:release-notes -->
|
|
34
18
|
|
|
35
19
|
## Exports
|
|
@@ -39,16 +23,16 @@ The package exposes thirteen subpath entries plus a root barrel:
|
|
|
39
23
|
| Entry | Description |
|
|
40
24
|
| ------------------- | ------------------------------------------------------------------------------ |
|
|
41
25
|
| `.` | The most-used types plus `buildVaultIndex` |
|
|
42
|
-
| `./check` | `check`:
|
|
43
|
-
| `./config` | `.kb/config.yaml` loading and the typed `KbLoaderError` the loaders throw
|
|
44
|
-
| `./create` | `create`:
|
|
26
|
+
| `./check` | `check`: Config-driven enumeration composed with vault integrity and the lints |
|
|
27
|
+
| `./config` | `.kb/config.yaml` loading and the typed `KbLoaderError` that the loaders throw |
|
|
28
|
+
| `./create` | `create`: Scaffold a new store and register it in `kb.yaml` |
|
|
45
29
|
| `./discovery` | KB root discovery and `kb.yaml` registry loading, merging, and writing |
|
|
46
30
|
| `./filesystem` | Filesystem-existence helpers with an explicit absence policy |
|
|
47
31
|
| `./frontmatter` | Note parsing into typed frontmatter |
|
|
48
32
|
| `./layout` | The store's on-disk layout: every path inside a `.kb/` store derives from here |
|
|
49
33
|
| `./note-io` | Type-blind note read/write as an ordered frontmatter field map |
|
|
50
34
|
| `./records` | The typed `assertion`/`event` record parsers and renderers |
|
|
51
|
-
| `./scaffold` | The canonical set
|
|
35
|
+
| `./scaffold` | The canonical set that a store contains, and the idempotent writer over it |
|
|
52
36
|
| `./tags` | `.kb/tag-aliases.yaml` loading and tag canonicalization |
|
|
53
37
|
| `./taxonomy` | `.kb/taxonomy.yaml` loading, comment-preserving declaration, and path mapping |
|
|
54
38
|
| `./vault-integrity` | Type-blind `[[link]]` scanning, resolution, and basename-uniqueness checks |
|
|
@@ -101,7 +85,7 @@ An entry's name is also what a [store-qualified wikilink](#linking-into-another-
|
|
|
101
85
|
|
|
102
86
|
- Project entries **replace** user entries with the same name.
|
|
103
87
|
- Project entries with a new name are **appended**.
|
|
104
|
-
- When both files set `default_kb`, the **project** value
|
|
88
|
+
- When both files set `default_kb`, the **project** value takes precedence; the resolved default is the named entry from the merged set.
|
|
105
89
|
- Path existence is not checked at load time.
|
|
106
90
|
|
|
107
91
|
```ts
|
|
@@ -113,19 +97,19 @@ const config = await loadKbRegistry({ projectDir: process.cwd() });
|
|
|
113
97
|
|
|
114
98
|
## The addressed-by/addresses relation
|
|
115
99
|
|
|
116
|
-
`addressed-by`/`addresses` is an inverse-pair relation that
|
|
100
|
+
`addressed-by`/`addresses` is an inverse-pair relation that links a problem record to whatever was done about it: a fix, a mitigation, an improved guidance note. Both are optional, multi-valued list fields:
|
|
117
101
|
|
|
118
102
|
- `addressed-by` (on the problem record, available on `assertion` and `event`) is the canonical, recall-facing field: a list of references to whatever addressed the problem. It is the only viable store when the responder is external, so its entries are heterogeneous: a KB wikilink or relative path, a commit SHA, a PR/issue ref, or a URL. The field's shape is validated as a list by the record parser, while its entries are free-form, like `sources`. It is set on events with `kb-update-events` and on assertions with `kb-edit`.
|
|
119
|
-
- `addresses` (on a KB-note responder, available on `assertion`) is the optional inverse for the rare "what does this address?" query. It is **non-authoritative**: keeping it in sync would be an N-file write, so `kb-curate` deliberately does not
|
|
103
|
+
- `addresses` (on a KB-note responder, available on `assertion`) is the optional inverse for the rare "what does this address?" query. It is **non-authoritative**: keeping it in sync would be an N-file write, so `kb-curate` deliberately does not enforce it.
|
|
120
104
|
|
|
121
|
-
The relation is many-to-many (one response can address many problems, and one problem can accrue many responses) and
|
|
105
|
+
The relation is many-to-many (one response can address many problems, and one problem can accrue many responses) and recall reports it flat, with no chain-walking. This is distinct from `supersedes`/`superseded-by`, which _deprecates_ a record through an enforced 1:1 chain; an addressed problem is not deprecated. It remains a true observation whose recurrence is worth keeping.
|
|
122
106
|
|
|
123
107
|
## Frontmatter parsing and writing
|
|
124
108
|
|
|
125
|
-
`parseNote({ path })` (or `parseNoteContent({ content })`) parses a note into a `ParsedNote`
|
|
109
|
+
`parseNote({ path })` (or `parseNoteContent({ content })`) parses a note into a `ParsedNote` containing typed `Frontmatter`:
|
|
126
110
|
The `title`, `recordType`, `created`, `updated`, and `tags` fields are strongly typed and any other fields are preserved in an `extra` map.
|
|
127
111
|
|
|
128
|
-
Date fields
|
|
112
|
+
Date fields are strings, never JS `Date` objects. YAML parse errors are recorded in `ParsedNote.frontmatterRaw.parseError` rather than thrown;
|
|
129
113
|
missing files (when a path is given) throw.
|
|
130
114
|
|
|
131
115
|
Writing is type-blind and lives in `@williamthorsen/kb/note-io`. `readNote(path)` (or `readNoteContent(content)`) splits a note into an insertion-ordered frontmatter field map and a body, and `writeNote(path, fields, body)` (or `renderNote(fields, body)`) renders them back, with string lists in flow style. A note written this way reads back to the same fields and body.
|
|
@@ -137,9 +121,9 @@ Writing is type-blind and lives in `@williamthorsen/kb/note-io`. `readNote(path)
|
|
|
137
121
|
|
|
138
122
|
## Vault integrity
|
|
139
123
|
|
|
140
|
-
`checkVaultIntegrity(notes)` runs whole-vault, type-blind checks over a `{ path, body, bodyStartLine }[]` note set:
|
|
124
|
+
`checkVaultIntegrity(notes)` runs whole-vault, type-blind checks over a `{ path, body, bodyStartLine }[]` note set: An unresolved `[[link]]` is an error (`wikilinks.unresolved`), and a basename shared by two or more notes is one vault-wide warning (`wikilinks.basename`). `buildVaultIndex(notes)` builds the basename → paths index shared by the layer and curate's wikilink rewriter.
|
|
141
125
|
|
|
142
|
-
A second argument, `{ foreignStores, sourceVisibility }`, resolves [store-qualified links](#linking-into-another-store) against the stores they name; supplied none, the layer treats every target as store-local, which is what `buildVaultIndex`'s other consumers get.
|
|
126
|
+
A second argument, `{ foreignStores, sourceVisibility }`, resolves [store-qualified links](#linking-into-another-store) against the stores that they name; supplied none, the layer treats every target as store-local, which is what `buildVaultIndex`'s other consumers get.
|
|
143
127
|
|
|
144
128
|
`scanWikilinks(body)` yields each link in a note body, with its store qualifier and target separated, and is the single definition of what counts as a link. `lookupKey(target)` reduces a target to the key that `VaultIndex` uses. The subpath exports `checkVaultIntegrity`, `buildVaultIndex`, `scanWikilinks`, and `lookupKey`, and not the parse primitives from which they are built: A consumer that detects or rewrites links calls `scanWikilinks`.
|
|
145
129
|
|
|
@@ -151,7 +135,7 @@ const findings = checkVaultIntegrity(notes);
|
|
|
151
135
|
|
|
152
136
|
## Checking a store
|
|
153
137
|
|
|
154
|
-
`check({ kbRoot })` runs a store's full check in one call:
|
|
138
|
+
`check({ kbRoot })` runs a store's full check in one call: It loads `.kb/config.yaml`, `.kb/tag-aliases.yaml`, and `.kb/taxonomy.yaml`, enumerates the notes that the config selects (inside a git working tree, the enumeration drops [ignored notes](#ignored-notes)), and composes whole-vault integrity and taxonomy drift with the `tag-alias` and `paths` lints. It performs no frontmatter validation; record types own that at write time. It returns **both** the enumerated notes and the findings, so that a consumer can layer its own detectors over the same enumeration without walking the store twice.
|
|
155
139
|
|
|
156
140
|
```ts
|
|
157
141
|
import { check } from '@williamthorsen/kb/check';
|
|
@@ -159,7 +143,7 @@ import { check } from '@williamthorsen/kb/check';
|
|
|
159
143
|
const { notes, findings } = await check({ kbRoot });
|
|
160
144
|
```
|
|
161
145
|
|
|
162
|
-
No subpath exports the lints, so `check`, and the `kb check` command built on it, is the only way to run them. Two type-blind per-note lints catch what write-time record validation can't: `tag-alias` (warning) reports alias-vocabulary drift, and `paths.user-home` (error) reports a hardcoded `/Users/{name}/` path in captured content. The `taxonomy.*` rules, all warnings, report
|
|
146
|
+
No subpath exports the lints, so `check`, and the `kb check` command built on it, is the only way to run them. Two type-blind per-note lints catch what write-time record validation can't: `tag-alias` (warning) reports alias-vocabulary drift, and `paths.user-home` (error) reports a hardcoded `/Users/{name}/` path in captured content. The `taxonomy.*` rules, all warnings, report drift between a store's assertion folders and its declared taxonomy (see [`.kb/taxonomy.yaml`](#the-declared-structure-kbtaxonomyyaml)). Their findings have `scope: 'vault'`: They describe the store rather than any one note. A consumer that narrows a report to selected notes must keep them rather than filter them out by path.
|
|
163
147
|
|
|
164
148
|
A structural defect in any loaded file throws a `KbLoaderError` (see below). Any other error from enumeration or the checks propagates unchanged.
|
|
165
149
|
|
|
@@ -184,21 +168,21 @@ visibility: private
|
|
|
184
168
|
| `exclude` | `['**/node_modules/**']` | Glob patterns excluded from enumeration even when a target matches |
|
|
185
169
|
| `visibility` | `private` | `shared` or `private`; decides which stores may link into this one |
|
|
186
170
|
|
|
187
|
-
`visibility` belongs here rather than in the registry entry because it is intrinsic to the store:
|
|
171
|
+
`visibility` belongs here rather than in the registry entry because it is intrinsic to the store: One store cloned on two machines has one visibility, whereas a per-machine declaration could let the two disagree silently. It governs [linking into another store](#linking-into-another-store).
|
|
188
172
|
|
|
189
173
|
Matching uses dotfile-insensitive globbing, so dot-directories (`.kb`, `.git`, `.agents`) are skipped without naming them. The default targets the `content/`-scoped layout; a store with a different layout overrides `targets` to match. `loadKbConfig({ kbRoot })` returns the effective config and is exported from `@williamthorsen/kb/config`.
|
|
190
174
|
|
|
191
175
|
#### Ignored notes
|
|
192
176
|
|
|
193
|
-
|
|
177
|
+
When the store is in a git working tree, the repository's ignore rules narrow the selection further. A note is enumerated when git tracks it, or when no ignore rule covers it. A note that the repository ignores is therefore neither checked nor available as a wikilink target, so a link pointing at one reports `wikilinks.unresolved`. That is the correct reading: Such a link is broken for every clone but the author's. This lets a store gitignore a scratch area (`local/`, `*.local.md`) and keep uncommitted notes there without the store's lints gating them.
|
|
194
178
|
|
|
195
|
-
A store outside a git working tree, or a machine
|
|
179
|
+
A store outside a git working tree, or a machine with no git, keeps the filesystem walk alone. When the repository ignores every note that the walk found, which happens when a parent repository ignores the store's own directory, the run says so on stderr rather than reporting an empty note set as clean.
|
|
196
180
|
|
|
197
181
|
### Linking into another store
|
|
198
182
|
|
|
199
|
-
A wikilink names a store by qualifying its target: `[[fde:Note title]]` resolves `Note title` in the store registered as `fde`,
|
|
183
|
+
A wikilink names a store by qualifying its target: `[[fde:Note title]]` resolves `Note title` in the store registered as `fde`, whereas a bare `[[Note title]]` stays inside the store being checked. A qualifier is recognized only when the text before the first colon is non-empty and contains no whitespace and no `/`, so a title that happens to contain a colon resolves whole.
|
|
200
184
|
|
|
201
|
-
Direction is decided by `visibility`:
|
|
185
|
+
Direction is decided by `visibility`: A link may point at a store as shareable as its own or more so, never at a less shareable one. A private note may therefore link to the share-safe assertion from which its detail was stripped, while the reverse is refused, because a note's title tends to be its claim and a link into a private store discloses that claim through the link itself.
|
|
202
186
|
|
|
203
187
|
Only the stores that a run's own links name are consulted, and each is enumerated under its own `targets`/`exclude` and its own repository's ignore rules, reading note paths alone. A run whose links qualify no store reads no registry.
|
|
204
188
|
|
|
@@ -208,15 +192,15 @@ Only the stores that a run's own links name are consulted, and each is enumerate
|
|
|
208
192
|
| `wikilinks.registry-unloadable` | error | A run with qualified links could not load `kb.yaml`; vault-scoped, reported once |
|
|
209
193
|
| `wikilinks.store-unavailable` | warning | The named store is registered but could not be read here, so the link is unverified |
|
|
210
194
|
| `wikilinks.unknown-store` | error | No `kb.yaml` entry declares the named store |
|
|
211
|
-
| `wikilinks.unresolved` | error | The named store
|
|
195
|
+
| `wikilinks.unresolved` | error | The named store contains no note of that basename |
|
|
212
196
|
|
|
213
|
-
`wikilinks.store-unavailable` is a warning rather than an error because a store absent from this machine leaves its links unverifiable rather than broken:
|
|
197
|
+
`wikilinks.store-unavailable` is a warning rather than an error because a store absent from this machine leaves its links unverifiable rather than broken: A correct link should not fail a check run on a machine that has not cloned the target.
|
|
214
198
|
|
|
215
199
|
A run that reports `wikilinks.registry-unloadable` reports nothing per link. No store was looked up, so whether one is registered is undetermined, and naming each link would send the reader to fix a registration that may already be correct.
|
|
216
200
|
|
|
217
201
|
### The declared structure: `.kb/taxonomy.yaml`
|
|
218
202
|
|
|
219
|
-
`.kb/taxonomy.yaml` states where a store's assertions are meant to live. It is the source of truth for intended structure:
|
|
203
|
+
`.kb/taxonomy.yaml` states where a store's assertions are meant to live. It is the source of truth for intended structure: Folders on disk are derived from it, not the reverse. It governs `content/assertions/` only, since `content/events/` is flat and ULID-keyed.
|
|
220
204
|
|
|
221
205
|
```yaml
|
|
222
206
|
# .kb/taxonomy.yaml
|
|
@@ -228,25 +212,25 @@ provisional:
|
|
|
228
212
|
languages:
|
|
229
213
|
```
|
|
230
214
|
|
|
231
|
-
Two disjoint maps of domain path to one-line description. `domains`
|
|
215
|
+
Two disjoint maps of domain path to one-line description. `domains` contains reviewed declarations and `provisional` contains those declared but not yet reviewed; promotion is writing a description and moving the line up. A domain may be declared without a description, as `languages` is above.
|
|
232
216
|
|
|
233
|
-
Keys are relative to `content/assertions/` and may nest to any depth. Parents are not implied: declaring `engineering/tooling` does not declare `engineering`. A path declared in both maps fails the load, as does a malformed key
|
|
217
|
+
Keys are relative to `content/assertions/` and may nest to any depth. Parents are not implied: declaring `engineering/tooling` does not declare `engineering`. A path declared in both maps fails the load, as does a malformed key: one that restates the `content/assertions/` prefix, or that has a leading or trailing slash, an empty segment, or a `.`/`..` segment.
|
|
234
218
|
|
|
235
219
|
An absent taxonomy, and one present but declaring nothing, are both valid and report nothing, so the rules apply only to a store that has adopted a taxonomy. Three warnings report drift once one has:
|
|
236
220
|
|
|
237
|
-
| Rule | Meaning
|
|
238
|
-
| --------------------- |
|
|
239
|
-
| `taxonomy.undeclared` | A folder
|
|
240
|
-
| `taxonomy.unused` | A declared domain has no note at or beneath it.
|
|
241
|
-
| `taxonomy.orphan` | A declared domain's parent is undeclared.
|
|
221
|
+
| Rule | Meaning |
|
|
222
|
+
| --------------------- | -------------------------------------------------- |
|
|
223
|
+
| `taxonomy.undeclared` | A folder contains notes but no domain declares it. |
|
|
224
|
+
| `taxonomy.unused` | A declared domain has no note at or beneath it. |
|
|
225
|
+
| `taxonomy.orphan` | A declared domain's parent is undeclared. |
|
|
242
226
|
|
|
243
|
-
A domain counts as used when any note lives at or beneath it, so a grouping domain that
|
|
227
|
+
A domain counts as used when any note lives at or beneath it, so a grouping domain that contains only subfolders is not reported unused. A domain inside a `config.exclude` subtree is exempt from `taxonomy.unused`, since its notes are never enumerated.
|
|
244
228
|
|
|
245
|
-
`loadTaxonomy({ kbRoot })` reads both blocks into one map of domain path to `{ description, provisional }`, and `writeTaxonomy({ kbRoot, declarations })` declares domains while preserving the file's existing comments, key order, and formatting. `resolveDomain(relativePath)` maps a store-root-relative note path to the domain
|
|
229
|
+
`loadTaxonomy({ kbRoot })` reads both blocks into one map of domain path to `{ description, provisional }`, and `writeTaxonomy({ kbRoot, declarations })` declares domains while preserving the file's existing comments, key order, and formatting. `resolveDomain(relativePath)` maps a store-root-relative note path to the domain that contains it (`undefined` for a non-assertion or a note at the assertions root), and `resolveParent(path)` yields a domain's parent (`undefined` at the top level). Because the drift rules and the back-fill both derive their answers from this pair, a consumer that classifies notes against the taxonomy stays in agreement with what `kb check` reports. All four are exported from `@williamthorsen/kb/taxonomy`.
|
|
246
230
|
|
|
247
231
|
## The `kb` command
|
|
248
232
|
|
|
249
|
-
The package
|
|
233
|
+
The package provides a `kb` bin with five subcommands: `check`, `create`, `scaffold`, `set-default`, and `taxonomy`.
|
|
250
234
|
|
|
251
235
|
### kb create
|
|
252
236
|
|
|
@@ -271,13 +255,13 @@ It creates these files and directories:
|
|
|
271
255
|
|
|
272
256
|
The check config is serialized from the in-package `defaultKbConfig` and the Prettier config from `canonicalPrettierConfig`, so a new store cannot drift from the bundled values.
|
|
273
257
|
|
|
274
|
-
The name defaults to the directory's base name; `--name` overrides it and `--no-register` scaffolds without writing the registry. `--description` sets the new entry's description, and requires registration: combining it with `--no-register` is a usage error. The registry write preserves any existing comments in `kb.yaml` and leaves the `kbs:` entries alphabetically ordered,
|
|
258
|
+
The name defaults to the directory's base name; `--name` overrides it and `--no-register` scaffolds without writing the registry. `--description` sets the new entry's description, and requires registration: combining it with `--no-register` is a usage error. The registry write preserves any existing comments in `kb.yaml` and leaves the `kbs:` entries alphabetically ordered, which tidies a registry that has drifted out of order as stores are added. `kb create` refuses to overwrite: It exits 2 if the directory already contains a `.kb/` store, or if the chosen name is already registered. Use `kb scaffold` to add canonical files to a store that already exists.
|
|
275
259
|
|
|
276
|
-
`kb create` also keeps a default knowledge base set. When the registry's top-level `default_kb` pointer is unset and the new store is the only registered KB, it becomes the default. When other KBs are already registered with no default, `kb create` prompts for one on an interactive terminal, and points to `kb set-default`
|
|
260
|
+
`kb create` also keeps a default knowledge base set. When the registry's top-level `default_kb` pointer is unset and the new store is the only registered KB, it becomes the default. When other KBs are already registered with no default, `kb create` prompts for one on an interactive terminal, and points to `kb set-default` when stdin is not interactive. An existing `default_kb` is never overwritten.
|
|
277
261
|
|
|
278
262
|
### kb scaffold
|
|
279
263
|
|
|
280
|
-
`kb scaffold` writes into an existing knowledge base any canonical file that it lacks, so a store created before a given file existed can acquire it.
|
|
264
|
+
`kb scaffold` writes into an existing knowledge base any canonical file that it lacks, so that a store created before a given file existed can acquire it.
|
|
281
265
|
|
|
282
266
|
```bash
|
|
283
267
|
kb scaffold # back-fill the nearest ancestor .kb/ store
|
|
@@ -285,11 +269,11 @@ kb scaffold --kb coding # back-fill the named store from the kb.yaml regist
|
|
|
285
269
|
kb scaffold --force # replace every canonical file with a fresh seed
|
|
286
270
|
```
|
|
287
271
|
|
|
288
|
-
The canonical set is the one that `kb create` writes, defined once and shared by both commands so neither can drift from the other. `.kb/taxonomy.yaml` is not part of it: `kb taxonomy init` derives that file's content from the notes a store already
|
|
272
|
+
The canonical set is the one that `kb create` writes, defined once and shared by both commands so that neither can drift from the other. `.kb/taxonomy.yaml` is not part of it: `kb taxonomy init` derives that file's content from the notes that a store already contains rather than writing a fixed template.
|
|
289
273
|
|
|
290
274
|
An existing file is left untouched unless `--force` is given, which replaces it with a fresh seed and discards any edits. A directory has no content to replace, so `--force` governs files alone. The command reports each canonical path as `created`, `present`, or `replaced`.
|
|
291
275
|
|
|
292
|
-
Store resolution matches `kb check`: the nearest ancestor `.kb/` directory, or a `--kb <name>` entry in the merged `kb.yaml` registry.
|
|
276
|
+
Store resolution matches `kb check`: the nearest ancestor `.kb/` directory, or a `--kb <name>` entry in the merged `kb.yaml` registry. The command exits 2 on three grounds: no store resolves, the registry marks the resolved store `readonly`, or the resolved path contains no `.kb/`. The last keeps the command to back-filling a store rather than creating one, which is `kb create`'s job; a registry entry names a path without proving a store is there.
|
|
293
277
|
|
|
294
278
|
### kb set-default
|
|
295
279
|
|
|
@@ -301,7 +285,7 @@ kb set-default --none # clear default_kb
|
|
|
301
285
|
kb set-default # list the registered KBs and choose interactively
|
|
302
286
|
```
|
|
303
287
|
|
|
304
|
-
With a name, it sets `default_kb` to that KB, exiting 2 if the name is not registered. With `--none`, it clears the pointer. With no arguments on an interactive terminal, it lists the registered KBs
|
|
288
|
+
With a name, it sets `default_kb` to that KB, exiting 2 if the name is not registered. With `--none`, it clears the pointer. With no arguments on an interactive terminal, it lists the registered KBs, marking the current default and offering a `(none)` option, and writes the choice; cancelling with an empty line leaves the registry unchanged. With no arguments on a non-interactive stdin, it exits 2 rather than hanging. Writes resolve against and target the user-global registry only, and preserve existing comments and formatting.
|
|
305
289
|
|
|
306
290
|
### kb check
|
|
307
291
|
|
|
@@ -312,18 +296,18 @@ kb check # check every note in the nearest ancestor .kb/ s
|
|
|
312
296
|
kb check --kb coding # check the named store from the kb.yaml registry
|
|
313
297
|
kb check --json # emit a JSON report
|
|
314
298
|
kb check content/assertions # check only the notes under a directory
|
|
315
|
-
kb check 'content/**/*.md' # check only the notes a glob matches (quote it)
|
|
299
|
+
kb check 'content/**/*.md' # check only the notes that a glob matches (quote it)
|
|
316
300
|
kb check --vs=main # check only the notes changed since a ref
|
|
317
301
|
```
|
|
318
302
|
|
|
319
|
-
`kb check` resolves the store from the nearest ancestor `.kb/` directory, or from a `--kb <name>` entry in the merged `kb.yaml` registry (project-local entries
|
|
303
|
+
`kb check` resolves the store from the nearest ancestor `.kb/` directory, or from a `--kb <name>` entry in the merged `kb.yaml` registry (project-local entries are merged into the user-global registry). The default output groups findings by file; `--json` emits `{ store, summary, findings }`. The command is read-only and never writes to the store.
|
|
320
304
|
|
|
321
|
-
**Targeting.** Path arguments and `--vs` each scope the run to a subset of notes; they are mutually exclusive, and both compose with `--kb` and `--json`. Cross-note rules always resolve against the whole store, and a store-qualified link against the whole store it names, so a targeted run never
|
|
305
|
+
**Targeting.** Path arguments and `--vs` each scope the run to a subset of notes; they are mutually exclusive, and both compose with `--kb` and `--json`. Cross-note rules always resolve against the whole store, and a store-qualified link against the whole store that it names, so a targeted run never misreports a link to an unselected note; only the report and the exit code narrow to the selection.
|
|
322
306
|
|
|
323
|
-
- **`[paths...]`**:
|
|
324
|
-
- **`--vs <ref>`**:
|
|
307
|
+
- **`[paths...]`**: One or more glob patterns, files, or directories (store-root-relative). Because the command expands globs itself, a quoted glob behaves the same as a shell-expanded one. A directory checks every note beneath it. A path that matches no note is a usage error, unless it names a real non-note (a README, a triage note, or a file that `exclude` or git rules out), which is skipped silently.
|
|
308
|
+
- **`--vs <ref>`**: The notes changed between the working tree and the merge-base of `<ref>` and HEAD. The diff follows renames (checking the destination), includes uncommitted edits to tracked notes, and excludes deletions, so a `git mv`-heavy migration batch reports the notes that it actually touched.
|
|
325
309
|
|
|
326
|
-
Because the exit code reflects only the selected notes, a per-batch or pre-commit gate can pass while the rest of the vault still
|
|
310
|
+
Because the exit code reflects only the selected notes, a per-batch or pre-commit gate can pass while the rest of the vault still has a migration backlog.
|
|
327
311
|
|
|
328
312
|
Exit codes:
|
|
329
313
|
|
|
@@ -333,19 +317,19 @@ Exit codes:
|
|
|
333
317
|
| `1` | One or more error-severity findings in the checked notes. |
|
|
334
318
|
| `2` | A usage error, an unresolvable store or `--vs` ref, a path matching no note, or a malformed `config`, `tag-aliases`, or `taxonomy` file. |
|
|
335
319
|
|
|
336
|
-
A finding
|
|
320
|
+
A finding with `scope: 'vault'` describes the store rather than any one note, so it is reported under every run, including a targeted one, a `--vs` one, and one that matched no notes at all. The taxonomy rules are the ones that produce them.
|
|
337
321
|
|
|
338
322
|
### kb taxonomy
|
|
339
323
|
|
|
340
|
-
`kb taxonomy init` derives a starting taxonomy from the notes a store already
|
|
324
|
+
`kb taxonomy init` derives a starting taxonomy from the notes that a store already contains, so that a taxonomy can be introduced to a populated store without every folder being reported as undeclared.
|
|
341
325
|
|
|
342
326
|
```bash
|
|
343
|
-
kb taxonomy init # declare every folder
|
|
327
|
+
kb taxonomy init # declare every folder containing notes, and its ancestors
|
|
344
328
|
kb taxonomy init --kb coding # back-fill the named store from the kb.yaml registry
|
|
345
|
-
kb taxonomy init --merge # add only the domains an existing taxonomy omits
|
|
329
|
+
kb taxonomy init --merge # add only the domains that an existing taxonomy omits
|
|
346
330
|
```
|
|
347
331
|
|
|
348
|
-
Every derived domain
|
|
332
|
+
Every derived domain is written under `provisional:` with no description: The command cannot invent descriptions, and provisional already means "declared, not yet reviewed". Because the derivation reads the same enumeration that `kb check` reads, a back-filled store reports no taxonomy drift.
|
|
349
333
|
|
|
350
334
|
Without `--merge`, a store that already declares a taxonomy is left untouched and the command exits 2.
|
|
351
335
|
|
|
@@ -358,11 +342,11 @@ prettier --write . # format the store
|
|
|
358
342
|
prettier --check . # report drift without writing
|
|
359
343
|
```
|
|
360
344
|
|
|
361
|
-
Prettier 3 reads `.gitignore` and `.prettierignore` by default, so neither command needs a flag to skip what the repository ignores, and it loads a config file from a directory
|
|
345
|
+
Prettier 3 reads `.gitignore` and `.prettierignore` by default, so neither command needs a flag to skip what the repository ignores, and it loads a config file from a directory containing no `package.json`.
|
|
362
346
|
|
|
363
347
|
### The two config files
|
|
364
348
|
|
|
365
|
-
`.editorconfig`
|
|
349
|
+
`.editorconfig` contains everything that more than one tool reads:
|
|
366
350
|
|
|
367
351
|
```ini
|
|
368
352
|
root = true
|
|
@@ -384,32 +368,32 @@ Prettier reads this file, and so does every editor that supports EditorConfig, w
|
|
|
384
368
|
|
|
385
369
|
The `[*.md]` exemption is the one entry that is not self-explanatory. Two trailing spaces are a hard line break in Markdown, and Prettier preserves them; an editor trimming trailing whitespace on save would destroy a break that the formatter deliberately keeps.
|
|
386
370
|
|
|
387
|
-
`.prettierrc.yaml`
|
|
371
|
+
`.prettierrc.yaml` contains the one setting that `.editorconfig` has no key for:
|
|
388
372
|
|
|
389
373
|
```yaml
|
|
390
374
|
embeddedLanguageFormatting: off
|
|
391
375
|
```
|
|
392
376
|
|
|
393
|
-
It is not stylistic, and the scaffolded file
|
|
377
|
+
It is not stylistic, and the scaffolded file states this reasoning in its own header so that the reasoning stays with the store. `embeddedLanguageFormatting: off` leaves a note's YAML frontmatter unformatted. Formatted, a long `tags` or `addressed-by` list breaks across several lines, which `renderNote` puts back onto one the next time anything writes the note. Without this option the formatter and the note writer rewrite each other's output without end, and every note with a list past the print width is rewritten on each pass.
|
|
394
378
|
|
|
395
|
-
A store that must keep a file that it cannot format, such as a lockfile or a fixture whose defect is the point, names it in a `.prettierignore`. That file is the
|
|
379
|
+
A store that must keep a file that it cannot format, such as a lockfile or a fixture whose defect is the point, names it in a `.prettierignore`. That file is the one exemption and is not scaffolded, since a fresh store has nothing to put in it.
|
|
396
380
|
|
|
397
381
|
### Migrating a store off a `package.json` toolchain
|
|
398
382
|
|
|
399
|
-
A store that
|
|
383
|
+
A store that keeps a `package.json` only to obtain Prettier can retire it.
|
|
400
384
|
|
|
401
385
|
1. Install Prettier where the store is edited, if it is not already there: `pnpm add --global prettier`.
|
|
402
|
-
2. Run `kb scaffold` in the store to write `.editorconfig` and `.prettierrc.yaml`. An `.editorconfig` the store already has is reported as `present` and left alone, so compare it against the canonical file above and reconcile the two by hand; `kb scaffold --force` replaces it outright.
|
|
386
|
+
2. Run `kb scaffold` in the store to write `.editorconfig` and `.prettierrc.yaml`. An `.editorconfig` that the store already has is reported as `present` and left alone, so compare it against the canonical file above and reconcile the two by hand; `kb scaffold --force` replaces it outright.
|
|
403
387
|
3. Delete `package.json`, the lockfile, `pnpm-workspace.yaml`, `.npmrc`, and any `eslint.config.*`, `tsconfig.json`, and dependency-upgrade config. Delete the superseded `.prettierrc.*` too; keep `.prettierignore` if it names anything still present.
|
|
404
|
-
4. Check that `.editorconfig` still governs the width. Prettier reads it for every key the Prettier config leaves unset, so a store whose `max_line_length` disagrees with the canonical 120 formats to its own value, and two stores that disagree format differently.
|
|
388
|
+
4. Check that `.editorconfig` still governs the width. Prettier reads it for every key that the Prettier config leaves unset, so a store whose `max_line_length` disagrees with the canonical 120 formats to its own value, and two stores that disagree format differently.
|
|
405
389
|
5. Repoint the pre-commit hook. A hook running `pnpm exec prettier --write {staged_files}` becomes `prettier --write {staged_files}`. Under lefthook, `stage_fixed: true` continues to apply. Note that lefthook was installed by `package.json`'s `prepare` script, so it now needs installing on the machine and enabling in the store with `lefthook install`.
|
|
406
390
|
6. Repoint CI. A workflow calling `pnpm run check` needs a command that assumes no `package.json`: `prettier --check .`, plus `kb check` for the store's own rules.
|
|
407
|
-
7. Format once: `prettier --write .`. Commit the result on its own, so the reformatting does not obscure later diffs.
|
|
391
|
+
7. Format once: `prettier --write .`. Commit the result on its own, so that the reformatting does not obscure later diffs.
|
|
408
392
|
|
|
409
|
-
A store
|
|
393
|
+
A store with its own note checker needs one more step before it is retired: compare its rules against what `kb check` covers, and port anything missing. `kb check` validates wikilinks, tag aliases, hardcoded home paths, and taxonomy drift, and deliberately leaves frontmatter validity to the record types that own it at write time.
|
|
410
394
|
|
|
411
|
-
A store with no `package.json` runs steps 1, 2, and 7, and skips 3 through 6:
|
|
395
|
+
A store with no `package.json` runs steps 1, 2, and 7, and skips 3 through 6: It has nothing to retire, only Prettier to install, the config to adopt, and a first format to run.
|
|
412
396
|
|
|
413
397
|
## Error and exception model
|
|
414
398
|
|
|
415
|
-
The checks **return** findings; they never throw. Loaders (`loadKbConfig`, `loadAliases`, `loadTaxonomy`) **throw** a typed `KbLoaderError` on structural defects or malformed YAML, with the offending file path named in the message. `KbLoaderError` (exported from `@williamthorsen/kb/config`)
|
|
399
|
+
The checks **return** findings; they never throw. Loaders (`loadKbConfig`, `loadAliases`, `loadTaxonomy`) **throw** a typed `KbLoaderError` on structural defects or malformed YAML, with the offending file path named in the message. `KbLoaderError` (exported from `@williamthorsen/kb/config`) declares a `kind: 'KbLoaderError'` discriminant, paired with an `isKbLoaderError` type guard, so that a caller can distinguish a recoverable config or alias defect from any other throw. An underlying failure, such as a YAML parse error, is attached as the thrown error's `cause`. `loadKbRegistry` throws a plain `Error` on its own structural defects. I/O errors other than a missing optional file propagate.
|
package/bin/kb.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// Imports only node builtins:
|
|
3
|
+
// Imports only node builtins: A top-level import resolves before the gate below runs, so an
|
|
4
4
|
// unresolvable dependency would replace this file's build-first message with ERR_MODULE_NOT_FOUND.
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
// The real entry point loads at runtime from the build output.
|
|
9
|
-
// See packages/agents/README.md ("Bin wrapper pattern") for details.
|
|
7
|
+
// See packages/agents/README.md ("Bin wrapper pattern") for why this indirection exists.
|
|
10
8
|
const entryPoint = new URL('../dist/esm/cli/index.js', import.meta.url);
|
|
11
9
|
|
|
12
10
|
// Gate on the entry file itself: Node raises ERR_MODULE_NOT_FOUND for any unresolved
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import type { KbConfig } from '../config/config-schema.js';
|
|
2
2
|
import type { Finding } from '../types.js';
|
|
3
3
|
import { type EnumeratedNote } from './enumerate.js';
|
|
4
|
-
/** The result of a check run: the effective config, every enumerated note, and every finding the checks produced. */
|
|
5
4
|
export interface CheckResult {
|
|
6
|
-
/** The effective `KbConfig` the run used
|
|
5
|
+
/** The effective `KbConfig` that the run used: loaded from `.kb/config.yaml`, or `defaultKbConfig` when absent. */
|
|
7
6
|
config: KbConfig;
|
|
8
7
|
/**
|
|
9
8
|
* Every note selected by the store's `config.targets` and, inside a git working tree, not ignored by the repository,
|
|
10
9
|
* in walk order.
|
|
11
10
|
*/
|
|
12
11
|
notes: readonly EnumeratedNote[];
|
|
13
|
-
/**
|
|
14
|
-
* Findings from whole-vault integrity (unresolved links, basename collisions), taxonomy drift, and the tag-alias and
|
|
15
|
-
* paths lints.
|
|
16
|
-
*/
|
|
17
12
|
findings: readonly Finding[];
|
|
18
13
|
}
|
|
19
14
|
/**
|
|
@@ -22,20 +17,17 @@ export interface CheckResult {
|
|
|
22
17
|
* the type-blind per-note lints across them. Frontmatter validity is owned by the record types at write time, so no
|
|
23
18
|
* frontmatter re-validation runs here.
|
|
24
19
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* up in the merged `kb.yaml` registry, which is read from `~/.agents/kb.yaml` and from `cwd`'s project-local registry;
|
|
30
|
-
* `cwd` defaults to the store root, so a caller that supplies none still resolves against the user-global registry.
|
|
31
|
-
* Only the stores this store's own links name are consulted.
|
|
20
|
+
* A `[[store:Target]]` link resolves against the store named by its prefix rather than this one. Those stores are looked
|
|
21
|
+
* up in the merged `kb.yaml` registry, which is read from `~/.agents/kb.yaml` and from `cwd`'s project-local registry.
|
|
22
|
+
* Because `cwd` defaults to the store root, a caller that supplies none still resolves against the user-global
|
|
23
|
+
* registry. Only the stores that this store's own links name are consulted.
|
|
32
24
|
*
|
|
33
|
-
* Returns the effective config alongside the enumerated notes and findings, so a consumer
|
|
34
|
-
*
|
|
35
|
-
*
|
|
25
|
+
* Returns the effective config alongside the enumerated notes and findings, so that a consumer can layer its own
|
|
26
|
+
* detectors over the same enumeration without walking the tree twice, and can read the resolved `targets`/`exclude`
|
|
27
|
+
* without re-loading `.kb/config.yaml`.
|
|
36
28
|
*
|
|
37
29
|
* A structural defect in any loaded file throws a `KbLoaderError` (the loaders' own contract); the caller decides how
|
|
38
|
-
* to
|
|
30
|
+
* to report it. Any other error from enumeration or the checks propagates unchanged; it is never relabeled as a
|
|
39
31
|
* config defect.
|
|
40
32
|
*/
|
|
41
33
|
export declare function check(input: {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { KbConfig } from '../config/config-schema.js';
|
|
2
|
-
/**
|
|
3
|
-
* A note reduced to the fields the check pipeline and curate consume: its paths, its frontmatter field map, its body
|
|
4
|
-
* and body-start line (for file-absolute link lines), its full content (for the paths lint), and any parse error.
|
|
5
|
-
*/
|
|
2
|
+
/** A note read and parsed once, so that every check in a run shares the result. */
|
|
6
3
|
export interface EnumeratedNote {
|
|
7
|
-
/** Absolute path the note was read
|
|
4
|
+
/** Absolute path from which the note was read. */
|
|
8
5
|
path: string;
|
|
9
6
|
/** The note's path relative to the KB root, slash-separated. */
|
|
10
7
|
relativePath: string;
|
|
@@ -22,9 +19,10 @@ export interface EnumeratedNote {
|
|
|
22
19
|
/**
|
|
23
20
|
* Walks a KB root and returns the KB-root-relative path of every note {@link enumerateNotes} selects, opening none of
|
|
24
21
|
* them. Scope selection runs through the same matcher and the same pruning, so a caller that needs only the note set's
|
|
25
|
-
* shape
|
|
22
|
+
* shape (which folders contain notes, and how many) sees exactly what the check pipeline admits.
|
|
26
23
|
*
|
|
27
|
-
* A note whose content cannot be read still contributes its path here,
|
|
24
|
+
* A note whose content cannot be read still contributes its path here, whereas `enumerateNotes` drops it with a
|
|
25
|
+
* warning.
|
|
28
26
|
*/
|
|
29
27
|
export declare function enumerateNotePaths(input: {
|
|
30
28
|
kbRoot: string;
|
|
@@ -34,20 +32,19 @@ export declare function enumerateNotePaths(input: {
|
|
|
34
32
|
* Walks a KB root and parses every note whose KB-root-relative path matches a `config.targets` glob and no
|
|
35
33
|
* `config.exclude` glob into an {@link EnumeratedNote}.
|
|
36
34
|
*
|
|
37
|
-
*
|
|
35
|
+
* Because matching uses `picomatch` with `dot:false`, dot-prefixed directories (`.kb`, `.git`, `.agents`) are excluded
|
|
38
36
|
* implicitly without naming them in `exclude`. The walk prunes the tree to each target's leading literal segment
|
|
39
37
|
* (`content/**` descends only into `content/`); a target with no leading literal (e.g. `**\/*.md`) falls back to a
|
|
40
|
-
* full walk. Excludes are honored during descent so an excluded subtree is never entered.
|
|
38
|
+
* full walk. Excludes are honored during descent so that an excluded subtree is never entered.
|
|
41
39
|
*
|
|
42
|
-
* If the store
|
|
40
|
+
* If the store is in a git working tree, the enumeration keeps only the notes that git tracks and the untracked
|
|
43
41
|
* notes that no ignore rule covers. A note that the repository ignores is therefore neither enumerated nor available
|
|
44
42
|
* as a wikilink target, so a link pointing at one resolves to nothing. A store outside a working tree, or a machine
|
|
45
|
-
*
|
|
43
|
+
* with no git, keeps what the walk alone found.
|
|
46
44
|
*
|
|
47
|
-
* Notes with malformed or absent frontmatter are kept
|
|
48
|
-
* returns an empty field map rather than throwing, so they remain valid wikilink targets. A note that cannot be
|
|
49
|
-
* or a directory that cannot be listed, is skipped with a `kb:` stderr warning rather than aborting the walk.
|
|
50
|
-
* note's `path` is absolute; `relativePath` is the slash-separated path from the KB root.
|
|
45
|
+
* Notes with malformed or absent frontmatter are kept: `readNoteContent` records the parse error in `error` and
|
|
46
|
+
* returns an empty field map rather than throwing, so that they remain valid wikilink targets. A note that cannot be
|
|
47
|
+
* read, or a directory that cannot be listed, is skipped with a `kb:` stderr warning rather than aborting the walk.
|
|
51
48
|
*/
|
|
52
49
|
export declare function enumerateNotes(input: {
|
|
53
50
|
kbRoot: string;
|
|
@@ -3,6 +3,6 @@ export declare function isGlobSegment(segment: string): boolean;
|
|
|
3
3
|
/**
|
|
4
4
|
* Returns the leading run of literal (non-glob) segments of a slash-separated pattern, slash-joined. A pattern whose
|
|
5
5
|
* first segment is a glob yields the empty string; a fully literal pattern yields itself. Locates the deepest concrete
|
|
6
|
-
* path a glob is anchored
|
|
6
|
+
* path to which a glob is anchored, e.g. `content/notes/**` yields `content/notes`.
|
|
7
7
|
*/
|
|
8
8
|
export declare function leadingLiteralPrefix(pattern: string): string;
|
|
@@ -2,20 +2,21 @@ import { type StoreVisibility } from '../config/config-schema.js';
|
|
|
2
2
|
import type { KbRegistry } from '../types.js';
|
|
3
3
|
import type { ForeignStore } from '../vault-integrity/check-vault-integrity.js';
|
|
4
4
|
/**
|
|
5
|
-
* Collects the distinct store names that a note set's wikilinks qualify, so a check run consults only the stores
|
|
6
|
-
* own links
|
|
7
|
-
* prefix inside a code sample
|
|
5
|
+
* Collects the distinct store names that a note set's wikilinks qualify, so that a check run consults only the stores
|
|
6
|
+
* that its own links name. It reads {@link scanWikilinks}, the same walk that later evaluates the links, so a
|
|
7
|
+
* store-shaped prefix inside a code sample selects no store and the two cannot disagree on what counts as a qualified
|
|
8
|
+
* link.
|
|
8
9
|
*/
|
|
9
10
|
export declare function collectStorePrefixes(notes: readonly {
|
|
10
11
|
body: string;
|
|
11
12
|
}[]): Set<string>;
|
|
12
13
|
/**
|
|
13
|
-
* Resolves each store name a run's links qualify against the merged registry, reading only note paths and each
|
|
14
|
-
* own `.kb/config.yaml`:
|
|
15
|
-
* ignore rules, as it would be under its own check run, and no foreign note is opened.
|
|
14
|
+
* Resolves each store name that a run's links qualify against the merged registry, reading only note paths and each
|
|
15
|
+
* store's own `.kb/config.yaml`: A foreign store is enumerated under its own `targets`/`exclude` and its own
|
|
16
|
+
* repository's ignore rules, as it would be under its own check run, and no foreign note is opened.
|
|
16
17
|
*
|
|
17
|
-
* A store that cannot be read
|
|
18
|
-
* `unavailable` rather than throwing, so one unrelated store cannot fail the run.
|
|
18
|
+
* A store that cannot be read (absent from this machine, or with a config file that will not load) resolves
|
|
19
|
+
* `unavailable` rather than throwing, so that one unrelated store cannot fail the run.
|
|
19
20
|
*/
|
|
20
21
|
export declare function resolveForeignStores(input: {
|
|
21
22
|
prefixes: Iterable<string>;
|
|
@@ -1,32 +1,26 @@
|
|
|
1
|
-
/** The outcome of a command run: the exit code plus the streams to write. */
|
|
2
1
|
export interface CommandOutput {
|
|
3
2
|
/** Process exit code: 0 clean, 1 error-severity findings, 2 usage/config error. */
|
|
4
3
|
exitCode: 0 | 1 | 2;
|
|
5
4
|
stdout: string;
|
|
6
5
|
stderr: string;
|
|
7
6
|
}
|
|
8
|
-
|
|
9
|
-
export declare const CHECK_HELP = "Usage: kb check [paths...] [options]\n\nValidate notes in a knowledge base against its tag aliases and cross-note\nlink and path rules. With no path arguments, every note is checked.\nCross-note rules always resolve against the whole store; a [[store:Target]]\nlink resolves against the store its prefix names in the kb.yaml registry.\n\nTargeting (mutually exclusive):\n [paths...] Check only the notes matching the given glob patterns, files,\n or directories. Quote globs so kb expands them itself. A\n directory checks every note beneath it. A path that matches no\n note is a usage error unless it names a real non-note.\n --vs <ref> Check only the notes changed between the working tree and the\n merge-base of <ref> and HEAD: follows renames, includes\n uncommitted edits, excludes deletions.\n\nOptions:\n --kb <name> Check the named store from the kb.yaml registry. Without it,\n the nearest ancestor .kb/ directory is used.\n --json Emit a JSON report instead of human-readable output.\n -h, --help Show this help.\n\nExit codes:\n 0 no error-severity findings in the checked notes (warnings allowed)\n 1 one or more error-severity findings in the checked notes\n 2 usage error, unresolvable store or --vs ref, a path matching no note, or\n malformed config or aliases\n";
|
|
7
|
+
export declare const CHECK_HELP = "Usage: kb check [paths...] [options]\n\nValidate notes in a knowledge base against its tag aliases and cross-note\nlink and path rules. With no path arguments, every note is checked.\nCross-note rules always resolve against the whole store; a [[store:Target]]\nlink resolves against the store named by its prefix in the kb.yaml registry.\n\nTargeting (mutually exclusive):\n [paths...] Check only the notes matching the given glob patterns, files,\n or directories. Quote globs so that kb expands them itself. A\n directory checks every note beneath it. A path that matches no\n note is a usage error unless it names a real non-note.\n --vs <ref> Check only the notes changed between the working tree and the\n merge-base of <ref> and HEAD: follows renames, includes\n uncommitted edits, excludes deletions.\n\nOptions:\n --kb <name> Check the named store from the kb.yaml registry. Without it,\n the nearest ancestor .kb/ directory is used.\n --json Emit a JSON report instead of human-readable output.\n -h, --help Show this help.\n\nExit codes:\n 0 no error-severity findings in the checked notes (warnings allowed)\n 1 one or more error-severity findings in the checked notes\n 2 usage error, unresolvable store or --vs ref, a path matching no note, or\n malformed config or aliases\n";
|
|
10
8
|
/**
|
|
11
9
|
* Runs `kb check`: parses options, resolves the store, runs the shared `check`, and formats the report.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
15
|
-
*
|
|
16
|
-
* to exit 2; any other error from `check` propagates to the caller as a real crash.
|
|
11
|
+
* The command writes nothing to the store, so it ignores the registry's `readonly` flag. `check` throws a
|
|
12
|
+
* `KbLoaderError` for a structural defect in a store file that it loads, and the command maps that error to exit 2;
|
|
13
|
+
* any other error from `check` propagates to the caller as a real crash.
|
|
17
14
|
*/
|
|
18
15
|
export declare function runCheck(input: {
|
|
19
16
|
argv: readonly string[];
|
|
20
17
|
cwd: string;
|
|
21
18
|
home?: string;
|
|
22
19
|
}): Promise<CommandOutput>;
|
|
23
|
-
/** Parsed `kb check` options. */
|
|
24
20
|
interface CheckOptions {
|
|
25
21
|
/** Explicit store name from `--kb`, or `null` for ancestor-walk discovery. */
|
|
26
22
|
kb: string | null;
|
|
27
|
-
/** Whether `--json` was supplied. */
|
|
28
23
|
json: boolean;
|
|
29
|
-
/** Whether `--help`/`-h` was supplied. */
|
|
30
24
|
help: boolean;
|
|
31
25
|
/** Positional glob/path/directory arguments selecting which notes to check; empty for a whole-vault run. */
|
|
32
26
|
patterns: string[];
|