@wiki0/core 0.0.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.
@@ -0,0 +1,3 @@
1
+ import Database from 'better-sqlite3';
2
+ export declare function wiki_db_path(root?: string): string;
3
+ export declare function open_wiki_database(root?: string): Database.Database;
@@ -0,0 +1,17 @@
1
+ import Database from 'better-sqlite3';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { resolve_wiki_root } from './paths.js';
5
+ import { schema_sql } from './schema.js';
6
+ export function wiki_db_path(root = '.') {
7
+ return join(resolve_wiki_root(root), '.wiki0', 'wiki0.sqlite');
8
+ }
9
+ export function open_wiki_database(root = '.') {
10
+ const wiki_root = resolve_wiki_root(root);
11
+ const db_path = wiki_db_path(wiki_root);
12
+ mkdirSync(dirname(db_path), { recursive: true });
13
+ const db = new Database(db_path);
14
+ db.exec(schema_sql);
15
+ return db;
16
+ }
17
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,YAAY,CAAC,IAAI,GAAG,GAAG;IACtC,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAI,GAAG,GAAG;IAC5C,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IACxC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC;AACX,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { open_wiki_database, wiki_db_path } from './database.js';
2
+ export { index_wiki } from './indexer.js';
3
+ export { page_title_from_markdown, parse_frontmatter, parse_markdown, parse_wikilinks, serialize_frontmatter, strip_fenced_code_blocks, strip_inline_code, } from './markdown.js';
4
+ export { append_page, create_page, list_markdown_page_paths, read_page, set_page_frontmatter, } from './pages.js';
5
+ export { display_page_title, page_file_path, page_relative_path, resolve_wiki_root, slugify_title, wikilink_target_path, } from './paths.js';
6
+ export { review_wiki } from './review.js';
7
+ export { schema_sql } from './schema.js';
8
+ export { backlinks_for_page, format_context_markdown, get_wiki_context, search_wiki, } from './search.js';
9
+ export type * from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { open_wiki_database, wiki_db_path } from './database.js';
2
+ export { index_wiki } from './indexer.js';
3
+ export { page_title_from_markdown, parse_frontmatter, parse_markdown, parse_wikilinks, serialize_frontmatter, strip_fenced_code_blocks, strip_inline_code, } from './markdown.js';
4
+ export { append_page, create_page, list_markdown_page_paths, read_page, set_page_frontmatter, } from './pages.js';
5
+ export { display_page_title, page_file_path, page_relative_path, resolve_wiki_root, slugify_title, wikilink_target_path, } from './paths.js';
6
+ export { review_wiki } from './review.js';
7
+ export { schema_sql } from './schema.js';
8
+ export { backlinks_for_page, format_context_markdown, get_wiki_context, search_wiki, } from './search.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACN,wBAAwB,EACxB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,wBAAwB,EACxB,iBAAiB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,WAAW,EACX,WAAW,EACX,wBAAwB,EACxB,SAAS,EACT,oBAAoB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,oBAAoB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACN,kBAAkB,EAClB,uBAAuB,EACvB,gBAAgB,EAChB,WAAW,GACX,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { IndexResult } from './types.js';
2
+ export declare function index_wiki(root?: string): IndexResult;
@@ -0,0 +1,77 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { statSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { open_wiki_database, wiki_db_path } from './database.js';
5
+ import { list_markdown_page_paths, read_page_by_path, } from './pages.js';
6
+ import { resolve_wiki_root, wikilink_target_path } from './paths.js';
7
+ export function index_wiki(root = '.') {
8
+ const wiki_root = resolve_wiki_root(root);
9
+ const db_path = wiki_db_path(wiki_root);
10
+ const db = open_wiki_database(wiki_root);
11
+ let page_count = 0;
12
+ let link_count = 0;
13
+ const upsert_page = db.prepare(`
14
+ INSERT INTO pages (path, title, body, content_hash, modified_at)
15
+ VALUES (@path, @title, @body, @content_hash, @modified_at)
16
+ ON CONFLICT(path) DO UPDATE SET
17
+ title = excluded.title,
18
+ body = excluded.body,
19
+ content_hash = excluded.content_hash,
20
+ modified_at = excluded.modified_at,
21
+ updated_at = datetime('now')
22
+ `);
23
+ const page_id_query = db.prepare('SELECT id FROM pages WHERE path = ?');
24
+ const delete_links = db.prepare('DELETE FROM page_links WHERE from_page_id = ?');
25
+ const insert_link = db.prepare(`
26
+ INSERT INTO page_links (from_page_id, to_path, raw_text, target, alias, embed, status)
27
+ VALUES (?, ?, ?, ?, ?, ?, ?)
28
+ `);
29
+ const clear_fts = db.prepare('DELETE FROM fts_pages');
30
+ const insert_fts = db.prepare('INSERT INTO fts_pages (path, title, body) VALUES (?, ?, ?)');
31
+ const page_paths = list_markdown_page_paths(wiki_root);
32
+ const known_paths = new Set(page_paths);
33
+ const transaction = db.transaction(() => {
34
+ clear_fts.run();
35
+ for (const page_path of page_paths) {
36
+ const file_path = join(wiki_root, 'wiki', page_path);
37
+ const page = read_page_by_path(page_path, wiki_root);
38
+ const modified_at = statSync(file_path).mtime.toISOString();
39
+ const content_hash = createHash('sha256')
40
+ .update(page.body)
41
+ .digest('hex');
42
+ upsert_page.run({
43
+ path: page.path,
44
+ title: page.title,
45
+ body: page.body,
46
+ content_hash,
47
+ modified_at,
48
+ });
49
+ const row = page_id_query.get(page.path);
50
+ delete_links.run(row.id);
51
+ for (const link of page.links) {
52
+ const to_path = wikilink_target_path(link.target);
53
+ const is_resolved = known_paths.has(to_path);
54
+ insert_link.run(row.id, is_resolved ? to_path : null, link.raw, link.target, link.alias ?? null, link.embed ? 1 : 0, is_resolved ? 'resolved' : 'unresolved');
55
+ link_count += 1;
56
+ }
57
+ insert_fts.run(page.path, page.title, page.content);
58
+ page_count += 1;
59
+ }
60
+ const placeholders = [...known_paths].map(() => '?').join(', ');
61
+ if (known_paths.size > 0) {
62
+ db.prepare(`DELETE FROM pages WHERE path NOT IN (${placeholders})`).run(...known_paths);
63
+ }
64
+ else {
65
+ db.prepare('DELETE FROM pages').run();
66
+ }
67
+ });
68
+ transaction();
69
+ db.close();
70
+ return {
71
+ root: wiki_root,
72
+ dbPath: db_path,
73
+ pageCount: page_count,
74
+ linkCount: link_count,
75
+ };
76
+ }
77
+ //# sourceMappingURL=indexer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { ParsedMarkdown, WikiFrontmatter, WikiLink } from './types.js';
2
+ export declare function parse_wikilinks(markdown: string): WikiLink[];
3
+ export declare function strip_fenced_code_blocks(markdown: string): string;
4
+ export declare function strip_inline_code(markdown: string): string;
5
+ export declare function parse_markdown(markdown: string): ParsedMarkdown;
6
+ export declare function parse_frontmatter(source: string): WikiFrontmatter;
7
+ export declare function serialize_frontmatter(frontmatter: WikiFrontmatter): string;
8
+ export declare function page_title_from_markdown(markdown: string, fallback: string): string;
@@ -0,0 +1,108 @@
1
+ export function parse_wikilinks(markdown) {
2
+ const links = [];
3
+ const pattern = /(!?)\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/gu;
4
+ const searchable_markdown = strip_inline_code(strip_fenced_code_blocks(parse_markdown(markdown).content));
5
+ for (const match of searchable_markdown.matchAll(pattern)) {
6
+ const [, embed_marker, target, alias] = match;
7
+ links.push({
8
+ raw: match[0],
9
+ target: target.trim(),
10
+ alias: alias?.trim(),
11
+ embed: embed_marker === '!',
12
+ });
13
+ }
14
+ return links;
15
+ }
16
+ export function strip_fenced_code_blocks(markdown) {
17
+ return markdown.replace(/^(```|~~~)[^\n]*\n[\s\S]*?^\1\s*$/gmu, '');
18
+ }
19
+ export function strip_inline_code(markdown) {
20
+ return markdown.replace(/`[^`\n]*`/gu, '');
21
+ }
22
+ export function parse_markdown(markdown) {
23
+ if (!markdown.startsWith('---\n') &&
24
+ !markdown.startsWith('---\r\n')) {
25
+ return { frontmatter: {}, content: markdown };
26
+ }
27
+ const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\s*\r?\n?/u);
28
+ if (!match)
29
+ return { frontmatter: {}, content: markdown };
30
+ return {
31
+ frontmatter: parse_frontmatter(match[1] ?? ''),
32
+ content: markdown.slice(match[0].length),
33
+ };
34
+ }
35
+ export function parse_frontmatter(source) {
36
+ const frontmatter = {};
37
+ let current_key;
38
+ for (const raw_line of source.split(/\r?\n/u)) {
39
+ const line = raw_line.trimEnd();
40
+ if (line.trim().length === 0)
41
+ continue;
42
+ const list_match = line.match(/^\s*-\s+(.+)$/u);
43
+ if (list_match && current_key) {
44
+ const value = frontmatter[current_key];
45
+ const values = Array.isArray(value)
46
+ ? value
47
+ : value === undefined
48
+ ? []
49
+ : [String(value)];
50
+ values.push(String(parse_frontmatter_scalar(list_match[1] ?? '')));
51
+ frontmatter[current_key] = values;
52
+ continue;
53
+ }
54
+ const pair_match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/u);
55
+ if (!pair_match)
56
+ continue;
57
+ const [, key, value] = pair_match;
58
+ current_key = key;
59
+ frontmatter[key] =
60
+ value.length > 0 ? parse_frontmatter_scalar(value) : [];
61
+ }
62
+ return frontmatter;
63
+ }
64
+ export function serialize_frontmatter(frontmatter) {
65
+ const lines = ['---'];
66
+ for (const [key, value] of Object.entries(frontmatter)) {
67
+ if (Array.isArray(value)) {
68
+ lines.push(`${key}:`);
69
+ for (const item of value)
70
+ lines.push(` - ${item}`);
71
+ continue;
72
+ }
73
+ lines.push(`${key}: ${String(value)}`);
74
+ }
75
+ lines.push('---', '');
76
+ return `${lines.join('\n')}\n`;
77
+ }
78
+ function parse_frontmatter_scalar(value) {
79
+ const trimmed_value = value.trim();
80
+ const unquoted_value = trimmed_value.replace(/^["']|["']$/gu, '');
81
+ if (trimmed_value === 'true')
82
+ return true;
83
+ if (trimmed_value === 'false')
84
+ return false;
85
+ if (/^-?\d+(?:\.\d+)?$/u.test(trimmed_value))
86
+ return Number(trimmed_value);
87
+ if (trimmed_value.startsWith('[') && trimmed_value.endsWith(']')) {
88
+ return trimmed_value
89
+ .slice(1, -1)
90
+ .split(',')
91
+ .map((item) => item.trim().replace(/^["']|["']$/gu, ''))
92
+ .filter((item) => item.length > 0);
93
+ }
94
+ return unquoted_value;
95
+ }
96
+ export function page_title_from_markdown(markdown, fallback) {
97
+ const parsed_markdown = parse_markdown(markdown);
98
+ const frontmatter_title = parsed_markdown.frontmatter.title;
99
+ if (typeof frontmatter_title === 'string' &&
100
+ frontmatter_title.length > 0) {
101
+ return frontmatter_title;
102
+ }
103
+ const heading = parsed_markdown.content
104
+ .match(/^#\s+(.+)$/mu)?.[1]
105
+ ?.trim();
106
+ return heading && heading.length > 0 ? heading : fallback;
107
+ }
108
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { serialize_frontmatter } from './markdown.js';
2
+ import type { PageFrontmatterOptions, PageWriteOptions, WikiPage } from './types.js';
3
+ export declare function create_page(title: string, body: string, options?: PageWriteOptions): WikiPage;
4
+ export declare function read_page(title: string, root?: string): WikiPage;
5
+ export declare function set_page_frontmatter(title: string, frontmatter: Parameters<typeof serialize_frontmatter>[0], options?: PageFrontmatterOptions): WikiPage;
6
+ export declare function append_page(title: string, body: string, root?: string): WikiPage;
7
+ export declare function list_markdown_page_paths(root?: string): string[];
8
+ export declare function read_page_by_path(page_path: string, root?: string): WikiPage;
package/dist/pages.js ADDED
@@ -0,0 +1,86 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'node:fs';
2
+ import { dirname, join, relative } from 'node:path';
3
+ import { page_title_from_markdown, parse_markdown, parse_wikilinks, serialize_frontmatter, } from './markdown.js';
4
+ import { display_page_title, page_file_path, page_relative_path, resolve_wiki_root, } from './paths.js';
5
+ export function create_page(title, body, options = {}) {
6
+ const file_path = page_file_path(title, options.root);
7
+ const page_body = body.match(/^#\s+/mu)
8
+ ? body
9
+ : `# ${display_page_title(title)}\n\n${body}`;
10
+ mkdirSync(dirname(file_path), { recursive: true });
11
+ writeFileSync(file_path, page_body.endsWith('\n') ? page_body : `${page_body}\n`, {
12
+ flag: options.overwrite ? 'w' : 'wx',
13
+ });
14
+ return read_page(title, options.root);
15
+ }
16
+ export function read_page(title, root = '.') {
17
+ const file_path = page_file_path(title, root);
18
+ const body = readFileSync(file_path, 'utf-8');
19
+ const parsed_markdown = parse_markdown(body);
20
+ return {
21
+ path: page_relative_path(title),
22
+ title: page_title_from_markdown(body, display_page_title(title)),
23
+ body,
24
+ content: parsed_markdown.content,
25
+ frontmatter: parsed_markdown.frontmatter,
26
+ links: parse_wikilinks(body),
27
+ };
28
+ }
29
+ export function set_page_frontmatter(title, frontmatter, options = {}) {
30
+ const file_path = page_file_path(title, options.root);
31
+ const body = readFileSync(file_path, 'utf-8');
32
+ const parsed_markdown = parse_markdown(body);
33
+ const next_frontmatter = options.merge
34
+ ? { ...parsed_markdown.frontmatter, ...frontmatter }
35
+ : frontmatter;
36
+ const next_body = `${serialize_frontmatter(next_frontmatter)}${parsed_markdown.content}`;
37
+ writeFileSync(file_path, next_body.endsWith('\n') ? next_body : `${next_body}\n`);
38
+ return read_page(title, options.root);
39
+ }
40
+ export function append_page(title, body, root = '.') {
41
+ const file_path = page_file_path(title, root);
42
+ const append_body = body.startsWith('\n') ? body : `\n${body}`;
43
+ writeFileSync(file_path, append_body.endsWith('\n') ? append_body : `${append_body}\n`, {
44
+ flag: 'a',
45
+ });
46
+ return read_page(title, root);
47
+ }
48
+ export function list_markdown_page_paths(root = '.') {
49
+ const wiki_root = resolve_wiki_root(root);
50
+ const wiki_dir = join(wiki_root, 'wiki');
51
+ if (!existsSync(wiki_dir))
52
+ return [];
53
+ const page_paths = [];
54
+ const visit_dir = (dir_path) => {
55
+ for (const entry of readdirSync(dir_path, {
56
+ withFileTypes: true,
57
+ })) {
58
+ const entry_path = join(dir_path, entry.name);
59
+ if (entry.isDirectory()) {
60
+ visit_dir(entry_path);
61
+ continue;
62
+ }
63
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
64
+ continue;
65
+ page_paths.push(relative(wiki_dir, entry_path));
66
+ }
67
+ };
68
+ visit_dir(wiki_dir);
69
+ return page_paths.sort();
70
+ }
71
+ export function read_page_by_path(page_path, root = '.') {
72
+ const wiki_root = resolve_wiki_root(root);
73
+ const file_path = join(wiki_root, 'wiki', page_path);
74
+ const body = readFileSync(file_path, 'utf-8');
75
+ const title_fallback = display_page_title(page_path.replace(/\.md$/u, ''));
76
+ const parsed_markdown = parse_markdown(body);
77
+ return {
78
+ path: page_path,
79
+ title: page_title_from_markdown(body, title_fallback),
80
+ body,
81
+ content: parsed_markdown.content,
82
+ frontmatter: parsed_markdown.frontmatter,
83
+ links: parse_wikilinks(body),
84
+ };
85
+ }
86
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../src/pages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function slugify_title(title: string): string;
2
+ export declare function resolve_wiki_root(start_path?: string): string;
3
+ export declare function page_relative_path(title: string): string;
4
+ export declare function page_file_path(title: string, root?: string): string;
5
+ export declare function wikilink_target_path(target: string): string;
6
+ export declare function display_page_title(title: string): string;
package/dist/paths.js ADDED
@@ -0,0 +1,46 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ export function slugify_title(title) {
4
+ return title
5
+ .trim()
6
+ .toLowerCase()
7
+ .normalize('NFKD')
8
+ .replace(/[\u0300-\u036f]/gu, '')
9
+ .replace(/[^a-z0-9]+/gu, '-')
10
+ .replace(/^-+|-+$/gu, '');
11
+ }
12
+ export function resolve_wiki_root(start_path = '.') {
13
+ let current_path = resolve(start_path);
14
+ while (true) {
15
+ if (existsSync(join(current_path, '.wiki0')) ||
16
+ existsSync(join(current_path, 'wiki'))) {
17
+ return current_path;
18
+ }
19
+ const parent_path = dirname(current_path);
20
+ if (parent_path === current_path) {
21
+ return resolve(start_path);
22
+ }
23
+ current_path = parent_path;
24
+ }
25
+ }
26
+ export function page_relative_path(title) {
27
+ const path_parts = title
28
+ .split('/')
29
+ .map((part) => slugify_title(part))
30
+ .filter((part) => part.length > 0);
31
+ if (path_parts.length === 0) {
32
+ throw new Error('Page title must include at least one slug character');
33
+ }
34
+ return `${path_parts.join('/')}.md`;
35
+ }
36
+ export function page_file_path(title, root = '.') {
37
+ return join(resolve_wiki_root(root), 'wiki', page_relative_path(title));
38
+ }
39
+ export function wikilink_target_path(target) {
40
+ const clean_target = target.split('#')[0]?.trim() ?? '';
41
+ return page_relative_path(clean_target);
42
+ }
43
+ export function display_page_title(title) {
44
+ return title.split('/').filter(Boolean).at(-1)?.trim() ?? title;
45
+ }
46
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,UAAU,aAAa,CAAC,KAAa;IAC1C,OAAO,KAAK;SACV,IAAI,EAAE;SACN,WAAW,EAAE;SACb,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAU,GAAG,GAAG;IACjD,IAAI,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvC,OAAO,IAAI,EAAE,CAAC;QACb,IACC,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;YACxC,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,EACrC,CAAC;YACF,OAAO,YAAY,CAAC;QACrB,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;QAC1C,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;YAClC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;QAC5B,CAAC;QACD,YAAY,GAAG,WAAW,CAAC;IAC5B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC/C,MAAM,UAAU,GAAG,KAAK;SACtB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;SAClC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEpC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACd,qDAAqD,CACrD,CAAC;IACH,CAAC;IAED,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,IAAI,GAAG,GAAG;IACvD,OAAO,IAAI,CACV,iBAAiB,CAAC,IAAI,CAAC,EACvB,MAAM,EACN,kBAAkB,CAAC,KAAK,CAAC,CACzB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAc;IAClD,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxD,OAAO,kBAAkB,CAAC,YAAY,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC/C,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,KAAK,CAAC;AACjE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { ReviewResult } from './types.js';
2
+ export declare function review_wiki(root?: string): ReviewResult[];
package/dist/review.js ADDED
@@ -0,0 +1,46 @@
1
+ import { list_markdown_page_paths, read_page_by_path, } from './pages.js';
2
+ export function review_wiki(root = '.') {
3
+ const review_statuses = new Set([
4
+ 'draft',
5
+ 'proposed',
6
+ 'review',
7
+ 'stale',
8
+ 'unverified',
9
+ ]);
10
+ const review_tags = new Set([
11
+ 'review',
12
+ 'needs-review',
13
+ 'unverified',
14
+ 'stale',
15
+ ]);
16
+ const results = [];
17
+ for (const page_path of list_markdown_page_paths(root)) {
18
+ const page = read_page_by_path(page_path, root);
19
+ const status = page.frontmatter.status;
20
+ const tags = page.frontmatter.tags;
21
+ const status_text = typeof status === 'string' ? status : null;
22
+ const tag_list = Array.isArray(tags) ? tags : [];
23
+ const matching_tag = tag_list.find((tag) => review_tags.has(tag));
24
+ if (status_text && review_statuses.has(status_text)) {
25
+ results.push({
26
+ path: page.path,
27
+ title: page.title,
28
+ status: status_text,
29
+ tags: tag_list,
30
+ reason: `status:${status_text}`,
31
+ });
32
+ continue;
33
+ }
34
+ if (matching_tag) {
35
+ results.push({
36
+ path: page.path,
37
+ title: page.title,
38
+ status: status_text,
39
+ tags: tag_list,
40
+ reason: `tag:${matching_tag}`,
41
+ });
42
+ }
43
+ }
44
+ return results.sort((left, right) => left.path.localeCompare(right.path));
45
+ }
46
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,wBAAwB,EACxB,iBAAiB,GACjB,MAAM,YAAY,CAAC;AAGpB,MAAM,UAAU,WAAW,CAAC,IAAI,GAAG,GAAG;IACrC,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;QAC/B,OAAO;QACP,UAAU;QACV,QAAQ;QACR,OAAO;QACP,YAAY;KACZ,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;QAC3B,QAAQ;QACR,cAAc;QACd,YAAY;QACZ,OAAO;KACP,CAAC,CAAC;IACH,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,SAAS,IAAI,wBAAwB,CAAC,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QACnC,MAAM,WAAW,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/D,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAElE,IAAI,WAAW,IAAI,eAAe,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YACrD,OAAO,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,WAAW;gBACnB,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,UAAU,WAAW,EAAE;aAC/B,CAAC,CAAC;YACH,SAAS;QACV,CAAC;QAED,IAAI,YAAY,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,WAAW;gBACnB,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,OAAO,YAAY,EAAE;aAC7B,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACnC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CACnC,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const schema_sql: string;
package/dist/schema.js ADDED
@@ -0,0 +1,3 @@
1
+ import { readFileSync } from 'node:fs';
2
+ export const schema_sql = readFileSync(new URL('./schema.sql', import.meta.url), 'utf-8');
3
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CACrC,IAAI,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EACxC,OAAO,CACP,CAAC"}
@@ -0,0 +1,37 @@
1
+ PRAGMA journal_mode = WAL;
2
+ PRAGMA foreign_keys = ON;
3
+
4
+ CREATE TABLE IF NOT EXISTS pages (
5
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6
+ path TEXT NOT NULL UNIQUE,
7
+ title TEXT NOT NULL,
8
+ body TEXT NOT NULL,
9
+ content_hash TEXT NOT NULL,
10
+ modified_at TEXT NOT NULL,
11
+ created_at TEXT DEFAULT (datetime('now')),
12
+ updated_at TEXT DEFAULT (datetime('now'))
13
+ );
14
+
15
+ CREATE TABLE IF NOT EXISTS page_links (
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ from_page_id INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE,
18
+ to_path TEXT,
19
+ raw_text TEXT NOT NULL,
20
+ target TEXT NOT NULL,
21
+ alias TEXT,
22
+ embed INTEGER NOT NULL DEFAULT 0,
23
+ status TEXT NOT NULL DEFAULT 'unresolved',
24
+ created_at TEXT DEFAULT (datetime('now'))
25
+ );
26
+
27
+ CREATE TABLE IF NOT EXISTS facts (
28
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29
+ page_id INTEGER REFERENCES pages(id) ON DELETE SET NULL,
30
+ category TEXT NOT NULL,
31
+ summary TEXT NOT NULL,
32
+ body TEXT,
33
+ confidence TEXT NOT NULL DEFAULT 'unknown',
34
+ created_at TEXT DEFAULT (datetime('now'))
35
+ );
36
+
37
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts_pages USING fts5(path, title, body);
@@ -0,0 +1,5 @@
1
+ import type { BacklinkResult, ContextResult, SearchResult } from './types.js';
2
+ export declare function search_wiki(query: string, root?: string, limit?: number): SearchResult[];
3
+ export declare function get_wiki_context(query: string, root?: string, limit?: number): ContextResult;
4
+ export declare function format_context_markdown(query: string, results: SearchResult[]): string;
5
+ export declare function backlinks_for_page(title: string, root?: string): BacklinkResult[];
package/dist/search.js ADDED
@@ -0,0 +1,50 @@
1
+ import { open_wiki_database } from './database.js';
2
+ import { wikilink_target_path } from './paths.js';
3
+ export function search_wiki(query, root = '.', limit = 10) {
4
+ const db = open_wiki_database(root);
5
+ const rows = db
6
+ .prepare(`SELECT path, title,
7
+ snippet(fts_pages, 2, '[', ']', '…', 12) AS snippet,
8
+ bm25(fts_pages) AS rank
9
+ FROM fts_pages
10
+ WHERE fts_pages MATCH ?
11
+ ORDER BY rank
12
+ LIMIT ?`)
13
+ .all(query, limit);
14
+ db.close();
15
+ return rows;
16
+ }
17
+ export function get_wiki_context(query, root = '.', limit = 5) {
18
+ const results = search_wiki(query, root, limit);
19
+ return {
20
+ query,
21
+ results,
22
+ markdown: format_context_markdown(query, results),
23
+ };
24
+ }
25
+ export function format_context_markdown(query, results) {
26
+ const lines = [`# wiki0 context: ${query}`, ''];
27
+ if (results.length === 0) {
28
+ lines.push('No indexed wiki context found.');
29
+ return `${lines.join('\n')}\n`;
30
+ }
31
+ for (const [index, result] of results.entries()) {
32
+ lines.push(`## ${index + 1}. ${result.title}`, `Source: \`wiki/${result.path}\``, '', result.snippet.replace(/\s+/gu, ' ').trim(), '');
33
+ }
34
+ return `${lines.join('\n').trimEnd()}\n`;
35
+ }
36
+ export function backlinks_for_page(title, root = '.') {
37
+ const db = open_wiki_database(root);
38
+ const page_path = wikilink_target_path(title);
39
+ const rows = db
40
+ .prepare(`SELECT pages.path, pages.title, page_links.raw_text AS rawText,
41
+ page_links.alias, page_links.embed
42
+ FROM page_links
43
+ JOIN pages ON pages.id = page_links.from_page_id
44
+ WHERE page_links.to_path = ?
45
+ ORDER BY pages.path`)
46
+ .all(page_path);
47
+ db.close();
48
+ return rows.map((row) => ({ ...row, embed: Boolean(row.embed) }));
49
+ }
50
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAOlD,MAAM,UAAU,WAAW,CAC1B,KAAa,EACb,IAAI,GAAG,GAAG,EACV,KAAK,GAAG,EAAE;IAEV,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,EAAE;SACb,OAAO,CACP;;;;;;WAMQ,CACR;SACA,GAAG,CAAC,KAAK,EAAE,KAAK,CAAmB,CAAC;IACtC,EAAE,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,IAAI,CAAC;AACb,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC/B,KAAa,EACb,IAAI,GAAG,GAAG,EACV,KAAK,GAAG,CAAC;IAET,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAChD,OAAO;QACN,KAAK;QACL,OAAO;QACP,QAAQ,EAAE,uBAAuB,CAAC,KAAK,EAAE,OAAO,CAAC;KACjD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CACtC,KAAa,EACb,OAAuB;IAEvB,MAAM,KAAK,GAAG,CAAC,oBAAoB,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAEhD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC7C,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAChC,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACjD,KAAK,CAAC,IAAI,CACT,MAAM,KAAK,GAAG,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,EAClC,kBAAkB,MAAM,CAAC,IAAI,IAAI,EACjC,EAAE,EACF,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAC3C,EAAE,CACF,CAAC;IACH,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,kBAAkB,CACjC,KAAa,EACb,IAAI,GAAG,GAAG;IAEV,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,EAAE;SACb,OAAO,CACP;;;;;uBAKoB,CACpB;SACA,GAAG,CAAC,SAAS,CAAqB,CAAC;IACrC,EAAE,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function make_wiki_root(): string;
@@ -0,0 +1,18 @@
1
+ import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { afterEach } from 'vitest';
5
+ const temp_roots = [];
6
+ export function make_wiki_root() {
7
+ const root = mkdtempSync(join(tmpdir(), 'wiki0-core-'));
8
+ mkdirSync(join(root, 'wiki'), { recursive: true });
9
+ mkdirSync(join(root, '.wiki0'), { recursive: true });
10
+ temp_roots.push(root);
11
+ return root;
12
+ }
13
+ afterEach(() => {
14
+ for (const root of temp_roots.splice(0)) {
15
+ rmSync(root, { recursive: true, force: true });
16
+ }
17
+ });
18
+ //# sourceMappingURL=test-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEnC,MAAM,UAAU,GAAa,EAAE,CAAC;AAEhC,MAAM,UAAU,cAAc;IAC7B,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;IACxD,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,IAAI,CAAC;AACb,CAAC;AAED,SAAS,CAAC,GAAG,EAAE;IACd,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACF,CAAC,CAAC,CAAC"}
@@ -0,0 +1,64 @@
1
+ export type WikiConfig = {
2
+ root: string;
3
+ wikiDir?: string;
4
+ dbPath?: string;
5
+ };
6
+ export type WikiLink = {
7
+ raw: string;
8
+ target: string;
9
+ alias?: string;
10
+ embed: boolean;
11
+ };
12
+ export type FrontmatterValue = string | number | boolean | string[];
13
+ export type WikiFrontmatter = Record<string, FrontmatterValue>;
14
+ export type ParsedMarkdown = {
15
+ frontmatter: WikiFrontmatter;
16
+ content: string;
17
+ };
18
+ export type WikiPage = {
19
+ path: string;
20
+ title: string;
21
+ body: string;
22
+ content: string;
23
+ frontmatter: WikiFrontmatter;
24
+ links: WikiLink[];
25
+ };
26
+ export type PageWriteOptions = {
27
+ root?: string;
28
+ overwrite?: boolean;
29
+ };
30
+ export type PageFrontmatterOptions = {
31
+ root?: string;
32
+ merge?: boolean;
33
+ };
34
+ export type IndexResult = {
35
+ root: string;
36
+ dbPath: string;
37
+ pageCount: number;
38
+ linkCount: number;
39
+ };
40
+ export type SearchResult = {
41
+ path: string;
42
+ title: string;
43
+ snippet: string;
44
+ rank: number;
45
+ };
46
+ export type ContextResult = {
47
+ query: string;
48
+ results: SearchResult[];
49
+ markdown: string;
50
+ };
51
+ export type BacklinkResult = {
52
+ path: string;
53
+ title: string;
54
+ rawText: string;
55
+ alias: string | null;
56
+ embed: boolean;
57
+ };
58
+ export type ReviewResult = {
59
+ path: string;
60
+ title: string;
61
+ status: string | null;
62
+ tags: string[];
63
+ reason: string;
64
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@wiki0/core",
3
+ "version": "0.0.1",
4
+ "description": "Core wiki0 Markdown wiki indexing and retrieval primitives",
5
+ "homepage": "https://github.com/spences10/wiki0/tree/main/packages/core#readme",
6
+ "license": "MIT",
7
+ "author": "Scott Spence <me@scottspence.com>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/spences10/wiki0.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "better-sqlite3": "^12.5.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/better-sqlite3": "^7.6.13",
34
+ "@types/node": "^25.8.0",
35
+ "typescript": "^6.0.3",
36
+ "vite-plus": "^0.1.22",
37
+ "vitest": "4.1.7"
38
+ },
39
+ "engines": {
40
+ "node": ">=24.15.0"
41
+ },
42
+ "scripts": {
43
+ "build:self": "vp exec tsc -p tsconfig.build.json && cp src/schema.sql dist/schema.sql",
44
+ "check:self": "vp exec tsc --noEmit -p tsconfig.json",
45
+ "test:self": "vitest run"
46
+ }
47
+ }