@williamthorsen/kb 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -82
- package/bin/kb.js +2 -4
- package/dist/esm/check/check.d.ts +9 -17
- package/dist/esm/check/enumerate.d.ts +12 -15
- package/dist/esm/check/glob-segments.d.ts +1 -1
- package/dist/esm/check/resolve-foreign-stores.d.ts +9 -8
- package/dist/esm/cli/commands/check.d.ts +4 -10
- package/dist/esm/cli/commands/check.js +2 -2
- package/dist/esm/cli/commands/create.d.ts +3 -7
- package/dist/esm/cli/commands/create.js +1 -1
- package/dist/esm/cli/commands/scaffold.d.ts +5 -9
- package/dist/esm/cli/commands/scaffold.js +5 -5
- package/dist/esm/cli/commands/set-default.d.ts +1 -5
- package/dist/esm/cli/commands/set-default.js +1 -1
- package/dist/esm/cli/commands/taxonomy.d.ts +5 -10
- package/dist/esm/cli/commands/taxonomy.js +10 -9
- package/dist/esm/cli/format.d.ts +8 -10
- package/dist/esm/cli/resolve-store.d.ts +8 -8
- package/dist/esm/cli/run.d.ts +4 -6
- package/dist/esm/cli/run.js +1 -1
- package/dist/esm/cli/select-kb-prompt.d.ts +6 -5
- package/dist/esm/cli/select-kb-prompt.js +1 -1
- package/dist/esm/cli/targeting/resolve-changed-paths.d.ts +3 -3
- package/dist/esm/cli/targeting/select-notes.d.ts +2 -2
- package/dist/esm/config/config-schema.d.ts +10 -9
- package/dist/esm/config/kb-loader-error.d.ts +3 -3
- package/dist/esm/config/load-config.d.ts +2 -2
- package/dist/esm/config/load-config.js +1 -1
- package/dist/esm/config/note-scope.d.ts +7 -7
- package/dist/esm/create/create.d.ts +8 -8
- package/dist/esm/discovery/kb-registry-schema.d.ts +0 -2
- package/dist/esm/discovery/load-registry.d.ts +7 -9
- package/dist/esm/discovery/load-registry.js +1 -1
- package/dist/esm/discovery/register-store.d.ts +3 -4
- package/dist/esm/discovery/register-store.js +2 -2
- package/dist/esm/discovery/registry-document.d.ts +2 -2
- package/dist/esm/discovery/set-default-kb.d.ts +5 -5
- package/dist/esm/discovery/set-default-kb.js +3 -3
- package/dist/esm/filesystem/exists.d.ts +4 -4
- package/dist/esm/frontmatter/parse-note.d.ts +3 -3
- package/dist/esm/git/list-git-scope.d.ts +6 -6
- package/dist/esm/git/run-git.d.ts +2 -2
- package/dist/esm/layout/store-layout.d.ts +5 -5
- package/dist/esm/lints/paths.d.ts +1 -1
- package/dist/esm/lints/paths.js +1 -1
- package/dist/esm/lints/tag-alias.d.ts +1 -1
- package/dist/esm/lints/tag-alias.js +1 -1
- package/dist/esm/lints/taxonomy.d.ts +9 -8
- package/dist/esm/lints/taxonomy.js +2 -2
- package/dist/esm/note-io/field-validators.d.ts +1 -1
- package/dist/esm/note-io/read-note.d.ts +2 -2
- package/dist/esm/note-io/write-note.d.ts +3 -6
- package/dist/esm/records/event.d.ts +3 -3
- package/dist/esm/scaffold/render-seeds.d.ts +3 -3
- package/dist/esm/scaffold/render-seeds.js +2 -2
- package/dist/esm/scaffold/scaffold.d.ts +7 -8
- package/dist/esm/tags/load-aliases.d.ts +3 -4
- package/dist/esm/taxonomy/domain-paths.d.ts +6 -6
- package/dist/esm/taxonomy/load-taxonomy.d.ts +3 -3
- package/dist/esm/taxonomy/load-taxonomy.js +2 -2
- package/dist/esm/taxonomy/taxonomy-schema.d.ts +5 -6
- package/dist/esm/taxonomy/write-taxonomy.d.ts +7 -8
- package/dist/esm/type-guards.d.ts +1 -1
- package/dist/esm/types.d.ts +6 -9
- package/dist/esm/vault-integrity/build-vault-index.d.ts +3 -4
- package/dist/esm/vault-integrity/check-vault-integrity.d.ts +13 -13
- package/dist/esm/vault-integrity/check-vault-integrity.js +1 -1
- package/dist/esm/vault-integrity/wikilink-parse.d.ts +21 -16
- package/package.json +5 -5
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { KbRegistry } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Loads and merges the user-global (`~/.agents/kb.yaml`) and project-local
|
|
4
4
|
* (`.agents/kb.yaml`) KB registries into a normalized `KbRegistry`.
|
|
5
5
|
*
|
|
6
6
|
* Project entries replace user entries by name on collision and append new names. The top-level `default_kb`
|
|
7
7
|
* pointer resolves by name against the merged entries (the project's value overriding the user's); the resolved
|
|
8
8
|
* entry is exposed as `defaultKb`.
|
|
9
|
-
* Within a single file, relative `path` values resolve against that file's directory and a leading `~/`
|
|
10
|
-
* against `$HOME`. Both files are optional; when neither exists the result has no entries.
|
|
9
|
+
* Within a single file, relative `path` values resolve against that file's directory and a leading `~` or `~/`
|
|
10
|
+
* expands against `$HOME`. Both files are optional; when neither exists the result has no entries.
|
|
11
11
|
* Malformed YAML, a structural defect, or a `default_kb` that names no registered KB throw.
|
|
12
12
|
*/
|
|
13
13
|
export declare function loadKbRegistry(input?: {
|
|
@@ -23,13 +23,11 @@ export interface KbRegistryLoadResult {
|
|
|
23
23
|
error?: string;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Loads the merged `kb.yaml` registry without throwing, capturing any failure message instead of presenting it.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* caller decides whether and how to surface that message; this wrapper neither writes to stderr nor builds a
|
|
32
|
-
* diagnostic.
|
|
28
|
+
* When `loadKbRegistry` throws, the result degrades to an empty config and contains the thrown message in `error`; the
|
|
29
|
+
* caller decides whether and how to report that message. Absent registry files are a success, which `loadKbRegistry`
|
|
30
|
+
* returns as an empty config.
|
|
33
31
|
*/
|
|
34
32
|
export declare function tryLoadKbRegistry(input?: {
|
|
35
33
|
userConfigPath?: string;
|
|
@@ -73,7 +73,7 @@ async function loadRegistryFile(path, source, home) {
|
|
|
73
73
|
}
|
|
74
74
|
const result = kbRegistryFileSchema.safeParse(parsed);
|
|
75
75
|
if (!result.success) {
|
|
76
|
-
throw new Error(`${path}: invalid kb.yaml
|
|
76
|
+
throw new Error(`${path}: invalid kb.yaml: ${result.error.issues[0]?.message ?? 'unknown error'}`);
|
|
77
77
|
}
|
|
78
78
|
const configDir = dirname(path);
|
|
79
79
|
const entries = [];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** The outcome of a registry write. */
|
|
2
1
|
export interface RegisterStoreResult {
|
|
3
2
|
/** `added` when a new entry was written; `already-present` when an entry of the same name already existed. */
|
|
4
3
|
status: 'added' | 'already-present';
|
|
@@ -7,9 +6,9 @@ export interface RegisterStoreResult {
|
|
|
7
6
|
* Inserts a knowledge-base entry under `kbs:` in a `kb.yaml` registry, creating the file and its parent directory when
|
|
8
7
|
* absent and preserving any existing comments and formatting. Every write leaves the `kbs:` entries alphabetically
|
|
9
8
|
* ordered, so a registry that has already drifted converges rather than merely staying sorted. An existing entry of the
|
|
10
|
-
* same name is left untouched and reported as `already-present`; that path writes nothing,
|
|
11
|
-
* reorders the file. The registry is validated against its schema both before mutation (so an already-corrupt file
|
|
12
|
-
* throws rather than being silently extended) and after (so an invalid entry, such as an empty `storePath` from a
|
|
9
|
+
* same name is left untouched and reported as `already-present`; because that path writes nothing, re-registering never
|
|
10
|
+
* reorders the file. The registry is validated against its schema both before mutation (so that an already-corrupt file
|
|
11
|
+
* throws rather than being silently extended) and after (so that an invalid entry, such as an empty `storePath` from a
|
|
13
12
|
* direct caller, throws rather than being written), never producing a corrupt file.
|
|
14
13
|
*/
|
|
15
14
|
export declare function registerStore(input: {
|
|
@@ -7,7 +7,7 @@ export async function registerStore(input) {
|
|
|
7
7
|
const doc = await loadRegistryDocument(input.registryPath);
|
|
8
8
|
const existing = kbRegistryFileSchema.safeParse(doc.toJS() ?? {});
|
|
9
9
|
if (!existing.success) {
|
|
10
|
-
throw new Error(`${input.registryPath}: invalid kb.yaml
|
|
10
|
+
throw new Error(`${input.registryPath}: invalid kb.yaml: ${existing.error.issues[0]?.message ?? 'unknown error'}`);
|
|
11
11
|
}
|
|
12
12
|
if (doc.hasIn(['kbs', input.name])) {
|
|
13
13
|
return { status: 'already-present' };
|
|
@@ -20,7 +20,7 @@ export async function registerStore(input) {
|
|
|
20
20
|
sortRegistryEntries(doc);
|
|
21
21
|
const result = kbRegistryFileSchema.safeParse(doc.toJS());
|
|
22
22
|
if (!result.success) {
|
|
23
|
-
throw new Error(`${input.registryPath}: cannot register "${input.name}"
|
|
23
|
+
throw new Error(`${input.registryPath}: cannot register "${input.name}": ${result.error.issues[0]?.message ?? 'invalid entry'}`);
|
|
24
24
|
}
|
|
25
25
|
await mkdir(dirname(input.registryPath), { recursive: true });
|
|
26
26
|
await writeFile(input.registryPath, doc.toString(), 'utf8');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Document } from 'yaml';
|
|
2
2
|
/**
|
|
3
3
|
* Reads and parses a `kb.yaml` registry as a `yaml` Document, returning a fresh empty document when the file is absent
|
|
4
|
-
* or empty. Parsing through `parseDocument` preserves comments and formatting so registry writers can mutate in
|
|
5
|
-
* without discarding them.
|
|
4
|
+
* or empty. Parsing through `parseDocument` preserves comments and formatting so that registry writers can mutate in
|
|
5
|
+
* place without discarding them.
|
|
6
6
|
*/
|
|
7
7
|
export declare function loadRegistryDocument(path: string): Promise<Document>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Removes the top-level `default_kb` pointer from a `kb.yaml` registry, preserving existing comments and formatting. A
|
|
3
|
-
* no-op when no default is set (or the file is absent):
|
|
4
|
-
* idempotent and never reformats. Validates an existing registry against its schema first, so a corrupt file
|
|
5
|
-
* rather than being rewritten.
|
|
3
|
+
* no-op when no default is set (or the file is absent): The file is left untouched rather than rewritten, so clearing is
|
|
4
|
+
* idempotent and never reformats. Validates an existing registry against its schema first, so that a corrupt file
|
|
5
|
+
* throws rather than being rewritten.
|
|
6
6
|
*/
|
|
7
7
|
export declare function clearDefaultKb(input: {
|
|
8
8
|
registryPath: string;
|
|
@@ -10,8 +10,8 @@ export declare function clearDefaultKb(input: {
|
|
|
10
10
|
/**
|
|
11
11
|
* Sets the top-level `default_kb` pointer in a `kb.yaml` registry to `name`, creating the file and its parent directory
|
|
12
12
|
* when absent and preserving existing comments and formatting. Validates the registry against its schema before mutating
|
|
13
|
-
* (so an already-corrupt file throws rather than being rewritten) and asserts `name` is registered under `kbs` in
|
|
14
|
-
* file (a `default_kb` naming no registered KB would fail every subsequent load), then re-validates the result.
|
|
13
|
+
* (so that an already-corrupt file throws rather than being rewritten) and asserts `name` is registered under `kbs` in
|
|
14
|
+
* the file (a `default_kb` naming no registered KB would fail every subsequent load), then re-validates the result.
|
|
15
15
|
*/
|
|
16
16
|
export declare function setDefaultKb(input: {
|
|
17
17
|
registryPath: string;
|
|
@@ -7,7 +7,7 @@ export async function clearDefaultKb(input) {
|
|
|
7
7
|
const doc = await loadRegistryDocument(input.registryPath);
|
|
8
8
|
const existing = kbRegistryFileSchema.safeParse(doc.toJS() ?? {});
|
|
9
9
|
if (!existing.success) {
|
|
10
|
-
throw new Error(`${input.registryPath}: invalid kb.yaml
|
|
10
|
+
throw new Error(`${input.registryPath}: invalid kb.yaml: ${existing.error.issues[0]?.message ?? 'unknown error'}`);
|
|
11
11
|
}
|
|
12
12
|
if (!doc.has('default_kb')) {
|
|
13
13
|
return;
|
|
@@ -19,7 +19,7 @@ export async function setDefaultKb(input) {
|
|
|
19
19
|
const doc = await loadRegistryDocument(input.registryPath);
|
|
20
20
|
const existing = kbRegistryFileSchema.safeParse(doc.toJS() ?? {});
|
|
21
21
|
if (!existing.success) {
|
|
22
|
-
throw new Error(`${input.registryPath}: invalid kb.yaml
|
|
22
|
+
throw new Error(`${input.registryPath}: invalid kb.yaml: ${existing.error.issues[0]?.message ?? 'unknown error'}`);
|
|
23
23
|
}
|
|
24
24
|
if (!doc.hasIn(['kbs', input.name])) {
|
|
25
25
|
throw new Error(`${input.registryPath}: "${input.name}" is not a registered knowledge base`);
|
|
@@ -27,7 +27,7 @@ export async function setDefaultKb(input) {
|
|
|
27
27
|
doc.set('default_kb', input.name);
|
|
28
28
|
const result = kbRegistryFileSchema.safeParse(doc.toJS());
|
|
29
29
|
if (!result.success) {
|
|
30
|
-
throw new Error(`${input.registryPath}: cannot set default_kb
|
|
30
|
+
throw new Error(`${input.registryPath}: cannot set default_kb: ${result.error.issues[0]?.message ?? 'invalid registry'}`);
|
|
31
31
|
}
|
|
32
32
|
await mkdir(dirname(input.registryPath), { recursive: true });
|
|
33
33
|
await writeFile(input.registryPath, doc.toString(), 'utf8');
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/** Controls which `stat` failures a filesystem-existence check treats as "absent". */
|
|
2
2
|
export interface ExistsOptions {
|
|
3
3
|
/**
|
|
4
|
-
* Stat-error codes treated as "absent" (the helper returns `false`). Any other failure is re-thrown so a
|
|
5
|
-
* error
|
|
4
|
+
* Stat-error codes treated as "absent" (the helper returns `false`). Any other failure is re-thrown so that a
|
|
5
|
+
* genuine error (most importantly a permission denial on a path that does exist) is not silently read as absence.
|
|
6
6
|
* Defaults to `['ENOENT']`.
|
|
7
7
|
*/
|
|
8
8
|
absentCodes?: readonly string[];
|
|
9
9
|
/**
|
|
10
10
|
* When `true`, every `stat` failure is treated as "absent" (`false`), including permission errors.
|
|
11
|
-
* Reserved for best-effort probes such as an ancestor walk,
|
|
12
|
-
* abort the operation. Takes precedence over `absentCodes`.
|
|
11
|
+
* Reserved for best-effort probes such as an ancestor walk, in which an unreadable path should be skipped rather
|
|
12
|
+
* than abort the operation. Takes precedence over `absentCodes`.
|
|
13
13
|
*/
|
|
14
14
|
treatErrorsAsAbsent?: boolean;
|
|
15
15
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { ParsedNote } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Reads a note from disk and
|
|
3
|
+
* Reads a note from disk and parses it into a `ParsedNote`. I/O errors (e.g. a missing file) are thrown;
|
|
4
4
|
* YAML parse errors are not.
|
|
5
5
|
*/
|
|
6
6
|
export declare function parseNote(input: {
|
|
7
7
|
path: string;
|
|
8
8
|
}): Promise<ParsedNote>;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* Parse errors are recorded in `frontmatterRaw.parseError`, never thrown
|
|
10
|
+
* Parses a note from a literal string into a `ParsedNote` containing typed frontmatter.
|
|
11
|
+
* Parse errors are recorded in `frontmatterRaw.parseError`, never thrown; the rule layer decides how to report them.
|
|
12
12
|
* `path` defaults to `<string>` and labels the result for diagnostics.
|
|
13
13
|
*/
|
|
14
14
|
export declare function parseNoteContent(input: {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Returns every path that git tracks under `root`, plus every untracked path that no ignore rule covers, as an
|
|
3
|
-
* NFC-normalized set of root-relative, slash-separated paths. Returns `undefined`
|
|
4
|
-
* because `root`
|
|
3
|
+
* NFC-normalized set of root-relative, slash-separated paths. Returns `undefined` when git cannot list the paths,
|
|
4
|
+
* because `root` is outside a working tree or git cannot be run at all, so a caller keeps its own scope instead of
|
|
5
5
|
* reading an empty set as "git ignores everything".
|
|
6
6
|
*
|
|
7
|
-
* A tracked file stays in scope even
|
|
8
|
-
* the
|
|
7
|
+
* A tracked file stays in scope even when an ignore rule matches it, since every clone still contains it. That is why
|
|
8
|
+
* the function unions the two `ls-files` forms rather than running `git check-ignore`, which would report it ignored.
|
|
9
9
|
*
|
|
10
|
-
* Paths are normalized because git reports NFC
|
|
11
|
-
* unnormalized drops every path whose name
|
|
10
|
+
* Paths are normalized because git reports NFC but `readdir` reports NFD on macOS, and comparing the two forms
|
|
11
|
+
* unnormalized drops every path whose name contains a combining mark.
|
|
12
12
|
*/
|
|
13
13
|
export declare function listGitScope(input: {
|
|
14
14
|
root: string;
|
|
@@ -7,8 +7,8 @@ export type GitResult = {
|
|
|
7
7
|
message: string;
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
* Runs `git -C cwd <args>` and returns its stdout, or a failure
|
|
11
|
-
* spawned fails the same way as one that exits non-zero, so
|
|
10
|
+
* Runs `git -C cwd <args>` and returns its stdout, or a failure that contains git's own stderr. A git that cannot be
|
|
11
|
+
* spawned fails the same way as one that exits non-zero, so the caller receives both as one kind of failure.
|
|
12
12
|
*/
|
|
13
13
|
export declare function runGit(input: {
|
|
14
14
|
cwd: string;
|
|
@@ -4,17 +4,17 @@ export declare const KB_DIR = ".kb";
|
|
|
4
4
|
export declare const CONTENT_DIR = "content";
|
|
5
5
|
/** The `content/` subdirectory holding assertion records. Named on its own because a caller may need to recognize the segment, not just the path. */
|
|
6
6
|
export declare const ASSERTIONS_SEGMENT = "assertions";
|
|
7
|
-
/** The tag-alias map. */
|
|
8
7
|
export declare const ALIASES_FILE = ".kb/tag-aliases.yaml";
|
|
9
|
-
/** The directory holding the store's assertion records. */
|
|
10
8
|
export declare const ASSERTIONS_DIR = "content/assertions";
|
|
11
9
|
/** The check configuration. */
|
|
12
10
|
export declare const CONFIG_FILE = ".kb/config.yaml";
|
|
13
|
-
/** The editor and formatter settings.
|
|
11
|
+
/** The editor and formatter settings. Located at the store root, where every tool that reads it looks. */
|
|
14
12
|
export declare const EDITORCONFIG_FILE = ".editorconfig";
|
|
15
|
-
/** The directory holding the store's event records. */
|
|
16
13
|
export declare const EVENTS_DIR = "content/events";
|
|
17
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The formatting configuration. Located at the store root rather than under `.kb/`, because Prettier discovers it
|
|
16
|
+
* there.
|
|
17
|
+
*/
|
|
18
18
|
export declare const PRETTIER_CONFIG_FILE = ".prettierrc.yaml";
|
|
19
19
|
/** The declared assertion taxonomy. */
|
|
20
20
|
export declare const TAXONOMY_FILE = ".kb/taxonomy.yaml";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Finding } from '../types.js';
|
|
2
|
-
/** The note fields the paths lint reads: its path and its full raw content. */
|
|
2
|
+
/** The note fields that the paths lint reads: its path and its full raw content. */
|
|
3
3
|
export interface PathsNote {
|
|
4
4
|
path: string;
|
|
5
5
|
content: string;
|
package/dist/esm/lints/paths.js
CHANGED
|
@@ -5,7 +5,7 @@ export function pathsFindings(note) {
|
|
|
5
5
|
line: countNewlines(note.content, match.index) + 1,
|
|
6
6
|
rule: 'paths.user-home',
|
|
7
7
|
severity: 'error',
|
|
8
|
-
message: `hardcoded "${match[0]}"
|
|
8
|
+
message: `hardcoded "${match[0]}"; replace with ~/`,
|
|
9
9
|
}));
|
|
10
10
|
return findings;
|
|
11
11
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AliasMap, Finding } from '../types.js';
|
|
2
|
-
/** The note fields the tag-alias lint reads: its path and its raw frontmatter field map. */
|
|
2
|
+
/** The note fields that the tag-alias lint reads: its path and its raw frontmatter field map. */
|
|
3
3
|
export interface TagAliasNote {
|
|
4
4
|
path: string;
|
|
5
5
|
fields: Record<string, unknown>;
|
|
@@ -14,7 +14,7 @@ export function tagAliasFindings(note, aliases) {
|
|
|
14
14
|
path: note.path,
|
|
15
15
|
rule: 'tag-alias',
|
|
16
16
|
severity: 'warning',
|
|
17
|
-
message: `tag "${tag}" is an alias
|
|
17
|
+
message: `tag "${tag}" is an alias; use canonical form "${canonical}"`,
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
return findings;
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import type { KbConfig } from '../config/config-schema.js';
|
|
2
2
|
import type { Taxonomy } from '../taxonomy/taxonomy-schema.js';
|
|
3
3
|
import type { Finding } from '../types.js';
|
|
4
|
-
/** The note fields the taxonomy rules read. */
|
|
4
|
+
/** The note fields that the taxonomy rules read. */
|
|
5
5
|
export interface TaxonomyNote {
|
|
6
6
|
/** The note's path relative to the KB root, slash-separated. */
|
|
7
7
|
relativePath: string;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Reports
|
|
11
|
-
* holding notes that no domain declares, `taxonomy.unused` for a declared domain holding no note at or beneath
|
|
12
|
-
* `taxonomy.orphan` for a declared domain whose parent is undeclared. All are warnings, so drift is reported
|
|
13
|
-
* failing the run
|
|
10
|
+
* Reports each disagreement between a store's assertion folders and its declared taxonomy: `taxonomy.undeclared` for a
|
|
11
|
+
* folder holding notes that no domain declares, `taxonomy.unused` for a declared domain holding no note at or beneath
|
|
12
|
+
* it, and `taxonomy.orphan` for a declared domain whose parent is undeclared. All are warnings, so drift is reported
|
|
13
|
+
* without failing the run. All are vault-scoped: A run narrowed to selected notes still reports them.
|
|
14
14
|
*
|
|
15
15
|
* A taxonomy declaring nothing disables all three, whether because the file is absent or because it declares no
|
|
16
|
-
* domains.
|
|
16
|
+
* domains. The rules therefore report nothing for a store that has not adopted a taxonomy, rather than flagging
|
|
17
|
+
* every folder that the store owns.
|
|
17
18
|
*
|
|
18
|
-
*
|
|
19
|
-
* no filesystem traversal and
|
|
19
|
+
* Because the observed structure comes from the enumerated notes' own paths rather than a directory listing, the rules
|
|
20
|
+
* add no filesystem traversal and consider exactly the notes that the run's `targets` and `exclude` admitted.
|
|
20
21
|
*/
|
|
21
22
|
export declare function taxonomyFindings(input: {
|
|
22
23
|
notes: readonly TaxonomyNote[];
|
|
@@ -18,14 +18,14 @@ export function taxonomyFindings(input) {
|
|
|
18
18
|
const findings = [];
|
|
19
19
|
for (const domain of [...observed].toSorted()) {
|
|
20
20
|
if (!taxonomy.has(domain)) {
|
|
21
|
-
findings.push(buildFinding(taxonomyPath, 'undeclared', `folder "${domain}"
|
|
21
|
+
findings.push(buildFinding(taxonomyPath, 'undeclared', `folder "${domain}" contains notes but no domain declares it`));
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
for (const domain of declared) {
|
|
25
25
|
if (holdsNote(domain, observed) || matcher.isExcluded(`${ASSERTIONS_DIR}/${domain}`)) {
|
|
26
26
|
continue;
|
|
27
27
|
}
|
|
28
|
-
findings.push(buildFinding(taxonomyPath, 'unused', `domain "${domain}" is declared but
|
|
28
|
+
findings.push(buildFinding(taxonomyPath, 'unused', `domain "${domain}" is declared but contains no notes`));
|
|
29
29
|
}
|
|
30
30
|
for (const domain of declared) {
|
|
31
31
|
const parent = resolveParent(domain);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Coerces a frontmatter field value to a string list: the array's string members when it is a sequence, an empty list
|
|
3
|
-
* when the value is absent, or `null` when the value is present but not list-shaped (so a parser can flag it).
|
|
3
|
+
* when the value is absent, or `null` when the value is present but not list-shaped (so that a parser can flag it).
|
|
4
4
|
*/
|
|
5
5
|
export declare function asStringList(value: unknown): string[] | null;
|
|
6
6
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** A note split into its frontmatter field map and its body. */
|
|
2
2
|
export interface ReadNote {
|
|
3
3
|
fields: Record<string, unknown>;
|
|
4
4
|
body: string;
|
|
@@ -10,6 +10,6 @@ export interface ReadNote {
|
|
|
10
10
|
export declare function readNote(path: string): Promise<ReadNote>;
|
|
11
11
|
/**
|
|
12
12
|
* Splits note content into a frontmatter field map and body. A missing frontmatter block or a YAML parse error is
|
|
13
|
-
* reported in `error` (never thrown), with the full content returned as the body so
|
|
13
|
+
* reported in `error` (never thrown), with the full content returned as the body so that a caller can still show it.
|
|
14
14
|
*/
|
|
15
15
|
export declare function readNoteContent(content: string): ReadNote;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Renders a field map and body to note content: the opening fence, the frontmatter, the closing fence, one blank line,
|
|
3
|
-
* then the body. A single leading newline on the body is dropped so the blank line is not doubled, keeping the
|
|
4
|
-
* stable across read/write cycles.
|
|
3
|
+
* then the body. A single leading newline on the body is dropped so that the blank line is not doubled, keeping the
|
|
4
|
+
* output stable across read/write cycles.
|
|
5
5
|
*/
|
|
6
6
|
export declare function renderNote(fields: Record<string, unknown>, body: string): string;
|
|
7
|
-
/**
|
|
8
|
-
* Atomically writes a note to `path`, so a concurrent reader never sees a partial write. See {@link writeAtomic} for
|
|
9
|
-
* the guarantee and its failure behavior.
|
|
10
|
-
*/
|
|
7
|
+
/** Atomically writes a note to `path`. See {@link writeAtomic} for the guarantee and its failure behavior. */
|
|
11
8
|
export declare function writeNote(path: string, fields: Record<string, unknown>, body: string): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The impact levels an event may
|
|
1
|
+
/** The impact levels that an event may declare, ordered lowest to highest. */
|
|
2
2
|
export declare const EVENT_IMPACT_LEVELS: readonly ["low", "medium", "high", "critical"];
|
|
3
3
|
/** An event's impact: the author's subjective, revisable rating of how much addressing the event matters. */
|
|
4
4
|
export type EventImpact = (typeof EVENT_IMPACT_LEVELS)[number];
|
|
@@ -31,8 +31,8 @@ export type ParseEventResult = {
|
|
|
31
31
|
export declare function parseEvent(fields: Record<string, unknown>, body: string): ParseEventResult;
|
|
32
32
|
/**
|
|
33
33
|
* Projects an event back to a frontmatter field map (declared fields first, then preserved `extra`) plus its body. An
|
|
34
|
-
* empty `session` is omitted like an absent one, mirroring {@link parseEvent}:
|
|
35
|
-
*
|
|
34
|
+
* empty `session` is omitted like an absent one, mirroring {@link parseEvent}: The two forms of "no session" have one
|
|
35
|
+
* representation on both parse and render, so no record can reacquire the empty field on a write.
|
|
36
36
|
*/
|
|
37
37
|
export declare function renderEvent(record: KbEvent): {
|
|
38
38
|
fields: Record<string, unknown>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The formatting option every store
|
|
3
|
-
* prose, so that the file that a store receives cannot diverge from the value that kb documents. See
|
|
2
|
+
* The formatting option that every store sets and that `.editorconfig` cannot express. Defined here rather than in
|
|
3
|
+
* the seed prose, so that the file that a store receives cannot diverge from the value that kb documents. See
|
|
4
4
|
* {@link renderPrettierSeed}'s header for what it prevents.
|
|
5
5
|
*/
|
|
6
6
|
export declare const canonicalPrettierConfig: {
|
|
@@ -10,7 +10,7 @@ export declare const canonicalPrettierConfig: {
|
|
|
10
10
|
export declare function renderAliasesSeed(): string;
|
|
11
11
|
/** Renders `.kb/config.yaml`: a fully-commented stub whose example values are the live `defaultKbConfig`. */
|
|
12
12
|
export declare function renderConfigSeed(): string;
|
|
13
|
-
/** Renders `.editorconfig`: the editor and formatter settings every store shares. */
|
|
13
|
+
/** Renders `.editorconfig`: the editor and formatter settings that every store shares. */
|
|
14
14
|
export declare function renderEditorconfigSeed(): string;
|
|
15
15
|
/** Renders `.prettierrc.yaml`: the canonical formatting option under a header explaining why it is set. */
|
|
16
16
|
export declare function renderPrettierSeed(): string;
|
|
@@ -12,7 +12,7 @@ const CONFIG_HEADER = `# Check configuration for this knowledge store.
|
|
|
12
12
|
# Every key is optional and falls back to the default shown below; an absent file uses these defaults too. Uncomment
|
|
13
13
|
# and edit to override. \`targets\` selects which notes \`kb check\` enumerates and \`exclude\` removes matches;
|
|
14
14
|
# patterns are slash-separated and relative to the store root. \`visibility\` declares how widely the store is
|
|
15
|
-
# published, which decides what may link into it:
|
|
15
|
+
# published, which decides what may link into it: A \`[[store:Note title]]\` link resolves only into a store as
|
|
16
16
|
# shareable as its own or more so.
|
|
17
17
|
#
|
|
18
18
|
`;
|
|
@@ -36,7 +36,7 @@ const PRETTIER_HEADER = `# Formatting configuration for this knowledge store.
|
|
|
36
36
|
#
|
|
37
37
|
# \`embeddedLanguageFormatting: off\` leaves a note's YAML frontmatter unformatted. Formatted, a long \`tags\` or
|
|
38
38
|
# \`addressed-by\` list breaks across several lines, which the note writer puts back onto one the next time it writes
|
|
39
|
-
# the note
|
|
39
|
+
# the note. The formatter and the writer would rewrite each other's output without end.
|
|
40
40
|
#
|
|
41
41
|
# Width, indentation, and line endings are set in \`.editorconfig\`, which Prettier reads and every editor reads too.
|
|
42
42
|
#
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What {@link scaffold} did about one canonical path: `created`
|
|
3
|
-
* found, `replaced`
|
|
2
|
+
* What {@link scaffold} did about one canonical path: `created` when it was absent, `present` when it was left as
|
|
3
|
+
* found, `replaced` when `force` overwrote it.
|
|
4
4
|
*/
|
|
5
5
|
export type ScaffoldAction = 'created' | 'present' | 'replaced';
|
|
6
6
|
/** One canonical path and what the scaffold did about it. */
|
|
7
7
|
export interface ScaffoldEntry {
|
|
8
|
-
/** The store-relative path, directories
|
|
8
|
+
/** The store-relative path, directories with a trailing slash. */
|
|
9
9
|
path: string;
|
|
10
10
|
action: ScaffoldAction;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Writes the canonical files and directories every knowledge-base store
|
|
14
|
-
* unless `force` is set. Directories are ensured either way, since a directory has no content to replace.
|
|
13
|
+
* Writes the canonical files and directories that every knowledge-base store contains, leaving an existing file as it
|
|
14
|
+
* found it unless `force` is set. Directories are ensured either way, since a directory has no content to replace.
|
|
15
15
|
*
|
|
16
|
-
* The function asserts nothing about the store:
|
|
17
|
-
* that itself.
|
|
18
|
-
* just confirmed does.
|
|
16
|
+
* The function asserts nothing about the store: A caller that requires one to exist, or requires one not to, checks
|
|
17
|
+
* that itself.
|
|
19
18
|
*/
|
|
20
19
|
export declare function scaffold(input: {
|
|
21
20
|
storePath: string;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { AliasMap, KbRoot } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Loads `.kb/tag-aliases.yaml` from a KB root into a typed `AliasMap`, returning an empty map when the file is absent.
|
|
4
|
-
* The thin I/O wrapper around {@link parseAliases}; structural
|
|
4
|
+
* The thin I/O wrapper around {@link parseAliases}; on a structural defect it throws, naming the file path.
|
|
5
5
|
*/
|
|
6
6
|
export declare function loadAliases(input: {
|
|
7
7
|
kbRoot: KbRoot;
|
|
8
8
|
}): Promise<AliasMap>;
|
|
9
9
|
/**
|
|
10
10
|
* Parses a tag-aliases registry from a string into an `AliasMap`.
|
|
11
|
-
* Aliases are lowercased on insertion so callers can look up case-insensitively.
|
|
12
|
-
* Throws on
|
|
13
|
-
* or cross-canonical collisions — with `contextLabel` prefixed onto every message.
|
|
11
|
+
* Aliases are lowercased on insertion so that callers can look up case-insensitively.
|
|
12
|
+
* Throws on malformed YAML and on any structural defect, with `contextLabel` prefixed onto every message.
|
|
14
13
|
*/
|
|
15
14
|
export declare function parseAliases(text: string, contextLabel?: string): AliasMap;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Derives every domain a note set
|
|
3
|
-
* A grouping domain that
|
|
4
|
-
* report a back-filled taxonomy's own entries as unused.
|
|
2
|
+
* Derives every domain implied by a note set: each note's domain, plus each of that domain's ancestors, sorted.
|
|
3
|
+
* A grouping domain that contains only subfolders is included, because the rules treat it as in use and would
|
|
4
|
+
* otherwise report a back-filled taxonomy's own entries as unused.
|
|
5
5
|
*/
|
|
6
6
|
export declare function deriveDomains(relativePaths: Iterable<string>): string[];
|
|
7
7
|
/**
|
|
8
|
-
* Derives
|
|
9
|
-
* Scoping to the assertions root
|
|
8
|
+
* Derives a note's domain, or `undefined` when the note is not an assertion or is at the assertions root.
|
|
9
|
+
* Scoping to the assertions root keeps event records from registering as domains.
|
|
10
10
|
*/
|
|
11
11
|
export declare function resolveDomain(relativePath: string): string | undefined;
|
|
12
|
-
/** Derives a slash-path's parent, or `undefined` when it has no separator and so
|
|
12
|
+
/** Derives a slash-path's parent, or `undefined` when it has no separator and so is at the top level. */
|
|
13
13
|
export declare function resolveParent(path: string): string | undefined;
|
|
@@ -3,10 +3,10 @@ import { type Taxonomy } from './taxonomy-schema.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Loads `.kb/taxonomy.yaml` into a single keyed map, returning an empty taxonomy when the file is absent or declares
|
|
5
5
|
* nothing. The two on-disk blocks are a file-format concern: a consumer looks a domain up once and reads `provisional`
|
|
6
|
-
* off the entry it finds.
|
|
6
|
+
* off the entry that it finds.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Structural defects (malformed YAML, a wrong type, a malformed key, a path declared in both blocks) throw a
|
|
9
|
+
* {@link KbLoaderError} naming the file. I/O errors other than a missing file propagate.
|
|
10
10
|
*/
|
|
11
11
|
export declare function loadTaxonomy(input: {
|
|
12
12
|
kbRoot: KbRoot;
|
|
@@ -53,8 +53,8 @@ function collectBlock(input) {
|
|
|
53
53
|
function describeIssueLocation(error) {
|
|
54
54
|
const issue = error.issues[0];
|
|
55
55
|
if (issue === undefined) {
|
|
56
|
-
return '
|
|
56
|
+
return ': unknown error';
|
|
57
57
|
}
|
|
58
58
|
const location = issue.path.length > 0 ? ` at ${issue.path.join('.')}` : '';
|
|
59
|
-
return `${location}
|
|
59
|
+
return `${location}: ${issue.message}`;
|
|
60
60
|
}
|
|
@@ -2,12 +2,11 @@ import { z } from 'zod';
|
|
|
2
2
|
/**
|
|
3
3
|
* Describes why a domain key cannot be used, or returns `undefined` when the key is well-formed. Keys are relative to
|
|
4
4
|
* the assertions root, so a restated `content/assertions/` prefix would declare the domain a level deeper than the
|
|
5
|
-
* author meant
|
|
5
|
+
* author meant.
|
|
6
6
|
*/
|
|
7
7
|
export declare function describeKeyDefect(key: string): string | undefined;
|
|
8
8
|
/** A knowledge base's declared assertion structure, keyed by assertions-root-relative slash-path. */
|
|
9
9
|
export type Taxonomy = ReadonlyMap<string, TaxonomyEntry>;
|
|
10
|
-
/** A single declared domain. */
|
|
11
10
|
export interface TaxonomyEntry {
|
|
12
11
|
/** The domain's one-line description; empty when it was declared without one. */
|
|
13
12
|
description: string;
|
|
@@ -18,11 +17,11 @@ export interface TaxonomyEntry {
|
|
|
18
17
|
* The on-disk `.kb/taxonomy.yaml` shape.
|
|
19
18
|
*
|
|
20
19
|
* Both blocks are optional and nullable, so a file may declare only one, and a block header left with nothing under it
|
|
21
|
-
* loads as declaring nothing rather than failing. YAML reads such a header as null, and it is the state
|
|
22
|
-
* last entry out of a block
|
|
20
|
+
* loads as declaring nothing rather than failing. YAML reads such a header as null, and it is the state left behind by
|
|
21
|
+
* promoting the last entry out of a block.
|
|
23
22
|
*
|
|
24
|
-
*
|
|
25
|
-
* types and what a domain declared without a description round-trips to.
|
|
23
|
+
* Because a description may likewise be null, a key written bare (`engineering/tooling:`) loads: That is what a hand
|
|
24
|
+
* editor types and what a domain declared without a description round-trips to.
|
|
26
25
|
*/
|
|
27
26
|
export declare const taxonomyFileShape: z.ZodObject<{
|
|
28
27
|
domains: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>>>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { KbRoot } from '../types.js';
|
|
2
|
-
/** A domain to declare. */
|
|
3
2
|
export interface TaxonomyDeclaration {
|
|
4
3
|
/** The domain's assertions-root-relative slash-path. */
|
|
5
4
|
path: string;
|
|
6
|
-
/** The one-line description; absent or empty writes the key bare, with no description. */
|
|
5
|
+
/** The one-line description; when absent or empty, `writeTaxonomy` writes the key bare, with no description. */
|
|
7
6
|
description?: string;
|
|
8
7
|
/** Whether to declare under `provisional:` rather than `domains:`. */
|
|
9
8
|
provisional: boolean;
|
|
@@ -12,13 +11,13 @@ export interface TaxonomyDeclaration {
|
|
|
12
11
|
* Declares domains in `.kb/taxonomy.yaml`, creating the file and either block as needed, and returns the paths added.
|
|
13
12
|
*
|
|
14
13
|
* Edits the parsed document rather than re-serializing a plain object, so existing comments, key order, and formatting
|
|
15
|
-
* survive; a plain parse-and-stringify round trip would discard every comment in a hand-curated file. New keys
|
|
16
|
-
* to the end of their block in path order, and a block header left with nothing under it is filled in
|
|
17
|
-
* than moved.
|
|
14
|
+
* survive; a plain parse-and-stringify round trip would discard every comment in a hand-curated file. New keys
|
|
15
|
+
* are appended to the end of their block in path order, and a block header left with nothing under it is filled in
|
|
16
|
+
* place rather than moved.
|
|
18
17
|
*
|
|
19
|
-
* A path either block
|
|
20
|
-
* is left to add, the file is not opened for writing at all. The write goes through the shared `writeAtomic`
|
|
21
|
-
* so an interrupted call cannot truncate the taxonomy.
|
|
18
|
+
* A path already declared by either block is skipped rather than overwritten. A repeat call adds nothing, and when
|
|
19
|
+
* nothing is left to add, the file is not opened for writing at all. The write goes through the shared `writeAtomic`
|
|
20
|
+
* helper, so an interrupted call cannot truncate the taxonomy.
|
|
22
21
|
*
|
|
23
22
|
* Throws a {@link KbLoaderError} on a malformed key, or on an existing file that cannot be safely appended to: one
|
|
24
23
|
* that fails to parse, one whose top level is not a mapping, and one whose `domains` or `provisional` block holds
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function isEnoent(error: unknown): boolean;
|
|
6
6
|
/**
|
|
7
|
-
* Returns true when `error`
|
|
7
|
+
* Returns true when `error` has the given Node `code` string (e.g. `'ENOENT'`, `'EACCES'`).
|
|
8
8
|
* @internal
|
|
9
9
|
*/
|
|
10
10
|
export declare function isErrorCode(error: unknown, code: string): boolean;
|