@williamthorsen/kb 0.7.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.
Files changed (77) hide show
  1. package/README.md +101 -96
  2. package/bin/kb.js +2 -4
  3. package/dist/esm/check/check.d.ts +14 -12
  4. package/dist/esm/check/check.js +41 -1
  5. package/dist/esm/check/enumerate.d.ts +13 -16
  6. package/dist/esm/check/glob-segments.d.ts +1 -1
  7. package/dist/esm/check/resolve-foreign-stores.d.ts +25 -0
  8. package/dist/esm/check/resolve-foreign-stores.js +50 -0
  9. package/dist/esm/cli/commands/check.d.ts +4 -10
  10. package/dist/esm/cli/commands/check.js +8 -3
  11. package/dist/esm/cli/commands/create.d.ts +3 -7
  12. package/dist/esm/cli/commands/create.js +1 -1
  13. package/dist/esm/cli/commands/scaffold.d.ts +5 -9
  14. package/dist/esm/cli/commands/scaffold.js +5 -5
  15. package/dist/esm/cli/commands/set-default.d.ts +1 -5
  16. package/dist/esm/cli/commands/set-default.js +1 -1
  17. package/dist/esm/cli/commands/taxonomy.d.ts +5 -10
  18. package/dist/esm/cli/commands/taxonomy.js +10 -9
  19. package/dist/esm/cli/format.d.ts +8 -10
  20. package/dist/esm/cli/resolve-store.d.ts +8 -8
  21. package/dist/esm/cli/run.d.ts +4 -6
  22. package/dist/esm/cli/run.js +1 -1
  23. package/dist/esm/cli/select-kb-prompt.d.ts +6 -5
  24. package/dist/esm/cli/select-kb-prompt.js +1 -1
  25. package/dist/esm/cli/targeting/resolve-changed-paths.d.ts +3 -3
  26. package/dist/esm/cli/targeting/select-notes.d.ts +2 -2
  27. package/dist/esm/config/config-schema.d.ts +24 -6
  28. package/dist/esm/config/config-schema.js +9 -0
  29. package/dist/esm/config/index.d.ts +1 -1
  30. package/dist/esm/config/index.js +1 -1
  31. package/dist/esm/config/kb-loader-error.d.ts +3 -3
  32. package/dist/esm/config/load-config.d.ts +3 -3
  33. package/dist/esm/config/load-config.js +2 -1
  34. package/dist/esm/config/note-scope.d.ts +7 -7
  35. package/dist/esm/create/create.d.ts +8 -8
  36. package/dist/esm/discovery/kb-registry-schema.d.ts +0 -2
  37. package/dist/esm/discovery/load-registry.d.ts +7 -9
  38. package/dist/esm/discovery/load-registry.js +1 -1
  39. package/dist/esm/discovery/register-store.d.ts +3 -4
  40. package/dist/esm/discovery/register-store.js +2 -2
  41. package/dist/esm/discovery/registry-document.d.ts +2 -2
  42. package/dist/esm/discovery/set-default-kb.d.ts +5 -5
  43. package/dist/esm/discovery/set-default-kb.js +3 -3
  44. package/dist/esm/filesystem/exists.d.ts +4 -4
  45. package/dist/esm/frontmatter/parse-note.d.ts +3 -3
  46. package/dist/esm/git/list-git-scope.d.ts +7 -7
  47. package/dist/esm/git/run-git.d.ts +2 -2
  48. package/dist/esm/layout/store-layout.d.ts +5 -5
  49. package/dist/esm/lints/paths.d.ts +1 -1
  50. package/dist/esm/lints/paths.js +1 -1
  51. package/dist/esm/lints/tag-alias.d.ts +1 -1
  52. package/dist/esm/lints/tag-alias.js +1 -1
  53. package/dist/esm/lints/taxonomy.d.ts +9 -8
  54. package/dist/esm/lints/taxonomy.js +2 -2
  55. package/dist/esm/note-io/field-validators.d.ts +1 -1
  56. package/dist/esm/note-io/read-note.d.ts +2 -2
  57. package/dist/esm/note-io/write-note.d.ts +3 -6
  58. package/dist/esm/records/event.d.ts +3 -3
  59. package/dist/esm/scaffold/render-seeds.d.ts +3 -3
  60. package/dist/esm/scaffold/render-seeds.js +5 -3
  61. package/dist/esm/scaffold/scaffold.d.ts +7 -8
  62. package/dist/esm/tags/load-aliases.d.ts +3 -4
  63. package/dist/esm/taxonomy/domain-paths.d.ts +6 -6
  64. package/dist/esm/taxonomy/load-taxonomy.d.ts +3 -3
  65. package/dist/esm/taxonomy/load-taxonomy.js +2 -2
  66. package/dist/esm/taxonomy/taxonomy-schema.d.ts +5 -6
  67. package/dist/esm/taxonomy/write-taxonomy.d.ts +7 -8
  68. package/dist/esm/type-guards.d.ts +1 -1
  69. package/dist/esm/types.d.ts +6 -9
  70. package/dist/esm/vault-integrity/build-vault-index.d.ts +3 -4
  71. package/dist/esm/vault-integrity/check-vault-integrity.d.ts +46 -12
  72. package/dist/esm/vault-integrity/check-vault-integrity.js +59 -20
  73. package/dist/esm/vault-integrity/index.d.ts +2 -2
  74. package/dist/esm/vault-integrity/index.js +2 -2
  75. package/dist/esm/vault-integrity/wikilink-parse.d.ts +45 -19
  76. package/dist/esm/vault-integrity/wikilink-parse.js +37 -9
  77. package/package.json +6 -6
@@ -1,22 +1,40 @@
1
1
  import { z } from 'zod';
2
2
  /**
3
- * The on-disk `.kb/config.yaml` shape. Both fields are optional so a file may override only the dimension it cares
3
+ * The on-disk `.kb/config.yaml` shape. Every field is optional so that a file may override only the key that it cares
4
4
  * about; an absent field falls back to {@link defaultKbConfig}.
5
5
  */
6
6
  export declare const configFileShape: z.ZodObject<{
7
7
  targets: z.ZodOptional<z.ZodArray<z.ZodString>>;
8
8
  exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
9
+ visibility: z.ZodOptional<z.ZodEnum<{
10
+ private: "private";
11
+ shared: "shared";
12
+ }>>;
9
13
  }, z.core.$strip>;
10
- /** The effective check configuration: the glob target set and the glob exclude set. */
14
+ /**
15
+ * The configuration applied when `.kb/config.yaml` is absent or omits a field. The default exclude names `node_modules`
16
+ * alone, because `createNoteScopeMatcher` already excludes dot-directories. Visibility defaults to the safer of the two
17
+ * values, so a store that has not declared itself never widens what may link into it.
18
+ */
19
+ export declare const defaultKbConfig: KbConfig;
20
+ /**
21
+ * Whether a link from a store of `source` visibility may resolve into one of `target` visibility. A link never
22
+ * increases disclosure: It may point at a store as shareable as its own or more so, never at a less shareable one.
23
+ */
24
+ export declare function isAtLeastAsShareable(input: {
25
+ source: StoreVisibility;
26
+ target: StoreVisibility;
27
+ }): boolean;
28
+ /** The effective check configuration, in which a default fills every field that `.kb/config.yaml` omits. */
11
29
  export interface KbConfig {
12
30
  /** Glob patterns (slash-separated, kbRoot-relative) selecting which notes `check` enumerates. */
13
31
  targets: readonly string[];
14
32
  /** Glob patterns excluded from enumeration even when a target matches. */
15
33
  exclude: readonly string[];
34
+ /** How widely the store is published, which decides what may link into it. */
35
+ visibility: StoreVisibility;
16
36
  }
17
37
  /**
18
- * The configuration applied when `.kb/config.yaml` is absent or omits a field. Targets the `content/`-scoped layout
19
- * the owner's stores use; with `picomatch` `dot:false`, dot-directories (`.kb`, `.git`, `.agents`) are excluded
20
- * implicitly, so the default exclude only names `node_modules`.
38
+ * How widely a store is published: `shared` is available to collaborators through a remote, `private` to nobody else.
21
39
  */
22
- export declare const defaultKbConfig: KbConfig;
40
+ export type StoreVisibility = NonNullable<z.infer<typeof configFileShape>['visibility']>;
@@ -3,8 +3,17 @@ import { CONTENT_DIR } from "../layout/index.js";
3
3
  export const configFileShape = z.object({
4
4
  targets: z.array(z.string()).optional(),
5
5
  exclude: z.array(z.string()).optional(),
6
+ visibility: z.enum(['private', 'shared']).optional(),
6
7
  });
7
8
  export const defaultKbConfig = {
8
9
  targets: [`${CONTENT_DIR}/**/*.md`],
9
10
  exclude: ['**/node_modules/**'],
11
+ visibility: 'private',
12
+ };
13
+ export function isAtLeastAsShareable(input) {
14
+ return VISIBILITY_RANK[input.target] >= VISIBILITY_RANK[input.source];
15
+ }
16
+ const VISIBILITY_RANK = {
17
+ private: 0,
18
+ shared: 1,
10
19
  };
@@ -1,4 +1,4 @@
1
- export { configFileShape, defaultKbConfig, type KbConfig } from './config-schema.js';
1
+ export { configFileShape, defaultKbConfig, isAtLeastAsShareable, type KbConfig, type StoreVisibility, } from './config-schema.js';
2
2
  export { isKbLoaderError, KbLoaderError } from './kb-loader-error.js';
3
3
  export { loadKbConfig } from './load-config.js';
4
4
  export { createNoteScopeMatcher, type NoteScopeMatcher } from './note-scope.js';
@@ -1,4 +1,4 @@
1
- export { configFileShape, defaultKbConfig } from "./config-schema.js";
1
+ export { configFileShape, defaultKbConfig, isAtLeastAsShareable, } from "./config-schema.js";
2
2
  export { isKbLoaderError, KbLoaderError } from "./kb-loader-error.js";
3
3
  export { loadKbConfig } from "./load-config.js";
4
4
  export { createNoteScopeMatcher } from "./note-scope.js";
@@ -1,12 +1,12 @@
1
1
  /**
2
- * The typed error thrown by the KB loaders (`loadKbConfig`, `loadAliases`) on a structural defect: malformed YAML or
3
- * wrong types. The `kind` discriminant lets a caller distinguish a recoverable config or alias defect from any other
2
+ * The typed error thrown by the loaders and writers of a KB's YAML files on a structural defect, such as malformed YAML
3
+ * or wrong types. The `kind` discriminant lets a caller distinguish a recoverable defect in such a file from any other
4
4
  * throw (an enumeration or detection crash) without matching on message text or relying on `instanceof` surviving a
5
5
  * bundle boundary.
6
6
  */
7
7
  export declare class KbLoaderError extends Error {
8
- /** Discriminant for narrow catch boundaries; survives serialization and bundling unlike `instanceof`. */
9
8
  readonly kind: "KbLoaderError";
9
+ /** Names the error after its class. */
10
10
  constructor(message: string, options?: ErrorOptions);
11
11
  }
12
12
  /** Type guard narrowing an unknown throw to a {@link KbLoaderError} via its `kind` discriminant. */
@@ -2,10 +2,10 @@ import type { KbRoot } from '../types.js';
2
2
  import { type KbConfig } from './config-schema.js';
3
3
  /**
4
4
  * Loads the effective check configuration for a KB root. Returns {@link defaultKbConfig} verbatim when no
5
- * `.kb/config.yaml` exists; a file present but omitting `targets` or `exclude` inherits that field's default.
5
+ * `.kb/config.yaml` exists; a file present but omitting a field inherits that field's default.
6
6
  *
7
- * Mirrors {@link loadAliases}: a single plain-object input, structural defects (malformed YAML, wrong types) throw a
8
- * {@link KbLoaderError} naming the file. I/O errors other than a missing file propagate.
7
+ * Structural defects (malformed YAML, wrong types) throw a {@link KbLoaderError} naming the file. I/O errors other than
8
+ * a missing file propagate.
9
9
  */
10
10
  export declare function loadKbConfig(input: {
11
11
  kbRoot: KbRoot;
@@ -27,10 +27,11 @@ export async function loadKbConfig(input) {
27
27
  }
28
28
  const result = configFileShape.safeParse(parsed ?? {});
29
29
  if (!result.success) {
30
- throw new KbLoaderError(`${path}: invalid config.yaml ${result.error.issues[0]?.message ?? 'unknown error'}`);
30
+ throw new KbLoaderError(`${path}: invalid config.yaml: ${result.error.issues[0]?.message ?? 'unknown error'}`);
31
31
  }
32
32
  return {
33
33
  targets: result.data.targets ?? defaultKbConfig.targets,
34
34
  exclude: result.data.exclude ?? defaultKbConfig.exclude,
35
+ visibility: result.data.visibility ?? defaultKbConfig.visibility,
35
36
  };
36
37
  }
@@ -1,5 +1,7 @@
1
1
  import type { KbConfig } from './config-schema.js';
2
- /** The note-membership predicates a {@link KbConfig} defines, all matching KB-root-relative, slash-separated paths. */
2
+ /**
3
+ * The note-membership predicates defined by a {@link KbConfig}, all matching KB-root-relative, slash-separated paths.
4
+ */
3
5
  export interface NoteScopeMatcher {
4
6
  /** True when the path matches a `targets` glob. */
5
7
  isTarget(relativePath: string): boolean;
@@ -9,12 +11,10 @@ export interface NoteScopeMatcher {
9
11
  isNote(relativePath: string): boolean;
10
12
  }
11
13
  /**
12
- * Builds the note-membership predicates for a KB config the single definition of "a note" shared by
13
- * `enumerateNotes` (which `kb check`/`kb-curate` drive) and `kb-retrieve`. Both axes match with `picomatch`'s
14
- * `dot:false`, so dot-directories (`.kb`, `.git`, `.agents`) are excluded implicitly without naming them in `exclude`.
14
+ * Builds the note-membership predicates for a KB config, the single definition of "a note". Both axes match with
15
+ * `picomatch`'s `dot:false`, so dot-directories (`.kb`, `.git`, `.agents`) are excluded implicitly without naming them
16
+ * in `exclude`.
15
17
  *
16
- * The `.md` extension gate is deliberately left to the caller: `enumerateNotes` applies its own `.endsWith('.md')`
17
- * during the walk, and `kb-retrieve` constrains ripgrep with `--glob '*.md'`. Keeping it out of `isNote` lets this
18
- * matcher govern only the `targets`/`exclude` dimension, the one place the two tools previously disagreed.
18
+ * The matcher applies `targets` and `exclude` alone: Each caller applies its own `.md` extension gate.
19
19
  */
20
20
  export declare function createNoteScopeMatcher(config: KbConfig): NoteScopeMatcher;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * What `create` did about the registry's `default_kb` pointer when registering a store:
3
- * `set` it was unset and the new store was the only KB, so the store became the default;
4
- * `unchanged` a default was already set and left untouched;
5
- * `needs-selection` it was unset but other KBs exist, so the caller should prompt for a choice.
3
+ * - `set`: It was unset and the new store was the only KB, so the store became the default.
4
+ * - `unchanged`: A default was already set and left untouched.
5
+ * - `needs-selection`: It was unset but other KBs exist; the caller should prompt for a choice.
6
6
  */
7
7
  export type DefaultKbOutcome = 'set' | 'unchanged' | 'needs-selection';
8
8
  /** A successfully created store and a record of what was written. */
@@ -13,7 +13,6 @@ export interface CreatedStore {
13
13
  storePath: string;
14
14
  /** The description written into the registry entry; absent when none was supplied. */
15
15
  description?: string;
16
- /** Whether the store was registered in the kb.yaml registry. */
17
16
  registered: boolean;
18
17
  /** Store-relative paths created by the scaffold. */
19
18
  created: readonly string[];
@@ -21,7 +20,7 @@ export interface CreatedStore {
21
20
  defaultKb?: DefaultKbOutcome;
22
21
  }
23
22
  /**
24
- * Inputs for {@link create}. `registryPath` and `description` belong to the registering form alone: a description is
23
+ * Inputs for {@link create}. `registryPath` and `description` belong to the registering form alone: A description is
25
24
  * only ever written into a registry entry, so supplying one without registering is a type error rather than a value
26
25
  * that would be silently discarded.
27
26
  */
@@ -46,8 +45,9 @@ export type CreateOutcome = {
46
45
  };
47
46
  /**
48
47
  * Scaffolds a new knowledge-base store in `targetDir` and, unless `register` is false, registers it in the kb.yaml
49
- * registry. Both preconditions an existing `.kb/`, and (when registering) an already-registered name are checked
50
- * before anything is written, so a precondition failure leaves the filesystem untouched. Genuine I/O failures
51
- * propagate.
48
+ * registry. Both preconditions, an existing `.kb/` and (when registering) an already-registered name, are checked
49
+ * before anything is written. A precondition failure leaves the filesystem untouched. A name collision that the
50
+ * registry writer detects only at write time also returns `name-registered`, after the scaffold is written. Genuine
51
+ * I/O failures propagate.
52
52
  */
53
53
  export declare function create(input: CreateInput): Promise<CreateOutcome>;
@@ -14,7 +14,5 @@ export declare const kbRegistryFileSchema: z.ZodObject<{
14
14
  readonly: z.ZodOptional<z.ZodBoolean>;
15
15
  }, z.core.$strip>>>;
16
16
  }, z.core.$strip>;
17
- /** The validated on-disk entry shape. */
18
17
  export type KbRegistryFileEntry = z.infer<typeof kbRegistryFileEntrySchema>;
19
- /** The validated on-disk file shape. */
20
18
  export type KbRegistryFile = z.infer<typeof kbRegistryFileSchema>;
@@ -1,13 +1,13 @@
1
1
  import type { KbRegistry } from '../types.js';
2
2
  /**
3
- * Load and merge the user-global (`~/.agents/kb.yaml`) and project-local
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 `~/` expands
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
- * Load the merged `kb.yaml` registry without throwing, capturing any failure message instead of presenting it.
26
+ * Loads the merged `kb.yaml` registry without throwing, capturing any failure message instead of presenting it.
27
27
  *
28
- * On success including the legitimate "no registry files present" case, which `loadKbRegistry` already returns as an
29
- * empty config `error` is absent. On a malformed file, a schema violation, an unresolvable `default_kb`, or a
30
- * non-ENOENT read failure, the result degrades to an empty config and carries the thrown message in `error`. Each
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 ${result.error.issues[0]?.message ?? 'unknown error'}`);
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, so re-registering never
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 ${existing.error.issues[0]?.message ?? 'unknown error'}`);
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}" ${result.error.issues[0]?.message ?? 'invalid entry'}`);
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 place
5
- * without discarding them. Shared by every registry writer (`registerStore`, `setDefaultKb`, `clearDefaultKb`).
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): 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 a corrupt file throws
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 the
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 ${existing.error.issues[0]?.message ?? 'unknown error'}`);
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 ${existing.error.issues[0]?.message ?? 'unknown error'}`);
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 ${result.error.issues[0]?.message ?? 'invalid registry'}`);
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 genuine
5
- * error most importantly a permission denial on a path that does exist is not silently read as absence.
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, where an unreadable path should be skipped rather than
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 parse it into a `ParsedNote`. I/O errors (e.g. a missing file) are thrown;
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
- * Parse a note from a literal string into a `ParsedNote` carrying typed frontmatter.
11
- * Parse errors are recorded in `frontmatterRaw.parseError`, never thrown the rule layer decides how to report them.
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
- * Returns every path git accounts for under `root` as an NFC-normalized set of root-relative, slash-separated paths:
3
- * tracked files, plus untracked ones that no ignore rule covers. Returns `undefined` where git holds no opinion,
4
- * because `root` lies outside a working tree or git cannot be run at all, so a caller keeps its own scope instead of
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` 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 where an ignore rule matches it, since every clone still sees it. That is why
8
- * the scope unions the two `ls-files` forms rather than asking `git check-ignore`, which would report it ignored.
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 where `readdir` reports NFD on macOS, and comparing the two forms
11
- * unnormalized drops every path whose name carries a combining mark.
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 carrying git's own stderr. A git that cannot be
11
- * spawned fails the same way as one that exits non-zero, so both reach the caller as a single "git did not answer".
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. Sits at the store root, where every tool that reads it looks. */
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
- /** The formatting configuration. Sits at the store root rather than under `.kb/`, because Prettier discovers it there. */
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;
@@ -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]}" replace with ~/`,
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 use canonical form "${canonical}"`,
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 where a store's assertion folders and its declared taxonomy disagree: `taxonomy.undeclared` for a folder
11
- * holding notes that no domain declares, `taxonomy.unused` for a declared domain holding no note at or beneath it, and
12
- * `taxonomy.orphan` for a declared domain whose parent is undeclared. All are warnings, so drift is reported without
13
- * failing the run, and all are vault-scoped, so a run narrowed to selected notes still sees them.
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. A store that has not adopted a taxonomy is therefore silent rather than reporting every folder it owns.
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
- * The observed structure comes from the enumerated notes' own paths rather than a directory listing, so the rules add
19
- * no filesystem traversal and see exactly the notes the run's `targets` and `exclude` admitted.
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}" holds notes but no domain declares it`));
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 holds no notes`));
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
- /** The outcome of splitting a note: its frontmatter field map, its body, the body's start line, and any parse error. */
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 callers can still surface it.
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 output
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 carry, ordered lowest to highest. */
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}: the two spellings of "no session" have a
35
- * single representation on both edges of the module, so no record can reacquire the empty field on a write.
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>;