@williamthorsen/kb 0.6.2 → 0.8.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.
Files changed (88) hide show
  1. package/README.md +161 -27
  2. package/dist/esm/check/check.d.ts +34 -0
  3. package/dist/esm/check/check.js +41 -1
  4. package/dist/esm/check/enumerate.d.ts +37 -0
  5. package/dist/esm/check/enumerate.js +12 -4
  6. package/dist/esm/check/glob-segments.d.ts +6 -0
  7. package/dist/esm/check/resolve-foreign-stores.d.ts +24 -0
  8. package/dist/esm/check/resolve-foreign-stores.js +50 -0
  9. package/dist/esm/cli/commands/check.d.ts +23 -1
  10. package/dist/esm/cli/commands/check.js +7 -2
  11. package/dist/esm/cli/commands/create.d.ts +19 -1
  12. package/dist/esm/cli/commands/create.js +2 -0
  13. package/dist/esm/cli/commands/scaffold.d.ts +30 -0
  14. package/dist/esm/cli/commands/scaffold.js +108 -0
  15. package/dist/esm/cli/commands/set-default.d.ts +15 -0
  16. package/dist/esm/cli/commands/taxonomy.d.ts +18 -0
  17. package/dist/esm/cli/format.d.ts +21 -0
  18. package/dist/esm/cli/parse-flag-value.d.ts +5 -0
  19. package/dist/esm/cli/resolve-store.d.ts +12 -0
  20. package/dist/esm/cli/run.d.ts +9 -1
  21. package/dist/esm/cli/run.js +5 -0
  22. package/dist/esm/cli/select-kb-prompt.d.ts +18 -0
  23. package/dist/esm/cli/targeting/resolve-changed-paths.d.ts +10 -0
  24. package/dist/esm/cli/targeting/resolve-changed-paths.js +7 -33
  25. package/dist/esm/cli/targeting/select-notes.d.ts +13 -0
  26. package/dist/esm/cli/targeting/select-notes.js +4 -3
  27. package/dist/esm/config/config-schema.d.ts +30 -1
  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 +8 -0
  32. package/dist/esm/config/load-config.d.ts +7 -0
  33. package/dist/esm/config/load-config.js +1 -0
  34. package/dist/esm/config/note-scope.d.ts +13 -0
  35. package/dist/esm/create/create.d.ts +25 -0
  36. package/dist/esm/create/create.js +8 -12
  37. package/dist/esm/create/index.d.ts +0 -1
  38. package/dist/esm/create/index.js +0 -1
  39. package/dist/esm/discovery/find-kb-root.d.ts +4 -0
  40. package/dist/esm/discovery/kb-registry-schema.d.ts +4 -0
  41. package/dist/esm/discovery/load-registry.d.ts +23 -0
  42. package/dist/esm/discovery/register-store.d.ts +11 -0
  43. package/dist/esm/discovery/registry-document.d.ts +5 -0
  44. package/dist/esm/discovery/set-default-kb.d.ts +12 -0
  45. package/dist/esm/filesystem/exists.d.ts +13 -0
  46. package/dist/esm/filesystem/write-atomic.d.ts +5 -0
  47. package/dist/esm/frontmatter/parse-note.d.ts +9 -0
  48. package/dist/esm/git/list-git-scope.d.ts +15 -0
  49. package/dist/esm/git/list-git-scope.js +22 -0
  50. package/dist/esm/git/run-git.d.ts +17 -0
  51. package/dist/esm/git/run-git.js +22 -0
  52. package/dist/esm/layout/index.d.ts +1 -1
  53. package/dist/esm/layout/index.js +1 -1
  54. package/dist/esm/layout/store-layout.d.ts +20 -0
  55. package/dist/esm/layout/store-layout.js +2 -0
  56. package/dist/esm/lints/paths.d.ts +7 -0
  57. package/dist/esm/lints/tag-alias.d.ts +6 -0
  58. package/dist/esm/lints/taxonomy.d.ts +15 -0
  59. package/dist/esm/note-io/field-validators.d.ts +9 -0
  60. package/dist/esm/note-io/read-note.d.ts +7 -0
  61. package/dist/esm/note-io/write-note.d.ts +9 -0
  62. package/dist/esm/note-io/yaml-fields.d.ts +5 -0
  63. package/dist/esm/records/assertion.d.ts +4 -0
  64. package/dist/esm/records/event.d.ts +12 -0
  65. package/dist/esm/scaffold/index.d.ts +2 -0
  66. package/dist/esm/scaffold/index.js +2 -0
  67. package/dist/esm/scaffold/render-seeds.d.ts +16 -0
  68. package/dist/esm/scaffold/render-seeds.js +65 -0
  69. package/dist/esm/scaffold/scaffold.d.ts +23 -0
  70. package/dist/esm/scaffold/scaffold.js +35 -0
  71. package/dist/esm/tags/canonicalize.d.ts +8 -0
  72. package/dist/esm/tags/load-aliases.d.ts +10 -0
  73. package/dist/esm/taxonomy/domain-paths.d.ts +10 -0
  74. package/dist/esm/taxonomy/load-taxonomy.d.ts +8 -0
  75. package/dist/esm/taxonomy/taxonomy-schema.d.ts +19 -0
  76. package/dist/esm/taxonomy/write-taxonomy.d.ts +20 -0
  77. package/dist/esm/type-guards.d.ts +13 -0
  78. package/dist/esm/types.d.ts +41 -0
  79. package/dist/esm/vault-integrity/build-vault-index.d.ts +11 -0
  80. package/dist/esm/vault-integrity/check-vault-integrity.d.ts +48 -1
  81. package/dist/esm/vault-integrity/check-vault-integrity.js +59 -20
  82. package/dist/esm/vault-integrity/index.d.ts +2 -2
  83. package/dist/esm/vault-integrity/index.js +2 -2
  84. package/dist/esm/vault-integrity/wikilink-parse.d.ts +47 -4
  85. package/dist/esm/vault-integrity/wikilink-parse.js +37 -9
  86. package/package.json +10 -4
  87. package/dist/esm/create/render-seeds.d.ts +0 -2
  88. package/dist/esm/create/render-seeds.js +0 -29
@@ -1,3 +1,3 @@
1
1
  export { buildVaultIndex } from "./build-vault-index.js";
2
- export { checkVaultIntegrity } from "./check-vault-integrity.js";
3
- export { countNewlines, extractTarget, hasNonMarkdownExtension, lookupKey, maskFencedCode, maskInlineCode, WIKILINK, } from "./wikilink-parse.js";
2
+ export { checkVaultIntegrity, } from "./check-vault-integrity.js";
3
+ export { lookupKey, scanWikilinks } from "./wikilink-parse.js";
@@ -1,7 +1,50 @@
1
- export declare const WIKILINK: RegExp;
1
+ /** Counts the newlines in `text` before byte offset `upTo`, used to locate a match's source line. */
2
2
  export declare function countNewlines(text: string, upTo: number): number;
3
+ /**
4
+ * Strips `|alias` and `#anchor` from a wikilink inner string and returns the target. Returns `null` for intra-doc
5
+ * links like `[[#heading]]`, which carry no target.
6
+ */
3
7
  export declare function extractTarget(inner: string): string | null;
4
- export declare function hasNonMarkdownExtension(target: string): boolean;
8
+ /** Reduces a wikilink target to the basename key the vault index is keyed on (drops any directory prefix and `.md`). */
5
9
  export declare function lookupKey(target: string): string;
6
- export declare function maskFencedCode(body: string): string;
7
- export declare function maskInlineCode(body: string): string;
10
+ /** A wikilink target separated into the store it names, where it names one, and the target within that store. */
11
+ export interface QualifiedTarget {
12
+ /** The store the link names, or `undefined` when the target is store-local. */
13
+ store?: string;
14
+ /** The target with any store qualifier removed. */
15
+ target: string;
16
+ }
17
+ /** One wikilink a body scan accepted, with its target already split into an optional store qualifier and a target. */
18
+ export interface ScannedWikilink {
19
+ /** The whole matched link, including any `!` embed prefix. */
20
+ match: string;
21
+ /** The text between the brackets, with alias and anchor intact. */
22
+ inner: string;
23
+ /** Offset of the match within the body. */
24
+ offset: number;
25
+ /** The store the link names, or `undefined` when the target is store-local. */
26
+ store?: string;
27
+ /** The target within that store, with alias, anchor, and any store qualifier stripped. */
28
+ target: string;
29
+ }
30
+ /**
31
+ * Walks a note body and yields every wikilink that carries a resolvable target: fenced and inline code are masked
32
+ * first, and backslash-escaped links, intra-doc anchors, and non-Markdown embeds are skipped. This is the single
33
+ * definition of what counts as a link and what its target is, so a consumer that detects links and one that rewrites
34
+ * them cannot drift apart on either question.
35
+ *
36
+ * `offset` indexes the body as passed in. Masking substitutes same-length whitespace, which this function asserts, so
37
+ * a consumer may slice the unmasked body at the offsets yielded here. It also leaves every newline where it was, so a
38
+ * consumer may count lines in the unmasked body at those offsets; nothing asserts that at runtime, so a masker that
39
+ * moved a newline while keeping the length would shift reported line numbers rather than fail.
40
+ */
41
+ export declare function scanWikilinks(body: string): Generator<ScannedWikilink>;
42
+ /**
43
+ * Separates a leading `store:` qualifier from a wikilink target, so `fde:Note title` names the note `Note title` in
44
+ * the store `fde`. A qualifier is recognized only when the text before the first colon is non-empty and carries no
45
+ * whitespace and no `/`, and something follows the colon; every other target passes through store-local, which leaves
46
+ * a title that happens to contain a colon resolving as it always has.
47
+ *
48
+ * Call it on the output of {@link extractTarget}, which has already stripped any alias and anchor.
49
+ */
50
+ export declare function splitStoreQualifier(target: string): QualifiedTarget;
@@ -1,4 +1,4 @@
1
- export const WIKILINK = /(?<!\\)!?\[\[([^\]\n]+?)\]\]/g;
1
+ const WIKILINK = /(?<!\\)!?\[\[([^\]\n]+?)\]\]/g;
2
2
  const NON_MD_EXTENSIONS = new Set([
3
3
  '.png',
4
4
  '.jpg',
@@ -26,7 +26,40 @@ export function extractTarget(inner) {
26
26
  const trimmed = beforeAnchor.trim();
27
27
  return trimmed === '' ? null : trimmed;
28
28
  }
29
- export function hasNonMarkdownExtension(target) {
29
+ export function lookupKey(target) {
30
+ const withoutExtension = target.endsWith('.md') ? target.slice(0, -3) : target;
31
+ const segments = withoutExtension.split('/');
32
+ return segments.at(-1) ?? withoutExtension;
33
+ }
34
+ export function* scanWikilinks(body) {
35
+ const masked = maskInlineCode(maskFencedCode(body));
36
+ if (masked.length !== body.length) {
37
+ throw new Error('code masking changed body length; wikilink offsets would be invalid');
38
+ }
39
+ for (const match of masked.matchAll(WIKILINK)) {
40
+ const inner = match[1];
41
+ if (inner === undefined)
42
+ continue;
43
+ const extracted = extractTarget(inner);
44
+ if (extracted === null)
45
+ continue;
46
+ if (hasNonMarkdownExtension(extracted))
47
+ continue;
48
+ const { store, target } = splitStoreQualifier(extracted);
49
+ yield { match: match[0], inner, offset: match.index, ...(store !== undefined && { store }), target };
50
+ }
51
+ }
52
+ export function splitStoreQualifier(target) {
53
+ const colonIndex = target.indexOf(':');
54
+ if (colonIndex <= 0)
55
+ return { target };
56
+ const store = target.slice(0, colonIndex);
57
+ const remainder = target.slice(colonIndex + 1);
58
+ if (remainder === '' || /[\s/]/.test(store))
59
+ return { target };
60
+ return { store, target: remainder };
61
+ }
62
+ function hasNonMarkdownExtension(target) {
30
63
  const dotIndex = target.lastIndexOf('.');
31
64
  if (dotIndex === -1)
32
65
  return false;
@@ -35,12 +68,7 @@ export function hasNonMarkdownExtension(target) {
35
68
  return false;
36
69
  return NON_MD_EXTENSIONS.has(ext);
37
70
  }
38
- export function lookupKey(target) {
39
- const withoutExtension = target.endsWith('.md') ? target.slice(0, -3) : target;
40
- const segments = withoutExtension.split('/');
41
- return segments.at(-1) ?? withoutExtension;
42
- }
43
- export function maskFencedCode(body) {
71
+ function maskFencedCode(body) {
44
72
  const lines = body.split('\n');
45
73
  let inFence = false;
46
74
  let fenceChar = '';
@@ -69,7 +97,7 @@ export function maskFencedCode(body) {
69
97
  }
70
98
  return lines.join('\n');
71
99
  }
72
- export function maskInlineCode(body) {
100
+ function maskInlineCode(body) {
73
101
  return body.replace(/`+[^`\n]+?`+/g, (match) => ' '.repeat(match.length));
74
102
  }
75
103
  const FENCE_LINE = /^\s{0,3}(`{3,}|~{3,})/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/kb",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "Knowledge-base foundation: discovery, config, frontmatter parsing, records, tags, and vault-integrity checks",
5
5
  "keywords": [
6
6
  "frontmatter",
@@ -72,6 +72,11 @@
72
72
  "types": "./dist/esm/records/index.d.ts",
73
73
  "import": "./dist/esm/records/index.js"
74
74
  },
75
+ "./scaffold": {
76
+ "source": "./src/scaffold/index.ts",
77
+ "types": "./dist/esm/scaffold/index.d.ts",
78
+ "import": "./dist/esm/scaffold/index.js"
79
+ },
75
80
  "./tags": {
76
81
  "source": "./src/tags/index.ts",
77
82
  "types": "./dist/esm/tags/index.d.ts",
@@ -97,15 +102,16 @@
97
102
  "dist"
98
103
  ],
99
104
  "dependencies": {
100
- "@williamthorsen/toolbelt.errors": "0.6.3",
105
+ "@williamthorsen/toolbelt.errors": "0.7.0",
101
106
  "picomatch": "4.0.7",
102
107
  "yaml": "2.9.0",
103
- "zod": "4.4.3"
108
+ "zod": "4.5.4"
104
109
  },
105
110
  "devDependencies": {
106
111
  "@types/node": "24.13.3",
107
112
  "@types/picomatch": "4.0.3",
108
- "eslint": "10.9.1",
113
+ "eslint": "10.10.0",
114
+ "prettier": "3.9.6",
109
115
  "vitest": "4.1.11"
110
116
  },
111
117
  "engines": {
@@ -1,2 +0,0 @@
1
- export declare function renderAliasesSeed(): string;
2
- export declare function renderConfigSeed(): string;
@@ -1,29 +0,0 @@
1
- import { stringify } from 'yaml';
2
- import { defaultKbConfig } from "../config/config-schema.js";
3
- const ALIASES_HEADER = `# Tag aliases for this knowledge store.
4
- #
5
- # Map each canonical tag to the aliases that should resolve to it, for example:
6
- # aliases:
7
- # typescript: [ts, type-script]
8
- # The \`aliases:\` key is required even when empty.
9
- `;
10
- const CONFIG_HEADER = `# Check configuration for this knowledge store.
11
- #
12
- # Both keys are optional and fall back to the defaults shown below; an absent file uses these defaults too. Uncomment
13
- # and edit to override. \`targets\` selects which notes \`kb check\` enumerates and \`exclude\` removes matches;
14
- # patterns are slash-separated and relative to the store root.
15
- #
16
- `;
17
- export function renderAliasesSeed() {
18
- return `${ALIASES_HEADER}aliases: {}\n`;
19
- }
20
- export function renderConfigSeed() {
21
- return `${CONFIG_HEADER}${commentBlock(stringify(defaultKbConfig))}\n`;
22
- }
23
- function commentBlock(text) {
24
- return text
25
- .trimEnd()
26
- .split('\n')
27
- .map((line) => (line === '' ? '#' : `# ${line}`))
28
- .join('\n');
29
- }