@williamthorsen/kb 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -7
- package/dist/esm/check/check.d.ts +24 -0
- package/dist/esm/check/enumerate.d.ts +37 -0
- package/dist/esm/check/enumerate.js +12 -4
- package/dist/esm/check/glob-segments.d.ts +6 -0
- package/dist/esm/cli/commands/check.d.ts +22 -0
- package/dist/esm/cli/commands/create.d.ts +19 -1
- package/dist/esm/cli/commands/create.js +2 -0
- package/dist/esm/cli/commands/scaffold.d.ts +30 -0
- package/dist/esm/cli/commands/scaffold.js +108 -0
- package/dist/esm/cli/commands/set-default.d.ts +15 -0
- package/dist/esm/cli/commands/taxonomy.d.ts +18 -0
- package/dist/esm/cli/format.d.ts +21 -0
- package/dist/esm/cli/parse-flag-value.d.ts +5 -0
- package/dist/esm/cli/resolve-store.d.ts +12 -0
- package/dist/esm/cli/run.d.ts +9 -1
- package/dist/esm/cli/run.js +5 -0
- package/dist/esm/cli/select-kb-prompt.d.ts +18 -0
- package/dist/esm/cli/targeting/resolve-changed-paths.d.ts +10 -0
- package/dist/esm/cli/targeting/resolve-changed-paths.js +7 -33
- package/dist/esm/cli/targeting/select-notes.d.ts +13 -0
- package/dist/esm/cli/targeting/select-notes.js +4 -3
- package/dist/esm/config/config-schema.d.ts +12 -0
- package/dist/esm/config/kb-loader-error.d.ts +8 -0
- package/dist/esm/config/load-config.d.ts +7 -0
- package/dist/esm/config/note-scope.d.ts +13 -0
- package/dist/esm/create/create.d.ts +25 -0
- package/dist/esm/create/create.js +8 -12
- package/dist/esm/create/index.d.ts +0 -1
- package/dist/esm/create/index.js +0 -1
- package/dist/esm/discovery/find-kb-root.d.ts +4 -0
- package/dist/esm/discovery/kb-registry-schema.d.ts +4 -0
- package/dist/esm/discovery/load-registry.d.ts +23 -0
- package/dist/esm/discovery/register-store.d.ts +11 -0
- package/dist/esm/discovery/registry-document.d.ts +5 -0
- package/dist/esm/discovery/set-default-kb.d.ts +12 -0
- package/dist/esm/filesystem/exists.d.ts +13 -0
- package/dist/esm/filesystem/write-atomic.d.ts +5 -0
- package/dist/esm/frontmatter/parse-note.d.ts +9 -0
- package/dist/esm/git/list-git-scope.d.ts +15 -0
- package/dist/esm/git/list-git-scope.js +22 -0
- package/dist/esm/git/run-git.d.ts +17 -0
- package/dist/esm/git/run-git.js +22 -0
- package/dist/esm/layout/index.d.ts +1 -1
- package/dist/esm/layout/index.js +1 -1
- package/dist/esm/layout/store-layout.d.ts +20 -0
- package/dist/esm/layout/store-layout.js +2 -0
- package/dist/esm/lints/paths.d.ts +7 -0
- package/dist/esm/lints/tag-alias.d.ts +6 -0
- package/dist/esm/lints/taxonomy.d.ts +15 -0
- package/dist/esm/note-io/field-validators.d.ts +9 -0
- package/dist/esm/note-io/read-note.d.ts +7 -0
- package/dist/esm/note-io/write-note.d.ts +9 -0
- package/dist/esm/note-io/yaml-fields.d.ts +5 -0
- package/dist/esm/records/assertion.d.ts +4 -0
- package/dist/esm/records/event.d.ts +12 -0
- package/dist/esm/scaffold/index.d.ts +2 -0
- package/dist/esm/scaffold/index.js +2 -0
- package/dist/esm/scaffold/render-seeds.d.ts +16 -0
- package/dist/esm/scaffold/render-seeds.js +63 -0
- package/dist/esm/scaffold/scaffold.d.ts +23 -0
- package/dist/esm/scaffold/scaffold.js +35 -0
- package/dist/esm/tags/canonicalize.d.ts +8 -0
- package/dist/esm/tags/load-aliases.d.ts +10 -0
- package/dist/esm/taxonomy/domain-paths.d.ts +10 -0
- package/dist/esm/taxonomy/load-taxonomy.d.ts +8 -0
- package/dist/esm/taxonomy/taxonomy-schema.d.ts +19 -0
- package/dist/esm/taxonomy/write-taxonomy.d.ts +20 -0
- package/dist/esm/type-guards.d.ts +13 -0
- package/dist/esm/types.d.ts +41 -0
- package/dist/esm/vault-integrity/build-vault-index.d.ts +11 -0
- package/dist/esm/vault-integrity/check-vault-integrity.d.ts +13 -0
- package/dist/esm/vault-integrity/wikilink-parse.d.ts +22 -0
- package/package.json +14 -5
- package/dist/esm/create/render-seeds.d.ts +0 -2
- package/dist/esm/create/render-seeds.js +0 -29
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import type { CommandOutput } from './check.js';
|
|
2
|
+
/** Usage text for `kb taxonomy`. */
|
|
2
3
|
export declare const TAXONOMY_HELP = "Usage: kb taxonomy init [options]\n\nDerive a starting taxonomy from the notes a knowledge base already holds, so a\ntaxonomy can be introduced to a populated store without every folder reporting\nas undeclared. Every folder holding notes is declared, along with each of its\nancestors, under \"provisional:\" with no description: the command cannot invent\ndescriptions, and provisional already means \"declared, not yet reviewed\".\n\nOptions:\n --kb <name> Use the named store from the kb.yaml registry. Without it, the\n nearest ancestor .kb/ directory is used.\n --merge Add only the domains an existing taxonomy does not declare.\n Without it, a store that already has a taxonomy is left\n untouched.\n -h, --help Show this help.\n\nExit codes:\n 0 the taxonomy was written, or already declared every derived domain\n 2 usage error, unresolvable store, a store marked readonly in kb.yaml,\n malformed config or taxonomy, or an existing taxonomy without --merge\n";
|
|
4
|
+
/**
|
|
5
|
+
* Runs `kb taxonomy`: parses options, resolves the store, derives the domains its notes imply, and declares them.
|
|
6
|
+
*
|
|
7
|
+
* The derivation reads the same enumeration `kb check` does, so a store back-filled by this command reports no
|
|
8
|
+
* taxonomy drift. A store the registry marks `readonly` is refused, matching `kb-curate --apply`. A malformed
|
|
9
|
+
* `.kb/config.yaml` or `.kb/taxonomy.yaml` surfaces as a `KbLoaderError` and maps to exit 2; any other error
|
|
10
|
+
* propagates to the caller as a real crash.
|
|
11
|
+
*/
|
|
3
12
|
export declare function runTaxonomy(input: {
|
|
4
13
|
argv: readonly string[];
|
|
5
14
|
cwd: string;
|
|
6
15
|
home?: string;
|
|
7
16
|
}): Promise<CommandOutput>;
|
|
17
|
+
/** Parsed `kb taxonomy` options. */
|
|
8
18
|
interface TaxonomyOptions {
|
|
19
|
+
/** The subcommand to run, or `null` when none was given. */
|
|
9
20
|
subcommand: 'init' | null;
|
|
21
|
+
/** Explicit store name from `--kb`, or `null` for ancestor-walk discovery. */
|
|
10
22
|
kb: string | null;
|
|
23
|
+
/** Whether `--merge` was supplied. */
|
|
11
24
|
merge: boolean;
|
|
25
|
+
/** Whether `--help`/`-h` was supplied. */
|
|
12
26
|
help: boolean;
|
|
13
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Parses `kb taxonomy` options. `--kb` accepts both the space (`--kb x`) and equals (`--kb=x`) forms. An unknown flag,
|
|
30
|
+
* an unknown subcommand, a missing `--kb` value, or a second subcommand throws with a usage-style message.
|
|
31
|
+
*/
|
|
14
32
|
export declare function parseTaxonomyArgs(argv: readonly string[]): TaxonomyOptions;
|
|
15
33
|
export {};
|
package/dist/esm/cli/format.d.ts
CHANGED
|
@@ -1,24 +1,45 @@
|
|
|
1
1
|
import type { Finding } from '../types.js';
|
|
2
|
+
/** A resolved store's identity, surfaced in both human and JSON output. */
|
|
2
3
|
export interface StoreRef {
|
|
4
|
+
/** The store's display name, or `null` for a `.kb/`-discovered store with no registry entry. */
|
|
3
5
|
name: string | null;
|
|
6
|
+
/** Absolute path to the store root. */
|
|
4
7
|
path: string;
|
|
5
8
|
}
|
|
9
|
+
/** Severity-partitioned counts over a finding set, plus the note total checked. */
|
|
6
10
|
export interface CheckSummary {
|
|
11
|
+
/** Notes enumerated and checked. */
|
|
7
12
|
notes: number;
|
|
13
|
+
/** Total findings. */
|
|
8
14
|
total: number;
|
|
15
|
+
/** Findings with `severity: 'error'`. */
|
|
9
16
|
errors: number;
|
|
17
|
+
/** Findings with `severity: 'warning'`. */
|
|
10
18
|
warnings: number;
|
|
11
19
|
}
|
|
20
|
+
/** Which selection produced a report, controlling the wording of the zero-match line. */
|
|
12
21
|
export type CheckScope = 'vault' | 'patterns' | 'vs';
|
|
22
|
+
/**
|
|
23
|
+
* Renders the default human output. Findings are grouped by file in path order, each line reading
|
|
24
|
+
* `<severity> <rule> (line N): message`. A clean run (notes checked, no findings) prints `✓ no findings (N notes
|
|
25
|
+
* checked)`; a run that checked nothing prints a zero-match line worded for its `scope` (naming the config targets for
|
|
26
|
+
* a whole-vault run, and a scope-appropriate line for a targeted one) without the `✓`, since no check ran.
|
|
27
|
+
*
|
|
28
|
+
* A run can check no notes and still carry vault-scoped findings, which describe the store rather than any note. The
|
|
29
|
+
* zero-match line then heads the report instead of replacing it: it explains why no note was checked, and the findings
|
|
30
|
+
* follow.
|
|
31
|
+
*/
|
|
13
32
|
export declare function formatHuman(input: {
|
|
14
33
|
summary: CheckSummary;
|
|
15
34
|
findings: readonly Finding[];
|
|
16
35
|
targets: readonly string[];
|
|
17
36
|
scope: CheckScope;
|
|
18
37
|
}): string;
|
|
38
|
+
/** Renders the `--json` payload: store identity, summary counts, and the raw findings. */
|
|
19
39
|
export declare function formatJson(input: {
|
|
20
40
|
store: StoreRef;
|
|
21
41
|
summary: CheckSummary;
|
|
22
42
|
findings: readonly Finding[];
|
|
23
43
|
}): string;
|
|
44
|
+
/** Partitions a finding set into total, error, and warning counts over `noteCount` notes. */
|
|
24
45
|
export declare function summarize(findings: readonly Finding[], noteCount: number): CheckSummary;
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
/** Reads the value from an inline flag (`--kb=x`), throwing when it is empty. */
|
|
1
2
|
export declare function takeInlineValue(arg: string, prefix: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Reads the value after a space-form flag (`--kb x`), throwing when it is missing, empty, or looks like another flag.
|
|
5
|
+
* An empty value is rejected on both forms, so the two spellings of a flag accept the same set of values.
|
|
6
|
+
*/
|
|
2
7
|
export declare function takeValue(argv: readonly string[], index: number, flag: string): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StoreRef } from './format.js';
|
|
2
|
+
/** The store-resolution outcome: a resolved store, or a categorical failure message for exit 2. */
|
|
2
3
|
export type ResolveStoreOutcome = {
|
|
3
4
|
ok: true;
|
|
4
5
|
store: StoreRef;
|
|
@@ -7,6 +8,17 @@ export type ResolveStoreOutcome = {
|
|
|
7
8
|
ok: false;
|
|
8
9
|
message: string;
|
|
9
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Resolves the store a command runs against. An explicit `--kb <name>` is looked up in the merged registry
|
|
13
|
+
* (`tryLoadKbRegistry` with `projectDir: cwd`, so project-local `.agents/kb.yaml` entries join the user-global
|
|
14
|
+
* registry); without a flag, the nearest ancestor `.kb/` directory is used. An unknown `--kb` name or a missing `.kb/`
|
|
15
|
+
* fails for exit 2.
|
|
16
|
+
*
|
|
17
|
+
* The registry's `readonly` flag is reported rather than enforced: a command that writes into the store refuses on it,
|
|
18
|
+
* and a read-only command ignores it. It is kept off {@link StoreRef}, which carries the identity a report renders. A
|
|
19
|
+
* discovered store is cross-referenced against the registry by path, so a vault marked readonly is reported as such
|
|
20
|
+
* however it was named; one with no registry entry has no metadata to consult and is reported writable.
|
|
21
|
+
*/
|
|
10
22
|
export declare function resolveStore(input: {
|
|
11
23
|
explicitKb: string | null;
|
|
12
24
|
cwd: string;
|
package/dist/esm/cli/run.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { type CommandOutput } from './commands/check.js';
|
|
2
2
|
import type { SelectKbPrompt } from './select-kb-prompt.js';
|
|
3
|
-
|
|
3
|
+
/** Top-level usage text for the `kb` bin. */
|
|
4
|
+
export declare const HELP = "Usage: kb <command> [options]\n\nCommands:\n check Validate a knowledge base, optionally scoped to selected notes.\n create Scaffold a new knowledge base and register it in the kb.yaml registry.\n scaffold Write into an existing knowledge base any canonical file that it lacks.\n set-default Set, clear, or choose the default knowledge base.\n taxonomy Derive a knowledge base's taxonomy from the notes it already holds.\n\nRun \"kb <command> --help\" for command options.\n";
|
|
5
|
+
/**
|
|
6
|
+
* Dispatches a `kb` subcommand and returns its {@link CommandOutput} without touching `process`, so tests drive the
|
|
7
|
+
* command directly. `check`, `create`, `scaffold`, `set-default`, and `taxonomy` are the subcommands; a bare
|
|
8
|
+
* invocation or `--help`/`-h` prints top-level usage (exit 0), and an unknown command prints usage to stderr (exit 2).
|
|
9
|
+
* The optional `selectKb` picker is forwarded to `set-default`'s interactive form and to `create`'s ambiguous
|
|
10
|
+
* default-KB prompt; `cli/index.ts` supplies it only when stdin is a TTY.
|
|
11
|
+
*/
|
|
4
12
|
export declare function run(input: {
|
|
5
13
|
argv: readonly string[];
|
|
6
14
|
cwd: string;
|
package/dist/esm/cli/run.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runCheck } from "./commands/check.js";
|
|
2
2
|
import { runCreate } from "./commands/create.js";
|
|
3
|
+
import { runScaffold } from "./commands/scaffold.js";
|
|
3
4
|
import { runSetDefault } from "./commands/set-default.js";
|
|
4
5
|
import { runTaxonomy } from "./commands/taxonomy.js";
|
|
5
6
|
export const HELP = `Usage: kb <command> [options]
|
|
@@ -7,6 +8,7 @@ export const HELP = `Usage: kb <command> [options]
|
|
|
7
8
|
Commands:
|
|
8
9
|
check Validate a knowledge base, optionally scoped to selected notes.
|
|
9
10
|
create Scaffold a new knowledge base and register it in the kb.yaml registry.
|
|
11
|
+
scaffold Write into an existing knowledge base any canonical file that it lacks.
|
|
10
12
|
set-default Set, clear, or choose the default knowledge base.
|
|
11
13
|
taxonomy Derive a knowledge base's taxonomy from the notes it already holds.
|
|
12
14
|
|
|
@@ -29,6 +31,9 @@ export async function run(input) {
|
|
|
29
31
|
...(input.selectKb !== undefined && { selectKb: input.selectKb }),
|
|
30
32
|
});
|
|
31
33
|
}
|
|
34
|
+
if (command === 'scaffold') {
|
|
35
|
+
return runScaffold({ argv: rest, cwd: input.cwd, ...(input.home !== undefined && { home: input.home }) });
|
|
36
|
+
}
|
|
32
37
|
if (command === 'taxonomy') {
|
|
33
38
|
return runTaxonomy({ argv: rest, cwd: input.cwd, ...(input.home !== undefined && { home: input.home }) });
|
|
34
39
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { KbRegistryEntry } from '../types.js';
|
|
2
|
+
/** The user's choice from the interactive default-KB picker. */
|
|
2
3
|
export type SelectKbChoice = {
|
|
3
4
|
kind: 'kb';
|
|
4
5
|
index: number;
|
|
@@ -7,10 +8,27 @@ export type SelectKbChoice = {
|
|
|
7
8
|
} | {
|
|
8
9
|
kind: 'cancel';
|
|
9
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Presents the registered KBs and resolves the user's choice. Injected into `runSetDefault` so the dispatcher itself
|
|
13
|
+
* stays free of terminal I/O and remains directly testable; the real implementation is {@link readlineSelectKbPrompt}.
|
|
14
|
+
*/
|
|
10
15
|
export type SelectKbPrompt = (input: {
|
|
11
16
|
entries: readonly KbRegistryEntry[];
|
|
12
17
|
currentDefaultName?: string;
|
|
13
18
|
}) => Promise<SelectKbChoice>;
|
|
19
|
+
/**
|
|
20
|
+
* Renders the numbered selection list: each registered KB (marking the current default) followed by a trailing
|
|
21
|
+
* `(none)` option numbered `entries.length + 1`. When no default is set, `(none)` carries the current marker instead.
|
|
22
|
+
*/
|
|
14
23
|
export declare function formatKbSelection(entries: readonly KbRegistryEntry[], currentDefaultName?: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Maps a trimmed answer to a {@link SelectKbChoice}: an empty line cancels, `1..kbCount` selects that KB, and
|
|
26
|
+
* `kbCount + 1` clears the default. Any other value returns `null`, signalling the caller to re-prompt.
|
|
27
|
+
*/
|
|
15
28
|
export declare function parseSelection(answer: string, kbCount: number): SelectKbChoice | null;
|
|
29
|
+
/**
|
|
30
|
+
* A readline-backed {@link SelectKbPrompt}: it prints the selection list, reads a line, and re-prompts until the answer
|
|
31
|
+
* resolves to a choice. This is the feature's sole interactive seam — `cli/index.ts` supplies it only when stdin is a
|
|
32
|
+
* TTY, and other commands (e.g. `kb create`) can reuse it for their own interactive default-KB selection.
|
|
33
|
+
*/
|
|
16
34
|
export declare const readlineSelectKbPrompt: SelectKbPrompt;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** The outcome of resolving a `--vs` ref to its changed-note paths: the store-relative paths, or a failure message. */
|
|
1
2
|
export type ChangedPathsResult = {
|
|
2
3
|
ok: true;
|
|
3
4
|
paths: string[];
|
|
@@ -5,6 +6,15 @@ export type ChangedPathsResult = {
|
|
|
5
6
|
ok: false;
|
|
6
7
|
message: string;
|
|
7
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the notes changed between `ref` and the working tree to store-relative paths.
|
|
11
|
+
*
|
|
12
|
+
* The change set is `git diff` of the working tree against `merge-base(ref, HEAD)` with `--diff-filter=AMR`, so it
|
|
13
|
+
* follows renames (reporting the destination), includes uncommitted edits to tracked files, and excludes deletions.
|
|
14
|
+
* Git emits toplevel-relative paths and resolves symlinks, so each path is rebased onto the real store root — a no-op
|
|
15
|
+
* when the store is the repository root, correct when it is nested. A git failure (unknown ref, not a repository)
|
|
16
|
+
* returns `{ ok: false }` for the caller to surface as a usage error rather than throwing.
|
|
17
|
+
*/
|
|
8
18
|
export declare function resolveChangedPaths(input: {
|
|
9
19
|
storeRoot: string;
|
|
10
20
|
ref: string;
|
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
1
|
import { realpathSync } from 'node:fs';
|
|
3
2
|
import { join, relative, sep } from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import { isRecord } from "../../type-guards.js";
|
|
3
|
+
import { runGit } from "../../git/run-git.js";
|
|
6
4
|
export function resolveChangedPaths(input) {
|
|
7
5
|
const { storeRoot, ref } = input;
|
|
8
|
-
const mergeBase =
|
|
6
|
+
const mergeBase = runGit({ cwd: storeRoot, args: ['merge-base', ref, 'HEAD'] });
|
|
9
7
|
if (!mergeBase.ok) {
|
|
10
8
|
return { ok: false, message: `could not resolve ref "${ref}": ${mergeBase.message}` };
|
|
11
9
|
}
|
|
12
|
-
const toplevel =
|
|
10
|
+
const toplevel = runGit({ cwd: storeRoot, args: ['rev-parse', '--show-toplevel'] });
|
|
13
11
|
if (!toplevel.ok) {
|
|
14
12
|
return { ok: false, message: toplevel.message };
|
|
15
13
|
}
|
|
16
|
-
const diff =
|
|
17
|
-
|
|
18
|
-
'--name-only',
|
|
19
|
-
|
|
20
|
-
'--diff-filter=AMR',
|
|
21
|
-
'--find-renames',
|
|
22
|
-
mergeBase.stdout.trim(),
|
|
23
|
-
'--',
|
|
24
|
-
'.',
|
|
25
|
-
]);
|
|
14
|
+
const diff = runGit({
|
|
15
|
+
cwd: storeRoot,
|
|
16
|
+
args: ['diff', '--name-only', '-z', '--diff-filter=AMR', '--find-renames', mergeBase.stdout.trim(), '--', '.'],
|
|
17
|
+
});
|
|
26
18
|
if (!diff.ok) {
|
|
27
19
|
return { ok: false, message: diff.message };
|
|
28
20
|
}
|
|
@@ -34,21 +26,3 @@ export function resolveChangedPaths(input) {
|
|
|
34
26
|
.map((entry) => relative(realStoreRoot, join(repoRoot, entry)).split(sep).join('/'));
|
|
35
27
|
return { ok: true, paths };
|
|
36
28
|
}
|
|
37
|
-
function extractGitErrorMessage(error) {
|
|
38
|
-
if (isRecord(error) && typeof error.stderr === 'string' && error.stderr.trim() !== '') {
|
|
39
|
-
return error.stderr.trim();
|
|
40
|
-
}
|
|
41
|
-
return describeError(error);
|
|
42
|
-
}
|
|
43
|
-
function tryGit(storeRoot, args) {
|
|
44
|
-
try {
|
|
45
|
-
const stdout = execFileSync('git', ['-C', storeRoot, ...args], {
|
|
46
|
-
encoding: 'utf8',
|
|
47
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
|
-
});
|
|
49
|
-
return { ok: true, stdout };
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
return { ok: false, message: extractGitErrorMessage(error) };
|
|
53
|
-
}
|
|
54
|
-
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import type { EnumeratedNote } from '../../check/enumerate.js';
|
|
2
|
+
/** The outcome of resolving selection patterns against an enumerated vault. */
|
|
2
3
|
export interface SelectionResult {
|
|
4
|
+
/** Notes matched by at least one pattern, in enumeration order, deduplicated. */
|
|
3
5
|
selected: EnumeratedNote[];
|
|
6
|
+
/** Patterns that matched no validatable note and are backed by no real on-disk path — likely typos. */
|
|
4
7
|
unmatched: string[];
|
|
5
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolves selection patterns against an already-enumerated note set, returning the matched notes plus the patterns
|
|
11
|
+
* that matched nothing real.
|
|
12
|
+
*
|
|
13
|
+
* Each pattern is matched as a `picomatch` glob against the notes' store-relative paths, so the store's
|
|
14
|
+
* `targets`/`exclude` filtering is inherited and a quoted glob behaves the same as a shell-expanded one. A bare
|
|
15
|
+
* directory expands to its subtree. A pattern matching no note is reported in `unmatched` unless a real on-disk path
|
|
16
|
+
* backs it (a non-validatable file such as a README, an excluded subtree, or an empty directory), in which case it is
|
|
17
|
+
* dropped silently — distinguishing a typo from a legitimately out-of-scope target.
|
|
18
|
+
*/
|
|
6
19
|
export declare function selectNotes(input: {
|
|
7
20
|
notes: readonly EnumeratedNote[];
|
|
8
21
|
patterns: readonly string[];
|
|
@@ -5,13 +5,14 @@ import { leadingLiteralPrefix } from "../../check/glob-segments.js";
|
|
|
5
5
|
import { isEnoent } from "../../type-guards.js";
|
|
6
6
|
export async function selectNotes(input) {
|
|
7
7
|
const { notes, storeRoot } = input;
|
|
8
|
-
const
|
|
8
|
+
const pathsByComposedForm = new Map(notes.map((entry) => [entry.relativePath.normalize('NFC'), entry.relativePath]));
|
|
9
9
|
const selectedPaths = new Set();
|
|
10
10
|
const unmatched = [];
|
|
11
11
|
for (const rawPattern of input.patterns) {
|
|
12
12
|
const pattern = normalizePattern(rawPattern);
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const exactPath = pathsByComposedForm.get(pattern.normalize('NFC'));
|
|
14
|
+
if (exactPath !== undefined) {
|
|
15
|
+
selectedPaths.add(exactPath);
|
|
15
16
|
continue;
|
|
16
17
|
}
|
|
17
18
|
const direct = matchPaths(notes, pattern);
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The on-disk `.kb/config.yaml` shape. Both fields are optional so a file may override only the dimension it cares
|
|
4
|
+
* about; an absent field falls back to {@link defaultKbConfig}.
|
|
5
|
+
*/
|
|
2
6
|
export declare const configFileShape: z.ZodObject<{
|
|
3
7
|
targets: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
4
8
|
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
5
9
|
}, z.core.$strip>;
|
|
10
|
+
/** The effective check configuration: the glob target set and the glob exclude set. */
|
|
6
11
|
export interface KbConfig {
|
|
12
|
+
/** Glob patterns (slash-separated, kbRoot-relative) selecting which notes `check` enumerates. */
|
|
7
13
|
targets: readonly string[];
|
|
14
|
+
/** Glob patterns excluded from enumeration even when a target matches. */
|
|
8
15
|
exclude: readonly string[];
|
|
9
16
|
}
|
|
17
|
+
/**
|
|
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`.
|
|
21
|
+
*/
|
|
10
22
|
export declare const defaultKbConfig: KbConfig;
|
|
@@ -1,5 +1,13 @@
|
|
|
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
|
|
4
|
+
* throw (an enumeration or detection crash) without matching on message text or relying on `instanceof` surviving a
|
|
5
|
+
* bundle boundary.
|
|
6
|
+
*/
|
|
1
7
|
export declare class KbLoaderError extends Error {
|
|
8
|
+
/** Discriminant for narrow catch boundaries; survives serialization and bundling unlike `instanceof`. */
|
|
2
9
|
readonly kind: "KbLoaderError";
|
|
3
10
|
constructor(message: string, options?: ErrorOptions);
|
|
4
11
|
}
|
|
12
|
+
/** Type guard narrowing an unknown throw to a {@link KbLoaderError} via its `kind` discriminant. */
|
|
5
13
|
export declare function isKbLoaderError(error: unknown): error is KbLoaderError;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { KbRoot } from '../types.js';
|
|
2
2
|
import { type KbConfig } from './config-schema.js';
|
|
3
|
+
/**
|
|
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.
|
|
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.
|
|
9
|
+
*/
|
|
3
10
|
export declare function loadKbConfig(input: {
|
|
4
11
|
kbRoot: KbRoot;
|
|
5
12
|
}): Promise<KbConfig>;
|
|
@@ -1,7 +1,20 @@
|
|
|
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
|
export interface NoteScopeMatcher {
|
|
4
|
+
/** True when the path matches a `targets` glob. */
|
|
3
5
|
isTarget(relativePath: string): boolean;
|
|
6
|
+
/** True when the path matches an `exclude` glob. */
|
|
4
7
|
isExcluded(relativePath: string): boolean;
|
|
8
|
+
/** True when the path is a note: matched by `targets` and not `exclude`d. The `.md` gate is the caller's. */
|
|
5
9
|
isNote(relativePath: string): boolean;
|
|
6
10
|
}
|
|
11
|
+
/**
|
|
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`.
|
|
15
|
+
*
|
|
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.
|
|
19
|
+
*/
|
|
7
20
|
export declare function createNoteScopeMatcher(config: KbConfig): NoteScopeMatcher;
|
|
@@ -1,12 +1,30 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
6
|
+
*/
|
|
1
7
|
export type DefaultKbOutcome = 'set' | 'unchanged' | 'needs-selection';
|
|
8
|
+
/** A successfully created store and a record of what was written. */
|
|
2
9
|
export interface CreatedStore {
|
|
10
|
+
/** The store's registry name (the directory's base name unless overridden). */
|
|
3
11
|
name: string;
|
|
12
|
+
/** Absolute path to the store root. */
|
|
4
13
|
storePath: string;
|
|
14
|
+
/** The description written into the registry entry; absent when none was supplied. */
|
|
5
15
|
description?: string;
|
|
16
|
+
/** Whether the store was registered in the kb.yaml registry. */
|
|
6
17
|
registered: boolean;
|
|
18
|
+
/** Store-relative paths created by the scaffold. */
|
|
7
19
|
created: readonly string[];
|
|
20
|
+
/** What happened to the registry's `default_kb` pointer; absent when the store was not registered. */
|
|
8
21
|
defaultKb?: DefaultKbOutcome;
|
|
9
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Inputs for {@link create}. `registryPath` and `description` belong to the registering form alone: a description is
|
|
25
|
+
* only ever written into a registry entry, so supplying one without registering is a type error rather than a value
|
|
26
|
+
* that would be silently discarded.
|
|
27
|
+
*/
|
|
10
28
|
export type CreateInput = {
|
|
11
29
|
targetDir: string;
|
|
12
30
|
name?: string;
|
|
@@ -17,6 +35,7 @@ export type CreateInput = {
|
|
|
17
35
|
registryPath: string;
|
|
18
36
|
description?: string;
|
|
19
37
|
});
|
|
38
|
+
/** The outcome of a {@link create} call: a created store, or a categorical precondition failure. */
|
|
20
39
|
export type CreateOutcome = {
|
|
21
40
|
ok: true;
|
|
22
41
|
created: CreatedStore;
|
|
@@ -25,4 +44,10 @@ export type CreateOutcome = {
|
|
|
25
44
|
reason: 'kb-exists' | 'name-registered';
|
|
26
45
|
message: string;
|
|
27
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* 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.
|
|
52
|
+
*/
|
|
28
53
|
export declare function create(input: CreateInput): Promise<CreateOutcome>;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { basename, join, resolve } from 'node:path';
|
|
1
|
+
import { basename, resolve } from 'node:path';
|
|
3
2
|
import { loadKbRegistry } from "../discovery/load-registry.js";
|
|
4
3
|
import { registerStore } from "../discovery/register-store.js";
|
|
5
4
|
import { setDefaultKb } from "../discovery/set-default-kb.js";
|
|
6
5
|
import { pathExists } from "../filesystem/exists.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { KB_DIR, resolveKbDir } from "../layout/index.js";
|
|
7
|
+
import { scaffold } from "../scaffold/scaffold.js";
|
|
9
8
|
export async function create(input) {
|
|
10
9
|
const storePath = resolve(input.targetDir);
|
|
11
10
|
const name = input.name ?? basename(storePath);
|
|
@@ -13,7 +12,7 @@ export async function create(input) {
|
|
|
13
12
|
return { ok: false, reason: 'kb-exists', message: `a ${KB_DIR}/ store already exists at ${storePath}` };
|
|
14
13
|
}
|
|
15
14
|
if (!input.register) {
|
|
16
|
-
const created = await
|
|
15
|
+
const created = await writeCanonicalSet(storePath);
|
|
17
16
|
return { ok: true, created: { name, storePath, registered: false, created } };
|
|
18
17
|
}
|
|
19
18
|
const { registryPath } = input;
|
|
@@ -21,7 +20,7 @@ export async function create(input) {
|
|
|
21
20
|
if (before.entries.some((entry) => entry.name === name)) {
|
|
22
21
|
return { ok: false, reason: 'name-registered', message: nameRegisteredMessage(name, registryPath) };
|
|
23
22
|
}
|
|
24
|
-
const created = await
|
|
23
|
+
const created = await writeCanonicalSet(storePath);
|
|
25
24
|
const described = input.description !== undefined && { description: input.description };
|
|
26
25
|
const result = await registerStore({ registryPath, name, storePath, ...described });
|
|
27
26
|
if (result.status === 'already-present') {
|
|
@@ -43,10 +42,7 @@ async function ensureDefaultKb(input) {
|
|
|
43
42
|
function nameRegisteredMessage(name, registryPath) {
|
|
44
43
|
return `a store named "${name}" is already registered in ${registryPath}`;
|
|
45
44
|
}
|
|
46
|
-
async function
|
|
47
|
-
await
|
|
48
|
-
|
|
49
|
-
await writeFile(join(storePath, ALIASES_FILE), renderAliasesSeed(), 'utf8');
|
|
50
|
-
await mkdir(resolveEventsDir(storePath), { recursive: true });
|
|
51
|
-
return [CONFIG_FILE, ALIASES_FILE, `${CONTENT_DIR}/`, `${EVENTS_DIR}/`];
|
|
45
|
+
async function writeCanonicalSet(storePath) {
|
|
46
|
+
const entries = await scaffold({ storePath });
|
|
47
|
+
return entries.filter((entry) => entry.action !== 'present').map((entry) => entry.path);
|
|
52
48
|
}
|
package/dist/esm/create/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { KbRoot } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Walks up the directory tree from `startDir` & returns the first ancestor containing a `.kb/` directory as a `KbRoot`.
|
|
4
|
+
* Returns `null` when the walk reaches the filesystem root without a hit.
|
|
5
|
+
*/
|
|
2
6
|
export declare function findKbRoot(input: {
|
|
3
7
|
startDir: string;
|
|
4
8
|
}): Promise<KbRoot | null>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
/** Schema for a single KB entry as written in `kb.yaml`. */
|
|
2
3
|
export declare const kbRegistryFileEntrySchema: z.ZodObject<{
|
|
3
4
|
path: z.ZodString;
|
|
4
5
|
description: z.ZodOptional<z.ZodString>;
|
|
5
6
|
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
6
7
|
}, z.core.$strip>;
|
|
8
|
+
/** Schema for the full `kb.yaml` file. */
|
|
7
9
|
export declare const kbRegistryFileSchema: z.ZodObject<{
|
|
8
10
|
default_kb: z.ZodOptional<z.ZodString>;
|
|
9
11
|
kbs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -12,5 +14,7 @@ export declare const kbRegistryFileSchema: z.ZodObject<{
|
|
|
12
14
|
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
13
15
|
}, z.core.$strip>>>;
|
|
14
16
|
}, z.core.$strip>;
|
|
17
|
+
/** The validated on-disk entry shape. */
|
|
15
18
|
export type KbRegistryFileEntry = z.infer<typeof kbRegistryFileEntrySchema>;
|
|
19
|
+
/** The validated on-disk file shape. */
|
|
16
20
|
export type KbRegistryFile = z.infer<typeof kbRegistryFileSchema>;
|
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import type { KbRegistry } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load and merge the user-global (`~/.agents/kb.yaml`) and project-local
|
|
4
|
+
* (`.agents/kb.yaml`) KB registries into a normalized `KbRegistry`.
|
|
5
|
+
*
|
|
6
|
+
* Project entries replace user entries by name on collision and append new names. The top-level `default_kb`
|
|
7
|
+
* pointer resolves by name against the merged entries (the project's value overriding the user's); the resolved
|
|
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.
|
|
11
|
+
* Malformed YAML, a structural defect, or a `default_kb` that names no registered KB throw.
|
|
12
|
+
*/
|
|
2
13
|
export declare function loadKbRegistry(input?: {
|
|
3
14
|
userConfigPath?: string;
|
|
4
15
|
projectDir?: string;
|
|
5
16
|
home?: string;
|
|
6
17
|
}): Promise<KbRegistry>;
|
|
18
|
+
/** The outcome of a no-throw registry load: the resolved config plus a captured error message when loading failed. */
|
|
7
19
|
export interface KbRegistryLoadResult {
|
|
20
|
+
/** The merged registry, or an empty config when loading threw. */
|
|
8
21
|
config: KbRegistry;
|
|
22
|
+
/** The thrown error's message, present only when `loadKbRegistry` failed. */
|
|
9
23
|
error?: string;
|
|
10
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Load the merged `kb.yaml` registry without throwing, capturing any failure message instead of presenting it.
|
|
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.
|
|
33
|
+
*/
|
|
11
34
|
export declare function tryLoadKbRegistry(input?: {
|
|
12
35
|
userConfigPath?: string;
|
|
13
36
|
projectDir?: string;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
/** The outcome of a registry write. */
|
|
1
2
|
export interface RegisterStoreResult {
|
|
3
|
+
/** `added` when a new entry was written; `already-present` when an entry of the same name already existed. */
|
|
2
4
|
status: 'added' | 'already-present';
|
|
3
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Inserts a knowledge-base entry under `kbs:` in a `kb.yaml` registry, creating the file and its parent directory when
|
|
8
|
+
* absent and preserving any existing comments and formatting. Every write leaves the `kbs:` entries alphabetically
|
|
9
|
+
* 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
|
|
13
|
+
* direct caller, throws rather than being written), never producing a corrupt file.
|
|
14
|
+
*/
|
|
4
15
|
export declare function registerStore(input: {
|
|
5
16
|
registryPath: string;
|
|
6
17
|
name: string;
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { Document } from 'yaml';
|
|
2
|
+
/**
|
|
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`).
|
|
6
|
+
*/
|
|
2
7
|
export declare function loadRegistryDocument(path: string): Promise<Document>;
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
6
|
+
*/
|
|
1
7
|
export declare function clearDefaultKb(input: {
|
|
2
8
|
registryPath: string;
|
|
3
9
|
}): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Sets the top-level `default_kb` pointer in a `kb.yaml` registry to `name`, creating the file and its parent directory
|
|
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.
|
|
15
|
+
*/
|
|
4
16
|
export declare function setDefaultKb(input: {
|
|
5
17
|
registryPath: string;
|
|
6
18
|
name: string;
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
/** Controls which `stat` failures a filesystem-existence check treats as "absent". */
|
|
1
2
|
export interface ExistsOptions {
|
|
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.
|
|
6
|
+
* Defaults to `['ENOENT']`.
|
|
7
|
+
*/
|
|
2
8
|
absentCodes?: readonly string[];
|
|
9
|
+
/**
|
|
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`.
|
|
13
|
+
*/
|
|
3
14
|
treatErrorsAsAbsent?: boolean;
|
|
4
15
|
}
|
|
16
|
+
/** Returns true when `path` exists and is a directory. See {@link ExistsOptions} for the absence policy. */
|
|
5
17
|
export declare function directoryExists(path: string, options?: ExistsOptions): Promise<boolean>;
|
|
18
|
+
/** Returns true when something exists at `path`. See {@link ExistsOptions} for the absence policy. */
|
|
6
19
|
export declare function pathExists(path: string, options?: ExistsOptions): Promise<boolean>;
|