kirograph 1.0.0 → 1.2.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 +4 -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/memory.js +29 -9
- 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/vulns.js +16 -1
- package/dist/bin/commands/vulns.js.map +2 -2
- package/dist/bin/commands/wiki.js +59 -2
- package/dist/bin/commands/wiki.js.map +2 -2
- package/dist/bin/installer/steering.js +9 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +2 -2
- package/dist/db/memory-schema.sql +2 -1
- package/dist/extraction/extractor.js +46 -41
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/notebook.js +15 -7
- package/dist/extraction/notebook.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 +15 -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/handlers/wiki.js +4 -2
- package/dist/mcp/handlers/wiki.js.map +2 -2
- package/dist/mcp/tools.js +3 -2
- package/dist/mcp/tools.js.map +2 -2
- package/dist/memory/database.js +17 -4
- package/dist/memory/database.js.map +2 -2
- package/dist/memory/index.js +31 -2
- 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 +92 -0
- package/dist/memory/schema.js.map +7 -0
- package/dist/memory/types.js.map +1 -1
- package/dist/patterns/loader.js +2 -146
- package/dist/patterns/loader.js.map +3 -3
- 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 +17 -3
- package/dist/security/manifest/adapter.js.map +3 -3
- 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 +101 -4
- package/dist/security/manifest/plugins/maven.js.map +2 -2
- package/dist/security/manifest/plugins/npm.js +117 -2
- 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 +1 -1
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +54 -5
- package/dist/security/reachability.js.map +3 -3
- 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/osv-adapter.js +45 -1
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- package/dist/shared/json-schema.js +136 -0
- package/dist/shared/json-schema.js.map +7 -0
- package/dist/shared/yaml.js +174 -0
- package/dist/shared/yaml.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 +16 -1
- 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 +17 -7
- package/dist/wiki/lint.js.map +2 -2
- package/dist/wiki/rename.js +103 -0
- package/dist/wiki/rename.js.map +7 -0
- package/dist/wiki/typed-pages.js +67 -0
- package/dist/wiki/typed-pages.js.map +7 -0
- package/dist/wiki/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
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 yaml_exports = {};
|
|
20
|
+
__export(yaml_exports, {
|
|
21
|
+
parseYaml: () => parseYaml
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(yaml_exports);
|
|
24
|
+
function parseYaml(text) {
|
|
25
|
+
const lines = text.split("\n");
|
|
26
|
+
return parseBlock(lines, 0, 0).result;
|
|
27
|
+
}
|
|
28
|
+
function parseBlock(lines, startIndex, baseIndent) {
|
|
29
|
+
const result = {};
|
|
30
|
+
let i = startIndex;
|
|
31
|
+
while (i < lines.length) {
|
|
32
|
+
const rawLine = lines[i];
|
|
33
|
+
const commentIdx = rawLine.indexOf(" #");
|
|
34
|
+
const line = commentIdx >= 0 ? rawLine.slice(0, commentIdx) : rawLine;
|
|
35
|
+
if (line.trim() === "" || line.trim().startsWith("#")) {
|
|
36
|
+
i++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const indent = countIndent(rawLine);
|
|
40
|
+
if (indent < baseIndent) break;
|
|
41
|
+
if (indent > baseIndent && Object.keys(result).length === 0) {
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (indent > baseIndent) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
const colonIdx = trimmed.indexOf(":");
|
|
50
|
+
if (colonIdx <= 0) {
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
55
|
+
const rest = trimmed.slice(colonIdx + 1).trimStart();
|
|
56
|
+
if (rest === "" || rest === "|" || rest === ">") {
|
|
57
|
+
const nextContentLine = findNextContent(lines, i + 1);
|
|
58
|
+
if (nextContentLine === -1) {
|
|
59
|
+
result[key] = null;
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const nextIndent = countIndent(lines[nextContentLine]);
|
|
64
|
+
if (nextIndent > indent) {
|
|
65
|
+
if (lines[nextContentLine].trim().startsWith("- ")) {
|
|
66
|
+
const { items, nextIndex } = parseSequence(lines, nextContentLine, nextIndent);
|
|
67
|
+
result[key] = items;
|
|
68
|
+
i = nextIndex;
|
|
69
|
+
} else {
|
|
70
|
+
const { result: subResult, nextIndex } = parseBlock(lines, nextContentLine, nextIndent);
|
|
71
|
+
result[key] = subResult;
|
|
72
|
+
i = nextIndex;
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
result[key] = null;
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
} else if (rest.startsWith("[")) {
|
|
79
|
+
result[key] = parseInlineArray(rest);
|
|
80
|
+
i++;
|
|
81
|
+
} else {
|
|
82
|
+
result[key] = parseScalar(rest);
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { result, nextIndex: i };
|
|
87
|
+
}
|
|
88
|
+
function parseSequence(lines, startIndex, baseIndent) {
|
|
89
|
+
const items = [];
|
|
90
|
+
let i = startIndex;
|
|
91
|
+
while (i < lines.length) {
|
|
92
|
+
const rawLine = lines[i];
|
|
93
|
+
if (rawLine.trim() === "" || rawLine.trim().startsWith("#")) {
|
|
94
|
+
i++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const indent = countIndent(rawLine);
|
|
98
|
+
if (indent < baseIndent) break;
|
|
99
|
+
const trimmed = rawLine.trim();
|
|
100
|
+
if (!trimmed.startsWith("- ") && trimmed !== "-") {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
const itemValue = trimmed.startsWith("- ") ? trimmed.slice(2).trim() : "";
|
|
104
|
+
if (itemValue === "" || itemValue === "|" || itemValue === ">") {
|
|
105
|
+
const nextContentLine = findNextContent(lines, i + 1);
|
|
106
|
+
if (nextContentLine !== -1 && countIndent(lines[nextContentLine]) > indent) {
|
|
107
|
+
const { result: subResult, nextIndex } = parseBlock(lines, nextContentLine, countIndent(lines[nextContentLine]));
|
|
108
|
+
items.push(subResult);
|
|
109
|
+
i = nextIndex;
|
|
110
|
+
} else {
|
|
111
|
+
items.push(null);
|
|
112
|
+
i++;
|
|
113
|
+
}
|
|
114
|
+
} else if (itemValue.includes(":")) {
|
|
115
|
+
const itemIndent = indent + 2;
|
|
116
|
+
const continuationLines = [];
|
|
117
|
+
let j = i + 1;
|
|
118
|
+
while (j < lines.length) {
|
|
119
|
+
const nextRaw = lines[j];
|
|
120
|
+
if (nextRaw.trim() === "" || nextRaw.trim().startsWith("#")) {
|
|
121
|
+
j++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (countIndent(nextRaw) >= itemIndent) {
|
|
125
|
+
continuationLines.push(nextRaw);
|
|
126
|
+
j++;
|
|
127
|
+
} else {
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const blockLines = [" ".repeat(itemIndent) + itemValue, ...continuationLines];
|
|
132
|
+
const { result: subResult } = parseBlock(blockLines, 0, itemIndent);
|
|
133
|
+
items.push(subResult);
|
|
134
|
+
i = j;
|
|
135
|
+
} else {
|
|
136
|
+
items.push(parseScalar(itemValue));
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { items, nextIndex: i };
|
|
141
|
+
}
|
|
142
|
+
function parseInlineArray(text) {
|
|
143
|
+
const inner = text.replace(/^\[/, "").replace(/\].*$/, "");
|
|
144
|
+
if (inner.trim() === "") return [];
|
|
145
|
+
return inner.split(",").map((s) => parseScalar(s.trim()));
|
|
146
|
+
}
|
|
147
|
+
function parseScalar(text) {
|
|
148
|
+
if (text === "true") return true;
|
|
149
|
+
if (text === "false") return false;
|
|
150
|
+
if (text === "null" || text === "~") return null;
|
|
151
|
+
if (/^-?\d+$/.test(text)) return parseInt(text, 10);
|
|
152
|
+
if (/^-?\d+\.\d+$/.test(text)) return parseFloat(text);
|
|
153
|
+
if (text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'")) {
|
|
154
|
+
return text.slice(1, -1);
|
|
155
|
+
}
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
function countIndent(line) {
|
|
159
|
+
let i = 0;
|
|
160
|
+
while (i < line.length && line[i] === " ") i++;
|
|
161
|
+
return i;
|
|
162
|
+
}
|
|
163
|
+
function findNextContent(lines, startIndex) {
|
|
164
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
165
|
+
const t = lines[i].trim();
|
|
166
|
+
if (t !== "" && !t.startsWith("#")) return i;
|
|
167
|
+
}
|
|
168
|
+
return -1;
|
|
169
|
+
}
|
|
170
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
171
|
+
0 && (module.exports = {
|
|
172
|
+
parseYaml
|
|
173
|
+
});
|
|
174
|
+
//# sourceMappingURL=yaml.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/shared/yaml.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph \u2014 Minimal YAML parser\n *\n * js-yaml is not a declared dependency, so this handles the subset of YAML\n * used across KiroGraph (pattern rule files, wiki frontmatter):\n * - Flat key: value (string, boolean, number)\n * - key: [item1, item2] (inline array)\n * - key:\\n - item (block sequence for simple values)\n * - nested-block:\\n (nested block as raw sub-object \u2014 parsed recursively)\n * - Quoted strings: \"...\" and '...'\n * - Comments: # ...\n *\n * Originally lived inline in src/patterns/loader.ts; extracted so wiki\n * frontmatter parsing (typed-pages.ts) can reuse it without duplication.\n */\n\nexport function parseYaml(text: string): Record<string, unknown> {\n const lines = text.split('\\n');\n return parseBlock(lines, 0, 0).result;\n}\n\ninterface BlockResult {\n result: Record<string, unknown>;\n nextIndex: number;\n}\n\nfunction parseBlock(lines: string[], startIndex: number, baseIndent: number): BlockResult {\n const result: Record<string, unknown> = {};\n let i = startIndex;\n\n while (i < lines.length) {\n const rawLine = lines[i];\n // Strip trailing comments (but not inside strings \u2014 simplified approach)\n const commentIdx = rawLine.indexOf(' #');\n const line = commentIdx >= 0 ? rawLine.slice(0, commentIdx) : rawLine;\n\n // Skip blank lines\n if (line.trim() === '' || line.trim().startsWith('#')) {\n i++;\n continue;\n }\n\n const indent = countIndent(rawLine);\n\n // If we've de-indented past our base, stop\n if (indent < baseIndent) break;\n\n // Skip lines that are at an indent level below our block's base\n if (indent > baseIndent && Object.keys(result).length === 0) {\n // Haven't started the block yet \u2014 shouldn't happen but skip\n i++;\n continue;\n }\n\n if (indent > baseIndent) {\n // This belongs to a child block already being processed \u2014 stop\n break;\n }\n\n const trimmed = line.trim();\n\n // Detect key: ... pattern\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx <= 0) {\n i++;\n continue;\n }\n\n const key = trimmed.slice(0, colonIdx).trim();\n const rest = trimmed.slice(colonIdx + 1).trimStart();\n\n if (rest === '' || rest === '|' || rest === '>') {\n // Value is on the following lines \u2014 could be a nested block or block scalar\n // Peek ahead: if next non-empty line is indented more, recurse\n const nextContentLine = findNextContent(lines, i + 1);\n if (nextContentLine === -1) {\n result[key] = null;\n i++;\n continue;\n }\n const nextIndent = countIndent(lines[nextContentLine]);\n if (nextIndent > indent) {\n // Check if next line is a sequence item (- ...)\n if (lines[nextContentLine].trim().startsWith('- ')) {\n const { items, nextIndex } = parseSequence(lines, nextContentLine, nextIndent);\n result[key] = items;\n i = nextIndex;\n } else {\n const { result: subResult, nextIndex } = parseBlock(lines, nextContentLine, nextIndent);\n result[key] = subResult;\n i = nextIndex;\n }\n } else {\n result[key] = null;\n i++;\n }\n } else if (rest.startsWith('[')) {\n // Inline array: [a, b, c]\n result[key] = parseInlineArray(rest);\n i++;\n } else {\n // Scalar value\n result[key] = parseScalar(rest);\n i++;\n }\n }\n\n return { result, nextIndex: i };\n}\n\nfunction parseSequence(lines: string[], startIndex: number, baseIndent: number): { items: unknown[]; nextIndex: number } {\n const items: unknown[] = [];\n let i = startIndex;\n\n while (i < lines.length) {\n const rawLine = lines[i];\n if (rawLine.trim() === '' || rawLine.trim().startsWith('#')) {\n i++;\n continue;\n }\n const indent = countIndent(rawLine);\n if (indent < baseIndent) break;\n\n const trimmed = rawLine.trim();\n if (!trimmed.startsWith('- ') && trimmed !== '-') {\n break;\n }\n\n const itemValue = trimmed.startsWith('- ') ? trimmed.slice(2).trim() : '';\n\n if (itemValue === '' || itemValue === '|' || itemValue === '>') {\n // Multi-line sequence item\n const nextContentLine = findNextContent(lines, i + 1);\n if (nextContentLine !== -1 && countIndent(lines[nextContentLine]) > indent) {\n const { result: subResult, nextIndex } = parseBlock(lines, nextContentLine, countIndent(lines[nextContentLine]));\n items.push(subResult);\n i = nextIndex;\n } else {\n items.push(null);\n i++;\n }\n } else if (itemValue.includes(':')) {\n // Inline key:value item \u2014 parse as sub-object, collecting any indented continuation lines\n const itemIndent = indent + 2;\n const continuationLines: string[] = [];\n let j = i + 1;\n while (j < lines.length) {\n const nextRaw = lines[j];\n if (nextRaw.trim() === '' || nextRaw.trim().startsWith('#')) { j++; continue; }\n if (countIndent(nextRaw) >= itemIndent) {\n continuationLines.push(nextRaw);\n j++;\n } else {\n break;\n }\n }\n const blockLines = [' '.repeat(itemIndent) + itemValue, ...continuationLines];\n const { result: subResult } = parseBlock(blockLines, 0, itemIndent);\n items.push(subResult);\n i = j;\n } else {\n items.push(parseScalar(itemValue));\n i++;\n }\n }\n\n return { items, nextIndex: i };\n}\n\nfunction parseInlineArray(text: string): unknown[] {\n const inner = text.replace(/^\\[/, '').replace(/\\].*$/, '');\n if (inner.trim() === '') return [];\n return inner.split(',').map(s => parseScalar(s.trim()));\n}\n\nfunction parseScalar(text: string): unknown {\n if (text === 'true') return true;\n if (text === 'false') return false;\n if (text === 'null' || text === '~') return null;\n if (/^-?\\d+$/.test(text)) return parseInt(text, 10);\n if (/^-?\\d+\\.\\d+$/.test(text)) return parseFloat(text);\n // Quoted string\n if ((text.startsWith('\"') && text.endsWith('\"')) || (text.startsWith(\"'\") && text.endsWith(\"'\"))) {\n return text.slice(1, -1);\n }\n return text;\n}\n\nfunction countIndent(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') i++;\n return i;\n}\n\nfunction findNextContent(lines: string[], startIndex: number): number {\n for (let i = startIndex; i < lines.length; i++) {\n const t = lines[i].trim();\n if (t !== '' && !t.startsWith('#')) return i;\n }\n return -1;\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,11 +38,15 @@ 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);
|
|
44
46
|
this.wikiDir = path.join(kirographDir, "wiki");
|
|
47
|
+
this.wikiSchemasDir = path.join(kirographDir, "wiki-schemas");
|
|
45
48
|
this.autoResolveConflicts = opts.autoResolveConflicts ?? false;
|
|
49
|
+
this.typedPages = opts.typedPages ?? false;
|
|
46
50
|
}
|
|
47
51
|
initialize() {
|
|
48
52
|
this.wikiDb.initialize();
|
|
@@ -117,7 +121,18 @@ class KiroGraphWiki {
|
|
|
117
121
|
// ── Lint ───────────────────────────────────────────────────────────────────
|
|
118
122
|
lint() {
|
|
119
123
|
this.initialize();
|
|
120
|
-
return (0, import_lint.lintWiki)(this.wikiDb);
|
|
124
|
+
return (0, import_lint.lintWiki)(this.wikiDb, this.typedPages ? { wikiSchemasDir: this.wikiSchemasDir } : {});
|
|
125
|
+
}
|
|
126
|
+
// ── Links ──────────────────────────────────────────────────────────────────
|
|
127
|
+
/** Outgoing/broken/incoming `[[slug]]` links for one page. Null if the slug is unknown. */
|
|
128
|
+
getLinks(slug) {
|
|
129
|
+
this.initialize();
|
|
130
|
+
return (0, import_links.getPageLinks)(this.wikiDb, slug);
|
|
131
|
+
}
|
|
132
|
+
/** Rename a page's slug, moving its file and rewriting every `[[slug]]` reference to it. */
|
|
133
|
+
rename(fromSlug, toSlug) {
|
|
134
|
+
this.initialize();
|
|
135
|
+
return (0, import_rename.renamePage)(this.wikiDir, this.wikiDb, fromSlug, toSlug);
|
|
121
136
|
}
|
|
122
137
|
// ── Reindex ────────────────────────────────────────────────────────────────
|
|
123
138
|
reindex() {
|
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 wikiSchemasDir: string;\n private autoResolveConflicts: boolean;\n private typedPages: boolean;\n\n constructor(\n db: any,\n kirographDir: string,\n opts: { autoResolveConflicts?: boolean; typedPages?: boolean } = {}\n ) {\n this.wikiDb = new WikiDatabase(db);\n this.wikiDir = path.join(kirographDir, 'wiki');\n this.wikiSchemasDir = path.join(kirographDir, 'wiki-schemas');\n this.autoResolveConflicts = opts.autoResolveConflicts ?? false;\n this.typedPages = opts.typedPages ?? 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, this.typedPages ? { wikiSchemasDir: this.wikiSchemasDir } : {});\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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,SAAoB;AACpB,WAAsB;AACtB,sBAA6B;AAC7B,oBAA+B;AAC/B,IAAAA,iBAA8B;AAC9B,oBAA8E;AAC9E,kBAAyB;AACzB,mBAAiD;AACjD,oBAAkD;AAW3C,MAAM,cAAc;AAAA,EAOzB,YACE,IACA,cACA,OAAiE,CAAC,GAClE;AACA,SAAK,SAAS,IAAI,6BAAa,EAAE;AACjC,SAAK,UAAU,KAAK,KAAK,cAAc,MAAM;AAC7C,SAAK,iBAAiB,KAAK,KAAK,cAAc,cAAc;AAC5D,SAAK,uBAAuB,KAAK,wBAAwB;AACzD,SAAK,aAAa,KAAK,cAAc;AAAA,EACvC;AAAA,EAEA,aAAmB;AACjB,SAAK,OAAO,WAAW;AACvB,OAAG,UAAU,KAAK,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAChD;AAAA;AAAA,EAIA,WAAiB;AACf,SAAK,WAAW;AAChB,UAAM,aAAa,KAAK,KAAK,KAAK,SAAS,WAAW;AACtD,QAAI,CAAC,GAAG,WAAW,UAAU,GAAG;AAC9B,SAAG,cAAc,YAAY,8BAAgB,MAAM;AAAA,IACrD;AACA,UAAM,eAAe,KAAK,KAAK,KAAK,SAAS,aAAa;AAC1D,QAAI,CAAC,GAAG,WAAW,YAAY,GAAG;AAChC,SAAG,cAAc,cAAc,gCAAgC,MAAM;AAAA,IACvE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgB,QAAgB,aAAa,UAAkB;AAC7D,SAAK,WAAW;AAChB,eAAO,iCAAkB,KAAK,SAAS,QAAQ,UAAU;AAAA,EAC3D;AAAA;AAAA,EAIA,YAAY,YAAoB,YAA0B;AACxD,SAAK,WAAW;AAChB,SAAK,OAAO,YAAY,YAAY,UAAU;AAAA,EAChD;AAAA,EAEA,kBAAiF;AAC/E,SAAK,WAAW;AAChB,WAAO,KAAK,OAAO,gBAAgB;AAAA,EACrC;AAAA,EAEA,aAAmB;AACjB,SAAK,WAAW;AAChB,SAAK,OAAO,WAAW;AAAA,EACzB;AAAA,EAEA,gBAAwB;AACtB,SAAK,WAAW;AAChB,WAAO,KAAK,OAAO,cAAc;AAAA,EACnC;AAAA,EAEA,MAAM,WAAW,WAAmB,QAAQ,OAAO;AACjD,SAAK,WAAW;AAChB,UAAM,EAAE,sBAAsB,IAAI,MAAM,QAAQ,QAAQ,EAAE,KAAK,MAAM,QAAQ,iBAAiB,CAAC;AAC/F,WAAO,sBAAsB,MAAM,WAAW,KAAK;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,SAAiB;AACzB,SAAK,WAAW;AAChB,UAAM,WAAO,8BAAc,OAAO;AAClC,eAAO,yBAAU,MAAM,KAAK,SAAS,KAAK,QAAQ;AAAA,MAChD,sBAAsB,KAAK;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,OAAO,OAAe,QAAQ,IAAsB;AAClD,SAAK,WAAW;AAChB,WAAO,KAAK,OAAO,OAAO,OAAO,KAAK;AAAA,EACxC;AAAA,EAEA,QAAQ,MAA+B;AACrC,SAAK,WAAW;AAChB,WAAO,KAAK,OAAO,QAAQ,IAAI;AAAA,EACjC;AAAA,EAEA,YAAwB;AACtB,SAAK,WAAW;AAChB,WAAO,KAAK,OAAO,UAAU;AAAA,EAC/B;AAAA;AAAA,EAIA,OAAwB;AACtB,SAAK,WAAW;AAChB,eAAO,sBAAS,KAAK,QAAQ,KAAK,aAAa,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC,CAAC;AAAA,EAC7F;AAAA;AAAA;AAAA,EAKA,SAAS,MAAoC;AAC3C,SAAK,WAAW;AAChB,eAAO,2BAAa,KAAK,QAAQ,IAAI;AAAA,EACvC;AAAA;AAAA,EAGA,OAAO,UAAkB,QAAkC;AACzD,SAAK,WAAW;AAChB,eAAO,0BAAW,KAAK,SAAS,KAAK,QAAQ,UAAU,MAAM;AAAA,EAC/D;AAAA;AAAA,EAIA,UAAkB;AAChB,SAAK,WAAW;AAChB,eAAO,+BAAgB,KAAK,SAAS,KAAK,MAAM;AAAA,EAClD;AAAA;AAAA,EAIA,WAAsB;AACpB,SAAK,WAAW;AAChB,WAAO,KAAK,OAAO,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgB,OAAe,QAAQ,GAAG,YAAY,KAAiB;AACrE,SAAK,WAAW;AAChB,WAAO,KAAK,OACT,OAAO,OAAO,QAAQ,CAAC,EACvB,OAAO,OAAK,EAAE,SAAS,SAAS,EAChC,MAAM,GAAG,KAAK,EACd,IAAI,OAAK,EAAE,IAAI;AAAA,EACpB;AAAA,EAEA,IAAI,MAAc;AAChB,WAAO,KAAK;AAAA,EACd;AACF;",
|
|
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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/wiki/links.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Link graph\n *\n * Read-only queries over the `[[slug]]` cross-reference graph between wiki\n * pages. Shared by lint.ts (broken-link/orphan detection) and the\n * `kirograph wiki links`/`wiki rename` CLI commands.\n */\n\nimport type { WikiDatabase } from './database';\n\nexport const WIKI_LINK_RE = /\\[\\[([^\\]]+)\\]\\]/g;\n\n/** Distinct slugs referenced via `[[slug]]` in `content`, in order of first appearance. */\nexport function extractLinks(content: string): string[] {\n const seen = new Set<string>();\n const re = new RegExp(WIKI_LINK_RE.source, 'g');\n let m: RegExpExecArray | null;\n while ((m = re.exec(content)) !== null) {\n seen.add(m[1].trim());\n }\n return [...seen];\n}\n\nexport interface WikiPageLinks {\n /** Slugs this page links to that exist. */\n outgoing: string[];\n /** Slugs this page links to that don't exist as a page. */\n broken: string[];\n /** Slugs of other pages that link to this one. */\n incoming: string[];\n}\n\n/**\n * Outgoing/broken/incoming `[[slug]]` links for one page.\n * Returns null if the slug isn't a known page.\n */\nexport function getPageLinks(wikiDb: WikiDatabase, slug: string): WikiPageLinks | null {\n const page = wikiDb.getPage(slug);\n if (!page) return null;\n\n const pages = wikiDb.listPages();\n const slugSet = new Set(pages.map(p => p.slug));\n\n const linked = extractLinks(page.content);\n const outgoing = linked.filter(s => slugSet.has(s));\n const broken = linked.filter(s => !slugSet.has(s));\n\n const incoming = pages\n .filter(p => p.slug !== slug && extractLinks(p.content).includes(slug))\n .map(p => p.slug);\n\n return { outgoing, broken, incoming };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUO,MAAM,eAAe;AAGrB,SAAS,aAAa,SAA2B;AACtD,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,KAAK,IAAI,OAAO,aAAa,QAAQ,GAAG;AAC9C,MAAI;AACJ,UAAQ,IAAI,GAAG,KAAK,OAAO,OAAO,MAAM;AACtC,SAAK,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC;AAAA,EACtB;AACA,SAAO,CAAC,GAAG,IAAI;AACjB;AAeO,SAAS,aAAa,QAAsB,MAAoC;AACrF,QAAM,OAAO,OAAO,QAAQ,IAAI;AAChC,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,QAAQ,OAAO,UAAU;AAC/B,QAAM,UAAU,IAAI,IAAI,MAAM,IAAI,OAAK,EAAE,IAAI,CAAC;AAE9C,QAAM,SAAS,aAAa,KAAK,OAAO;AACxC,QAAM,WAAW,OAAO,OAAO,OAAK,QAAQ,IAAI,CAAC,CAAC;AAClD,QAAM,SAAS,OAAO,OAAO,OAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;AAEjD,QAAM,WAAW,MACd,OAAO,OAAK,EAAE,SAAS,QAAQ,aAAa,EAAE,OAAO,EAAE,SAAS,IAAI,CAAC,EACrE,IAAI,OAAK,EAAE,IAAI;AAElB,SAAO,EAAE,UAAU,QAAQ,SAAS;AACtC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/wiki/lint.js
CHANGED
|
@@ -21,16 +21,26 @@ __export(lint_exports, {
|
|
|
21
21
|
lintWiki: () => lintWiki
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(lint_exports);
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
var import_links = require("./links");
|
|
25
|
+
var import_typed_pages = require("./typed-pages");
|
|
26
|
+
function lintWiki(wikiDb, opts = {}) {
|
|
26
27
|
const issues = [];
|
|
27
28
|
const pages = wikiDb.listPages();
|
|
28
29
|
const slugSet = new Set(pages.map((p) => p.slug));
|
|
29
30
|
for (const page of pages) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
if (opts.wikiSchemasDir) {
|
|
32
|
+
const { type, fields } = (0, import_typed_pages.parseFrontmatter)(page.content);
|
|
33
|
+
if (type) {
|
|
34
|
+
for (const violation of (0, import_typed_pages.validatePageSchema)(fields, type, opts.wikiSchemasDir)) {
|
|
35
|
+
issues.push({
|
|
36
|
+
kind: "schema_error",
|
|
37
|
+
slug: page.slug,
|
|
38
|
+
detail: `[${type}] ${violation}`
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const linked of (0, import_links.extractLinks)(page.content)) {
|
|
34
44
|
if (!slugSet.has(linked)) {
|
|
35
45
|
issues.push({
|
|
36
46
|
kind: "broken_link",
|
|
@@ -41,7 +51,7 @@ function lintWiki(wikiDb) {
|
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
const hasRelated = /^## Related/m.test(page.content);
|
|
44
|
-
const hasIncoming = pages.some((p) => p.slug !== page.slug && p.content.includes(
|
|
54
|
+
const hasIncoming = pages.some((p) => p.slug !== page.slug && (0, import_links.extractLinks)(p.content).includes(page.slug));
|
|
45
55
|
if (!hasRelated && !hasIncoming && pages.length > 1) {
|
|
46
56
|
issues.push({
|
|
47
57
|
kind: "orphan",
|
package/dist/wiki/lint.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/wiki/lint.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Lint\n *\n * Health checks: contradictions (FTS similarity), orphan pages,\n * broken [[slug]] cross-references.\n */\n\nimport * as path from 'path';\nimport type { WikiLintIssue } from './types';\nimport type { WikiDatabase } from './database';\n\
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Lint\n *\n * Health checks: contradictions (FTS similarity), orphan pages,\n * broken [[slug]] cross-references.\n */\n\nimport * as path from 'path';\nimport type { WikiLintIssue } from './types';\nimport type { WikiDatabase } from './database';\nimport { extractLinks } from './links';\nimport { parseFrontmatter, validatePageSchema } from './typed-pages';\n\nexport interface LintWikiOptions {\n /**\n * When set, pages declaring `_type` in their frontmatter are validated\n * against `.kirograph/wiki-schemas/<type>.schema.json` (wikiTypedPages).\n */\n wikiSchemasDir?: string;\n}\n\nexport function lintWiki(wikiDb: WikiDatabase, opts: LintWikiOptions = {}): WikiLintIssue[] {\n const issues: WikiLintIssue[] = [];\n const pages = wikiDb.listPages();\n const slugSet = new Set(pages.map(p => p.slug));\n\n for (const page of pages) {\n // Typed-page schema validation (opt-in via wikiTypedPages)\n if (opts.wikiSchemasDir) {\n const { type, fields } = parseFrontmatter(page.content);\n if (type) {\n for (const violation of validatePageSchema(fields, type, opts.wikiSchemasDir)) {\n issues.push({\n kind: 'schema_error',\n slug: page.slug,\n detail: `[${type}] ${violation}`,\n });\n }\n }\n }\n\n // Broken [[slug]] links\n for (const linked of extractLinks(page.content)) {\n if (!slugSet.has(linked)) {\n issues.push({\n kind: 'broken_link',\n slug: page.slug,\n detail: `Broken link to [[${linked}]] \u2014 page does not exist`,\n relatedSlug: linked,\n });\n }\n }\n\n // Orphan: no ## Related section and no incoming links from other pages\n const hasRelated = /^## Related/m.test(page.content);\n const hasIncoming = pages.some(p => p.slug !== page.slug && extractLinks(p.content).includes(page.slug));\n if (!hasRelated && !hasIncoming && pages.length > 1) {\n issues.push({\n kind: 'orphan',\n slug: page.slug,\n detail: 'Page has no ## Related section and no incoming links from other pages',\n });\n }\n\n // Stale: ## Sources section present but no date found (can't verify freshness)\n const sourcesMatch = page.content.match(/## Sources\\n([\\s\\S]*?)(?=\\n## |\\n*$)/);\n if (sourcesMatch) {\n const sourcesBlock = sourcesMatch[1];\n const hasDates = /\\d{4}-\\d{2}-\\d{2}/.test(sourcesBlock);\n if (!hasDates) {\n issues.push({\n kind: 'stale',\n slug: page.slug,\n detail: '## Sources section has no dates \u2014 cannot verify freshness',\n });\n }\n }\n\n // Contradictions: FTS similarity with other pages on the same topic\n // Simple heuristic: search for the page title and flag pages with conflicting signals\n const similar = wikiDb.search(page.title, 5);\n for (const { page: other } of similar) {\n if (other.slug === page.slug) continue;\n // Check for negation keywords close to shared terms\n const contradictionSignals = ['instead of', 'not', 'replaced by', 'superseded', 'deprecated'];\n const bothMentionSignal = contradictionSignals.some(\n sig => page.content.toLowerCase().includes(sig) && other.content.toLowerCase().includes(sig)\n );\n if (bothMentionSignal) {\n const key = [page.slug, other.slug].sort().join('|');\n const alreadyReported = issues.some(\n i => i.kind === 'contradiction' && i.detail.includes(other.slug)\n );\n if (!alreadyReported) {\n issues.push({\n kind: 'contradiction',\n slug: page.slug,\n detail: `Possible contradiction with [[${other.slug}]] \u2014 both pages contain negation signals on shared topics`,\n relatedSlug: other.slug,\n });\n }\n }\n }\n }\n\n return issues;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,mBAA6B;AAC7B,yBAAqD;AAU9C,SAAS,SAAS,QAAsB,OAAwB,CAAC,GAAoB;AAC1F,QAAM,SAA0B,CAAC;AACjC,QAAM,QAAQ,OAAO,UAAU;AAC/B,QAAM,UAAU,IAAI,IAAI,MAAM,IAAI,OAAK,EAAE,IAAI,CAAC;AAE9C,aAAW,QAAQ,OAAO;AAExB,QAAI,KAAK,gBAAgB;AACvB,YAAM,EAAE,MAAM,OAAO,QAAI,qCAAiB,KAAK,OAAO;AACtD,UAAI,MAAM;AACR,mBAAW,iBAAa,uCAAmB,QAAQ,MAAM,KAAK,cAAc,GAAG;AAC7E,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,MAAM,KAAK;AAAA,YACX,QAAQ,IAAI,IAAI,KAAK,SAAS;AAAA,UAChC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAGA,eAAW,cAAU,2BAAa,KAAK,OAAO,GAAG;AAC/C,UAAI,CAAC,QAAQ,IAAI,MAAM,GAAG;AACxB,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,UACX,QAAQ,oBAAoB,MAAM;AAAA,UAClC,aAAa;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF;AAGA,UAAM,aAAa,eAAe,KAAK,KAAK,OAAO;AACnD,UAAM,cAAc,MAAM,KAAK,OAAK,EAAE,SAAS,KAAK,YAAQ,2BAAa,EAAE,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC;AACvG,QAAI,CAAC,cAAc,CAAC,eAAe,MAAM,SAAS,GAAG;AACnD,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAGA,UAAM,eAAe,KAAK,QAAQ,MAAM,sCAAsC;AAC9E,QAAI,cAAc;AAChB,YAAM,eAAe,aAAa,CAAC;AACnC,YAAM,WAAW,oBAAoB,KAAK,YAAY;AACtD,UAAI,CAAC,UAAU;AACb,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,UACX,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AAIA,UAAM,UAAU,OAAO,OAAO,KAAK,OAAO,CAAC;AAC3C,eAAW,EAAE,MAAM,MAAM,KAAK,SAAS;AACrC,UAAI,MAAM,SAAS,KAAK,KAAM;AAE9B,YAAM,uBAAuB,CAAC,cAAc,OAAO,eAAe,cAAc,YAAY;AAC5F,YAAM,oBAAoB,qBAAqB;AAAA,QAC7C,SAAO,KAAK,QAAQ,YAAY,EAAE,SAAS,GAAG,KAAK,MAAM,QAAQ,YAAY,EAAE,SAAS,GAAG;AAAA,MAC7F;AACA,UAAI,mBAAmB;AACrB,cAAM,MAAM,CAAC,KAAK,MAAM,MAAM,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG;AACnD,cAAM,kBAAkB,OAAO;AAAA,UAC7B,OAAK,EAAE,SAAS,mBAAmB,EAAE,OAAO,SAAS,MAAM,IAAI;AAAA,QACjE;AACA,YAAI,CAAC,iBAAiB;AACpB,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,MAAM,KAAK;AAAA,YACX,QAAQ,iCAAiC,MAAM,IAAI;AAAA,YACnD,aAAa,MAAM;AAAA,UACrB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|