@williamthorsen/kb 0.0.0 → 0.3.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/LICENSE +7 -0
- package/README.md +236 -1
- package/bin/kb.js +26 -0
- package/dist/esm/check/check.d.ts +11 -0
- package/dist/esm/check/check.js +17 -0
- package/dist/esm/check/enumerate.d.ts +14 -0
- package/dist/esm/check/enumerate.js +71 -0
- package/dist/esm/check/glob-segments.d.ts +2 -0
- package/dist/esm/check/glob-segments.js +12 -0
- package/dist/esm/check/index.d.ts +2 -0
- package/dist/esm/check/index.js +2 -0
- package/dist/esm/cli/commands/check.d.ts +20 -0
- package/dist/esm/cli/commands/check.js +179 -0
- package/dist/esm/cli/commands/create.d.ts +16 -0
- package/dist/esm/cli/commands/create.js +111 -0
- package/dist/esm/cli/commands/set-default.d.ts +15 -0
- package/dist/esm/cli/commands/set-default.js +123 -0
- package/dist/esm/cli/format.d.ts +24 -0
- package/dist/esm/cli/format.js +55 -0
- package/dist/esm/cli/index.d.ts +1 -0
- package/dist/esm/cli/index.js +25 -0
- package/dist/esm/cli/run.d.ts +9 -0
- package/dist/esm/cli/run.js +37 -0
- package/dist/esm/cli/select-kb-prompt.d.ts +16 -0
- package/dist/esm/cli/select-kb-prompt.js +44 -0
- package/dist/esm/cli/targeting/resolve-changed-paths.d.ts +11 -0
- package/dist/esm/cli/targeting/resolve-changed-paths.js +53 -0
- package/dist/esm/cli/targeting/select-notes.d.ts +10 -0
- package/dist/esm/cli/targeting/select-notes.js +60 -0
- package/dist/esm/config/config-schema.d.ts +10 -0
- package/dist/esm/config/config-schema.js +10 -0
- package/dist/esm/config/index.d.ts +4 -0
- package/dist/esm/config/index.js +4 -0
- package/dist/esm/config/kb-loader-error.d.ts +5 -0
- package/dist/esm/config/kb-loader-error.js +14 -0
- package/dist/esm/config/load-config.d.ts +5 -0
- package/dist/esm/config/load-config.js +36 -0
- package/dist/esm/config/note-scope.d.ts +7 -0
- package/dist/esm/config/note-scope.js +10 -0
- package/dist/esm/create/create.d.ts +26 -0
- package/dist/esm/create/create.js +51 -0
- package/dist/esm/create/index.d.ts +3 -0
- package/dist/esm/create/index.js +2 -0
- package/dist/esm/create/render-seeds.d.ts +2 -0
- package/dist/esm/create/render-seeds.js +29 -0
- package/dist/esm/discovery/find-kb-root.d.ts +4 -0
- package/dist/esm/discovery/find-kb-root.js +22 -0
- package/dist/esm/discovery/index.d.ts +9 -0
- package/dist/esm/discovery/index.js +5 -0
- package/dist/esm/discovery/kb-registry-schema.d.ts +16 -0
- package/dist/esm/discovery/kb-registry-schema.js +10 -0
- package/dist/esm/discovery/load-registry.d.ts +15 -0
- package/dist/esm/discovery/load-registry.js +114 -0
- package/dist/esm/discovery/register-store.d.ts +9 -0
- package/dist/esm/discovery/register-store.js +26 -0
- package/dist/esm/discovery/registry-document.d.ts +2 -0
- package/dist/esm/discovery/registry-document.js +17 -0
- package/dist/esm/discovery/set-default-kb.d.ts +7 -0
- package/dist/esm/discovery/set-default-kb.js +53 -0
- package/dist/esm/filesystem/exists.d.ts +6 -0
- package/dist/esm/filesystem/exists.js +24 -0
- package/dist/esm/filesystem/index.d.ts +1 -0
- package/dist/esm/filesystem/index.js +1 -0
- package/dist/esm/frontmatter/index.d.ts +2 -0
- package/dist/esm/frontmatter/index.js +1 -0
- package/dist/esm/frontmatter/parse-note.d.ts +8 -0
- package/dist/esm/frontmatter/parse-note.js +98 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/layout/index.d.ts +1 -0
- package/dist/esm/layout/index.js +1 -0
- package/dist/esm/layout/store-layout.d.ts +15 -0
- package/dist/esm/layout/store-layout.js +23 -0
- package/dist/esm/lints/index.d.ts +2 -0
- package/dist/esm/lints/index.js +2 -0
- package/dist/esm/lints/paths.d.ts +6 -0
- package/dist/esm/lints/paths.js +12 -0
- package/dist/esm/lints/tag-alias.d.ts +6 -0
- package/dist/esm/lints/tag-alias.js +21 -0
- package/dist/esm/note-io/field-validators.d.ts +2 -0
- package/dist/esm/note-io/field-validators.js +30 -0
- package/dist/esm/note-io/index.d.ts +4 -0
- package/dist/esm/note-io/index.js +4 -0
- package/dist/esm/note-io/read-note.d.ts +8 -0
- package/dist/esm/note-io/read-note.js +22 -0
- package/dist/esm/note-io/write-note.d.ts +2 -0
- package/dist/esm/note-io/write-note.js +21 -0
- package/dist/esm/note-io/yaml-fields.d.ts +5 -0
- package/dist/esm/note-io/yaml-fields.js +49 -0
- package/dist/esm/records/assertion.d.ts +25 -0
- package/dist/esm/records/assertion.js +136 -0
- package/dist/esm/records/event.d.ts +28 -0
- package/dist/esm/records/event.js +128 -0
- package/dist/esm/records/index.d.ts +2 -0
- package/dist/esm/records/index.js +2 -0
- package/dist/esm/tags/canonicalize.d.ts +3 -0
- package/dist/esm/tags/canonicalize.js +6 -0
- package/dist/esm/tags/index.d.ts +3 -0
- package/dist/esm/tags/index.js +2 -0
- package/dist/esm/tags/load-aliases.d.ts +5 -0
- package/dist/esm/tags/load-aliases.js +72 -0
- package/dist/esm/type-guards.d.ts +3 -0
- package/dist/esm/type-guards.js +12 -0
- package/dist/esm/types.d.ts +50 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/vault-integrity/build-vault-index.d.ts +4 -0
- package/dist/esm/vault-integrity/build-vault-index.js +14 -0
- package/dist/esm/vault-integrity/check-vault-integrity.d.ts +7 -0
- package/dist/esm/vault-integrity/check-vault-integrity.js +49 -0
- package/dist/esm/vault-integrity/index.d.ts +3 -0
- package/dist/esm/vault-integrity/index.js +3 -0
- package/dist/esm/vault-integrity/wikilink-parse.d.ts +7 -0
- package/dist/esm/vault-integrity/wikilink-parse.js +75 -0
- package/package.json +108 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 William Thorsen
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
The software is provided "as is" and the author disclaims all warranties with regard to this software including all implied warranties of merchantability and fitness. In no event shall the author be liable for any special, direct, indirect, or consequential damages or any damages whatsoever resulting from loss of use, data or profits, whether in an action of contract, negligence or other tortious action, arising out of or in connection with the use or performance of this software.
|
package/README.md
CHANGED
|
@@ -1 +1,236 @@
|
|
|
1
|
-
|
|
1
|
+
# @williamthorsen/kb
|
|
2
|
+
|
|
3
|
+
Foundation library for knowledge-base tooling.
|
|
4
|
+
Provides knowledge-base discovery, registry loading, frontmatter parsing and writing, tag canonicalization, and type-blind vault-integrity checks.
|
|
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
|
+
|
|
7
|
+
<!-- section:release-notes -->
|
|
8
|
+
## Release notes — v0.3.0 (2026-08-04)
|
|
9
|
+
|
|
10
|
+
### 🎉 Features
|
|
11
|
+
|
|
12
|
+
- Make codeassembly and kb CLI tools publishable (#1164)
|
|
13
|
+
|
|
14
|
+
The `codeassembly` CLI now installs from npm, and its `install` and `sync` commands deploy the rulebooks, skills, and subagents it ships into any consuming project. The knowledge-base library `@williamthorsen/kb` and the session-lifecycle event package `codeassembly-lifecycle` are published alongside it.
|
|
15
|
+
<!-- /section:release-notes -->
|
|
16
|
+
|
|
17
|
+
## Exports
|
|
18
|
+
|
|
19
|
+
The package exposes ten subpath entries plus a root barrel:
|
|
20
|
+
|
|
21
|
+
| Entry | Description |
|
|
22
|
+
| ------------------- | ------------------------------------------------------------------------------ |
|
|
23
|
+
| `.` | The most-used types plus `buildVaultIndex` |
|
|
24
|
+
| `./check` | `check`: config-driven enumeration composed with vault integrity and the lints |
|
|
25
|
+
| `./config` | `.kb/config.yaml` loading and the typed `KbLoaderError` the loaders throw |
|
|
26
|
+
| `./create` | `create`: scaffold a new store and register it in `kb.yaml` |
|
|
27
|
+
| `./discovery` | KB root discovery and `kb.yaml` registry loading, merging, and writing |
|
|
28
|
+
| `./filesystem` | Filesystem-existence helpers with an explicit absence policy |
|
|
29
|
+
| `./frontmatter` | Note parsing into typed frontmatter and writing it back to YAML |
|
|
30
|
+
| `./note-io` | Type-blind note read/write as an ordered frontmatter field map |
|
|
31
|
+
| `./records` | The typed `assertion`/`event` record parsers and renderers |
|
|
32
|
+
| `./tags` | `.kb/tag-aliases.yaml` loading and tag canonicalization |
|
|
33
|
+
| `./vault-integrity` | Type-blind `[[link]]` resolution and basename-uniqueness over a note set |
|
|
34
|
+
|
|
35
|
+
Every public function takes a single plain-object input so a future MCP wrapper can mechanically bind Zod-validated payloads.
|
|
36
|
+
The library throws on errors; success/failure shaping is left to consumers.
|
|
37
|
+
|
|
38
|
+
## Knowledge-base discovery
|
|
39
|
+
|
|
40
|
+
`findKbRoot({ startDir })` walks ancestor directories looking for a `.kb/` folder and returns the first match (or `null` at the filesystem root).
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { findKbRoot } from '@williamthorsen/kb/discovery';
|
|
44
|
+
|
|
45
|
+
const root = await findKbRoot({ startDir: process.cwd() });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## The `kb.yaml` registry
|
|
49
|
+
|
|
50
|
+
A KB registry declares one or more knowledge bases. `loadKbRegistry` reads two optional registry files and merges them:
|
|
51
|
+
|
|
52
|
+
- **user-global**: `~/.agents/kb.yaml`
|
|
53
|
+
- **project-local**: `<projectDir>/.agents/kb.yaml`
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
# .agents/kb.yaml
|
|
57
|
+
default_kb: coding
|
|
58
|
+
kbs:
|
|
59
|
+
coding:
|
|
60
|
+
path: ~/vaults/coding
|
|
61
|
+
description: Personal coding knowledge base
|
|
62
|
+
readonly: false
|
|
63
|
+
team:
|
|
64
|
+
path: ../shared/team-kb
|
|
65
|
+
description: Shared team knowledge base
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The top-level `default_kb` key names the machine's default knowledge base: the single KB that searches and discovery-based writes fall back on when no store is named or discovered. `capture-event` writes there only when explicitly selected with `--store @default`, never by omission. It must name an entry under `kbs`; a value that matches none fails the load. Set, change, or clear it from the command line with `kb set-default`.
|
|
69
|
+
|
|
70
|
+
Configuration keys, per KB entry under `kbs.<name>`:
|
|
71
|
+
|
|
72
|
+
| Key | Required | Meaning |
|
|
73
|
+
| ------------- | -------- | ------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `path` | yes | KB root directory; `~` expands to `$HOME`, relative paths resolve against the registry file's directory |
|
|
75
|
+
| `description` | no | Human-readable description |
|
|
76
|
+
| `readonly` | no | Marks the KB as read-only |
|
|
77
|
+
|
|
78
|
+
### Merge semantics
|
|
79
|
+
|
|
80
|
+
`loadKbRegistry` merges the two registries by KB name:
|
|
81
|
+
|
|
82
|
+
- Project entries **replace** user entries with the same name.
|
|
83
|
+
- Project entries with a new name are **appended**.
|
|
84
|
+
- When both files set `default_kb`, the **project** value wins; the resolved default is the named entry from the merged set.
|
|
85
|
+
- Path existence is not checked at load time.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { loadKbRegistry } from '@williamthorsen/kb/discovery';
|
|
89
|
+
|
|
90
|
+
const config = await loadKbRegistry({ projectDir: process.cwd() });
|
|
91
|
+
// config.entries: KbRegistryEntry[] with absolute, resolved paths
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## The addressed-by/addresses relation
|
|
95
|
+
|
|
96
|
+
`addressed-by`/`addresses` is an inverse-pair relation that threads a problem record to whatever was done about it: a fix, a mitigation, an improved guidance note. Both are optional, multi-valued list fields:
|
|
97
|
+
|
|
98
|
+
- `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`.
|
|
99
|
+
- `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 police it.
|
|
100
|
+
|
|
101
|
+
The relation is many-to-many (one response can address many problems, and one problem can accrue many responses) and is surfaced flat by recall, with no chain-walking. This is distinct from `supersedes`/`superseded-by`, which _deprecates_ a record through a policed 1:1 chain; an addressed problem is not deprecated. It remains a true observation whose recurrence is worth keeping.
|
|
102
|
+
|
|
103
|
+
## Frontmatter parsing and writing
|
|
104
|
+
|
|
105
|
+
`parseNote({ path })` (or `parseNoteContent({ content })`) parses a note into a `ParsedNote` carrying typed `Frontmatter`:
|
|
106
|
+
The `title`, `recordType`, `created`, `updated`, and `tags` fields are strongly typed and any other fields are preserved in an `extra` map.
|
|
107
|
+
`writeFrontmatter({ frontmatter, body })` renders it back to a note string with a fixed field order and flow-style tags; the round trip is idempotent.
|
|
108
|
+
|
|
109
|
+
Date fields surface as strings, never JS `Date` objects. YAML parse errors are recorded in `ParsedNote.frontmatterRaw.parseError` rather than thrown;
|
|
110
|
+
missing files (when a path is given) throw.
|
|
111
|
+
|
|
112
|
+
## Tags
|
|
113
|
+
|
|
114
|
+
`loadAliases({ kbRoot })` reads `.kb/tag-aliases.yaml` into an `AliasMap`, rejecting collisions and self-aliases at load time; an absent file yields an empty map.
|
|
115
|
+
`canonicalize(tag, aliases)` resolves a tag to its canonical form; `findAliasFor(tag, aliases)` returns the canonical form only when the input is a known alias.
|
|
116
|
+
|
|
117
|
+
## Vault integrity and lints
|
|
118
|
+
|
|
119
|
+
`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 the layer and curate's wikilink rewriter share.
|
|
120
|
+
|
|
121
|
+
The type-blind per-note lints — `tagAliasFindings(note, aliases)` (`tag-alias`, warning) and `pathsFindings(note)` (`paths.user-home`, error) — catch what write-time record validation can't: alias-vocabulary drift and hardcoded `/Users/{name}/` paths in captured content.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { checkVaultIntegrity } from '@williamthorsen/kb/vault-integrity';
|
|
125
|
+
|
|
126
|
+
const findings = checkVaultIntegrity(notes);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Checking a store
|
|
130
|
+
|
|
131
|
+
`check({ kbRoot })` runs a store's full check in one call: it loads `.kb/config.yaml` and `.kb/tag-aliases.yaml`, enumerates the notes the config selects, and composes whole-vault integrity 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.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { check } from '@williamthorsen/kb/check';
|
|
135
|
+
|
|
136
|
+
const { notes, findings } = await check({ kbRoot });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A structural defect in either loaded file throws a `KbLoaderError` (see below). Any other error from enumeration or the checks propagates unchanged.
|
|
140
|
+
|
|
141
|
+
### Which notes are checked: `.kb/config.yaml`
|
|
142
|
+
|
|
143
|
+
`.kb/config.yaml` configures which notes a check enumerates. Both keys are optional; an absent file or an omitted key falls back to the default.
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
# .kb/config.yaml
|
|
147
|
+
targets:
|
|
148
|
+
- 'content/**/*.md'
|
|
149
|
+
exclude:
|
|
150
|
+
- '**/node_modules/**'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Key | Default | Meaning |
|
|
154
|
+
| --------- | ------------------------ | ---------------------------------------------------------------------------- |
|
|
155
|
+
| `targets` | `['content/**/*.md']` | Glob patterns (store-root-relative) selecting which notes a check enumerates |
|
|
156
|
+
| `exclude` | `['**/node_modules/**']` | Glob patterns excluded from enumeration even when a target matches |
|
|
157
|
+
|
|
158
|
+
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`.
|
|
159
|
+
|
|
160
|
+
## The `kb` command
|
|
161
|
+
|
|
162
|
+
The package ships a `kb` bin with three subcommands: `create`, `set-default`, and `check`.
|
|
163
|
+
|
|
164
|
+
### kb create
|
|
165
|
+
|
|
166
|
+
`kb create` scaffolds a new knowledge base in the current directory and registers it in the user-global `~/.agents/kb.yaml`.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
kb create # scaffold the current directory, register under its name
|
|
170
|
+
kb create --name coding # register under an explicit name
|
|
171
|
+
kb create --no-register # scaffold without writing the registry
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
It creates these files and directories:
|
|
175
|
+
|
|
176
|
+
| Path | Contents |
|
|
177
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
178
|
+
| `.kb/config.yaml` | A fully-commented check config; the bundled defaults apply as-is |
|
|
179
|
+
| `.kb/tag-aliases.yaml` | An empty `aliases: {}` map |
|
|
180
|
+
| `content/`, `content/events/` | The note tree; `capture-event` writes events to `content/events/`, `kb-update-events` edits them in place |
|
|
181
|
+
|
|
182
|
+
The config seed is serialized from the in-package `defaultKbConfig`, so a new store cannot drift from the bundled default.
|
|
183
|
+
|
|
184
|
+
The name defaults to the directory's base name; `--name` overrides it and `--no-register` scaffolds without writing the registry. The registry write preserves any existing comments in `kb.yaml`. `kb create` refuses to clobber: it exits 2 if the directory already contains a `.kb/` store, or if the chosen name is already registered.
|
|
185
|
+
|
|
186
|
+
`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.
|
|
187
|
+
|
|
188
|
+
### kb set-default
|
|
189
|
+
|
|
190
|
+
`kb set-default` sets, clears, or interactively chooses the user-global default knowledge base: the top-level `default_kb` pointer in `~/.agents/kb.yaml`.
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
kb set-default coding # set default_kb to the registered KB "coding"
|
|
194
|
+
kb set-default --none # clear default_kb
|
|
195
|
+
kb set-default # list the registered KBs and choose interactively
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
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.
|
|
199
|
+
|
|
200
|
+
### kb check
|
|
201
|
+
|
|
202
|
+
`kb check` validates a store's notes and reports the findings. With no path arguments it checks every note; path arguments or `--vs` scope the run to a subset.
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
kb check # check every note in the nearest ancestor .kb/ store
|
|
206
|
+
kb check --kb coding # check the named store from the kb.yaml registry
|
|
207
|
+
kb check --json # emit a JSON report
|
|
208
|
+
kb check content/assertions # check only the notes under a directory
|
|
209
|
+
kb check 'content/**/*.md' # check only the notes a glob matches (quote it)
|
|
210
|
+
kb check --vs=main # check only the notes changed since a ref
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`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 join 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.
|
|
214
|
+
|
|
215
|
+
**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.
|
|
216
|
+
|
|
217
|
+
- **`[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 an excluded file), which is skipped silently.
|
|
218
|
+
- **`--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.
|
|
219
|
+
|
|
220
|
+
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.
|
|
221
|
+
|
|
222
|
+
Exit codes:
|
|
223
|
+
|
|
224
|
+
| Code | Meaning |
|
|
225
|
+
| ---- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
226
|
+
| `0` | No error-severity findings in the checked notes (warnings are allowed). A run that selects no notes also exits 0. |
|
|
227
|
+
| `1` | One or more error-severity findings in the checked notes. |
|
|
228
|
+
| `2` | A usage error, an unresolvable store or `--vs` ref, a path matching no note, or a malformed `config` or `tag-aliases` file. |
|
|
229
|
+
|
|
230
|
+
## Error and exception model
|
|
231
|
+
|
|
232
|
+
The checks **return** findings; they never throw. Loaders (`loadKbConfig`, `loadAliases`) **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. `loadKbRegistry` throws a plain `Error` on its own structural defects. I/O errors other than a missing optional file propagate.
|
|
233
|
+
|
|
234
|
+
## MCP wrappability
|
|
235
|
+
|
|
236
|
+
Every public function input is a plain object with primitive or `unknown`-typed fields, and no function takes a callback. A future `kb-mcp` server can bind Zod-validated request payloads directly onto these inputs without refactoring.
|
package/bin/kb.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
// Thin wrapper so pnpm can symlink the bin at install time, before `dist/` exists.
|
|
6
|
+
// The real entry point loads at runtime from the build output.
|
|
7
|
+
// See packages/agents/README.md ("Bin wrapper pattern") for details.
|
|
8
|
+
const entryPoint = new URL('../dist/esm/cli/index.js', import.meta.url);
|
|
9
|
+
|
|
10
|
+
// Gate on the entry file itself: Node raises ERR_MODULE_NOT_FOUND for any unresolved
|
|
11
|
+
// module in the graph, so keying the build-first message off the error code would also
|
|
12
|
+
// fire when the build is present and one of its imports is missing.
|
|
13
|
+
if (!existsSync(entryPoint)) {
|
|
14
|
+
process.stderr.write(
|
|
15
|
+
'kb: build output not found. In a source checkout, run `pnpm run bootstrap`; otherwise reinstall the package.\n',
|
|
16
|
+
);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await import(entryPoint.href);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
process.stderr.write(`kb: failed to load: ${message}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { KbConfig } from '../config/config-schema.js';
|
|
2
|
+
import type { Finding } from '../types.js';
|
|
3
|
+
import { type EnumeratedNote } from './enumerate.js';
|
|
4
|
+
export interface CheckResult {
|
|
5
|
+
config: KbConfig;
|
|
6
|
+
notes: readonly EnumeratedNote[];
|
|
7
|
+
findings: readonly Finding[];
|
|
8
|
+
}
|
|
9
|
+
export declare function check(input: {
|
|
10
|
+
kbRoot: string;
|
|
11
|
+
}): Promise<CheckResult>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { loadKbConfig } from "../config/load-config.js";
|
|
2
|
+
import { resolveKbDir } from "../layout/index.js";
|
|
3
|
+
import { pathsFindings } from "../lints/paths.js";
|
|
4
|
+
import { tagAliasFindings } from "../lints/tag-alias.js";
|
|
5
|
+
import { loadAliases } from "../tags/load-aliases.js";
|
|
6
|
+
import { checkVaultIntegrity } from "../vault-integrity/check-vault-integrity.js";
|
|
7
|
+
import { enumerateNotes } from "./enumerate.js";
|
|
8
|
+
export async function check(input) {
|
|
9
|
+
const kbRoot = { path: input.kbRoot, kbDir: resolveKbDir(input.kbRoot) };
|
|
10
|
+
const [config, aliases] = await Promise.all([loadKbConfig({ kbRoot }), loadAliases({ kbRoot })]);
|
|
11
|
+
const notes = await enumerateNotes({ kbRoot: input.kbRoot, config });
|
|
12
|
+
const findings = [
|
|
13
|
+
...checkVaultIntegrity(notes),
|
|
14
|
+
...notes.flatMap((note) => [...tagAliasFindings(note, aliases), ...pathsFindings(note)]),
|
|
15
|
+
];
|
|
16
|
+
return { config, notes, findings };
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { KbConfig } from '../config/config-schema.js';
|
|
2
|
+
export interface EnumeratedNote {
|
|
3
|
+
path: string;
|
|
4
|
+
relativePath: string;
|
|
5
|
+
fields: Record<string, unknown>;
|
|
6
|
+
body: string;
|
|
7
|
+
content: string;
|
|
8
|
+
bodyStartLine: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function enumerateNotes(input: {
|
|
12
|
+
kbRoot: string;
|
|
13
|
+
config: KbConfig;
|
|
14
|
+
}): Promise<EnumeratedNote[]>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, sep } from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { createNoteScopeMatcher } from "../config/note-scope.js";
|
|
5
|
+
import { readNoteContent } from "../note-io/read-note.js";
|
|
6
|
+
import { isGlobSegment } from "./glob-segments.js";
|
|
7
|
+
export async function enumerateNotes(input) {
|
|
8
|
+
const { kbRoot, config } = input;
|
|
9
|
+
const matcher = createNoteScopeMatcher(config);
|
|
10
|
+
const topLevelDirs = leadingLiteralSegments(config.targets);
|
|
11
|
+
const notes = [];
|
|
12
|
+
await walk({ root: kbRoot, dir: kbRoot, matcher, topLevelDirs, out: notes });
|
|
13
|
+
return notes;
|
|
14
|
+
}
|
|
15
|
+
function leadingLiteralSegments(targets) {
|
|
16
|
+
const dirs = new Set();
|
|
17
|
+
for (const target of targets) {
|
|
18
|
+
const firstSegment = target.split('/', 1)[0] ?? '';
|
|
19
|
+
if (firstSegment === '' || isGlobSegment(firstSegment)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
dirs.add(firstSegment);
|
|
23
|
+
}
|
|
24
|
+
return dirs;
|
|
25
|
+
}
|
|
26
|
+
async function walk(input) {
|
|
27
|
+
const { root, dir, matcher, topLevelDirs, out } = input;
|
|
28
|
+
let entries;
|
|
29
|
+
try {
|
|
30
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
+
process.stderr.write(`kb: warning: could not read directory ${dir}; skipping: ${message}\n`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const atRoot = dir === root;
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const absolutePath = join(dir, entry.name);
|
|
40
|
+
const relativePath = relative(root, absolutePath).split(sep).join('/');
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
if (atRoot && topLevelDirs !== null && !topLevelDirs.has(entry.name))
|
|
43
|
+
continue;
|
|
44
|
+
if (matcher.isExcluded(relativePath))
|
|
45
|
+
continue;
|
|
46
|
+
await walk({ root, dir: absolutePath, matcher, topLevelDirs, out });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (!entry.name.endsWith('.md'))
|
|
50
|
+
continue;
|
|
51
|
+
if (!matcher.isNote(relativePath))
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
const content = await readFile(absolutePath, 'utf8');
|
|
55
|
+
const { fields, body, bodyStartLine, error: parseError } = readNoteContent(content);
|
|
56
|
+
out.push({
|
|
57
|
+
path: absolutePath,
|
|
58
|
+
relativePath,
|
|
59
|
+
fields,
|
|
60
|
+
body,
|
|
61
|
+
content,
|
|
62
|
+
bodyStartLine,
|
|
63
|
+
...(parseError !== undefined && { error: parseError }),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
process.stderr.write(`kb: warning: could not read note ${absolutePath}; skipping: ${message}\n`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function isGlobSegment(segment) {
|
|
2
|
+
return /[*?[\]{}()!+@]/.test(segment);
|
|
3
|
+
}
|
|
4
|
+
export function leadingLiteralPrefix(pattern) {
|
|
5
|
+
const literal = [];
|
|
6
|
+
for (const segment of pattern.split('/')) {
|
|
7
|
+
if (isGlobSegment(segment))
|
|
8
|
+
break;
|
|
9
|
+
literal.push(segment);
|
|
10
|
+
}
|
|
11
|
+
return literal.join('/');
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface CommandOutput {
|
|
2
|
+
exitCode: 0 | 1 | 2;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
}
|
|
6
|
+
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";
|
|
7
|
+
export declare function runCheck(input: {
|
|
8
|
+
argv: readonly string[];
|
|
9
|
+
cwd: string;
|
|
10
|
+
home?: string;
|
|
11
|
+
}): Promise<CommandOutput>;
|
|
12
|
+
interface CheckOptions {
|
|
13
|
+
kb: string | null;
|
|
14
|
+
json: boolean;
|
|
15
|
+
help: boolean;
|
|
16
|
+
patterns: string[];
|
|
17
|
+
vs: string | null;
|
|
18
|
+
}
|
|
19
|
+
export declare function parseCheckArgs(argv: readonly string[]): CheckOptions;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { check } from "../../check/check.js";
|
|
2
|
+
import { isKbLoaderError } from "../../config/kb-loader-error.js";
|
|
3
|
+
import { findKbRoot } from "../../discovery/find-kb-root.js";
|
|
4
|
+
import { tryLoadKbRegistry } from "../../discovery/load-registry.js";
|
|
5
|
+
import { formatHuman, formatJson, summarize } from "../format.js";
|
|
6
|
+
import { resolveChangedPaths } from "../targeting/resolve-changed-paths.js";
|
|
7
|
+
import { selectNotes } from "../targeting/select-notes.js";
|
|
8
|
+
export const CHECK_HELP = `Usage: kb check [paths...] [options]
|
|
9
|
+
|
|
10
|
+
Validate notes in a knowledge base against its tag aliases and cross-note
|
|
11
|
+
link and path rules. With no path arguments, every note is checked.
|
|
12
|
+
Cross-note rules always resolve against the whole vault.
|
|
13
|
+
|
|
14
|
+
Targeting (mutually exclusive):
|
|
15
|
+
[paths...] Check only the notes matching the given glob patterns, files,
|
|
16
|
+
or directories. Quote globs so kb expands them itself. A
|
|
17
|
+
directory checks every note beneath it. A path that matches no
|
|
18
|
+
note is a usage error unless it names a real non-note.
|
|
19
|
+
--vs <ref> Check only the notes changed between the working tree and the
|
|
20
|
+
merge-base of <ref> and HEAD: follows renames, includes
|
|
21
|
+
uncommitted edits, excludes deletions.
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--kb <name> Check the named store from the kb.yaml registry. Without it,
|
|
25
|
+
the nearest ancestor .kb/ directory is used.
|
|
26
|
+
--json Emit a JSON report instead of human-readable output.
|
|
27
|
+
-h, --help Show this help.
|
|
28
|
+
|
|
29
|
+
Exit codes:
|
|
30
|
+
0 no error-severity findings in the checked notes (warnings allowed)
|
|
31
|
+
1 one or more error-severity findings in the checked notes
|
|
32
|
+
2 usage error, unresolvable store or --vs ref, a path matching no note, or
|
|
33
|
+
malformed config or aliases
|
|
34
|
+
`;
|
|
35
|
+
export async function runCheck(input) {
|
|
36
|
+
let options;
|
|
37
|
+
try {
|
|
38
|
+
options = parseCheckArgs(input.argv);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return buildUsageError(error);
|
|
42
|
+
}
|
|
43
|
+
if (options.help) {
|
|
44
|
+
return { exitCode: 0, stdout: CHECK_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 check: ${resolved.message}\n` };
|
|
53
|
+
}
|
|
54
|
+
const store = resolved.store;
|
|
55
|
+
let result;
|
|
56
|
+
try {
|
|
57
|
+
result = await check({ kbRoot: store.path });
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (isKbLoaderError(error)) {
|
|
61
|
+
return { exitCode: 2, stdout: '', stderr: `kb check: ${error.message}\n` };
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
const selection = await resolveSelection({ options, store, result });
|
|
66
|
+
if (!selection.ok) {
|
|
67
|
+
return { exitCode: 2, stdout: '', stderr: `kb check: ${selection.message}\n` };
|
|
68
|
+
}
|
|
69
|
+
const summary = summarize(selection.findings, selection.notes.length);
|
|
70
|
+
const stdout = options.json
|
|
71
|
+
? formatJson({ store, summary, findings: selection.findings })
|
|
72
|
+
: formatHuman({ summary, findings: selection.findings, targets: result.config.targets, scope: selection.scope });
|
|
73
|
+
return { exitCode: summary.errors > 0 ? 1 : 0, stdout, stderr: '' };
|
|
74
|
+
}
|
|
75
|
+
export function parseCheckArgs(argv) {
|
|
76
|
+
let kb = null;
|
|
77
|
+
let json = false;
|
|
78
|
+
let help = false;
|
|
79
|
+
let vs = null;
|
|
80
|
+
const patterns = [];
|
|
81
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
82
|
+
const arg = argv[index];
|
|
83
|
+
if (arg === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
if (arg === '--help' || arg === '-h') {
|
|
86
|
+
help = true;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (arg === '--json') {
|
|
90
|
+
json = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (arg === '--kb') {
|
|
94
|
+
kb = takeValue(argv, index, '--kb');
|
|
95
|
+
index += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (arg.startsWith('--kb=')) {
|
|
99
|
+
kb = takeInlineValue(arg, '--kb=');
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (arg === '--vs') {
|
|
103
|
+
vs = takeValue(argv, index, '--vs');
|
|
104
|
+
index += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (arg.startsWith('--vs=')) {
|
|
108
|
+
vs = takeInlineValue(arg, '--vs=');
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (arg.startsWith('-')) {
|
|
112
|
+
throw new Error(`unknown flag: ${arg}`);
|
|
113
|
+
}
|
|
114
|
+
patterns.push(arg);
|
|
115
|
+
}
|
|
116
|
+
if (vs !== null && patterns.length > 0) {
|
|
117
|
+
throw new Error('--vs cannot be combined with path arguments');
|
|
118
|
+
}
|
|
119
|
+
return { kb, json, help, patterns, vs };
|
|
120
|
+
}
|
|
121
|
+
function buildUsageError(error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
return { exitCode: 2, stdout: '', stderr: `kb check: ${message}\n${CHECK_HELP}` };
|
|
124
|
+
}
|
|
125
|
+
async function resolveSelection(input) {
|
|
126
|
+
const { options, store, result } = input;
|
|
127
|
+
if (options.vs === null && options.patterns.length === 0) {
|
|
128
|
+
return { ok: true, scope: 'vault', notes: result.notes, findings: result.findings };
|
|
129
|
+
}
|
|
130
|
+
let scope = 'patterns';
|
|
131
|
+
let patterns = options.patterns;
|
|
132
|
+
if (options.vs !== null) {
|
|
133
|
+
scope = 'vs';
|
|
134
|
+
const changed = resolveChangedPaths({ storeRoot: store.path, ref: options.vs });
|
|
135
|
+
if (!changed.ok) {
|
|
136
|
+
return { ok: false, message: changed.message };
|
|
137
|
+
}
|
|
138
|
+
patterns = changed.paths;
|
|
139
|
+
}
|
|
140
|
+
const selection = await selectNotes({ notes: result.notes, patterns, storeRoot: store.path });
|
|
141
|
+
if (selection.unmatched.length > 0) {
|
|
142
|
+
return { ok: false, message: `no notes matched: ${selection.unmatched.join(', ')}` };
|
|
143
|
+
}
|
|
144
|
+
const selectedPaths = new Set(selection.selected.map((entry) => entry.path));
|
|
145
|
+
const findings = result.findings.filter((finding) => selectedPaths.has(finding.path));
|
|
146
|
+
return { ok: true, scope, notes: selection.selected, findings };
|
|
147
|
+
}
|
|
148
|
+
async function resolveStore(input) {
|
|
149
|
+
if (input.explicitKb !== null) {
|
|
150
|
+
const { config } = await tryLoadKbRegistry({
|
|
151
|
+
projectDir: input.cwd,
|
|
152
|
+
...(input.home !== undefined && { home: input.home }),
|
|
153
|
+
});
|
|
154
|
+
const match = config.entries.find((entry) => entry.name === input.explicitKb);
|
|
155
|
+
if (match === undefined) {
|
|
156
|
+
return { ok: false, message: `--kb "${input.explicitKb}" does not match any registered knowledge base` };
|
|
157
|
+
}
|
|
158
|
+
return { ok: true, store: { name: match.name, path: match.path } };
|
|
159
|
+
}
|
|
160
|
+
const discovered = await findKbRoot({ startDir: input.cwd });
|
|
161
|
+
if (discovered === null) {
|
|
162
|
+
return { ok: false, message: 'no .kb/ directory found in the current directory or any ancestor' };
|
|
163
|
+
}
|
|
164
|
+
return { ok: true, store: { name: null, path: discovered.path } };
|
|
165
|
+
}
|
|
166
|
+
function takeInlineValue(arg, prefix) {
|
|
167
|
+
const value = arg.slice(prefix.length);
|
|
168
|
+
if (value === '') {
|
|
169
|
+
throw new Error(`${prefix.replace(/=$/, '')} requires a value`);
|
|
170
|
+
}
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
function takeValue(argv, index, flag) {
|
|
174
|
+
const next = argv[index + 1] ?? null;
|
|
175
|
+
if (next === null || next.startsWith('--')) {
|
|
176
|
+
throw new Error(`${flag} requires a value`);
|
|
177
|
+
}
|
|
178
|
+
return next;
|
|
179
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SelectKbPrompt } from '../select-kb-prompt.js';
|
|
2
|
+
import type { CommandOutput } from './check.js';
|
|
3
|
+
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\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 .kb/config.yaml check configuration (commented; defaults apply)\n .kb/tag-aliases.yaml tag-alias map (empty)\n content/, content/events/\n\nOptions:\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";
|
|
4
|
+
export declare function runCreate(input: {
|
|
5
|
+
argv: readonly string[];
|
|
6
|
+
cwd: string;
|
|
7
|
+
home?: string;
|
|
8
|
+
selectKb?: SelectKbPrompt;
|
|
9
|
+
}): Promise<CommandOutput>;
|
|
10
|
+
interface CreateOptions {
|
|
11
|
+
name: string | null;
|
|
12
|
+
noRegister: boolean;
|
|
13
|
+
help: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseCreateArgs(argv: readonly string[]): CreateOptions;
|
|
16
|
+
export {};
|