kirograph 0.28.1 → 1.1.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.
- package/README.md +3 -2
- package/dist/architecture/index.js +4 -1
- package/dist/architecture/index.js.map +2 -2
- package/dist/architecture/manifest/index.js +13 -2
- package/dist/architecture/manifest/index.js.map +2 -2
- package/dist/architecture/manifest/python.js +2 -2
- package/dist/architecture/manifest/python.js.map +2 -2
- package/dist/bin/commands/help.js +3 -1
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/commands/install.js +2 -1
- package/dist/bin/commands/install.js.map +2 -2
- package/dist/bin/commands/memory.js +18 -8
- package/dist/bin/commands/memory.js.map +2 -2
- package/dist/bin/commands/reachability.js +7 -2
- package/dist/bin/commands/reachability.js.map +2 -2
- package/dist/bin/commands/status.js +2 -1
- package/dist/bin/commands/status.js.map +2 -2
- package/dist/bin/commands/uninit.js +2 -1
- package/dist/bin/commands/uninit.js.map +2 -2
- package/dist/bin/commands/vulns.js +26 -6
- package/dist/bin/commands/vulns.js.map +2 -2
- package/dist/bin/commands/wiki.js +56 -0
- package/dist/bin/commands/wiki.js.map +2 -2
- package/dist/bin/installer/common.js +7 -1
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/detect.js +10 -0
- package/dist/bin/installer/detect.js.map +2 -2
- package/dist/bin/installer/hooks.js +13 -8
- package/dist/bin/installer/hooks.js.map +2 -2
- package/dist/bin/installer/steering.js +9 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/antigravity.js +1 -1
- package/dist/bin/installer/targets/antigravity.js.map +2 -2
- package/dist/bin/installer/targets/claude.js +1 -1
- package/dist/bin/installer/targets/claude.js.map +2 -2
- package/dist/bin/installer/targets/codex.js +1 -1
- package/dist/bin/installer/targets/codex.js.map +2 -2
- package/dist/bin/installer/targets/commandcode.js +84 -0
- package/dist/bin/installer/targets/commandcode.js.map +7 -0
- package/dist/bin/installer/targets/copilot.js +1 -1
- package/dist/bin/installer/targets/copilot.js.map +2 -2
- package/dist/bin/installer/targets/cursor.js +2 -2
- package/dist/bin/installer/targets/cursor.js.map +2 -2
- package/dist/bin/installer/targets/devin.js +1 -1
- package/dist/bin/installer/targets/devin.js.map +2 -2
- package/dist/bin/installer/targets/gemini-cli.js +1 -1
- package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
- package/dist/bin/installer/targets/index.js +10 -0
- package/dist/bin/installer/targets/index.js.map +2 -2
- package/dist/bin/installer/targets/opencode.js +1 -1
- package/dist/bin/installer/targets/opencode.js.map +2 -2
- package/dist/bin/installer/targets/windsurf.js +1 -1
- package/dist/bin/installer/targets/windsurf.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/bin/kirograph.js.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +18 -6
- package/dist/core/pipeline.js.map +2 -2
- package/dist/extraction/extractor.js +88 -26
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/grammars.js +38 -3
- package/dist/extraction/grammars.js.map +2 -2
- package/dist/extraction/notebook.js +15 -7
- package/dist/extraction/notebook.js.map +2 -2
- package/dist/extraction/wasm/tree-sitter-bash.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-csharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-css.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elisp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elixir.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elm.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embeddedtemplate.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-html.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-json.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-objc.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ocaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ql.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-solidity.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-toml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vue.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-yaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-zig.wasm +0 -0
- package/dist/frameworks/index.js +68 -7
- package/dist/frameworks/index.js.map +2 -2
- package/dist/jev/client.js +99 -0
- package/dist/jev/client.js.map +7 -0
- package/dist/jev/types.js +17 -0
- package/dist/jev/types.js.map +7 -0
- package/dist/mcp/handlers/memory.js +14 -8
- package/dist/mcp/handlers/memory.js.map +2 -2
- package/dist/mcp/handlers/security.js +5 -3
- package/dist/mcp/handlers/security.js.map +2 -2
- package/dist/mcp/tools.js +1 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/memory/index.js +13 -1
- package/dist/memory/index.js.map +3 -3
- package/dist/memory/relation-jev.js +59 -0
- package/dist/memory/relation-jev.js.map +7 -0
- package/dist/memory/schema.js +57 -0
- package/dist/memory/schema.js.map +7 -0
- package/dist/security/auth-detection-jev.js +47 -0
- package/dist/security/auth-detection-jev.js.map +7 -0
- package/dist/security/manifest/adapter.js +34 -5
- package/dist/security/manifest/adapter.js.map +3 -3
- package/dist/security/manifest/parser.js +2 -2
- package/dist/security/manifest/parser.js.map +2 -2
- package/dist/security/manifest/plugins/cargo.js +23 -0
- package/dist/security/manifest/plugins/cargo.js.map +2 -2
- package/dist/security/manifest/plugins/composer.js +12 -0
- package/dist/security/manifest/plugins/composer.js.map +2 -2
- package/dist/security/manifest/plugins/gradle.js +12 -0
- package/dist/security/manifest/plugins/gradle.js.map +2 -2
- package/dist/security/manifest/plugins/hex.js +12 -0
- package/dist/security/manifest/plugins/hex.js.map +2 -2
- package/dist/security/manifest/plugins/maven.js +68 -1
- package/dist/security/manifest/plugins/maven.js.map +2 -2
- package/dist/security/manifest/plugins/npm.js +86 -0
- package/dist/security/manifest/plugins/npm.js.map +2 -2
- package/dist/security/manifest/plugins/nuget.js +13 -0
- package/dist/security/manifest/plugins/nuget.js.map +2 -2
- package/dist/security/manifest/plugins/pubspec.js +12 -0
- package/dist/security/manifest/plugins/pubspec.js.map +2 -2
- package/dist/security/manifest/plugins/pyproject.js +12 -0
- package/dist/security/manifest/plugins/pyproject.js.map +2 -2
- package/dist/security/manifest/plugins/rubygems.js +12 -0
- package/dist/security/manifest/plugins/rubygems.js.map +2 -2
- package/dist/security/pipeline.js +5 -2
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +115 -50
- package/dist/security/reachability.js.map +2 -2
- package/dist/security/types.js.map +1 -1
- package/dist/security/vuln/client.js +45 -1
- package/dist/security/vuln/client.js.map +2 -2
- package/dist/security/vuln/epss-client.js +1 -1
- package/dist/security/vuln/epss-client.js.map +1 -1
- package/dist/security/vuln/osv-adapter.js +144 -32
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- package/dist/sync/index.js +9 -2
- package/dist/sync/index.js.map +2 -2
- package/dist/vectors/libsql-index.js +287 -0
- package/dist/vectors/libsql-index.js.map +7 -0
- package/dist/vectors/turso-index.js +307 -0
- package/dist/vectors/turso-index.js.map +7 -0
- package/dist/wiki/contradiction-jev.js +49 -0
- package/dist/wiki/contradiction-jev.js.map +7 -0
- package/dist/wiki/index.js +13 -0
- package/dist/wiki/index.js.map +2 -2
- package/dist/wiki/ingest.js +2 -0
- package/dist/wiki/ingest.js.map +2 -2
- package/dist/wiki/links.js +53 -0
- package/dist/wiki/links.js.map +7 -0
- package/dist/wiki/lint.js +3 -6
- package/dist/wiki/lint.js.map +2 -2
- package/dist/wiki/rename.js +103 -0
- package/dist/wiki/rename.js.map +7 -0
- package/package.json +3 -2
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var turso_index_exports = {};
|
|
30
|
+
__export(turso_index_exports, {
|
|
31
|
+
TursoIndex: () => TursoIndex
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(turso_index_exports);
|
|
34
|
+
var path = __toESM(require("path"));
|
|
35
|
+
var import_errors = require("../errors");
|
|
36
|
+
const DEFAULT_DIM = 768;
|
|
37
|
+
const DB_FILE = "turso.db";
|
|
38
|
+
const VECTOR_WEIGHT = 0.7;
|
|
39
|
+
const FTS_WEIGHT = 0.3;
|
|
40
|
+
class TursoIndex {
|
|
41
|
+
constructor(kirographDir, dim = DEFAULT_DIM) {
|
|
42
|
+
this.kirographDir = kirographDir;
|
|
43
|
+
this.dim = dim;
|
|
44
|
+
this.db = null;
|
|
45
|
+
this._available = false;
|
|
46
|
+
this._failReason = null;
|
|
47
|
+
this.dbPath = path.join(kirographDir, DB_FILE);
|
|
48
|
+
}
|
|
49
|
+
isAvailable() {
|
|
50
|
+
return this._available;
|
|
51
|
+
}
|
|
52
|
+
getFailReason() {
|
|
53
|
+
return this._failReason;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load @tursodatabase/database, open a local file-based database, and apply the schema.
|
|
57
|
+
* Silent no-op when the optional dep is missing.
|
|
58
|
+
*/
|
|
59
|
+
async initialize() {
|
|
60
|
+
if (this._available) return;
|
|
61
|
+
let connect;
|
|
62
|
+
try {
|
|
63
|
+
const mod = require("@tursodatabase/database");
|
|
64
|
+
connect = mod.connect ?? mod.default?.connect ?? mod;
|
|
65
|
+
} catch {
|
|
66
|
+
(0, import_errors.logDebug)("TursoIndex: @tursodatabase/database not installed \u2014 Turso engine unavailable");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const fs = await import("fs");
|
|
71
|
+
let lastErr;
|
|
72
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
73
|
+
try {
|
|
74
|
+
this.db = await connect(this.dbPath);
|
|
75
|
+
lastErr = null;
|
|
76
|
+
break;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
lastErr = err;
|
|
79
|
+
const msg = String(err);
|
|
80
|
+
if (msg.includes("locked") || msg.includes("Locking")) {
|
|
81
|
+
(0, import_errors.logDebug)(`TursoIndex: DB locked, retry ${attempt + 1}/3`);
|
|
82
|
+
if (attempt < 2) {
|
|
83
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (lastErr) {
|
|
91
|
+
const msg = String(lastErr);
|
|
92
|
+
if (msg.includes("locked") || msg.includes("Locking")) {
|
|
93
|
+
(0, import_errors.logDebug)("TursoIndex: removing stale locked DB and retrying");
|
|
94
|
+
try {
|
|
95
|
+
fs.unlinkSync(this.dbPath);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
fs.unlinkSync(this.dbPath + "-wal");
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
fs.unlinkSync(this.dbPath + "-shm");
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
this.db = await connect(this.dbPath);
|
|
107
|
+
} else {
|
|
108
|
+
throw lastErr;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await this.db.exec(`
|
|
112
|
+
CREATE TABLE IF NOT EXISTS kg_nodes (
|
|
113
|
+
node_id TEXT PRIMARY KEY,
|
|
114
|
+
name TEXT NOT NULL DEFAULT '',
|
|
115
|
+
kind TEXT NOT NULL DEFAULT '',
|
|
116
|
+
file_path TEXT NOT NULL DEFAULT '',
|
|
117
|
+
signature TEXT NOT NULL DEFAULT '',
|
|
118
|
+
embedding vector32(${this.dim})
|
|
119
|
+
);
|
|
120
|
+
`);
|
|
121
|
+
try {
|
|
122
|
+
await this.db.exec(`
|
|
123
|
+
CREATE INDEX IF NOT EXISTS kg_nodes_fts ON kg_nodes USING fts (name, signature);
|
|
124
|
+
`);
|
|
125
|
+
} catch {
|
|
126
|
+
(0, import_errors.logDebug)("TursoIndex: FTS index creation failed \u2014 hybrid search will fall back to pure vector");
|
|
127
|
+
}
|
|
128
|
+
this._available = true;
|
|
129
|
+
(0, import_errors.logDebug)("TursoIndex: ready", { path: this.dbPath, dim: this.dim });
|
|
130
|
+
} catch (err) {
|
|
131
|
+
this._failReason = String(err);
|
|
132
|
+
(0, import_errors.logError)("TursoIndex: initialization failed", { error: String(err) });
|
|
133
|
+
this._available = false;
|
|
134
|
+
this.db = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Insert or update a node's record using ON CONFLICT upsert.
|
|
139
|
+
*/
|
|
140
|
+
async upsert(node, embedding) {
|
|
141
|
+
if (!this._available || !this.db) return;
|
|
142
|
+
try {
|
|
143
|
+
const vecLiteral = `vector32('[${Array.from(embedding).join(",")}]')`;
|
|
144
|
+
const stmt = this.db.prepare(`
|
|
145
|
+
INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)
|
|
146
|
+
VALUES (?, ?, ?, ?, ?, ${vecLiteral})
|
|
147
|
+
ON CONFLICT (node_id) DO UPDATE SET
|
|
148
|
+
name = excluded.name,
|
|
149
|
+
kind = excluded.kind,
|
|
150
|
+
file_path = excluded.file_path,
|
|
151
|
+
signature = excluded.signature,
|
|
152
|
+
embedding = excluded.embedding
|
|
153
|
+
`);
|
|
154
|
+
await stmt.run(
|
|
155
|
+
node.id,
|
|
156
|
+
node.name,
|
|
157
|
+
node.kind,
|
|
158
|
+
node.filePath,
|
|
159
|
+
node.signature ?? ""
|
|
160
|
+
);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
(0, import_errors.logWarn)("TursoIndex: upsert failed", { nodeId: node.id, error: String(err) });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Bulk upsert for batch embedding. Uses a transaction for atomicity and speed.
|
|
167
|
+
*/
|
|
168
|
+
async bulkUpsert(nodes, embeddings) {
|
|
169
|
+
if (!this._available || !this.db) return;
|
|
170
|
+
if (nodes.length === 0) return;
|
|
171
|
+
try {
|
|
172
|
+
await this.db.exec("BEGIN");
|
|
173
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
174
|
+
const node = nodes[i];
|
|
175
|
+
const embedding = embeddings[i];
|
|
176
|
+
const vecLiteral = `vector32('[${Array.from(embedding).join(",")}]')`;
|
|
177
|
+
const stmt = this.db.prepare(`
|
|
178
|
+
INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)
|
|
179
|
+
VALUES (?, ?, ?, ?, ?, ${vecLiteral})
|
|
180
|
+
ON CONFLICT (node_id) DO UPDATE SET
|
|
181
|
+
name = excluded.name,
|
|
182
|
+
kind = excluded.kind,
|
|
183
|
+
file_path = excluded.file_path,
|
|
184
|
+
signature = excluded.signature,
|
|
185
|
+
embedding = excluded.embedding
|
|
186
|
+
`);
|
|
187
|
+
await stmt.run(
|
|
188
|
+
node.id,
|
|
189
|
+
node.name,
|
|
190
|
+
node.kind,
|
|
191
|
+
node.filePath,
|
|
192
|
+
node.signature ?? ""
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
await this.db.exec("COMMIT");
|
|
196
|
+
} catch (err) {
|
|
197
|
+
try {
|
|
198
|
+
await this.db.exec("ROLLBACK");
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
(0, import_errors.logWarn)("TursoIndex: bulkUpsert failed", { count: nodes.length, error: String(err) });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Remove a node's record from the index.
|
|
206
|
+
*/
|
|
207
|
+
async delete(nodeId) {
|
|
208
|
+
if (!this._available || !this.db) return;
|
|
209
|
+
try {
|
|
210
|
+
const stmt = this.db.prepare("DELETE FROM kg_nodes WHERE node_id = ?");
|
|
211
|
+
await stmt.run(nodeId);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
(0, import_errors.logWarn)("TursoIndex: delete failed", { nodeId, error: String(err) });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Hybrid search: combines Turso native vector_distance_cos with Tantivy FTS.
|
|
218
|
+
* Returns node IDs ordered by combined score.
|
|
219
|
+
*
|
|
220
|
+
* Score = VECTOR_WEIGHT * (1 - cosine_distance) + FTS_WEIGHT * fts_score
|
|
221
|
+
*
|
|
222
|
+
* Falls back to pure vector search when the query text produces no FTS matches
|
|
223
|
+
* or when FTS is not available.
|
|
224
|
+
*/
|
|
225
|
+
async search(queryText, queryVec, topN = 10) {
|
|
226
|
+
if (!this._available || !this.db) return [];
|
|
227
|
+
const vecLiteral = `vector32('[${Array.from(queryVec).join(",")}]')`;
|
|
228
|
+
try {
|
|
229
|
+
const stmt = this.db.prepare(`
|
|
230
|
+
SELECT
|
|
231
|
+
n.node_id,
|
|
232
|
+
(${VECTOR_WEIGHT} * (1.0 - vector_distance_cos(n.embedding, ${vecLiteral}))
|
|
233
|
+
+ ${FTS_WEIGHT} * COALESCE(fts_score(n.name, n.signature, ?), 0.0)
|
|
234
|
+
) AS score
|
|
235
|
+
FROM kg_nodes n
|
|
236
|
+
WHERE n.embedding IS NOT NULL
|
|
237
|
+
ORDER BY score DESC
|
|
238
|
+
LIMIT ?
|
|
239
|
+
`);
|
|
240
|
+
const rows = await stmt.all(queryText, topN);
|
|
241
|
+
if (rows && rows.length > 0) {
|
|
242
|
+
return rows.map((r) => r.node_id);
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
(0, import_errors.logDebug)("TursoIndex: hybrid search unavailable, using pure vector");
|
|
246
|
+
}
|
|
247
|
+
return this.vectorSearch(queryVec, topN);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Pure vector similarity search using Turso's native vector_distance_cos.
|
|
251
|
+
*/
|
|
252
|
+
async vectorSearch(queryVec, topN) {
|
|
253
|
+
const vecLiteral = `vector32('[${Array.from(queryVec).join(",")}]')`;
|
|
254
|
+
try {
|
|
255
|
+
const stmt = this.db.prepare(`
|
|
256
|
+
SELECT node_id, vector_distance_cos(embedding, ${vecLiteral}) AS distance
|
|
257
|
+
FROM kg_nodes
|
|
258
|
+
WHERE embedding IS NOT NULL
|
|
259
|
+
ORDER BY distance ASC
|
|
260
|
+
LIMIT ?
|
|
261
|
+
`);
|
|
262
|
+
const rows = await stmt.all(topN);
|
|
263
|
+
return (rows ?? []).map((r) => r.node_id);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
(0, import_errors.logWarn)("TursoIndex: vector search failed", { error: String(err) });
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** Return all node IDs currently stored in the index. */
|
|
270
|
+
async getEmbeddedNodeIds() {
|
|
271
|
+
if (!this._available || !this.db) return [];
|
|
272
|
+
try {
|
|
273
|
+
const stmt = this.db.prepare("SELECT node_id FROM kg_nodes");
|
|
274
|
+
const rows = await stmt.all();
|
|
275
|
+
return (rows ?? []).map((r) => r.node_id);
|
|
276
|
+
} catch {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/** Number of documents currently in the index. */
|
|
281
|
+
async count() {
|
|
282
|
+
if (!this._available || !this.db) return 0;
|
|
283
|
+
try {
|
|
284
|
+
const stmt = this.db.prepare("SELECT COUNT(*) AS c FROM kg_nodes");
|
|
285
|
+
const row = await stmt.get();
|
|
286
|
+
return row ? row.c : 0;
|
|
287
|
+
} catch {
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/** Close the database connection and release resources. */
|
|
292
|
+
close() {
|
|
293
|
+
if (this.db) {
|
|
294
|
+
try {
|
|
295
|
+
if (typeof this.db.close === "function") this.db.close();
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
this.db = null;
|
|
299
|
+
this._available = false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
304
|
+
0 && (module.exports = {
|
|
305
|
+
TursoIndex
|
|
306
|
+
});
|
|
307
|
+
//# sourceMappingURL=turso-index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/vectors/turso-index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Turso Index\n *\n * Hybrid search (native vector similarity + Tantivy FTS) backed by Turso,\n * the ground-up rewrite of SQLite with MVCC, async I/O, and built-in vector search.\n * The database is persisted to .kirograph/turso.db (local-only, no cloud sync).\n *\n * Opt-in: set config.semanticEngine = 'turso'\n * Required optional dependency (not installed by default):\n * npm install @tursodatabase/database\n *\n * Key advantages:\n * - Native vector_distance_cos() \u2014 no extensions, built into the engine\n * - Tantivy-powered FTS with BM25 ranking (more powerful than FTS5)\n * - MVCC concurrent writes \u2014 no \"database is locked\" errors\n * - Async I/O \u2014 non-blocking operations\n * - SQLite-compatible \u2014 all standard SQL works unchanged\n * - Local-only: no cloud dependency, no network calls\n */\n\nimport * as path from 'path';\nimport { logDebug, logWarn, logError } from '../errors';\nimport type { Node } from '../types';\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst DEFAULT_DIM = 768;\nconst DB_FILE = 'turso.db';\n\n// Weight for vector score vs full-text score in hybrid ranking (must sum to 1.0)\nconst VECTOR_WEIGHT = 0.7;\nconst FTS_WEIGHT = 0.3;\n\n// \u2500\u2500 TursoIndex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class TursoIndex {\n private db: any = null;\n private _available = false;\n private dbPath: string;\n private _failReason: string | null = null;\n\n constructor(\n private readonly kirographDir: string,\n private readonly dim = DEFAULT_DIM,\n ) {\n this.dbPath = path.join(kirographDir, DB_FILE);\n }\n\n isAvailable(): boolean {\n return this._available;\n }\n\n getFailReason(): string | null {\n return this._failReason;\n }\n\n /**\n * Load @tursodatabase/database, open a local file-based database, and apply the schema.\n * Silent no-op when the optional dep is missing.\n */\n async initialize(): Promise<void> {\n if (this._available) return;\n\n let connect: any;\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mod = require('@tursodatabase/database');\n connect = mod.connect ?? mod.default?.connect ?? mod;\n } catch {\n logDebug('TursoIndex: @tursodatabase/database not installed \u2014 Turso engine unavailable');\n return;\n }\n\n try {\n // Turso uses exclusive file locking. If another process (e.g. MCP server) holds\n // the DB open, the connect call will fail. Retry after a brief delay.\n // If all retries fail due to locking, remove the stale DB and try once more\n // (safe because the DB is rebuilt from embeddings on next index).\n const fs = await import('fs');\n let lastErr: unknown;\n for (let attempt = 0; attempt < 3; attempt++) {\n try {\n this.db = await connect(this.dbPath);\n lastErr = null;\n break;\n } catch (err) {\n lastErr = err;\n const msg = String(err);\n if (msg.includes('locked') || msg.includes('Locking')) {\n logDebug(`TursoIndex: DB locked, retry ${attempt + 1}/3`);\n if (attempt < 2) {\n await new Promise(r => setTimeout(r, 500 * (attempt + 1)));\n }\n } else {\n throw err;\n }\n }\n }\n // If still locked after retries, remove the stale DB and try one final time\n if (lastErr) {\n const msg = String(lastErr);\n if (msg.includes('locked') || msg.includes('Locking')) {\n logDebug('TursoIndex: removing stale locked DB and retrying');\n try { fs.unlinkSync(this.dbPath); } catch { /* ignore */ }\n try { fs.unlinkSync(this.dbPath + '-wal'); } catch { /* ignore */ }\n try { fs.unlinkSync(this.dbPath + '-shm'); } catch { /* ignore */ }\n this.db = await connect(this.dbPath);\n } else {\n throw lastErr;\n }\n }\n\n // Create table with native vector column\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS kg_nodes (\n node_id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n kind TEXT NOT NULL DEFAULT '',\n file_path TEXT NOT NULL DEFAULT '',\n signature TEXT NOT NULL DEFAULT '',\n embedding vector32(${this.dim})\n );\n `);\n\n // Tantivy-powered FTS index for hybrid search\n // Note: Turso FTS uses CREATE INDEX ... USING fts syntax\n try {\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS kg_nodes_fts ON kg_nodes USING fts (name, signature);\n `);\n } catch {\n // FTS index creation may fail on older Turso versions \u2014 fall back to pure vector\n logDebug('TursoIndex: FTS index creation failed \u2014 hybrid search will fall back to pure vector');\n }\n\n this._available = true;\n logDebug('TursoIndex: ready', { path: this.dbPath, dim: this.dim });\n } catch (err) {\n this._failReason = String(err);\n logError('TursoIndex: initialization failed', { error: String(err) });\n this._available = false;\n this.db = null;\n }\n }\n\n /**\n * Insert or update a node's record using ON CONFLICT upsert.\n */\n async upsert(node: Node, embedding: Float32Array): Promise<void> {\n if (!this._available || !this.db) return;\n\n try {\n const vecLiteral = `vector32('[${Array.from(embedding).join(',')}]')`;\n const stmt = this.db.prepare(`\n INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)\n VALUES (?, ?, ?, ?, ?, ${vecLiteral})\n ON CONFLICT (node_id) DO UPDATE SET\n name = excluded.name,\n kind = excluded.kind,\n file_path = excluded.file_path,\n signature = excluded.signature,\n embedding = excluded.embedding\n `);\n await stmt.run(\n node.id,\n node.name,\n node.kind,\n node.filePath,\n node.signature ?? '',\n );\n } catch (err) {\n logWarn('TursoIndex: upsert failed', { nodeId: node.id, error: String(err) });\n }\n }\n\n /**\n * Bulk upsert for batch embedding. Uses a transaction for atomicity and speed.\n */\n async bulkUpsert(nodes: Node[], embeddings: Float32Array[]): Promise<void> {\n if (!this._available || !this.db) return;\n if (nodes.length === 0) return;\n\n try {\n await this.db.exec('BEGIN');\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]!;\n const embedding = embeddings[i]!;\n const vecLiteral = `vector32('[${Array.from(embedding).join(',')}]')`;\n const stmt = this.db.prepare(`\n INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)\n VALUES (?, ?, ?, ?, ?, ${vecLiteral})\n ON CONFLICT (node_id) DO UPDATE SET\n name = excluded.name,\n kind = excluded.kind,\n file_path = excluded.file_path,\n signature = excluded.signature,\n embedding = excluded.embedding\n `);\n await stmt.run(\n node.id,\n node.name,\n node.kind,\n node.filePath,\n node.signature ?? '',\n );\n }\n await this.db.exec('COMMIT');\n } catch (err) {\n try { await this.db.exec('ROLLBACK'); } catch { /* ignore */ }\n logWarn('TursoIndex: bulkUpsert failed', { count: nodes.length, error: String(err) });\n }\n }\n\n /**\n * Remove a node's record from the index.\n */\n async delete(nodeId: string): Promise<void> {\n if (!this._available || !this.db) return;\n\n try {\n const stmt = this.db.prepare('DELETE FROM kg_nodes WHERE node_id = ?');\n await stmt.run(nodeId);\n } catch (err) {\n logWarn('TursoIndex: delete failed', { nodeId, error: String(err) });\n }\n }\n\n /**\n * Hybrid search: combines Turso native vector_distance_cos with Tantivy FTS.\n * Returns node IDs ordered by combined score.\n *\n * Score = VECTOR_WEIGHT * (1 - cosine_distance) + FTS_WEIGHT * fts_score\n *\n * Falls back to pure vector search when the query text produces no FTS matches\n * or when FTS is not available.\n */\n async search(queryText: string, queryVec: Float32Array, topN = 10): Promise<string[]> {\n if (!this._available || !this.db) return [];\n\n const vecLiteral = `vector32('[${Array.from(queryVec).join(',')}]')`;\n\n // Try hybrid search first\n try {\n const stmt = this.db.prepare(`\n SELECT\n n.node_id,\n (${VECTOR_WEIGHT} * (1.0 - vector_distance_cos(n.embedding, ${vecLiteral}))\n + ${FTS_WEIGHT} * COALESCE(fts_score(n.name, n.signature, ?), 0.0)\n ) AS score\n FROM kg_nodes n\n WHERE n.embedding IS NOT NULL\n ORDER BY score DESC\n LIMIT ?\n `);\n const rows = await stmt.all(queryText, topN);\n\n if (rows && rows.length > 0) {\n return rows.map((r: any) => r.node_id as string);\n }\n } catch {\n // FTS functions may not be available \u2014 fall back to pure vector\n logDebug('TursoIndex: hybrid search unavailable, using pure vector');\n }\n\n // Fallback: pure vector search\n return this.vectorSearch(queryVec, topN);\n }\n\n /**\n * Pure vector similarity search using Turso's native vector_distance_cos.\n */\n private async vectorSearch(queryVec: Float32Array, topN: number): Promise<string[]> {\n const vecLiteral = `vector32('[${Array.from(queryVec).join(',')}]')`;\n try {\n const stmt = this.db.prepare(`\n SELECT node_id, vector_distance_cos(embedding, ${vecLiteral}) AS distance\n FROM kg_nodes\n WHERE embedding IS NOT NULL\n ORDER BY distance ASC\n LIMIT ?\n `);\n const rows = await stmt.all(topN);\n return (rows ?? []).map((r: any) => r.node_id as string);\n } catch (err) {\n logWarn('TursoIndex: vector search failed', { error: String(err) });\n return [];\n }\n }\n\n /** Return all node IDs currently stored in the index. */\n async getEmbeddedNodeIds(): Promise<string[]> {\n if (!this._available || !this.db) return [];\n try {\n const stmt = this.db.prepare('SELECT node_id FROM kg_nodes');\n const rows = await stmt.all();\n return (rows ?? []).map((r: any) => r.node_id as string);\n } catch {\n return [];\n }\n }\n\n /** Number of documents currently in the index. */\n async count(): Promise<number> {\n if (!this._available || !this.db) return 0;\n try {\n const stmt = this.db.prepare('SELECT COUNT(*) AS c FROM kg_nodes');\n const row = await stmt.get();\n return row ? (row as any).c : 0;\n } catch {\n return 0;\n }\n }\n\n /** Close the database connection and release resources. */\n close(): void {\n // @tursodatabase/database may not have a sync close \u2014 best-effort\n if (this.db) {\n try {\n if (typeof this.db.close === 'function') this.db.close();\n } catch { /* ignore */ }\n this.db = null;\n this._available = false;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var contradiction_jev_exports = {};
|
|
20
|
+
__export(contradiction_jev_exports, {
|
|
21
|
+
checkContradictionWithJev: () => checkContradictionWithJev
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(contradiction_jev_exports);
|
|
24
|
+
async function checkContradictionWithJev(client, contentA, contentB) {
|
|
25
|
+
const state = `Page A:
|
|
26
|
+
${contentA}
|
|
27
|
+
|
|
28
|
+
Page B:
|
|
29
|
+
${contentB}`;
|
|
30
|
+
const response = await client.ask(state, {
|
|
31
|
+
contradicts: {
|
|
32
|
+
type: "noul",
|
|
33
|
+
instructions: "Page A and Page B make claims that directly contradict each other about the same subject \u2014 not just related, actually incompatible."
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const answer = response.answers.contradicts;
|
|
37
|
+
if (!answer || answer.type !== "noul") {
|
|
38
|
+
throw new Error('jev did not return a noul answer for "contradicts"');
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
contradicts: answer.noul >= 0.5,
|
|
42
|
+
confidence: answer.confidence ?? answer.noul
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
46
|
+
0 && (module.exports = {
|
|
47
|
+
checkContradictionWithJev
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=contradiction-jev.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/wiki/contradiction-jev.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 jev-based contradiction detection (opt-in, wikiContradictionMode: 'jev')\n *\n * Replaces the keyword-heuristic contradiction check (co-occurring negation\n * words like \"instead of\"/\"superseded\" on FTS-similar pages) with an actual\n * judgment call, still restricted to the same FTS-similar candidate pairs.\n */\nimport type { JevClient } from '../jev/client';\n\nexport interface JevContradictionResult {\n contradicts: boolean;\n confidence: number;\n}\n\nexport async function checkContradictionWithJev(\n client: JevClient,\n contentA: string,\n contentB: string,\n): Promise<JevContradictionResult> {\n const state = `Page A:\\n${contentA}\\n\\nPage B:\\n${contentB}`;\n const response = await client.ask(state, {\n contradicts: {\n type: 'noul',\n instructions: 'Page A and Page B make claims that directly contradict each other about the same subject \u2014 not just related, actually incompatible.',\n },\n });\n\n const answer = response.answers.contradicts;\n if (!answer || answer.type !== 'noul') {\n throw new Error('jev did not return a noul answer for \"contradicts\"');\n }\n\n return {\n contradicts: answer.noul >= 0.5,\n confidence: answer.confidence ?? answer.noul,\n };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,eAAsB,0BACpB,QACA,UACA,UACiC;AACjC,QAAM,QAAQ;AAAA,EAAY,QAAQ;AAAA;AAAA;AAAA,EAAgB,QAAQ;AAC1D,QAAM,WAAW,MAAM,OAAO,IAAI,OAAO;AAAA,IACvC,aAAa;AAAA,MACX,MAAM;AAAA,MACN,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AAED,QAAM,SAAS,SAAS,QAAQ;AAChC,MAAI,CAAC,UAAU,OAAO,SAAS,QAAQ;AACrC,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AAEA,SAAO;AAAA,IACL,aAAa,OAAO,QAAQ;AAAA,IAC5B,YAAY,OAAO,cAAc,OAAO;AAAA,EAC1C;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/wiki/index.js
CHANGED
|
@@ -38,6 +38,8 @@ var import_schema = require("./schema");
|
|
|
38
38
|
var import_schema2 = require("./schema");
|
|
39
39
|
var import_ingest = require("./ingest");
|
|
40
40
|
var import_lint = require("./lint");
|
|
41
|
+
var import_links = require("./links");
|
|
42
|
+
var import_rename = require("./rename");
|
|
41
43
|
class KiroGraphWiki {
|
|
42
44
|
constructor(db, kirographDir, opts = {}) {
|
|
43
45
|
this.wikiDb = new import_database.WikiDatabase(db);
|
|
@@ -119,6 +121,17 @@ class KiroGraphWiki {
|
|
|
119
121
|
this.initialize();
|
|
120
122
|
return (0, import_lint.lintWiki)(this.wikiDb);
|
|
121
123
|
}
|
|
124
|
+
// ── Links ──────────────────────────────────────────────────────────────────
|
|
125
|
+
/** Outgoing/broken/incoming `[[slug]]` links for one page. Null if the slug is unknown. */
|
|
126
|
+
getLinks(slug) {
|
|
127
|
+
this.initialize();
|
|
128
|
+
return (0, import_links.getPageLinks)(this.wikiDb, slug);
|
|
129
|
+
}
|
|
130
|
+
/** Rename a page's slug, moving its file and rewriting every `[[slug]]` reference to it. */
|
|
131
|
+
rename(fromSlug, toSlug) {
|
|
132
|
+
this.initialize();
|
|
133
|
+
return (0, import_rename.renamePage)(this.wikiDir, this.wikiDb, fromSlug, toSlug);
|
|
134
|
+
}
|
|
122
135
|
// ── Reindex ────────────────────────────────────────────────────────────────
|
|
123
136
|
reindex() {
|
|
124
137
|
this.initialize();
|
package/dist/wiki/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/wiki/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Public API\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { WikiDatabase } from './database';\nimport { DEFAULT_SCHEMA } from './schema';\nimport { parseWikiDiff } from './schema';\nimport { buildIngestPrompt, applyDiff, reindexFromDisk, updateManifest } from './ingest';\nimport { lintWiki } from './lint';\nimport type {\n WikiPage,\n ScoredWikiPage,\n WikiDiff,\n WikiLintIssue,\n WikiStats,\n} from './types';\n\nexport type { WikiPage, ScoredWikiPage, WikiDiff, WikiLintIssue, WikiStats };\n\nexport class KiroGraphWiki {\n private wikiDb: WikiDatabase;\n private wikiDir: string;\n private autoResolveConflicts: boolean;\n\n constructor(\n db: any,\n kirographDir: string,\n opts: { autoResolveConflicts?: boolean } = {}\n ) {\n this.wikiDb = new WikiDatabase(db);\n this.wikiDir = path.join(kirographDir, 'wiki');\n this.autoResolveConflicts = opts.autoResolveConflicts ?? false;\n }\n\n initialize(): void {\n this.wikiDb.initialize();\n fs.mkdirSync(this.wikiDir, { recursive: true });\n }\n\n // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n initWiki(): void {\n this.initialize();\n const schemaPath = path.join(this.wikiDir, 'SCHEMA.md');\n if (!fs.existsSync(schemaPath)) {\n fs.writeFileSync(schemaPath, DEFAULT_SCHEMA, 'utf8');\n }\n const manifestPath = path.join(this.wikiDir, 'MANIFEST.md');\n if (!fs.existsSync(manifestPath)) {\n fs.writeFileSync(manifestPath, '# Wiki Manifest\\n\\n(empty)\\n', 'utf8');\n }\n }\n\n // \u2500\u2500 Ingest \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns the ingest prompt for the LLM (agent mode).\n * The agent uses this to produce a WIKI_DIFF, then calls applyDiff.\n */\n getIngestPrompt(source: string, sourceName = 'source'): string {\n this.initialize();\n return buildIngestPrompt(this.wikiDir, source, sourceName);\n }\n\n // \u2500\u2500 Queue (local synthesis mode) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n queueSource(sourceText: string, sourceName: string): void {\n this.initialize();\n this.wikiDb.queueSource(sourceName, sourceText);\n }\n\n getPendingQueue(): Array<{ id: number; sourceName: string; sourceText: string }> {\n this.initialize();\n return this.wikiDb.getPendingQueue();\n }\n\n clearQueue(): void {\n this.initialize();\n this.wikiDb.clearQueue();\n }\n\n getQueueCount(): number {\n this.initialize();\n return this.wikiDb.getQueueCount();\n }\n\n async synthesize(modelName: string, quiet = false) {\n this.initialize();\n const { runWikiLocalSynthesis } = await Promise.resolve().then(() => require('./synthesize.js'));\n return runWikiLocalSynthesis(this, modelName, quiet);\n }\n\n /**\n * Parse and apply a WIKI_DIFF string produced by the LLM.\n */\n applyDiff(rawDiff: string) {\n this.initialize();\n const diff = parseWikiDiff(rawDiff);\n return applyDiff(diff, this.wikiDir, this.wikiDb, {\n autoResolveConflicts: this.autoResolveConflicts,\n });\n }\n\n // \u2500\u2500 Search & Read \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n search(query: string, limit = 10): ScoredWikiPage[] {\n this.initialize();\n return this.wikiDb.search(query, limit);\n }\n\n getPage(slug: string): WikiPage | null {\n this.initialize();\n return this.wikiDb.getPage(slug);\n }\n\n listPages(): WikiPage[] {\n this.initialize();\n return this.wikiDb.listPages();\n }\n\n // \u2500\u2500 Lint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n lint(): WikiLintIssue[] {\n this.initialize();\n return lintWiki(this.wikiDb);\n }\n\n // \u2500\u2500 Reindex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n reindex(): number {\n this.initialize();\n return reindexFromDisk(this.wikiDir, this.wikiDb);\n }\n\n // \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getStats(): WikiStats {\n this.initialize();\n return this.wikiDb.getStats();\n }\n\n // \u2500\u2500 Context enrichment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns wiki pages relevant to a query, for inclusion in kirograph_context.\n * Returns up to `limit` pages with score >= threshold.\n */\n getContextPages(query: string, limit = 3, threshold = 0.4): WikiPage[] {\n this.initialize();\n return this.wikiDb\n .search(query, limit * 2)\n .filter(r => r.score >= threshold)\n .slice(0, limit)\n .map(r => r.page);\n }\n\n get dir(): string {\n return this.wikiDir;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,SAAoB;AACpB,WAAsB;AACtB,sBAA6B;AAC7B,oBAA+B;AAC/B,IAAAA,iBAA8B;AAC9B,oBAA8E;AAC9E,kBAAyB;
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Public API\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { WikiDatabase } from './database';\nimport { DEFAULT_SCHEMA } from './schema';\nimport { parseWikiDiff } from './schema';\nimport { buildIngestPrompt, applyDiff, reindexFromDisk, updateManifest } from './ingest';\nimport { lintWiki } from './lint';\nimport { getPageLinks, type WikiPageLinks } from './links';\nimport { renamePage, type RenamePageResult } from './rename';\nimport type {\n WikiPage,\n ScoredWikiPage,\n WikiDiff,\n WikiLintIssue,\n WikiStats,\n} from './types';\n\nexport type { WikiPage, ScoredWikiPage, WikiDiff, WikiLintIssue, WikiStats, WikiPageLinks, RenamePageResult };\n\nexport class KiroGraphWiki {\n private wikiDb: WikiDatabase;\n private wikiDir: string;\n private autoResolveConflicts: boolean;\n\n constructor(\n db: any,\n kirographDir: string,\n opts: { autoResolveConflicts?: boolean } = {}\n ) {\n this.wikiDb = new WikiDatabase(db);\n this.wikiDir = path.join(kirographDir, 'wiki');\n this.autoResolveConflicts = opts.autoResolveConflicts ?? false;\n }\n\n initialize(): void {\n this.wikiDb.initialize();\n fs.mkdirSync(this.wikiDir, { recursive: true });\n }\n\n // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n initWiki(): void {\n this.initialize();\n const schemaPath = path.join(this.wikiDir, 'SCHEMA.md');\n if (!fs.existsSync(schemaPath)) {\n fs.writeFileSync(schemaPath, DEFAULT_SCHEMA, 'utf8');\n }\n const manifestPath = path.join(this.wikiDir, 'MANIFEST.md');\n if (!fs.existsSync(manifestPath)) {\n fs.writeFileSync(manifestPath, '# Wiki Manifest\\n\\n(empty)\\n', 'utf8');\n }\n }\n\n // \u2500\u2500 Ingest \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns the ingest prompt for the LLM (agent mode).\n * The agent uses this to produce a WIKI_DIFF, then calls applyDiff.\n */\n getIngestPrompt(source: string, sourceName = 'source'): string {\n this.initialize();\n return buildIngestPrompt(this.wikiDir, source, sourceName);\n }\n\n // \u2500\u2500 Queue (local synthesis mode) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n queueSource(sourceText: string, sourceName: string): void {\n this.initialize();\n this.wikiDb.queueSource(sourceName, sourceText);\n }\n\n getPendingQueue(): Array<{ id: number; sourceName: string; sourceText: string }> {\n this.initialize();\n return this.wikiDb.getPendingQueue();\n }\n\n clearQueue(): void {\n this.initialize();\n this.wikiDb.clearQueue();\n }\n\n getQueueCount(): number {\n this.initialize();\n return this.wikiDb.getQueueCount();\n }\n\n async synthesize(modelName: string, quiet = false) {\n this.initialize();\n const { runWikiLocalSynthesis } = await Promise.resolve().then(() => require('./synthesize.js'));\n return runWikiLocalSynthesis(this, modelName, quiet);\n }\n\n /**\n * Parse and apply a WIKI_DIFF string produced by the LLM.\n */\n applyDiff(rawDiff: string) {\n this.initialize();\n const diff = parseWikiDiff(rawDiff);\n return applyDiff(diff, this.wikiDir, this.wikiDb, {\n autoResolveConflicts: this.autoResolveConflicts,\n });\n }\n\n // \u2500\u2500 Search & Read \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n search(query: string, limit = 10): ScoredWikiPage[] {\n this.initialize();\n return this.wikiDb.search(query, limit);\n }\n\n getPage(slug: string): WikiPage | null {\n this.initialize();\n return this.wikiDb.getPage(slug);\n }\n\n listPages(): WikiPage[] {\n this.initialize();\n return this.wikiDb.listPages();\n }\n\n // \u2500\u2500 Lint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n lint(): WikiLintIssue[] {\n this.initialize();\n return lintWiki(this.wikiDb);\n }\n\n // \u2500\u2500 Links \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Outgoing/broken/incoming `[[slug]]` links for one page. Null if the slug is unknown. */\n getLinks(slug: string): WikiPageLinks | null {\n this.initialize();\n return getPageLinks(this.wikiDb, slug);\n }\n\n /** Rename a page's slug, moving its file and rewriting every `[[slug]]` reference to it. */\n rename(fromSlug: string, toSlug: string): RenamePageResult {\n this.initialize();\n return renamePage(this.wikiDir, this.wikiDb, fromSlug, toSlug);\n }\n\n // \u2500\u2500 Reindex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n reindex(): number {\n this.initialize();\n return reindexFromDisk(this.wikiDir, this.wikiDb);\n }\n\n // \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getStats(): WikiStats {\n this.initialize();\n return this.wikiDb.getStats();\n }\n\n // \u2500\u2500 Context enrichment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns wiki pages relevant to a query, for inclusion in kirograph_context.\n * Returns up to `limit` pages with score >= threshold.\n */\n getContextPages(query: string, limit = 3, threshold = 0.4): WikiPage[] {\n this.initialize();\n return this.wikiDb\n .search(query, limit * 2)\n .filter(r => r.score >= threshold)\n .slice(0, limit)\n .map(r => r.page);\n }\n\n get dir(): string {\n return this.wikiDir;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_schema"]
|
|
7
7
|
}
|
package/dist/wiki/ingest.js
CHANGED
|
@@ -31,6 +31,7 @@ __export(ingest_exports, {
|
|
|
31
31
|
applyDiff: () => applyDiff,
|
|
32
32
|
buildIngestPrompt: () => buildIngestPrompt,
|
|
33
33
|
reindexFromDisk: () => reindexFromDisk,
|
|
34
|
+
slugToFilePath: () => slugToFilePath,
|
|
34
35
|
updateManifest: () => updateManifest
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(ingest_exports);
|
|
@@ -254,6 +255,7 @@ function reindexFromDisk(wikiDir, wikiDb) {
|
|
|
254
255
|
applyDiff,
|
|
255
256
|
buildIngestPrompt,
|
|
256
257
|
reindexFromDisk,
|
|
258
|
+
slugToFilePath,
|
|
257
259
|
updateManifest
|
|
258
260
|
});
|
|
259
261
|
//# sourceMappingURL=ingest.js.map
|
package/dist/wiki/ingest.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/wiki/ingest.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Ingest & apply-diff logic\n *\n * applyDiff: writes markdown files + updates SQLite from a parsed WIKI_DIFF\n * buildIngestPrompt: returns the prompt for the LLM (SCHEMA + MANIFEST + source)\n * resolveConflicts: deterministic auto-resolve by source date\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { WikiDiff, WikiDiffEntry, WikiDiffConflict, WikiPage } from './types';\nimport type { WikiDatabase } from './database';\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction slugToFilePath(slug: string): string {\n return slug.replace(/\\//g, path.sep) + '.md';\n}\n\nfunction ensureDir(p: string): void {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n}\n\nfunction upsertSection(content: string, section: string, newContent: string, mode: 'replace' | 'append'): string {\n const heading = `## ${section}`;\n const idx = content.indexOf(heading);\n\n if (idx === -1) {\n // Section doesn't exist \u2014 append at end\n return content.trimEnd() + `\\n\\n${heading}\\n${newContent.trimEnd()}\\n`;\n }\n\n // Find end of section (next ## heading or EOF)\n const afterHeading = idx + heading.length;\n const nextHeading = content.indexOf('\\n## ', afterHeading);\n const sectionEnd = nextHeading === -1 ? content.length : nextHeading;\n\n if (mode === 'replace') {\n return content.slice(0, idx) + heading + '\\n' + newContent.trimEnd() + '\\n' + content.slice(sectionEnd);\n } else {\n // append: insert before next heading\n const sectionContent = content.slice(afterHeading, sectionEnd).trimEnd();\n return content.slice(0, idx) + heading + sectionContent + '\\n' + newContent.trimEnd() + '\\n' + content.slice(sectionEnd);\n }\n}\n\n// \u2500\u2500 buildIngestPrompt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function buildIngestPrompt(\n wikiDir: string,\n sourceContent: string,\n sourceName: string,\n): string {\n const schemaPath = path.join(wikiDir, 'SCHEMA.md');\n const manifestPath = path.join(wikiDir, 'MANIFEST.md');\n\n const schema = fs.existsSync(schemaPath)\n ? fs.readFileSync(schemaPath, 'utf8')\n : '(no SCHEMA.md found \u2014 use default wiki page structure)';\n\n const manifest = fs.existsSync(manifestPath)\n ? fs.readFileSync(manifestPath, 'utf8')\n : '(wiki is empty \u2014 no pages yet)';\n\n return `You are maintaining a project wiki following the schema below.\n\n## SCHEMA\n${schema}\n\n## MANIFEST (existing pages)\n${manifest}\n\n## SOURCE TO INGEST\nSource name: ${sourceName}\n${sourceContent}\n\n## INSTRUCTIONS\n\nRead the source above. Following the schema:\n1. Identify entities, decisions, and patterns mentioned\n2. For existing pages (listed in MANIFEST): update relevant sections\n3. For new entities: create pages using the standard structure\n4. Emit one WIKI_DIFF block per page operation\n5. If incoming info contradicts existing content, emit a WIKI_DIFF_CONFLICTS block\n\nProduce ONLY the WIKI_DIFF blocks and WIKI_DIFF_CONFLICTS blocks. No other output.`;\n}\n\n// \u2500\u2500 applyDiff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ApplyDiffResult {\n created: string[];\n updated: string[];\n conflictsResolved: string[];\n conflictsPending: WikiDiffConflict[];\n}\n\nexport function applyDiff(\n diff: WikiDiff,\n wikiDir: string,\n wikiDb: WikiDatabase,\n opts: { autoResolveConflicts?: boolean } = {}\n): ApplyDiffResult {\n const result: ApplyDiffResult = {\n created: [],\n updated: [],\n conflictsResolved: [],\n conflictsPending: [],\n };\n\n for (const entry of diff.entries) {\n if (!entry.page) continue;\n\n const relPath = slugToFilePath(entry.page);\n const absPath = path.join(wikiDir, relPath);\n\n if (entry.action === 'create') {\n ensureDir(absPath);\n const title = entry.title ?? entry.page;\n const fileContent = `# ${title}\\n\\n${entry.content.trimEnd()}\\n`;\n fs.writeFileSync(absPath, fileContent, 'utf8');\n wikiDb.upsertPage({\n slug: entry.page,\n title,\n content: fileContent,\n filePath: relPath,\n sourceCount: 1,\n });\n result.created.push(entry.page);\n\n } else if (entry.action === 'upsert') {\n let current = '';\n let isNew = false;\n if (fs.existsSync(absPath)) {\n current = fs.readFileSync(absPath, 'utf8');\n } else {\n ensureDir(absPath);\n current = `# ${entry.page}\\n\\n`;\n isNew = true;\n }\n\n const updated = entry.section\n ? upsertSection(current, entry.section, entry.content, entry.mode ?? 'append')\n : current.trimEnd() + '\\n\\n' + entry.content.trimEnd() + '\\n';\n\n fs.writeFileSync(absPath, updated, 'utf8');\n const titleMatch = updated.match(/^#\\s+(.+)$/m);\n wikiDb.upsertPage({\n slug: entry.page,\n title: titleMatch?.[1] ?? entry.page,\n content: updated,\n filePath: relPath,\n sourceCount: 1,\n });\n if (isNew) {\n result.created.push(entry.page);\n } else {\n result.updated.push(entry.page);\n }\n\n } else if (entry.action === 'append') {\n let current = '';\n if (fs.existsSync(absPath)) {\n current = fs.readFileSync(absPath, 'utf8');\n } else {\n ensureDir(absPath);\n current = `# ${entry.page}\\n\\n`;\n }\n const appended = current.trimEnd() + '\\n\\n' + entry.content.trimEnd() + '\\n';\n fs.writeFileSync(absPath, appended, 'utf8');\n const titleMatch = appended.match(/^#\\s+(.+)$/m);\n wikiDb.upsertPage({\n slug: entry.page,\n title: titleMatch?.[1] ?? entry.page,\n content: appended,\n filePath: relPath,\n sourceCount: 1,\n });\n result.updated.push(entry.page);\n }\n }\n\n // Handle conflicts\n for (const conflict of diff.conflicts) {\n if (opts.autoResolveConflicts) {\n const resolved = resolveConflictByDate(conflict, wikiDir, wikiDb);\n if (resolved) {\n result.conflictsResolved.push(conflict.page);\n continue;\n }\n }\n result.conflictsPending.push(conflict);\n }\n\n updateManifest(wikiDir, wikiDb);\n\n return result;\n}\n\n// \u2500\u2500 resolveConflictByDate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction resolveConflictByDate(\n conflict: WikiDiffConflict,\n wikiDir: string,\n wikiDb: WikiDatabase,\n): boolean {\n const existingDate = conflict.existingDate ? new Date(conflict.existingDate).getTime() : 0;\n const incomingDate = conflict.incomingDate ? new Date(conflict.incomingDate).getTime() : 0;\n\n // Incoming is newer \u2014 replace existing claim and add superseded note\n if (incomingDate >= existingDate && conflict.page && conflict.section) {\n const relPath = slugToFilePath(conflict.page);\n const absPath = path.join(wikiDir, relPath);\n if (!fs.existsSync(absPath)) return false;\n\n let content = fs.readFileSync(absPath, 'utf8');\n const supersededNote = `\\n> \u26A0 Superseded by ${conflict.source} on ${conflict.incomingDate ?? 'unknown date'}\\n`;\n const oldClaim = conflict.existing;\n content = content.replace(oldClaim, conflict.incoming + supersededNote);\n fs.writeFileSync(absPath, content, 'utf8');\n\n const titleMatch = content.match(/^#\\s+(.+)$/m);\n wikiDb.upsertPage({\n slug: conflict.page,\n title: titleMatch?.[1] ?? conflict.page,\n content,\n filePath: relPath,\n sourceCount: 0,\n });\n return true;\n }\n\n return false;\n}\n\n// \u2500\u2500 updateManifest \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function updateManifest(wikiDir: string, wikiDb: WikiDatabase): void {\n const pages = wikiDb.listPages();\n if (pages.length === 0) return;\n\n const rows = pages\n .map(p => `| [${p.slug}](${p.filePath}) | ${p.title} | ${p.sourceCount} | ${new Date(p.updatedAt).toISOString().slice(0, 10)} |`)\n .join('\\n');\n\n const manifest = `# Wiki Manifest\\n\\n| Slug | Title | Sources | Updated |\\n|------|-------|---------|----------|\\n${rows}\\n`;\n fs.writeFileSync(path.join(wikiDir, 'MANIFEST.md'), manifest, 'utf8');\n}\n\n// \u2500\u2500 reindex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function reindexFromDisk(wikiDir: string, wikiDb: WikiDatabase): number {\n wikiDb.clearAll();\n\n let count = 0;\n\n function walkDir(dir: string): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const abs = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkDir(abs);\n } else if (entry.name.endsWith('.md') && entry.name !== 'SCHEMA.md' && entry.name !== 'MANIFEST.md') {\n const rel = path.relative(wikiDir, abs);\n const slug = rel.replace(/\\.md$/, '').replace(/\\\\/g, '/');\n const content = fs.readFileSync(abs, 'utf8');\n const titleMatch = content.match(/^#\\s+(.+)$/m);\n const stat = fs.statSync(abs);\n wikiDb.upsertPage({\n slug,\n title: titleMatch?.[1] ?? slug,\n content,\n filePath: rel,\n updatedAt: stat.mtimeMs,\n sourceCount: 0,\n });\n count++;\n }\n }\n }\n\n walkDir(wikiDir);\n updateManifest(wikiDir, wikiDb);\n return count;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,SAAoB;AACpB,WAAsB;
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Ingest & apply-diff logic\n *\n * applyDiff: writes markdown files + updates SQLite from a parsed WIKI_DIFF\n * buildIngestPrompt: returns the prompt for the LLM (SCHEMA + MANIFEST + source)\n * resolveConflicts: deterministic auto-resolve by source date\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { WikiDiff, WikiDiffEntry, WikiDiffConflict, WikiPage } from './types';\nimport type { WikiDatabase } from './database';\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function slugToFilePath(slug: string): string {\n return slug.replace(/\\//g, path.sep) + '.md';\n}\n\nfunction ensureDir(p: string): void {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n}\n\nfunction upsertSection(content: string, section: string, newContent: string, mode: 'replace' | 'append'): string {\n const heading = `## ${section}`;\n const idx = content.indexOf(heading);\n\n if (idx === -1) {\n // Section doesn't exist \u2014 append at end\n return content.trimEnd() + `\\n\\n${heading}\\n${newContent.trimEnd()}\\n`;\n }\n\n // Find end of section (next ## heading or EOF)\n const afterHeading = idx + heading.length;\n const nextHeading = content.indexOf('\\n## ', afterHeading);\n const sectionEnd = nextHeading === -1 ? content.length : nextHeading;\n\n if (mode === 'replace') {\n return content.slice(0, idx) + heading + '\\n' + newContent.trimEnd() + '\\n' + content.slice(sectionEnd);\n } else {\n // append: insert before next heading\n const sectionContent = content.slice(afterHeading, sectionEnd).trimEnd();\n return content.slice(0, idx) + heading + sectionContent + '\\n' + newContent.trimEnd() + '\\n' + content.slice(sectionEnd);\n }\n}\n\n// \u2500\u2500 buildIngestPrompt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function buildIngestPrompt(\n wikiDir: string,\n sourceContent: string,\n sourceName: string,\n): string {\n const schemaPath = path.join(wikiDir, 'SCHEMA.md');\n const manifestPath = path.join(wikiDir, 'MANIFEST.md');\n\n const schema = fs.existsSync(schemaPath)\n ? fs.readFileSync(schemaPath, 'utf8')\n : '(no SCHEMA.md found \u2014 use default wiki page structure)';\n\n const manifest = fs.existsSync(manifestPath)\n ? fs.readFileSync(manifestPath, 'utf8')\n : '(wiki is empty \u2014 no pages yet)';\n\n return `You are maintaining a project wiki following the schema below.\n\n## SCHEMA\n${schema}\n\n## MANIFEST (existing pages)\n${manifest}\n\n## SOURCE TO INGEST\nSource name: ${sourceName}\n${sourceContent}\n\n## INSTRUCTIONS\n\nRead the source above. Following the schema:\n1. Identify entities, decisions, and patterns mentioned\n2. For existing pages (listed in MANIFEST): update relevant sections\n3. For new entities: create pages using the standard structure\n4. Emit one WIKI_DIFF block per page operation\n5. If incoming info contradicts existing content, emit a WIKI_DIFF_CONFLICTS block\n\nProduce ONLY the WIKI_DIFF blocks and WIKI_DIFF_CONFLICTS blocks. No other output.`;\n}\n\n// \u2500\u2500 applyDiff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ApplyDiffResult {\n created: string[];\n updated: string[];\n conflictsResolved: string[];\n conflictsPending: WikiDiffConflict[];\n}\n\nexport function applyDiff(\n diff: WikiDiff,\n wikiDir: string,\n wikiDb: WikiDatabase,\n opts: { autoResolveConflicts?: boolean } = {}\n): ApplyDiffResult {\n const result: ApplyDiffResult = {\n created: [],\n updated: [],\n conflictsResolved: [],\n conflictsPending: [],\n };\n\n for (const entry of diff.entries) {\n if (!entry.page) continue;\n\n const relPath = slugToFilePath(entry.page);\n const absPath = path.join(wikiDir, relPath);\n\n if (entry.action === 'create') {\n ensureDir(absPath);\n const title = entry.title ?? entry.page;\n const fileContent = `# ${title}\\n\\n${entry.content.trimEnd()}\\n`;\n fs.writeFileSync(absPath, fileContent, 'utf8');\n wikiDb.upsertPage({\n slug: entry.page,\n title,\n content: fileContent,\n filePath: relPath,\n sourceCount: 1,\n });\n result.created.push(entry.page);\n\n } else if (entry.action === 'upsert') {\n let current = '';\n let isNew = false;\n if (fs.existsSync(absPath)) {\n current = fs.readFileSync(absPath, 'utf8');\n } else {\n ensureDir(absPath);\n current = `# ${entry.page}\\n\\n`;\n isNew = true;\n }\n\n const updated = entry.section\n ? upsertSection(current, entry.section, entry.content, entry.mode ?? 'append')\n : current.trimEnd() + '\\n\\n' + entry.content.trimEnd() + '\\n';\n\n fs.writeFileSync(absPath, updated, 'utf8');\n const titleMatch = updated.match(/^#\\s+(.+)$/m);\n wikiDb.upsertPage({\n slug: entry.page,\n title: titleMatch?.[1] ?? entry.page,\n content: updated,\n filePath: relPath,\n sourceCount: 1,\n });\n if (isNew) {\n result.created.push(entry.page);\n } else {\n result.updated.push(entry.page);\n }\n\n } else if (entry.action === 'append') {\n let current = '';\n if (fs.existsSync(absPath)) {\n current = fs.readFileSync(absPath, 'utf8');\n } else {\n ensureDir(absPath);\n current = `# ${entry.page}\\n\\n`;\n }\n const appended = current.trimEnd() + '\\n\\n' + entry.content.trimEnd() + '\\n';\n fs.writeFileSync(absPath, appended, 'utf8');\n const titleMatch = appended.match(/^#\\s+(.+)$/m);\n wikiDb.upsertPage({\n slug: entry.page,\n title: titleMatch?.[1] ?? entry.page,\n content: appended,\n filePath: relPath,\n sourceCount: 1,\n });\n result.updated.push(entry.page);\n }\n }\n\n // Handle conflicts\n for (const conflict of diff.conflicts) {\n if (opts.autoResolveConflicts) {\n const resolved = resolveConflictByDate(conflict, wikiDir, wikiDb);\n if (resolved) {\n result.conflictsResolved.push(conflict.page);\n continue;\n }\n }\n result.conflictsPending.push(conflict);\n }\n\n updateManifest(wikiDir, wikiDb);\n\n return result;\n}\n\n// \u2500\u2500 resolveConflictByDate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction resolveConflictByDate(\n conflict: WikiDiffConflict,\n wikiDir: string,\n wikiDb: WikiDatabase,\n): boolean {\n const existingDate = conflict.existingDate ? new Date(conflict.existingDate).getTime() : 0;\n const incomingDate = conflict.incomingDate ? new Date(conflict.incomingDate).getTime() : 0;\n\n // Incoming is newer \u2014 replace existing claim and add superseded note\n if (incomingDate >= existingDate && conflict.page && conflict.section) {\n const relPath = slugToFilePath(conflict.page);\n const absPath = path.join(wikiDir, relPath);\n if (!fs.existsSync(absPath)) return false;\n\n let content = fs.readFileSync(absPath, 'utf8');\n const supersededNote = `\\n> \u26A0 Superseded by ${conflict.source} on ${conflict.incomingDate ?? 'unknown date'}\\n`;\n const oldClaim = conflict.existing;\n content = content.replace(oldClaim, conflict.incoming + supersededNote);\n fs.writeFileSync(absPath, content, 'utf8');\n\n const titleMatch = content.match(/^#\\s+(.+)$/m);\n wikiDb.upsertPage({\n slug: conflict.page,\n title: titleMatch?.[1] ?? conflict.page,\n content,\n filePath: relPath,\n sourceCount: 0,\n });\n return true;\n }\n\n return false;\n}\n\n// \u2500\u2500 updateManifest \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function updateManifest(wikiDir: string, wikiDb: WikiDatabase): void {\n const pages = wikiDb.listPages();\n if (pages.length === 0) return;\n\n const rows = pages\n .map(p => `| [${p.slug}](${p.filePath}) | ${p.title} | ${p.sourceCount} | ${new Date(p.updatedAt).toISOString().slice(0, 10)} |`)\n .join('\\n');\n\n const manifest = `# Wiki Manifest\\n\\n| Slug | Title | Sources | Updated |\\n|------|-------|---------|----------|\\n${rows}\\n`;\n fs.writeFileSync(path.join(wikiDir, 'MANIFEST.md'), manifest, 'utf8');\n}\n\n// \u2500\u2500 reindex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function reindexFromDisk(wikiDir: string, wikiDb: WikiDatabase): number {\n wikiDb.clearAll();\n\n let count = 0;\n\n function walkDir(dir: string): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const abs = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkDir(abs);\n } else if (entry.name.endsWith('.md') && entry.name !== 'SCHEMA.md' && entry.name !== 'MANIFEST.md') {\n const rel = path.relative(wikiDir, abs);\n const slug = rel.replace(/\\.md$/, '').replace(/\\\\/g, '/');\n const content = fs.readFileSync(abs, 'utf8');\n const titleMatch = content.match(/^#\\s+(.+)$/m);\n const stat = fs.statSync(abs);\n wikiDb.upsertPage({\n slug,\n title: titleMatch?.[1] ?? slug,\n content,\n filePath: rel,\n updatedAt: stat.mtimeMs,\n sourceCount: 0,\n });\n count++;\n }\n }\n }\n\n walkDir(wikiDir);\n updateManifest(wikiDir, wikiDb);\n return count;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var links_exports = {};
|
|
20
|
+
__export(links_exports, {
|
|
21
|
+
WIKI_LINK_RE: () => WIKI_LINK_RE,
|
|
22
|
+
extractLinks: () => extractLinks,
|
|
23
|
+
getPageLinks: () => getPageLinks
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(links_exports);
|
|
26
|
+
const WIKI_LINK_RE = /\[\[([^\]]+)\]\]/g;
|
|
27
|
+
function extractLinks(content) {
|
|
28
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29
|
+
const re = new RegExp(WIKI_LINK_RE.source, "g");
|
|
30
|
+
let m;
|
|
31
|
+
while ((m = re.exec(content)) !== null) {
|
|
32
|
+
seen.add(m[1].trim());
|
|
33
|
+
}
|
|
34
|
+
return [...seen];
|
|
35
|
+
}
|
|
36
|
+
function getPageLinks(wikiDb, slug) {
|
|
37
|
+
const page = wikiDb.getPage(slug);
|
|
38
|
+
if (!page) return null;
|
|
39
|
+
const pages = wikiDb.listPages();
|
|
40
|
+
const slugSet = new Set(pages.map((p) => p.slug));
|
|
41
|
+
const linked = extractLinks(page.content);
|
|
42
|
+
const outgoing = linked.filter((s) => slugSet.has(s));
|
|
43
|
+
const broken = linked.filter((s) => !slugSet.has(s));
|
|
44
|
+
const incoming = pages.filter((p) => p.slug !== slug && extractLinks(p.content).includes(slug)).map((p) => p.slug);
|
|
45
|
+
return { outgoing, broken, incoming };
|
|
46
|
+
}
|
|
47
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
48
|
+
0 && (module.exports = {
|
|
49
|
+
WIKI_LINK_RE,
|
|
50
|
+
extractLinks,
|
|
51
|
+
getPageLinks
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=links.js.map
|