@sdsrs/llm-wiki 0.6.3 → 0.6.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.4 (2026-07-11)
4
+
5
+ Six fixes from a full-pipeline self-test pass (as a real user across every
6
+ command). No breaking changes, no KB migration, no config change. Suite 179 →
7
+ 185.
8
+
9
+ - **fix(convert): PDF conversion was 100% broken** and is now restored. The code
10
+ imported `pdf-parse/lib/pdf-parse.js` — the v1 subpath — but the pinned
11
+ dependency is pdf-parse v2, whose export map has no such subpath, so *every*
12
+ PDF failed at import time before any parsing. Switched to the v2 API
13
+ (`new PDFParse({ data }).getText()`). It slipped through because there was no
14
+ PDF fixture and the only test fed garbage bytes, which the import failure also
15
+ satisfied; added a real `test/fixtures/hello.pdf` + a positive extraction test.
16
+ - fix(ask): `ask --retrieve-only` no longer prints nothing (exit 0) when
17
+ retrieval locates zero pages — the state right after `init`/`scan`, before the
18
+ wiki is built. It now emits a diagnostic to stderr (stdout stays pipe-clean),
19
+ matching the LLM path which already errored.
20
+ - fix(convert): a source file whose whole basename is emoji/symbols
21
+ (e.g. `😀.md`) no longer produces a hidden `raw/.md` that `status` skips
22
+ (silently dropping the source from the incremental workflow). `slugify` now
23
+ falls back to `untitled`, so the existing `-N` collision handler yields
24
+ visible, shell-safe names (`untitled.md`, `untitled-2.md`, …).
25
+ - fix(scan): a missing or non-directory source path now gives a human-readable
26
+ error ("source directory not found: X" / "source path is not a directory: X")
27
+ instead of a raw `ENOENT`/`ENOTDIR`; also improves `status --src`.
28
+ - fix(export): `export --out path/with/missing/dirs/graph.graphml` now creates
29
+ the parent directory (matching the markdown-export sibling) instead of leaking
30
+ a raw `ENOENT`.
31
+ - docs: the layout diagram placed `llms.txt` under `wiki/`, but `index` writes it
32
+ to the KB root (the llms.txt convention); corrected the diagram and command
33
+ table.
34
+
3
35
  ## 0.6.3 (2026-07-11)
4
36
 
5
37
  Two cosmetic fixes from an independent review of 0.6.0–0.6.2; no behavior
package/README.md CHANGED
@@ -84,7 +84,7 @@ npx @sdsrs/llm-wiki@0 ask "what did we decide about X?"
84
84
  | `init [dir]` | scaffold a knowledge base (raw/, wiki/, AGENTS.md, wiki.config.json) |
85
85
  | `scan <srcDir>` | inventory a source dir: dedup, batches, incremental diff, token estimate |
86
86
  | `convert` | convert files from the scan plan into `raw/` markdown |
87
- | `index` | rebuild `wiki/index.md`, `graph.json`, `llms.txt` from page frontmatter |
87
+ | `index` | rebuild `wiki/index.md`, `wiki/graph.json`, and root `llms.txt` from page frontmatter |
88
88
  | `ask <question>` | answer from the KB using full pages (never chunks), with citations |
89
89
  | `lint` | mechanical checks + a semantic worklist for the agent |
90
90
  | `status` | incremental state: uncompiled raw files, source-dir diff, affected wiki pages |
@@ -216,7 +216,8 @@ my-kb/
216
216
  raw/ immutable converted source documents (agents only read)
217
217
  wiki/
218
218
  sources/ entities/ concepts/ comparisons/ full pages, one file each
219
- index.md graph.json llms.txt generated by `index`
219
+ index.md graph.json generated by `index`
220
+ llms.txt flat page listing for agents, KB root (generated by `index`)
220
221
  AGENTS.md the contract every maintaining agent follows
221
222
  wiki.config.json thresholds, batch size, language
222
223
  ```
package/bin/llm-wiki.mjs CHANGED
@@ -74,6 +74,10 @@ program.command('ask <question>')
74
74
  if (!Number.isFinite(k) || k < 1) { console.error(`invalid -k value: ${opts.k} (expected a positive integer)`); process.exit(1) }
75
75
  const r = await askKb(opts.kb, question, { k, retrieveOnly: opts.retrieveOnly })
76
76
  if (opts.retrieveOnly) {
77
+ if (r.pages.length === 0) {
78
+ console.error('no pages located — the KB has no matching wiki pages (build them with the wiki-build skill, or check that wiki/ contains pages)')
79
+ return
80
+ }
77
81
  for (const h of r.pages) console.log(`${h.score.toFixed(3)} ${h.relPath} [${(h.sources ?? ['bm25']).join('+')}]`)
78
82
  } else {
79
83
  console.log(r.answer)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdsrs/llm-wiki",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "Compile messy document directories into Karpathy-style llm_wiki knowledge bases — standalone Q&A CLI + Claude Code/Codex skills",
5
5
  "directories": {
6
6
  "test": "test"
package/src/convert.mjs CHANGED
@@ -4,10 +4,14 @@ import path from 'node:path'
4
4
  export const SUPPORTED_EXTS = ['.md', '.markdown', '.txt', '.html', '.htm', '.pdf', '.docx']
5
5
 
6
6
  export function slugify(name) {
7
- return name.toLowerCase()
7
+ const slug = name.toLowerCase()
8
8
  .replace(/\.[a-z0-9]+$/i, '')
9
9
  .replace(/[^\p{L}\p{N}]+/gu, '-')
10
10
  .replace(/^-+|-+$/g, '')
11
+ // A basename made entirely of emoji/symbols slugs to '' → a hidden `raw/.md`
12
+ // dotfile that `status` skips (source silently dropped). Never return empty;
13
+ // the caller's -N collision handler disambiguates untitled/untitled-2/…
14
+ return slug || 'untitled'
11
15
  }
12
16
 
13
17
  function titleFrom(markdown, fallback) {
@@ -44,9 +48,9 @@ export async function convertFile(srcPath) {
44
48
  return { markdown: `# ${article.title || base}\n\n${md}`, title: article.title || base, warnings }
45
49
  }
46
50
  if (ext === '.pdf') {
47
- const pdfParse = (await import('pdf-parse/lib/pdf-parse.js')).default
48
- const data = await pdfParse(fs.readFileSync(srcPath))
49
- return { markdown: data.text, title: titleFrom(data.text, base), warnings }
51
+ const { PDFParse } = await import('pdf-parse')
52
+ const { text } = await new PDFParse({ data: fs.readFileSync(srcPath) }).getText()
53
+ return { markdown: text, title: titleFrom(text, base), warnings }
50
54
  }
51
55
  if (ext === '.docx') {
52
56
  const mammoth = await import('mammoth')
package/src/export.mjs CHANGED
@@ -301,6 +301,9 @@ export function exportGraph(kbRoot, { format, out } = {}) {
301
301
  if (!render) throw new Error(`unknown format: ${format} (expected ${Object.keys(RENDERERS).join(' | ')} | markdown)`)
302
302
  const graph = loadGraph(kbRoot)
303
303
  const outPath = out ?? path.join(kbRoot, `graph.${EXT[format]}`)
304
+ // Match the markdown-export sibling: create the parent of an explicit --out so
305
+ // `--out new/dir/graph.graphml` writes instead of leaking a raw ENOENT.
306
+ fs.mkdirSync(path.dirname(path.resolve(outPath)), { recursive: true })
304
307
  fs.writeFileSync(outPath, render(graph))
305
308
  return { out: outPath, nodeCount: graph.nodes.length, edgeCount: graph.edges.length }
306
309
  }
package/src/scanner.mjs CHANGED
@@ -71,6 +71,9 @@ function* walk(dir, base = dir, skippedDirs = [], exclude = []) {
71
71
  // `persist: false` runs a read-only scan (e.g. for `status`) that does not
72
72
  // overwrite the .scan-plan.json a previous explicit `scan` produced.
73
73
  export async function scanSource(srcDir, kbRoot, { exclude = [], persist = true } = {}) {
74
+ const st = fs.statSync(srcDir, { throwIfNoEntry: false })
75
+ if (!st) throw new Error(`source directory not found: ${srcDir}`)
76
+ if (!st.isDirectory()) throw new Error(`source path is not a directory: ${srcDir}`)
74
77
  const cfg = loadKbConfig(kbRoot)
75
78
  const files = []
76
79
  const skipped = []