@sdsrs/llm-wiki 0.6.1 → 0.6.2
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 +13 -0
- package/README.md +4 -1
- package/bin/llm-wiki.mjs +2 -2
- package/package.json +1 -1
- package/src/export.mjs +60 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.2 (2026-07-11)
|
|
4
|
+
|
|
5
|
+
No breaking changes, no KB migration, no default-behavior change — one additive
|
|
6
|
+
export format.
|
|
7
|
+
|
|
8
|
+
- feat(export): `--format canvas` writes a JSON Canvas 1.0 file
|
|
9
|
+
(`<kb>/graph.canvas`) — a domain map of `file` cards laid out in columns by
|
|
10
|
+
page type (source / entity / concept / comparison / raw), ordered by link
|
|
11
|
+
degree, colored by type, with invalidated pages flagged red. The cards open
|
|
12
|
+
as live, clickable notes in Obsidian Canvas when the KB is opened as its own
|
|
13
|
+
vault. Zero-LLM, alongside the existing graphml / cypher / html / markdown
|
|
14
|
+
exports. Suite 174 → 178.
|
|
15
|
+
|
|
3
16
|
## 0.6.1 (2026-07-11)
|
|
4
17
|
|
|
5
18
|
No breaking changes, no KB migration, no default-behavior change: the only
|
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ npx @sdsrs/llm-wiki@0 ask "what did we decide about X?"
|
|
|
90
90
|
| `status` | incremental state: uncompiled raw files, source-dir diff, affected wiki pages |
|
|
91
91
|
| `graph` | query `graph.json` with `path` / `neighbors` / `hubs` (zero-LLM traversal) |
|
|
92
92
|
| `embed` | compute/update page embeddings (`wiki/.vectors.json`) for optional vector location |
|
|
93
|
-
| `export` | export the graph as GraphML, Cypher, or an interactive HTML viewer; or the wiki as standard-markdown copies |
|
|
93
|
+
| `export` | export the graph as GraphML, Cypher, JSON Canvas, or an interactive HTML viewer; or the wiki as standard-markdown copies |
|
|
94
94
|
| `mcp` | run a read-only MCP server (stdio) over the KB |
|
|
95
95
|
|
|
96
96
|
All commands take `--kb <dir>` (default `.`). `ask` supports `-k <n>` (pages to
|
|
@@ -145,6 +145,9 @@ relations:
|
|
|
145
145
|
`lint` validates each relation target and type; `ambiguous` marks edges for a
|
|
146
146
|
human to resolve.
|
|
147
147
|
|
|
148
|
+
`export --format canvas` writes a JSON Canvas (`graph.canvas`) domain map —
|
|
149
|
+
`file` cards laid out by page type that open as live notes in Obsidian Canvas.
|
|
150
|
+
|
|
148
151
|
### Skills (Claude Code / Codex)
|
|
149
152
|
|
|
150
153
|
The bundled skills (`wiki-build`, `wiki-ingest`, `wiki-query`, `wiki-lint`,
|
package/bin/llm-wiki.mjs
CHANGED
|
@@ -128,9 +128,9 @@ program.command('install-skills')
|
|
|
128
128
|
})
|
|
129
129
|
|
|
130
130
|
program.command('export')
|
|
131
|
-
.description('export wiki/graph.json (graphml | cypher | html) or a markdown-links copy of the wiki (markdown)')
|
|
131
|
+
.description('export wiki/graph.json (graphml | cypher | html | canvas) or a markdown-links copy of the wiki (markdown)')
|
|
132
132
|
.option('--kb <dir>', 'knowledge base root', '.')
|
|
133
|
-
.requiredOption('--format <format>', 'graphml | cypher | html | markdown')
|
|
133
|
+
.requiredOption('--format <format>', 'graphml | cypher | html | canvas | markdown')
|
|
134
134
|
.option('--out <file>', 'output path (default: <kb>/graph.<ext>, or <kb>/wiki-md/ for markdown)')
|
|
135
135
|
.action((opts) => {
|
|
136
136
|
if (opts.format === 'markdown') {
|
package/package.json
CHANGED
package/src/export.mjs
CHANGED
|
@@ -236,13 +236,71 @@ step()
|
|
|
236
236
|
`
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
// JSON Canvas 1.0 (jsoncanvas.org) — an open node/edge format Obsidian Canvas and
|
|
240
|
+
// other tools read. Nodes are `file` cards pointing at the real KB pages
|
|
241
|
+
// (vault-relative from the KB root, where the .canvas is written), so opening the
|
|
242
|
+
// map in Obsidian gives live, clickable notes. Layout is deterministic — one
|
|
243
|
+
// column per node type, ordered by degree — so exports are stable and diffable.
|
|
244
|
+
const CANVAS_COLORS = { source: '5', entity: '4', concept: '6', comparison: '2' } // JSON Canvas preset ids; raw/unknown → default gray
|
|
245
|
+
const CANVAS_TYPE_ORDER = ['source', 'entity', 'concept', 'comparison', 'raw']
|
|
246
|
+
const CANVAS_NODE_W = 260
|
|
247
|
+
const CANVAS_NODE_H = 60
|
|
248
|
+
const CANVAS_COL_GAP = 340
|
|
249
|
+
const CANVAS_ROW_GAP = 90
|
|
250
|
+
|
|
251
|
+
export function toCanvas(graph) {
|
|
252
|
+
const degree = new Map()
|
|
253
|
+
for (const e of graph.edges) for (const end of [e.source, e.target]) degree.set(end, (degree.get(end) ?? 0) + 1)
|
|
254
|
+
|
|
255
|
+
const cols = new Map()
|
|
256
|
+
for (const n of graph.nodes) {
|
|
257
|
+
const key = CANVAS_TYPE_ORDER.includes(n.type) ? n.type : 'raw'
|
|
258
|
+
if (!cols.has(key)) cols.set(key, [])
|
|
259
|
+
cols.get(key).push(n)
|
|
260
|
+
}
|
|
261
|
+
const rank = (t) => { const i = CANVAS_TYPE_ORDER.indexOf(t); return i === -1 ? CANVAS_TYPE_ORDER.length : i }
|
|
262
|
+
const colKeys = [...cols.keys()].sort((a, b) => rank(a) - rank(b))
|
|
263
|
+
|
|
264
|
+
const nodes = []
|
|
265
|
+
colKeys.forEach((key, ci) => {
|
|
266
|
+
const column = cols.get(key).sort((a, b) =>
|
|
267
|
+
(degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0) || a.id.localeCompare(b.id))
|
|
268
|
+
column.forEach((n, ri) => {
|
|
269
|
+
const node = {
|
|
270
|
+
id: n.id,
|
|
271
|
+
type: 'file',
|
|
272
|
+
file: n.id.startsWith('raw/') ? n.id : `wiki/${n.id}.md`,
|
|
273
|
+
x: ci * CANVAS_COL_GAP,
|
|
274
|
+
y: ri * CANVAS_ROW_GAP,
|
|
275
|
+
width: CANVAS_NODE_W,
|
|
276
|
+
height: CANVAS_NODE_H,
|
|
277
|
+
}
|
|
278
|
+
const color = n.status === 'invalidated' ? '1' : CANVAS_COLORS[n.type]
|
|
279
|
+
if (color) node.color = color
|
|
280
|
+
nodes.push(node)
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const present = new Set(nodes.map(n => n.id))
|
|
285
|
+
const edges = graph.edges
|
|
286
|
+
.filter(e => present.has(e.source) && present.has(e.target))
|
|
287
|
+
.map((e, i) => {
|
|
288
|
+
const edge = { id: `e${i}`, fromNode: e.source, toNode: e.target }
|
|
289
|
+
if (e.type) edge.label = e.type
|
|
290
|
+
return edge
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
return JSON.stringify({ nodes, edges }, null, 2) + '\n'
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const RENDERERS = { graphml: toGraphML, cypher: toCypher, html: toHtml, canvas: toCanvas }
|
|
297
|
+
const EXT = { graphml: 'graphml', cypher: 'cypher', html: 'html', canvas: 'canvas' }
|
|
240
298
|
|
|
241
299
|
export function exportGraph(kbRoot, { format, out } = {}) {
|
|
242
300
|
const render = RENDERERS[format]
|
|
243
301
|
if (!render) throw new Error(`unknown format: ${format} (expected ${Object.keys(RENDERERS).join(' | ')} | markdown)`)
|
|
244
302
|
const graph = loadGraph(kbRoot)
|
|
245
|
-
const outPath = out ?? path.join(kbRoot, `graph.${format
|
|
303
|
+
const outPath = out ?? path.join(kbRoot, `graph.${EXT[format]}`)
|
|
246
304
|
fs.writeFileSync(outPath, render(graph))
|
|
247
305
|
return { out: outPath, nodeCount: graph.nodes.length, edgeCount: graph.edges.length }
|
|
248
306
|
}
|