@williamthorsen/kb 0.6.2 → 0.7.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/README.md +120 -7
- package/dist/esm/check/check.d.ts +24 -0
- package/dist/esm/check/enumerate.d.ts +37 -0
- package/dist/esm/check/enumerate.js +12 -4
- package/dist/esm/check/glob-segments.d.ts +6 -0
- package/dist/esm/cli/commands/check.d.ts +22 -0
- package/dist/esm/cli/commands/create.d.ts +19 -1
- package/dist/esm/cli/commands/create.js +2 -0
- package/dist/esm/cli/commands/scaffold.d.ts +30 -0
- package/dist/esm/cli/commands/scaffold.js +108 -0
- package/dist/esm/cli/commands/set-default.d.ts +15 -0
- package/dist/esm/cli/commands/taxonomy.d.ts +18 -0
- package/dist/esm/cli/format.d.ts +21 -0
- package/dist/esm/cli/parse-flag-value.d.ts +5 -0
- package/dist/esm/cli/resolve-store.d.ts +12 -0
- package/dist/esm/cli/run.d.ts +9 -1
- package/dist/esm/cli/run.js +5 -0
- package/dist/esm/cli/select-kb-prompt.d.ts +18 -0
- package/dist/esm/cli/targeting/resolve-changed-paths.d.ts +10 -0
- package/dist/esm/cli/targeting/resolve-changed-paths.js +7 -33
- package/dist/esm/cli/targeting/select-notes.d.ts +13 -0
- package/dist/esm/cli/targeting/select-notes.js +4 -3
- package/dist/esm/config/config-schema.d.ts +12 -0
- package/dist/esm/config/kb-loader-error.d.ts +8 -0
- package/dist/esm/config/load-config.d.ts +7 -0
- package/dist/esm/config/note-scope.d.ts +13 -0
- package/dist/esm/create/create.d.ts +25 -0
- package/dist/esm/create/create.js +8 -12
- package/dist/esm/create/index.d.ts +0 -1
- package/dist/esm/create/index.js +0 -1
- package/dist/esm/discovery/find-kb-root.d.ts +4 -0
- package/dist/esm/discovery/kb-registry-schema.d.ts +4 -0
- package/dist/esm/discovery/load-registry.d.ts +23 -0
- package/dist/esm/discovery/register-store.d.ts +11 -0
- package/dist/esm/discovery/registry-document.d.ts +5 -0
- package/dist/esm/discovery/set-default-kb.d.ts +12 -0
- package/dist/esm/filesystem/exists.d.ts +13 -0
- package/dist/esm/filesystem/write-atomic.d.ts +5 -0
- package/dist/esm/frontmatter/parse-note.d.ts +9 -0
- package/dist/esm/git/list-git-scope.d.ts +15 -0
- package/dist/esm/git/list-git-scope.js +22 -0
- package/dist/esm/git/run-git.d.ts +17 -0
- package/dist/esm/git/run-git.js +22 -0
- package/dist/esm/layout/index.d.ts +1 -1
- package/dist/esm/layout/index.js +1 -1
- package/dist/esm/layout/store-layout.d.ts +20 -0
- package/dist/esm/layout/store-layout.js +2 -0
- package/dist/esm/lints/paths.d.ts +7 -0
- package/dist/esm/lints/tag-alias.d.ts +6 -0
- package/dist/esm/lints/taxonomy.d.ts +15 -0
- package/dist/esm/note-io/field-validators.d.ts +9 -0
- package/dist/esm/note-io/read-note.d.ts +7 -0
- package/dist/esm/note-io/write-note.d.ts +9 -0
- package/dist/esm/note-io/yaml-fields.d.ts +5 -0
- package/dist/esm/records/assertion.d.ts +4 -0
- package/dist/esm/records/event.d.ts +12 -0
- package/dist/esm/scaffold/index.d.ts +2 -0
- package/dist/esm/scaffold/index.js +2 -0
- package/dist/esm/scaffold/render-seeds.d.ts +16 -0
- package/dist/esm/scaffold/render-seeds.js +63 -0
- package/dist/esm/scaffold/scaffold.d.ts +23 -0
- package/dist/esm/scaffold/scaffold.js +35 -0
- package/dist/esm/tags/canonicalize.d.ts +8 -0
- package/dist/esm/tags/load-aliases.d.ts +10 -0
- package/dist/esm/taxonomy/domain-paths.d.ts +10 -0
- package/dist/esm/taxonomy/load-taxonomy.d.ts +8 -0
- package/dist/esm/taxonomy/taxonomy-schema.d.ts +19 -0
- package/dist/esm/taxonomy/write-taxonomy.d.ts +20 -0
- package/dist/esm/type-guards.d.ts +13 -0
- package/dist/esm/types.d.ts +41 -0
- package/dist/esm/vault-integrity/build-vault-index.d.ts +11 -0
- package/dist/esm/vault-integrity/check-vault-integrity.d.ts +13 -0
- package/dist/esm/vault-integrity/wikilink-parse.d.ts +22 -0
- package/package.json +9 -3
- package/dist/esm/create/render-seeds.d.ts +0 -2
- package/dist/esm/create/render-seeds.js +0 -29
package/README.md
CHANGED
|
@@ -4,11 +4,38 @@ Foundation library for knowledge-base tooling.
|
|
|
4
4
|
Provides knowledge-base discovery, registry loading, frontmatter parsing and writing, tag canonicalization, and type-blind vault-integrity checks.
|
|
5
5
|
It underpins 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` — and the planned `@williamthorsen/kb-mcp` server.
|
|
6
6
|
|
|
7
|
-
<!-- section:release-notes
|
|
7
|
+
<!-- section:release-notes -->
|
|
8
|
+
## Release notes — v0.7.0 (2026-09-08)
|
|
9
|
+
|
|
10
|
+
### 🎉 Features
|
|
11
|
+
|
|
12
|
+
- 🚨 **Breaking:** Add kb scaffold to back-fill canonical files into an existing store (#1584)
|
|
13
|
+
|
|
14
|
+
- Adds the `kb scaffold` command, which writes the canonical files (`.kb/config.yaml` and `.kb/tag-aliases.yaml`) into a knowledge base. Files are created if missing, or replaced if `--force` is used.
|
|
15
|
+
- Narrows what `kb create` reports to the paths that it actually created.
|
|
16
|
+
|
|
17
|
+
Migration: import `renderAliasesSeed` and `renderConfigSeed` from `@williamthorsen/kb/scaffold` instead of `@williamthorsen/kb/create`.
|
|
18
|
+
|
|
19
|
+
- Add canonical formatting configs so a store formats without a toolchain (#1586)
|
|
20
|
+
|
|
21
|
+
- Adds `.editorconfig` and `.prettierrc.yaml` to the canonical file set, so that `kb create` writes them into a new store and `kb scaffold` back-fills them into an existing one.
|
|
22
|
+
- Puts width, indentation, and line endings in `.editorconfig`, which editors read as well as Prettier.
|
|
23
|
+
- Sets `embeddedLanguageFormatting: off` in `.prettierrc.yaml`, which keeps Prettier from reflowing note frontmatter that the kb's note writer would flatten again.
|
|
24
|
+
|
|
25
|
+
### 🐛 Bug fixes
|
|
26
|
+
|
|
27
|
+
- Stop checking gitignored notes (#1587)
|
|
28
|
+
|
|
29
|
+
- Stops `kb check` from checking notes matching patterns in `.gitignore`. It now checks only those files that are tracked by git or new and not gitignored.
|
|
30
|
+
- Makes a link pointing at a gitignored note report `wikilinks.unresolved`, since such a link is broken for every clone but the author's.
|
|
31
|
+
- Fixes `kb check <path>` and `kb check --vs <ref>` for a note with an accented name, which on macOS matched nothing and could select unrelated files in its place.
|
|
32
|
+
|
|
33
|
+
Migration: Track a gitignored note that other notes link to, or remove the wikilinks pointing at it.
|
|
34
|
+
<!-- /section:release-notes -->
|
|
8
35
|
|
|
9
36
|
## Exports
|
|
10
37
|
|
|
11
|
-
The package exposes
|
|
38
|
+
The package exposes thirteen subpath entries plus a root barrel:
|
|
12
39
|
|
|
13
40
|
| Entry | Description |
|
|
14
41
|
| ------------------- | ------------------------------------------------------------------------------ |
|
|
@@ -22,6 +49,7 @@ The package exposes twelve subpath entries plus a root barrel:
|
|
|
22
49
|
| `./layout` | The store's on-disk layout: every path inside a `.kb/` store derives from here |
|
|
23
50
|
| `./note-io` | Type-blind note read/write as an ordered frontmatter field map |
|
|
24
51
|
| `./records` | The typed `assertion`/`event` record parsers and renderers |
|
|
52
|
+
| `./scaffold` | The canonical set held by a store, and the idempotent writer over it |
|
|
25
53
|
| `./tags` | `.kb/tag-aliases.yaml` loading and tag canonicalization |
|
|
26
54
|
| `./taxonomy` | `.kb/taxonomy.yaml` loading, comment-preserving declaration, and path mapping |
|
|
27
55
|
| `./vault-integrity` | Type-blind `[[link]]` resolution and basename-uniqueness over a note set |
|
|
@@ -124,7 +152,7 @@ const findings = checkVaultIntegrity(notes);
|
|
|
124
152
|
|
|
125
153
|
## Checking a store
|
|
126
154
|
|
|
127
|
-
`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 the config selects, 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 a consumer can layer its own detectors over the same enumeration without walking the store twice.
|
|
155
|
+
`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 (narrowed by [the git dimension](#the-git-dimension) inside a working tree), 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 a consumer can layer its own detectors over the same enumeration without walking the store twice.
|
|
128
156
|
|
|
129
157
|
```ts
|
|
130
158
|
import { check } from '@williamthorsen/kb/check';
|
|
@@ -155,6 +183,12 @@ exclude:
|
|
|
155
183
|
|
|
156
184
|
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`.
|
|
157
185
|
|
|
186
|
+
#### The git dimension
|
|
187
|
+
|
|
188
|
+
Where the store sits in a git working tree, `targets`/`exclude` is not the whole of note scope: what git accounts for narrows it further. A note is enumerated when git tracks it, or when git would track it, meaning 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 is what lets a store gitignore a scratch area (`local/`, `*.local.md`) and keep uncommitted notes there without the store's lints gating them.
|
|
189
|
+
|
|
190
|
+
A store outside a git working tree, or a machine carrying no git, keeps the filesystem walk alone. Where git accounts for none of the notes 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 a clean bill over an empty note set.
|
|
191
|
+
|
|
158
192
|
### The declared structure: `.kb/taxonomy.yaml`
|
|
159
193
|
|
|
160
194
|
`.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.
|
|
@@ -187,7 +221,7 @@ A domain counts as used when any note lives at or beneath it, so a grouping doma
|
|
|
187
221
|
|
|
188
222
|
## The `kb` command
|
|
189
223
|
|
|
190
|
-
The package ships a `kb` bin with
|
|
224
|
+
The package ships a `kb` bin with five subcommands: `check`, `create`, `scaffold`, `set-default`, and `taxonomy`.
|
|
191
225
|
|
|
192
226
|
### kb create
|
|
193
227
|
|
|
@@ -204,16 +238,34 @@ It creates these files and directories:
|
|
|
204
238
|
|
|
205
239
|
| Path | Contents |
|
|
206
240
|
| ----------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
241
|
+
| `.editorconfig` | Width, indentation, and line endings; see [Formatting a knowledge base](#formatting-a-knowledge-base) |
|
|
207
242
|
| `.kb/config.yaml` | A fully-commented check config; the bundled defaults apply as-is |
|
|
208
243
|
| `.kb/tag-aliases.yaml` | An empty `aliases: {}` map |
|
|
244
|
+
| `.prettierrc.yaml` | The canonical formatting config; see [Formatting a knowledge base](#formatting-a-knowledge-base) |
|
|
209
245
|
| `content/`, `content/events/` | The note tree; `capture-event` writes events to `content/events/`, `kb-update-events` edits them in place |
|
|
210
246
|
|
|
211
|
-
The config
|
|
247
|
+
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.
|
|
212
248
|
|
|
213
|
-
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, so a registry that has drifted out of order is tidied as stores are added. `kb create` refuses to clobber: it exits 2 if the directory already contains a `.kb/` store, or if the chosen name is already registered.
|
|
249
|
+
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, so a registry that has drifted out of order is tidied as stores are added. `kb create` refuses to clobber: 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.
|
|
214
250
|
|
|
215
251
|
`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 you to choose one on an interactive terminal — or, when stdin is not interactive, points you to `kb set-default`. An existing `default_kb` is never overwritten.
|
|
216
252
|
|
|
253
|
+
### kb scaffold
|
|
254
|
+
|
|
255
|
+
`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.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
kb scaffold # back-fill the nearest ancestor .kb/ store
|
|
259
|
+
kb scaffold --kb coding # back-fill the named store from the kb.yaml registry
|
|
260
|
+
kb scaffold --force # replace every canonical file with a fresh seed
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
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 holds rather than writing a fixed template.
|
|
264
|
+
|
|
265
|
+
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`.
|
|
266
|
+
|
|
267
|
+
Store resolution matches `kb check`: the nearest ancestor `.kb/` directory, or a `--kb <name>` entry in the merged `kb.yaml` registry. Three grounds exit 2: no store resolves, the registry marks the resolved store `readonly`, or the resolved path holds 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.
|
|
268
|
+
|
|
217
269
|
### kb set-default
|
|
218
270
|
|
|
219
271
|
`kb set-default` sets, clears, or interactively chooses the user-global default knowledge base: the top-level `default_kb` pointer in `~/.agents/kb.yaml`.
|
|
@@ -243,7 +295,7 @@ kb check --vs=main # check only the notes changed since a ref
|
|
|
243
295
|
|
|
244
296
|
**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 vault, so a targeted run never false-flags a link to an unselected note; only the report and the exit code narrow to the selection.
|
|
245
297
|
|
|
246
|
-
- **`[paths...]`**: one or more glob patterns, files, or directories (store-root-relative). The command expands globs itself, so 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
|
|
298
|
+
- **`[paths...]`**: one or more glob patterns, files, or directories (store-root-relative). The command expands globs itself, so 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.
|
|
247
299
|
- **`--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 it actually touched.
|
|
248
300
|
|
|
249
301
|
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 carries a migration backlog.
|
|
@@ -272,6 +324,67 @@ Every derived domain lands under `provisional:` with no description: the command
|
|
|
272
324
|
|
|
273
325
|
Without `--merge`, a store that already declares a taxonomy is left untouched and the command exits 2.
|
|
274
326
|
|
|
327
|
+
## Formatting a knowledge base
|
|
328
|
+
|
|
329
|
+
A knowledge base is formatted by Prettier, invoked directly. kb owns the configuration and scaffolds it; it does not run the formatter and does not bundle one. A store therefore needs Prettier available on the machine and the two config files that `kb create` and `kb scaffold` write, and nothing else: no `package.json`, no lockfile, and no linter or TypeScript configuration.
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
prettier --write . # format the store
|
|
333
|
+
prettier --check . # report drift without writing
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
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 holding no `package.json`.
|
|
337
|
+
|
|
338
|
+
### The two config files
|
|
339
|
+
|
|
340
|
+
`.editorconfig` carries everything that more than one tool reads:
|
|
341
|
+
|
|
342
|
+
```ini
|
|
343
|
+
root = true
|
|
344
|
+
|
|
345
|
+
[*]
|
|
346
|
+
charset = utf-8
|
|
347
|
+
end_of_line = lf
|
|
348
|
+
indent_size = 2
|
|
349
|
+
indent_style = space
|
|
350
|
+
insert_final_newline = true
|
|
351
|
+
max_line_length = 120
|
|
352
|
+
trim_trailing_whitespace = true
|
|
353
|
+
|
|
354
|
+
[*.md]
|
|
355
|
+
trim_trailing_whitespace = false
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Prettier reads this file, and so does every editor that supports EditorConfig, which is why the width and the indentation live here rather than in the Prettier config. `max_line_length` becomes Prettier's `printWidth` and `indent_size` its `tabWidth`, so a store that left them unset would format at Prettier's default width of 80 and would tell an editor nothing at all.
|
|
359
|
+
|
|
360
|
+
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.
|
|
361
|
+
|
|
362
|
+
`.prettierrc.yaml` carries the one setting that `.editorconfig` has no key for:
|
|
363
|
+
|
|
364
|
+
```yaml
|
|
365
|
+
embeddedLanguageFormatting: off
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
It is not stylistic, and the scaffolded file carries this reasoning in its own header so it travels 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 `writeFrontmatter` 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 carrying a list past the print width churns on each pass.
|
|
369
|
+
|
|
370
|
+
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 escape hatch and is not scaffolded, since a fresh store has nothing to put in it.
|
|
371
|
+
|
|
372
|
+
### Migrating a store off a `package.json` toolchain
|
|
373
|
+
|
|
374
|
+
A store that carries a `package.json` only to obtain Prettier can retire it.
|
|
375
|
+
|
|
376
|
+
1. Install Prettier where the store is edited, if it is not already there: `pnpm add --global prettier`.
|
|
377
|
+
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.
|
|
378
|
+
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.
|
|
379
|
+
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.
|
|
380
|
+
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`.
|
|
381
|
+
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.
|
|
382
|
+
7. Format once: `prettier --write .`. Commit the result on its own, so the reformatting does not obscure later diffs.
|
|
383
|
+
|
|
384
|
+
A store carrying its own note checker deserves 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.
|
|
385
|
+
|
|
386
|
+
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.
|
|
387
|
+
|
|
275
388
|
## Error and exception model
|
|
276
389
|
|
|
277
390
|
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`) carries a `kind: 'KbLoaderError'` discriminant — and an `isKbLoaderError` type guard — so 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.
|
|
@@ -1,11 +1,35 @@
|
|
|
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. */
|
|
4
5
|
export interface CheckResult {
|
|
6
|
+
/** The effective `KbConfig` the run used — loaded from `.kb/config.yaml`, or `defaultKbConfig` when absent. */
|
|
5
7
|
config: KbConfig;
|
|
8
|
+
/** Every note that the store's `config.targets` and, inside a git working tree, its git scope admit, in walk order. */
|
|
6
9
|
notes: readonly EnumeratedNote[];
|
|
10
|
+
/**
|
|
11
|
+
* Findings from whole-vault integrity (unresolved links, basename collisions), taxonomy drift, and the tag-alias and
|
|
12
|
+
* paths lints.
|
|
13
|
+
*/
|
|
7
14
|
findings: readonly Finding[];
|
|
8
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Runs the store's config-driven check: load `.kb/config.yaml`, `.kb/tag-aliases.yaml`, and `.kb/taxonomy.yaml`,
|
|
18
|
+
* enumerate notes under the config's `targets`/`exclude`, and compose whole-vault integrity and taxonomy drift with
|
|
19
|
+
* the type-blind per-note lints across them. Frontmatter validity is owned by the record types at write time, so no
|
|
20
|
+
* frontmatter re-validation runs here.
|
|
21
|
+
*
|
|
22
|
+
* Inside a git working tree the enumeration narrows to what git accounts for, so a note the repository ignores is
|
|
23
|
+
* neither checked nor available as a wikilink target; see {@link enumerateNotes} for the rule.
|
|
24
|
+
*
|
|
25
|
+
* Returns the effective config alongside the enumerated notes and findings, so a consumer (e.g. `kb-curate`) can layer
|
|
26
|
+
* its own detectors over the same enumeration without walking the tree twice, and can read the resolved
|
|
27
|
+
* `targets`/`exclude` without re-loading `.kb/config.yaml`.
|
|
28
|
+
*
|
|
29
|
+
* A structural defect in any loaded file throws a `KbLoaderError` (the loaders' own contract); the caller decides how
|
|
30
|
+
* to surface it. Any other error from enumeration or the checks propagates unchanged — it is never relabeled as a
|
|
31
|
+
* config defect.
|
|
32
|
+
*/
|
|
9
33
|
export declare function check(input: {
|
|
10
34
|
kbRoot: string;
|
|
11
35
|
}): Promise<CheckResult>;
|
|
@@ -1,17 +1,54 @@
|
|
|
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
6
|
export interface EnumeratedNote {
|
|
7
|
+
/** Absolute path the note was read from. */
|
|
3
8
|
path: string;
|
|
9
|
+
/** The note's path relative to the KB root, slash-separated. */
|
|
4
10
|
relativePath: string;
|
|
11
|
+
/** The frontmatter field map, empty when the block is absent or unparseable. */
|
|
5
12
|
fields: Record<string, unknown>;
|
|
13
|
+
/** The note body (everything after the frontmatter block). */
|
|
6
14
|
body: string;
|
|
15
|
+
/** The full original note content. */
|
|
7
16
|
content: string;
|
|
17
|
+
/** 1-based file line where the body begins. */
|
|
8
18
|
bodyStartLine: number;
|
|
19
|
+
/** Frontmatter parse or absence diagnostic, when the block was missing or could not be parsed. */
|
|
9
20
|
error?: string;
|
|
10
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Walks a KB root and returns the KB-root-relative path of every note {@link enumerateNotes} selects, opening none of
|
|
24
|
+
* them. Scope selection runs through the same matcher and the same pruning, so a caller that needs only the note set's
|
|
25
|
+
* shape — which folders hold notes, and how many — sees exactly what the check pipeline admits.
|
|
26
|
+
*
|
|
27
|
+
* A note whose content cannot be read still contributes its path here, where `enumerateNotes` drops it with a warning.
|
|
28
|
+
*/
|
|
11
29
|
export declare function enumerateNotePaths(input: {
|
|
12
30
|
kbRoot: string;
|
|
13
31
|
config: KbConfig;
|
|
14
32
|
}): Promise<string[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Walks a KB root and parses every note whose KB-root-relative path matches a `config.targets` glob and no
|
|
35
|
+
* `config.exclude` glob into an {@link EnumeratedNote}.
|
|
36
|
+
*
|
|
37
|
+
* Matching uses `picomatch` with `dot:false`, so dot-prefixed directories (`.kb`, `.git`, `.agents`) are excluded
|
|
38
|
+
* implicitly without naming them in `exclude`. The walk prunes the tree to each target's leading literal segment
|
|
39
|
+
* (`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.
|
|
41
|
+
*
|
|
42
|
+
* Where the store sits in a git working tree, scope narrows further to what git accounts for: tracked notes plus
|
|
43
|
+
* untracked ones that no ignore rule covers. A note that the repository ignores is therefore neither enumerated nor available
|
|
44
|
+
* as a wikilink target, so a link pointing at one resolves to nothing. A store outside a working tree, or a machine
|
|
45
|
+
* carrying no git, keeps the walk's own scope.
|
|
46
|
+
*
|
|
47
|
+
* Notes with malformed or absent frontmatter are kept — `readNoteContent` records the parse error in `error` and
|
|
48
|
+
* returns an empty field map rather than throwing, so they remain valid wikilink targets. A note that cannot be read,
|
|
49
|
+
* or a directory that cannot be listed, is skipped with a `kb:` stderr warning rather than aborting the walk. Each
|
|
50
|
+
* note's `path` is absolute; `relativePath` is the slash-separated path from the KB root.
|
|
51
|
+
*/
|
|
15
52
|
export declare function enumerateNotes(input: {
|
|
16
53
|
kbRoot: string;
|
|
17
54
|
config: KbConfig;
|
|
@@ -3,6 +3,7 @@ import { join, relative, sep } from 'node:path';
|
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { createNoteScopeMatcher } from "../config/note-scope.js";
|
|
6
|
+
import { listGitScope } from "../git/list-git-scope.js";
|
|
6
7
|
import { readNoteContent } from "../note-io/read-note.js";
|
|
7
8
|
import { isGlobSegment } from "./glob-segments.js";
|
|
8
9
|
export async function enumerateNotePaths(input) {
|
|
@@ -36,8 +37,15 @@ async function collectNoteLocations(input) {
|
|
|
36
37
|
const { kbRoot, config } = input;
|
|
37
38
|
const matcher = createNoteScopeMatcher(config);
|
|
38
39
|
const topLevelDirs = leadingLiteralSegments(config.targets);
|
|
39
|
-
const
|
|
40
|
-
await walk({ root: kbRoot, dir: kbRoot, matcher, topLevelDirs, out:
|
|
40
|
+
const candidates = [];
|
|
41
|
+
await walk({ root: kbRoot, dir: kbRoot, matcher, topLevelDirs, out: candidates });
|
|
42
|
+
const gitScope = listGitScope({ root: kbRoot });
|
|
43
|
+
if (gitScope === undefined)
|
|
44
|
+
return candidates;
|
|
45
|
+
const locations = candidates.filter((candidate) => gitScope.has(candidate.relativePath.normalize('NFC')));
|
|
46
|
+
if (candidates.length > 0 && locations.length === 0) {
|
|
47
|
+
process.stderr.write(`kb: warning: git ignores every note under ${kbRoot}, so nothing was checked\n`);
|
|
48
|
+
}
|
|
41
49
|
return locations;
|
|
42
50
|
}
|
|
43
51
|
function leadingLiteralSegments(targets) {
|
|
@@ -45,7 +53,7 @@ function leadingLiteralSegments(targets) {
|
|
|
45
53
|
for (const target of targets) {
|
|
46
54
|
const firstSegment = target.split('/', 1)[0] ?? '';
|
|
47
55
|
if (firstSegment === '' || isGlobSegment(firstSegment)) {
|
|
48
|
-
return
|
|
56
|
+
return undefined;
|
|
49
57
|
}
|
|
50
58
|
dirs.add(firstSegment);
|
|
51
59
|
}
|
|
@@ -66,7 +74,7 @@ async function walk(input) {
|
|
|
66
74
|
const absolutePath = join(dir, entry.name);
|
|
67
75
|
const relativePath = relative(root, absolutePath).split(sep).join('/');
|
|
68
76
|
if (entry.isDirectory()) {
|
|
69
|
-
if (atRoot && topLevelDirs !==
|
|
77
|
+
if (atRoot && topLevelDirs !== undefined && !topLevelDirs.has(entry.name))
|
|
70
78
|
continue;
|
|
71
79
|
if (matcher.isExcluded(relativePath))
|
|
72
80
|
continue;
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
/** Reports whether a path segment contains a glob metacharacter, making it non-literal. */
|
|
1
2
|
export declare function isGlobSegment(segment: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Returns the leading run of literal (non-glob) segments of a slash-separated pattern, slash-joined. A pattern whose
|
|
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 to, e.g. `content/notes/**` yields `content/notes`.
|
|
7
|
+
*/
|
|
2
8
|
export declare function leadingLiteralPrefix(pattern: string): string;
|
|
@@ -1,20 +1,42 @@
|
|
|
1
|
+
/** The outcome of a command run: the exit code plus the streams to write. */
|
|
1
2
|
export interface CommandOutput {
|
|
3
|
+
/** Process exit code: 0 clean, 1 error-severity findings, 2 usage/config error. */
|
|
2
4
|
exitCode: 0 | 1 | 2;
|
|
3
5
|
stdout: string;
|
|
4
6
|
stderr: string;
|
|
5
7
|
}
|
|
8
|
+
/** Usage text for `kb check`. */
|
|
6
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 vault.\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";
|
|
10
|
+
/**
|
|
11
|
+
* Runs `kb check`: parses options, resolves the store, runs the shared `check`, and formats the report.
|
|
12
|
+
*
|
|
13
|
+
* Store resolution composes the package's own exports inline — `findKbRoot` for the default ancestor-walk and
|
|
14
|
+
* `tryLoadKbRegistry` for an explicit `--kb <name>`. The lookup is read-only, so a store's registry `readonly` flag
|
|
15
|
+
* is ignored. A malformed `.kb/config.yaml`/`tag-aliases.yaml` surfaces as a `KbLoaderError` from `check`, which maps
|
|
16
|
+
* to exit 2; any other error from `check` propagates to the caller as a real crash.
|
|
17
|
+
*/
|
|
7
18
|
export declare function runCheck(input: {
|
|
8
19
|
argv: readonly string[];
|
|
9
20
|
cwd: string;
|
|
10
21
|
home?: string;
|
|
11
22
|
}): Promise<CommandOutput>;
|
|
23
|
+
/** Parsed `kb check` options. */
|
|
12
24
|
interface CheckOptions {
|
|
25
|
+
/** Explicit store name from `--kb`, or `null` for ancestor-walk discovery. */
|
|
13
26
|
kb: string | null;
|
|
27
|
+
/** Whether `--json` was supplied. */
|
|
14
28
|
json: boolean;
|
|
29
|
+
/** Whether `--help`/`-h` was supplied. */
|
|
15
30
|
help: boolean;
|
|
31
|
+
/** Positional glob/path/directory arguments selecting which notes to check; empty for a whole-vault run. */
|
|
16
32
|
patterns: string[];
|
|
33
|
+
/** The `--vs` ref to diff against, or `null` when not supplied. */
|
|
17
34
|
vs: string | null;
|
|
18
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Parses `kb check` options. `--kb` and `--vs` each accept both the space (`--kb x`) and equals (`--kb=x`) forms;
|
|
38
|
+
* non-flag arguments are collected as selection patterns. Unknown flags, a missing `--kb`/`--vs` value, or combining
|
|
39
|
+
* patterns with `--vs` throw with a usage-style message.
|
|
40
|
+
*/
|
|
19
41
|
export declare function parseCheckArgs(argv: readonly string[]): CheckOptions;
|
|
20
42
|
export {};
|
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
import type { SelectKbPrompt } from '../select-kb-prompt.js';
|
|
2
2
|
import type { CommandOutput } from './check.js';
|
|
3
|
-
|
|
3
|
+
/** Usage text for `kb create`. */
|
|
4
|
+
export declare const CREATE_HELP = "Usage: kb create [options]\n\nScaffold a new knowledge base in the current directory and register it in the user-global kb.yaml registry.\n\nRegistering leaves the registry's entries in alphabetical order, preserving its comments and formatting.\n\nWhen the registry has no default knowledge base, the new store becomes the default.\nIf other knowledge bases are already registered, you are prompted to choose one (or set it later with \"kb set-default\").\n\nCreates:\n .editorconfig editor and formatter settings (width, indent, line endings)\n .kb/config.yaml check configuration (commented; defaults apply)\n .kb/tag-aliases.yaml tag-alias map (empty)\n .prettierrc.yaml formatting configuration (commented)\n content/, content/events/\n\nOptions:\n --description <text> Description for the registry entry; cannot be combined with --no-register.\n --name <name> Registry name for the store. Defaults to the directory name.\n --no-register Scaffold without writing the kb.yaml registry entry.\n -h, --help Show this help.\n\nExit codes:\n 0 store created\n 2 usage error, an existing .kb/ in the directory, or an already-registered name\n";
|
|
5
|
+
/**
|
|
6
|
+
* Runs `kb create`: parses options, scaffolds a store in `cwd`, and (unless `--no-register`) registers it in the
|
|
7
|
+
* user-global `~/.agents/kb.yaml`. After registering, it ensures a default knowledge base: the new store becomes the
|
|
8
|
+
* default when none is set and it is the only KB; when other KBs already exist with no default, it delegates to
|
|
9
|
+
* `kb set-default`'s picker (or, with no `selectKb` on a non-interactive stdin, points the user there). A precondition
|
|
10
|
+
* failure from `create` — an existing `.kb/` or an already-registered name — maps to exit 2; a genuine I/O error
|
|
11
|
+
* propagates to the caller.
|
|
12
|
+
*/
|
|
4
13
|
export declare function runCreate(input: {
|
|
5
14
|
argv: readonly string[];
|
|
6
15
|
cwd: string;
|
|
7
16
|
home?: string;
|
|
8
17
|
selectKb?: SelectKbPrompt;
|
|
9
18
|
}): Promise<CommandOutput>;
|
|
19
|
+
/** Parsed `kb create` options. */
|
|
10
20
|
interface CreateOptions {
|
|
21
|
+
/** Description from `--description`, or `null` to write the entry without one. */
|
|
11
22
|
description: string | null;
|
|
23
|
+
/** Explicit registry name from `--name`, or `null` to default to the directory name. */
|
|
12
24
|
name: string | null;
|
|
25
|
+
/** Whether `--no-register` was supplied. */
|
|
13
26
|
noRegister: boolean;
|
|
27
|
+
/** Whether `--help`/`-h` was supplied. */
|
|
14
28
|
help: boolean;
|
|
15
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Parses `kb create` options. Each value-taking flag accepts both the space form and the equals form. Unknown flags, a
|
|
32
|
+
* missing value, or a description supplied alongside `--no-register` throw with a usage-style message.
|
|
33
|
+
*/
|
|
16
34
|
export declare function parseCreateArgs(argv: readonly string[]): CreateOptions;
|
|
17
35
|
export {};
|
|
@@ -14,8 +14,10 @@ When the registry has no default knowledge base, the new store becomes the defau
|
|
|
14
14
|
If other knowledge bases are already registered, you are prompted to choose one (or set it later with "kb set-default").
|
|
15
15
|
|
|
16
16
|
Creates:
|
|
17
|
+
.editorconfig editor and formatter settings (width, indent, line endings)
|
|
17
18
|
.kb/config.yaml check configuration (commented; defaults apply)
|
|
18
19
|
.kb/tag-aliases.yaml tag-alias map (empty)
|
|
20
|
+
.prettierrc.yaml formatting configuration (commented)
|
|
19
21
|
content/, content/events/
|
|
20
22
|
|
|
21
23
|
Options:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CommandOutput } from './check.js';
|
|
2
|
+
/** Usage text for `kb scaffold`. */
|
|
3
|
+
export declare const SCAFFOLD_HELP = "Usage: kb scaffold [options]\n\nWrite into an existing knowledge base any canonical file that it lacks, so a store\ncreated before a given file existed can acquire it. An existing file is left\nuntouched unless --force is given.\n\nWrites:\n .editorconfig editor and formatter settings (width, indent, line endings)\n .kb/config.yaml check configuration (commented; defaults apply)\n .kb/tag-aliases.yaml tag-alias map (empty)\n .prettierrc.yaml formatting configuration (commented)\n content/, content/events/\n\n.kb/taxonomy.yaml is not part of this set: \"kb taxonomy init\" derives it from\nthe notes that the store already holds. Use \"kb create\" to make a new store.\n\nOptions:\n --force Replace an existing canonical file with a fresh seed. A\n directory has no content to replace and is left as it is.\n --kb <name> Use the named store from the kb.yaml registry. Without it, the\n nearest ancestor .kb/ directory is used.\n -h, --help Show this help.\n\nExit codes:\n 0 every canonical file is present, whether it was written or already there\n 2 usage error, unresolvable store, a store marked readonly in kb.yaml, or a\n resolved path holding no .kb/\n";
|
|
4
|
+
/**
|
|
5
|
+
* Runs `kb scaffold`: parses options, resolves the store, and writes the canonical files it lacks.
|
|
6
|
+
*
|
|
7
|
+
* Store resolution matches `kb check`. A store the registry marks `readonly` is refused, as `kb taxonomy init` does.
|
|
8
|
+
* A resolved path holding no `.kb/` is refused too: the command back-fills a store rather than creating one, and a
|
|
9
|
+
* registry entry names a path without proving a store is there.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runScaffold(input: {
|
|
12
|
+
argv: readonly string[];
|
|
13
|
+
cwd: string;
|
|
14
|
+
home?: string;
|
|
15
|
+
}): Promise<CommandOutput>;
|
|
16
|
+
/** Parsed `kb scaffold` options. */
|
|
17
|
+
interface ScaffoldOptions {
|
|
18
|
+
/** Whether `--force` was supplied. */
|
|
19
|
+
force: boolean;
|
|
20
|
+
/** Whether `--help`/`-h` was supplied. */
|
|
21
|
+
help: boolean;
|
|
22
|
+
/** Explicit store name from `--kb`, or `null` for ancestor-walk discovery. */
|
|
23
|
+
kb: string | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parses `kb scaffold` options. `--kb` accepts both the space (`--kb x`) and equals (`--kb=x`) forms. An unknown flag,
|
|
27
|
+
* a positional argument, or a missing `--kb` value throws with a usage-style message.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseScaffoldArgs(argv: readonly string[]): ScaffoldOptions;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
|
+
import { directoryExists } from "../../filesystem/exists.js";
|
|
3
|
+
import { KB_DIR, resolveKbDir } from "../../layout/index.js";
|
|
4
|
+
import { scaffold } from "../../scaffold/scaffold.js";
|
|
5
|
+
import { takeInlineValue, takeValue } from "../parse-flag-value.js";
|
|
6
|
+
import { resolveStore } from "../resolve-store.js";
|
|
7
|
+
export const SCAFFOLD_HELP = `Usage: kb scaffold [options]
|
|
8
|
+
|
|
9
|
+
Write into an existing knowledge base any canonical file that it lacks, so a store
|
|
10
|
+
created before a given file existed can acquire it. An existing file is left
|
|
11
|
+
untouched unless --force is given.
|
|
12
|
+
|
|
13
|
+
Writes:
|
|
14
|
+
.editorconfig editor and formatter settings (width, indent, line endings)
|
|
15
|
+
.kb/config.yaml check configuration (commented; defaults apply)
|
|
16
|
+
.kb/tag-aliases.yaml tag-alias map (empty)
|
|
17
|
+
.prettierrc.yaml formatting configuration (commented)
|
|
18
|
+
content/, content/events/
|
|
19
|
+
|
|
20
|
+
.kb/taxonomy.yaml is not part of this set: "kb taxonomy init" derives it from
|
|
21
|
+
the notes that the store already holds. Use "kb create" to make a new store.
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--force Replace an existing canonical file with a fresh seed. A
|
|
25
|
+
directory has no content to replace and is left as it is.
|
|
26
|
+
--kb <name> Use the named store from the kb.yaml registry. Without it, the
|
|
27
|
+
nearest ancestor .kb/ directory is used.
|
|
28
|
+
-h, --help Show this help.
|
|
29
|
+
|
|
30
|
+
Exit codes:
|
|
31
|
+
0 every canonical file is present, whether it was written or already there
|
|
32
|
+
2 usage error, unresolvable store, a store marked readonly in kb.yaml, or a
|
|
33
|
+
resolved path holding no ${KB_DIR}/
|
|
34
|
+
`;
|
|
35
|
+
export async function runScaffold(input) {
|
|
36
|
+
let options;
|
|
37
|
+
try {
|
|
38
|
+
options = parseScaffoldArgs(input.argv);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return { exitCode: 2, stdout: '', stderr: `kb scaffold: ${describeError(error)}\n${SCAFFOLD_HELP}` };
|
|
42
|
+
}
|
|
43
|
+
if (options.help) {
|
|
44
|
+
return { exitCode: 0, stdout: SCAFFOLD_HELP, stderr: '' };
|
|
45
|
+
}
|
|
46
|
+
const resolved = await resolveStore({
|
|
47
|
+
explicitKb: options.kb,
|
|
48
|
+
cwd: input.cwd,
|
|
49
|
+
...(input.home !== undefined && { home: input.home }),
|
|
50
|
+
});
|
|
51
|
+
if (!resolved.ok) {
|
|
52
|
+
return { exitCode: 2, stdout: '', stderr: `kb scaffold: ${resolved.message}\n` };
|
|
53
|
+
}
|
|
54
|
+
const store = resolved.store;
|
|
55
|
+
const label = store.name ?? store.path;
|
|
56
|
+
if (resolved.readonly) {
|
|
57
|
+
return buildRefusal(`knowledge base "${label}" is marked readonly in kb.yaml; scaffold is refused`);
|
|
58
|
+
}
|
|
59
|
+
if (!(await directoryExists(resolveKbDir(store.path)))) {
|
|
60
|
+
return buildRefusal(`no ${KB_DIR}/ directory at ${store.path}; run "kb create" to make a store there`);
|
|
61
|
+
}
|
|
62
|
+
const entries = await scaffold({ storePath: store.path, force: options.force });
|
|
63
|
+
return { exitCode: 0, stdout: formatScaffolded(store, entries), stderr: '' };
|
|
64
|
+
}
|
|
65
|
+
export function parseScaffoldArgs(argv) {
|
|
66
|
+
let force = false;
|
|
67
|
+
let help = false;
|
|
68
|
+
let kb = null;
|
|
69
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
70
|
+
const arg = argv[index];
|
|
71
|
+
if (arg === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
if (arg === '--help' || arg === '-h') {
|
|
74
|
+
help = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (arg === '--force') {
|
|
78
|
+
force = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (arg === '--kb') {
|
|
82
|
+
kb = takeValue(argv, index, '--kb');
|
|
83
|
+
index += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (arg.startsWith('--kb=')) {
|
|
87
|
+
kb = takeInlineValue(arg, '--kb=');
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg.startsWith('-')) {
|
|
91
|
+
throw new Error(`unknown flag: ${arg}`);
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`unexpected argument: ${arg}`);
|
|
94
|
+
}
|
|
95
|
+
return { force, help, kb };
|
|
96
|
+
}
|
|
97
|
+
const ACTION_WIDTH = 8;
|
|
98
|
+
function buildRefusal(message) {
|
|
99
|
+
return { exitCode: 2, stdout: '', stderr: `kb scaffold: ${message}\n` };
|
|
100
|
+
}
|
|
101
|
+
function formatScaffolded(store, entries) {
|
|
102
|
+
const named = store.name === null ? '' : ` "${store.name}"`;
|
|
103
|
+
const lines = [`Scaffolded knowledge base${named} at ${store.path}`];
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
lines.push(` ${entry.action.padEnd(ACTION_WIDTH)} ${entry.path}`);
|
|
106
|
+
}
|
|
107
|
+
return `${lines.join('\n')}\n`;
|
|
108
|
+
}
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import type { SelectKbPrompt } from '../select-kb-prompt.js';
|
|
2
2
|
import type { CommandOutput } from './check.js';
|
|
3
|
+
/** Usage text for `kb set-default`. */
|
|
3
4
|
export declare const SET_DEFAULT_HELP = "Usage: kb set-default [name] [options]\n\nSet, clear, or interactively choose the user-global default knowledge base \u2014\nthe top-level default_kb pointer in ~/.agents/kb.yaml.\n\n kb set-default <name> Set default_kb to the named registered KB.\n kb set-default --none Clear default_kb.\n kb set-default List the registered KBs and prompt for a choice\n (including a \"(none)\" option to clear).\n\nOptions:\n --none Clear the default knowledge base.\n -h, --help Show this help.\n\nExit codes:\n 0 default set or cleared, or the selection was cancelled\n 2 usage error, an unregistered name, no registered KBs, or no name supplied\n when stdin is not interactive\n";
|
|
5
|
+
/**
|
|
6
|
+
* Runs `kb set-default`: sets, clears, or interactively selects the user-global `default_kb`. Resolution reads the
|
|
7
|
+
* user-global registry only (no project overlay), so the chosen default resolves in every project context. The picker
|
|
8
|
+
* is injected via `selectKb`, keeping the dispatcher free of terminal I/O; its absence means stdin is non-interactive,
|
|
9
|
+
* making the no-argument form a usage error rather than a hang.
|
|
10
|
+
*/
|
|
4
11
|
export declare function runSetDefault(input: {
|
|
5
12
|
argv: readonly string[];
|
|
6
13
|
home?: string;
|
|
7
14
|
selectKb?: SelectKbPrompt;
|
|
8
15
|
}): Promise<CommandOutput>;
|
|
16
|
+
/** Parsed `kb set-default` options. */
|
|
9
17
|
interface SetDefaultOptions {
|
|
18
|
+
/** The positional KB name, or `null` when none was supplied (interactive form). */
|
|
10
19
|
name: string | null;
|
|
20
|
+
/** Whether `--none` was supplied. */
|
|
11
21
|
none: boolean;
|
|
22
|
+
/** Whether `--help`/`-h` was supplied. */
|
|
12
23
|
help: boolean;
|
|
13
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Parses `kb set-default` options. At most one positional name is accepted; `--none` clears. A name combined with
|
|
27
|
+
* `--none`, a second positional, or an unknown flag throws with a usage-style message.
|
|
28
|
+
*/
|
|
14
29
|
export declare function parseSetDefaultArgs(argv: readonly string[]): SetDefaultOptions;
|
|
15
30
|
export {};
|