project-librarian 0.3.0 → 0.4.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.ko.md +42 -11
- package/README.md +39 -9
- package/SKILL.md +1 -0
- package/dist/args.js +20 -3
- package/dist/code-index/evidence.js +79 -0
- package/dist/code-index/extractors/config.js +58 -0
- package/dist/code-index/extractors/light-languages.js +52 -0
- package/dist/code-index/extractors/registry.js +137 -0
- package/dist/code-index/extractors/shared.js +36 -0
- package/dist/code-index/extractors/tree-sitter.js +319 -0
- package/dist/code-index/extractors/types.js +2 -0
- package/dist/code-index/extractors/typescript.js +211 -0
- package/dist/code-index/incremental.js +16 -0
- package/dist/code-index/index-health.js +164 -0
- package/dist/code-index/modes.js +303 -0
- package/dist/code-index/ownership.js +183 -0
- package/dist/code-index/reports.js +322 -0
- package/dist/code-index/schema.js +196 -0
- package/dist/code-index/search.js +153 -0
- package/dist/code-index-file-policy.js +21 -27
- package/dist/code-index.js +224 -1657
- package/dist/init-project-wiki.js +65 -37
- package/dist/mcp-server.js +325 -11
- package/dist/migration.js +10 -6
- package/dist/modes.js +139 -54
- package/dist/path-ignore-policy.js +30 -0
- package/dist/retrieval-eval.js +68 -0
- package/dist/wiki-concepts.js +49 -0
- package/dist/wiki-corpus.js +25 -0
- package/dist/wiki-diagnostics.js +19 -0
- package/dist/wiki-files.js +107 -1
- package/dist/wiki-graph.js +26 -2
- package/dist/wiki-visualizer.js +558 -0
- package/package.json +11 -4
package/dist/wiki-files.js
CHANGED
|
@@ -37,6 +37,8 @@ exports.ignoredDirs = exports.standardWikiFiles = void 0;
|
|
|
37
37
|
exports.walkMarkdownFiles = walkMarkdownFiles;
|
|
38
38
|
exports.firstHeading = firstHeading;
|
|
39
39
|
exports.compactSummary = compactSummary;
|
|
40
|
+
exports.markdownBlockSnippet = markdownBlockSnippet;
|
|
41
|
+
exports.extractMarkdownBlocks = extractMarkdownBlocks;
|
|
40
42
|
exports.splitMarkdownRow = splitMarkdownRow;
|
|
41
43
|
exports.parseMarkdownTableRows = parseMarkdownTableRows;
|
|
42
44
|
exports.wikiMarkdownFiles = wikiMarkdownFiles;
|
|
@@ -52,6 +54,7 @@ exports.firstTldrBullet = firstTldrBullet;
|
|
|
52
54
|
exports.canonicalBodyForLint = canonicalBodyForLint;
|
|
53
55
|
const fs = __importStar(require("node:fs"));
|
|
54
56
|
const path = __importStar(require("node:path"));
|
|
57
|
+
const path_ignore_policy_1 = require("./path-ignore-policy");
|
|
55
58
|
const workspace_1 = require("./workspace");
|
|
56
59
|
exports.standardWikiFiles = new Set([
|
|
57
60
|
"AGENTS.md",
|
|
@@ -97,7 +100,7 @@ exports.standardWikiFiles = new Set([
|
|
|
97
100
|
"tools/project-librarian/agents/openai.yaml",
|
|
98
101
|
"tools/project-librarian/dist/init-project-wiki.js",
|
|
99
102
|
]);
|
|
100
|
-
exports.ignoredDirs =
|
|
103
|
+
exports.ignoredDirs = (0, path_ignore_policy_1.ignoredDirectorySet)();
|
|
101
104
|
function walkMarkdownFiles(dir = workspace_1.root, acc = [], baseDir = workspace_1.root) {
|
|
102
105
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
103
106
|
const fullPath = path.join(dir, entry.name);
|
|
@@ -134,6 +137,109 @@ function compactSummary(text) {
|
|
|
134
137
|
.trim()
|
|
135
138
|
.slice(0, 180);
|
|
136
139
|
}
|
|
140
|
+
function slugForBlockId(value) {
|
|
141
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 48) || "block";
|
|
142
|
+
}
|
|
143
|
+
function isMarkdownHeading(line) {
|
|
144
|
+
return line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
|
|
145
|
+
}
|
|
146
|
+
function isMarkdownTableSeparator(cells) {
|
|
147
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell.replace(/\s/g, "")));
|
|
148
|
+
}
|
|
149
|
+
function normalizeBlockText(text) {
|
|
150
|
+
return text.replace(/\s+/g, " ").trim();
|
|
151
|
+
}
|
|
152
|
+
function blockId(kind, line, text) {
|
|
153
|
+
return `${kind}:${line}:${slugForBlockId(text)}`;
|
|
154
|
+
}
|
|
155
|
+
function markdownBlockSnippet(block, maxLength = 180) {
|
|
156
|
+
const prefix = block.headingPath.length > 0 && block.kind !== "heading" ? `${block.headingPath.join(" > ")}: ` : "";
|
|
157
|
+
return `${prefix}${normalizeBlockText(block.text)}`.slice(0, maxLength);
|
|
158
|
+
}
|
|
159
|
+
function extractMarkdownBlocks(text) {
|
|
160
|
+
const body = (0, workspace_1.stripMetadataHeader)(text);
|
|
161
|
+
const lines = body.split(/\r?\n/);
|
|
162
|
+
const blocks = [];
|
|
163
|
+
const headingPath = [];
|
|
164
|
+
let paragraph = null;
|
|
165
|
+
let fence = null;
|
|
166
|
+
function addBlock(kind, line, blockText) {
|
|
167
|
+
const normalized = normalizeBlockText(blockText);
|
|
168
|
+
if (!normalized)
|
|
169
|
+
return;
|
|
170
|
+
blocks.push({
|
|
171
|
+
headingPath: [...headingPath],
|
|
172
|
+
id: blockId(kind, line, normalized),
|
|
173
|
+
kind,
|
|
174
|
+
line,
|
|
175
|
+
text: normalized,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function flushParagraph() {
|
|
179
|
+
if (!paragraph)
|
|
180
|
+
return;
|
|
181
|
+
addBlock("paragraph", paragraph.line, paragraph.lines.join(" "));
|
|
182
|
+
paragraph = null;
|
|
183
|
+
}
|
|
184
|
+
lines.forEach((line, index) => {
|
|
185
|
+
const lineNumber = index + 1;
|
|
186
|
+
const trimmed = line.trim();
|
|
187
|
+
const fenceMatch = trimmed.match(/^(```+|~~~+)\s*(.*)$/);
|
|
188
|
+
if (fence) {
|
|
189
|
+
const closingFence = fenceMatch?.[1] ?? "";
|
|
190
|
+
if (closingFence.startsWith(fence.fence.slice(0, 3)) && closingFence.length >= fence.fence.length) {
|
|
191
|
+
const sample = fence.lines.map((item) => item.trim()).filter(Boolean).slice(0, 3).join(" ");
|
|
192
|
+
addBlock("code_fence", fence.line, `code fence${fence.lang ? ` ${fence.lang}` : ""}: ${sample}`);
|
|
193
|
+
fence = null;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
fence.lines.push(line);
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (fenceMatch) {
|
|
201
|
+
flushParagraph();
|
|
202
|
+
fence = { fence: fenceMatch[1] ?? "```", lang: (fenceMatch[2] ?? "").trim(), line: lineNumber, lines: [] };
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (!trimmed) {
|
|
206
|
+
flushParagraph();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const heading = isMarkdownHeading(line);
|
|
210
|
+
if (heading?.[1] && heading[2]) {
|
|
211
|
+
flushParagraph();
|
|
212
|
+
const level = heading[1].length;
|
|
213
|
+
const title = heading[2].trim();
|
|
214
|
+
headingPath.splice(level - 1);
|
|
215
|
+
headingPath[level - 1] = title;
|
|
216
|
+
addBlock("heading", lineNumber, title);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (/^\s{0,3}([-*+]|\d+\.)\s+\S/.test(line)) {
|
|
220
|
+
flushParagraph();
|
|
221
|
+
addBlock("list_item", lineNumber, trimmed);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (/^\|.+\|$/.test(trimmed)) {
|
|
225
|
+
flushParagraph();
|
|
226
|
+
const cells = splitMarkdownRow(line);
|
|
227
|
+
if (!isMarkdownTableSeparator(cells))
|
|
228
|
+
addBlock("table_row", lineNumber, cells.join(" | "));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (!paragraph)
|
|
232
|
+
paragraph = { line: lineNumber, lines: [] };
|
|
233
|
+
paragraph.lines.push(trimmed);
|
|
234
|
+
});
|
|
235
|
+
const unclosedFence = fence;
|
|
236
|
+
if (unclosedFence) {
|
|
237
|
+
const sample = unclosedFence.lines.map((item) => item.trim()).filter(Boolean).slice(0, 3).join(" ");
|
|
238
|
+
addBlock("code_fence", unclosedFence.line, `code fence${unclosedFence.lang ? ` ${unclosedFence.lang}` : ""}: ${sample}`);
|
|
239
|
+
}
|
|
240
|
+
flushParagraph();
|
|
241
|
+
return blocks;
|
|
242
|
+
}
|
|
137
243
|
function splitMarkdownRow(line) {
|
|
138
244
|
const trimmed = line.trim();
|
|
139
245
|
const row = trimmed.replace(/^\|/, "").replace(/\|$/, "");
|
package/dist/wiki-graph.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.wikiAnswerTruncationNotice = exports.wikiAnswerCharCap = exports.wikiRou
|
|
|
4
4
|
exports.finalizeWikiAnswer = finalizeWikiAnswer;
|
|
5
5
|
exports.buildWikiGraph = buildWikiGraph;
|
|
6
6
|
exports.wikiRouterDepths = wikiRouterDepths;
|
|
7
|
+
exports.wikiQueryGraphEvidence = wikiQueryGraphEvidence;
|
|
7
8
|
exports.wikiImpactAnswer = wikiImpactAnswer;
|
|
8
9
|
const wiki_files_1 = require("./wiki-files");
|
|
9
10
|
const workspace_1 = require("./workspace");
|
|
@@ -99,8 +100,31 @@ function uniqueSorted(values) {
|
|
|
99
100
|
function plural(count, noun) {
|
|
100
101
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
101
102
|
}
|
|
102
|
-
function
|
|
103
|
-
const
|
|
103
|
+
function wikiQueryGraphEvidence(graph, file, depths = wikiRouterDepths(graph), listCap = 3) {
|
|
104
|
+
const outgoing = uniqueSorted((graph.outgoingLinks.get(file) ?? [])
|
|
105
|
+
.map((link) => link.normalizedTarget)
|
|
106
|
+
.filter((target) => target !== file && graph.files.has(target)));
|
|
107
|
+
const incoming = uniqueSorted((graph.incomingLinks.get(file) ?? [])
|
|
108
|
+
.map((link) => link.file)
|
|
109
|
+
.filter((source) => source !== file && graph.files.has(source)));
|
|
110
|
+
const incomingRefs = uniqueSorted((graph.incomingDecisionRefs.get(file) ?? [])
|
|
111
|
+
.filter((source) => source !== file && graph.files.has(source)));
|
|
112
|
+
const outgoingRef = graph.outgoingDecisionRef.get(file);
|
|
113
|
+
const depth = depths.get(file);
|
|
114
|
+
const parts = [
|
|
115
|
+
depth === undefined ? `router unreachable from ${exports.wikiRouterRoot}` : `router depth ${depth}`,
|
|
116
|
+
];
|
|
117
|
+
if (outgoing.length > 0)
|
|
118
|
+
parts.push(`links-out ${outgoing.length}: ${sampled(outgoing, listCap)}`);
|
|
119
|
+
if (outgoingRef && outgoingRef !== file && graph.files.has(outgoingRef))
|
|
120
|
+
parts.push(`decision_ref-> ${outgoingRef}`);
|
|
121
|
+
if (incoming.length > 0)
|
|
122
|
+
parts.push(`linked-by ${incoming.length}: ${sampled(incoming, listCap)}`);
|
|
123
|
+
if (incomingRefs.length > 0)
|
|
124
|
+
parts.push(`decision_ref-by ${incomingRefs.length}: ${sampled(incomingRefs, listCap)}`);
|
|
125
|
+
return parts.join("; ");
|
|
126
|
+
}
|
|
127
|
+
function wikiImpactAnswer(pages, term, graph = buildWikiGraph(pages)) {
|
|
104
128
|
const depths = wikiRouterDepths(graph);
|
|
105
129
|
const textByFile = new Map(pages.map((page) => [page.file, page.text]));
|
|
106
130
|
const lowered = term.toLowerCase();
|