project-librarian 0.3.0 → 0.4.0
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 +25 -9
- package/README.md +25 -9
- package/SKILL.md +1 -0
- package/dist/args.js +17 -3
- package/dist/code-index.js +268 -18
- package/dist/init-project-wiki.js +34 -4
- package/dist/mcp-server.js +266 -6
- package/dist/modes.js +69 -15
- package/dist/retrieval-eval.js +68 -0
- package/dist/wiki-concepts.js +49 -0
- package/dist/wiki-files.js +105 -0
- package/dist/wiki-graph.js +25 -0
- package/dist/wiki-visualizer.js +558 -0
- package/package.json +3 -2
package/dist/code-index.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CodeEvidenceIndexUnavailableError = void 0;
|
|
36
|
+
exports.CodeEvidenceIndexUnavailableError = exports.codeContextPackTruncationNotice = exports.codeContextPackCharCap = void 0;
|
|
37
37
|
exports.codeIndexStaleness = codeIndexStaleness;
|
|
38
38
|
exports.codeownerRules = codeownerRules;
|
|
39
39
|
exports.matchedCodeownerRules = matchedCodeownerRules;
|
|
@@ -43,8 +43,10 @@ exports.evidenceCoverage = evidenceCoverage;
|
|
|
43
43
|
exports.workspaceSummary = workspaceSummary;
|
|
44
44
|
exports.workspaceDependencyGraph = workspaceDependencyGraph;
|
|
45
45
|
exports.codeReportMetadata = codeReportMetadata;
|
|
46
|
-
exports.codeImpact = codeImpact;
|
|
47
46
|
exports.searchSymbols = searchSymbols;
|
|
47
|
+
exports.codeImpact = codeImpact;
|
|
48
|
+
exports.codeContextPack = codeContextPack;
|
|
49
|
+
exports.codeIndexSnapshot = codeIndexSnapshot;
|
|
48
50
|
exports.openCodeEvidenceDatabaseForServing = openCodeEvidenceDatabaseForServing;
|
|
49
51
|
exports.runCodeIndexMode = runCodeIndexMode;
|
|
50
52
|
exports.runCodeQueryMode = runCodeQueryMode;
|
|
@@ -52,6 +54,7 @@ exports.runCodeReportMode = runCodeReportMode;
|
|
|
52
54
|
exports.runCodeStatusMode = runCodeStatusMode;
|
|
53
55
|
exports.runCodeFilesMode = runCodeFilesMode;
|
|
54
56
|
exports.runCodeImpactMode = runCodeImpactMode;
|
|
57
|
+
exports.runCodeContextPackMode = runCodeContextPackMode;
|
|
55
58
|
exports.runCodeSearchSymbolMode = runCodeSearchSymbolMode;
|
|
56
59
|
exports.isCodeEvidenceMode = isCodeEvidenceMode;
|
|
57
60
|
exports.isCodeEvidenceModeFor = isCodeEvidenceModeFor;
|
|
@@ -64,6 +67,8 @@ const code_index_db_1 = require("./code-index-db");
|
|
|
64
67
|
const code_index_file_policy_1 = require("./code-index-file-policy");
|
|
65
68
|
const code_index_sql_1 = require("./code-index-sql");
|
|
66
69
|
const workspace_1 = require("./workspace");
|
|
70
|
+
exports.codeContextPackCharCap = 4000;
|
|
71
|
+
exports.codeContextPackTruncationNotice = "[truncated - refine the query]";
|
|
67
72
|
const codeIndexSchemaVersion = "3";
|
|
68
73
|
const httpMethods = new Set(["all", "delete", "get", "patch", "post", "put"]);
|
|
69
74
|
const treeSitterGrammarPackages = {
|
|
@@ -1576,14 +1581,138 @@ function codeReportForRequestedSection(database) {
|
|
|
1576
1581
|
data: codeReportSectionData(database, section),
|
|
1577
1582
|
};
|
|
1578
1583
|
}
|
|
1584
|
+
function escapeLikeTerm(term) {
|
|
1585
|
+
return term.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
1586
|
+
}
|
|
1587
|
+
function containsLikePattern(term) {
|
|
1588
|
+
return `%${escapeLikeTerm(term)}%`;
|
|
1589
|
+
}
|
|
1590
|
+
function prefixLikePattern(term) {
|
|
1591
|
+
return `${escapeLikeTerm(term)}%`;
|
|
1592
|
+
}
|
|
1593
|
+
function ftsPrefixQuery(term) {
|
|
1594
|
+
const tokens = Array.from(new Set(term.match(/[\p{L}\p{N}_]+/gu) ?? []));
|
|
1595
|
+
return tokens.slice(0, 8).map((token) => `"${token.replace(/"/g, "\"\"")}"*`).join(" AND ");
|
|
1596
|
+
}
|
|
1597
|
+
function stringValue(row, key) {
|
|
1598
|
+
const value = row[key];
|
|
1599
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : "";
|
|
1600
|
+
}
|
|
1601
|
+
function addRankedRow(rowsByKey, row, key, score) {
|
|
1602
|
+
const current = rowsByKey.get(key);
|
|
1603
|
+
if (!current || score > current.score)
|
|
1604
|
+
rowsByKey.set(key, { row, score });
|
|
1605
|
+
}
|
|
1606
|
+
function rankedRows(rowsByKey, limit, stableKeys) {
|
|
1607
|
+
return Array.from(rowsByKey.values())
|
|
1608
|
+
.sort((left, right) => {
|
|
1609
|
+
const scoreDelta = right.score - left.score;
|
|
1610
|
+
if (scoreDelta !== 0)
|
|
1611
|
+
return scoreDelta;
|
|
1612
|
+
for (const key of stableKeys) {
|
|
1613
|
+
const compared = stringValue(left.row, key).localeCompare(stringValue(right.row, key));
|
|
1614
|
+
if (compared !== 0)
|
|
1615
|
+
return compared;
|
|
1616
|
+
}
|
|
1617
|
+
return 0;
|
|
1618
|
+
})
|
|
1619
|
+
.slice(0, limit)
|
|
1620
|
+
.map((ranked) => ranked.row);
|
|
1621
|
+
}
|
|
1622
|
+
function searchFiles(database, term, limit = 25) {
|
|
1623
|
+
const normalized = term.trim();
|
|
1624
|
+
if (!normalized)
|
|
1625
|
+
return [];
|
|
1626
|
+
const contains = containsLikePattern(normalized);
|
|
1627
|
+
const prefix = prefixLikePattern(normalized);
|
|
1628
|
+
const rowsByKey = new Map();
|
|
1629
|
+
const exactRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path = ? ORDER BY path LIMIT ?").all(normalized, limit);
|
|
1630
|
+
exactRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 900));
|
|
1631
|
+
const prefixRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path LIKE ? ESCAPE '\\' ORDER BY path LIMIT ?").all(prefix, limit);
|
|
1632
|
+
prefixRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 750));
|
|
1633
|
+
const ftsQuery = ftsPrefixQuery(normalized);
|
|
1634
|
+
if (ftsQuery) {
|
|
1635
|
+
const ftsRows = database.prepare(`
|
|
1636
|
+
SELECT files.path, files.language, files.profile, files.lines, files.bytes
|
|
1637
|
+
FROM files_fts
|
|
1638
|
+
JOIN files ON files.path = files_fts.path
|
|
1639
|
+
WHERE files_fts MATCH ?
|
|
1640
|
+
ORDER BY bm25(files_fts, 8.0, 1.0, 1.0, 0.25), files.path
|
|
1641
|
+
LIMIT ?
|
|
1642
|
+
`).all(ftsQuery, limit);
|
|
1643
|
+
ftsRows.forEach((row, index) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 650 - index));
|
|
1644
|
+
}
|
|
1645
|
+
const containsRows = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path LIKE ? ESCAPE '\\' ORDER BY path LIMIT ?").all(contains, limit);
|
|
1646
|
+
containsRows.forEach((row) => addRankedRow(rowsByKey, row, stringValue(row, "path"), 500));
|
|
1647
|
+
return rankedRows(rowsByKey, limit, ["path"]);
|
|
1648
|
+
}
|
|
1649
|
+
function symbolKey(row) {
|
|
1650
|
+
return [
|
|
1651
|
+
stringValue(row, "file_path"),
|
|
1652
|
+
stringValue(row, "line"),
|
|
1653
|
+
stringValue(row, "kind"),
|
|
1654
|
+
stringValue(row, "name"),
|
|
1655
|
+
stringValue(row, "signature"),
|
|
1656
|
+
].join("\u0000");
|
|
1657
|
+
}
|
|
1658
|
+
function searchSymbols(database, term, limit = 50) {
|
|
1659
|
+
const normalized = term.trim();
|
|
1660
|
+
if (!normalized)
|
|
1661
|
+
return [];
|
|
1662
|
+
const contains = containsLikePattern(normalized);
|
|
1663
|
+
const prefix = prefixLikePattern(normalized);
|
|
1664
|
+
const rowsByKey = new Map();
|
|
1665
|
+
const exactRows = database.prepare(`
|
|
1666
|
+
SELECT name, kind, file_path, line, signature
|
|
1667
|
+
FROM symbols
|
|
1668
|
+
WHERE name = ? OR signature = ?
|
|
1669
|
+
ORDER BY file_path, line
|
|
1670
|
+
LIMIT ?
|
|
1671
|
+
`).all(normalized, normalized, limit);
|
|
1672
|
+
exactRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 1000));
|
|
1673
|
+
const prefixRows = database.prepare(`
|
|
1674
|
+
SELECT name, kind, file_path, line, signature
|
|
1675
|
+
FROM symbols
|
|
1676
|
+
WHERE name LIKE ? ESCAPE '\\' OR signature LIKE ? ESCAPE '\\'
|
|
1677
|
+
ORDER BY file_path, line
|
|
1678
|
+
LIMIT ?
|
|
1679
|
+
`).all(prefix, prefix, limit);
|
|
1680
|
+
prefixRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 850));
|
|
1681
|
+
const ftsQuery = ftsPrefixQuery(normalized);
|
|
1682
|
+
if (ftsQuery) {
|
|
1683
|
+
const ftsRows = database.prepare(`
|
|
1684
|
+
SELECT symbols.name, symbols.kind, symbols.file_path, symbols.line, symbols.signature
|
|
1685
|
+
FROM symbols_fts
|
|
1686
|
+
JOIN symbols
|
|
1687
|
+
ON symbols.name = symbols_fts.name
|
|
1688
|
+
AND symbols.kind = symbols_fts.kind
|
|
1689
|
+
AND symbols.file_path = symbols_fts.file_path
|
|
1690
|
+
AND symbols.signature = symbols_fts.signature
|
|
1691
|
+
WHERE symbols_fts MATCH ?
|
|
1692
|
+
ORDER BY bm25(symbols_fts, 8.0, 1.0, 4.0, 2.0), symbols.file_path, symbols.line
|
|
1693
|
+
LIMIT ?
|
|
1694
|
+
`).all(ftsQuery, limit);
|
|
1695
|
+
ftsRows.forEach((row, index) => addRankedRow(rowsByKey, row, symbolKey(row), 700 - index));
|
|
1696
|
+
}
|
|
1697
|
+
const containsRows = database.prepare(`
|
|
1698
|
+
SELECT name, kind, file_path, line, signature
|
|
1699
|
+
FROM symbols
|
|
1700
|
+
WHERE name LIKE ? ESCAPE '\\' OR signature LIKE ? ESCAPE '\\' OR file_path LIKE ? ESCAPE '\\'
|
|
1701
|
+
ORDER BY file_path, line
|
|
1702
|
+
LIMIT ?
|
|
1703
|
+
`).all(contains, contains, contains, limit);
|
|
1704
|
+
containsRows.forEach((row) => addRankedRow(rowsByKey, row, symbolKey(row), 500));
|
|
1705
|
+
return rankedRows(rowsByKey, limit, ["file_path", "line", "kind", "name", "signature"]);
|
|
1706
|
+
}
|
|
1579
1707
|
function codeImpact(database, target) {
|
|
1580
|
-
const
|
|
1581
|
-
const
|
|
1582
|
-
const
|
|
1583
|
-
const
|
|
1584
|
-
const
|
|
1585
|
-
const
|
|
1586
|
-
const
|
|
1708
|
+
const normalized = target.trim();
|
|
1709
|
+
const like = containsLikePattern(normalized);
|
|
1710
|
+
const fileMatches = searchFiles(database, normalized, 25);
|
|
1711
|
+
const symbolMatches = searchSymbols(database, normalized, 50);
|
|
1712
|
+
const routeMatches = database.prepare("SELECT method, route, file_path, line, handler FROM routes WHERE route LIKE ? ESCAPE '\\' OR handler LIKE ? ESCAPE '\\' OR file_path LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 50").all(like, like, like);
|
|
1713
|
+
const importMatches = database.prepare("SELECT from_file, to_ref, imported, line, raw FROM imports WHERE from_file LIKE ? ESCAPE '\\' OR to_ref LIKE ? ESCAPE '\\' OR imported LIKE ? ESCAPE '\\' ORDER BY from_file, line LIMIT 75").all(like, like, like);
|
|
1714
|
+
const outgoingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE file_path LIKE ? ESCAPE '\\' OR source LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 100").all(like, like);
|
|
1715
|
+
const incomingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE target LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 100").all(like);
|
|
1587
1716
|
const routeTargets = routeMatches.map((row) => `${String(row.method)} ${String(row.route)}`);
|
|
1588
1717
|
const routeEdges = routeTargets.length === 0 ? [] : database.prepare(`SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE source IN (${routeTargets.map(() => "?").join(", ")}) ORDER BY file_path, line LIMIT 100`).all(...routeTargets);
|
|
1589
1718
|
const relatedFilePaths = Array.from(new Set([
|
|
@@ -1638,11 +1767,117 @@ function codeImpact(database, target) {
|
|
|
1638
1767
|
})).sort((left, right) => right.files - left.files || left.owner.localeCompare(right.owner)),
|
|
1639
1768
|
};
|
|
1640
1769
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
return
|
|
1770
|
+
function sampleLines(items, limit, render) {
|
|
1771
|
+
const lines = items.slice(0, limit).map(render);
|
|
1772
|
+
if (items.length > limit)
|
|
1773
|
+
lines.push(` ...+${items.length - limit} more`);
|
|
1774
|
+
return lines;
|
|
1775
|
+
}
|
|
1776
|
+
function pushBudgetedLine(lines, line) {
|
|
1777
|
+
const candidate = [...lines, line].join("\n");
|
|
1778
|
+
if (candidate.length > exports.codeContextPackCharCap)
|
|
1779
|
+
return false;
|
|
1780
|
+
lines.push(line);
|
|
1781
|
+
return true;
|
|
1782
|
+
}
|
|
1783
|
+
function pushBudgetedSection(lines, title, items, limit, render) {
|
|
1784
|
+
if (items.length === 0)
|
|
1785
|
+
return;
|
|
1786
|
+
if (!pushBudgetedLine(lines, title))
|
|
1787
|
+
return;
|
|
1788
|
+
for (const line of sampleLines(items, limit, render)) {
|
|
1789
|
+
if (!pushBudgetedLine(lines, line)) {
|
|
1790
|
+
pushBudgetedLine(lines, " ...more omitted; refine the query");
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
function finalizeCodeContextPack(body) {
|
|
1796
|
+
if (body.length <= exports.codeContextPackCharCap)
|
|
1797
|
+
return body;
|
|
1798
|
+
const budget = exports.codeContextPackCharCap - exports.codeContextPackTruncationNotice.length - 1;
|
|
1799
|
+
return `${body.slice(0, budget > 0 ? budget : 0).trimEnd()}\n${exports.codeContextPackTruncationNotice}`;
|
|
1800
|
+
}
|
|
1801
|
+
function codeContextScaleLine(fileCount) {
|
|
1802
|
+
return fileCount < code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD
|
|
1803
|
+
? `scale small (${fileCount} indexed files < ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD}); direct reads are usually cheaper for simple lookups`
|
|
1804
|
+
: `scale large (${fileCount} indexed files >= ${code_index_file_policy_1.SMALL_REPO_FILE_THRESHOLD}); indexed traversal is useful for impact-style context`;
|
|
1805
|
+
}
|
|
1806
|
+
function structuralSignature(value) {
|
|
1807
|
+
const signature = oneLine(String(value ?? ""));
|
|
1808
|
+
const bodyStart = signature.indexOf("{");
|
|
1809
|
+
return bodyStart >= 0 ? signature.slice(0, bodyStart).trimEnd() : signature;
|
|
1810
|
+
}
|
|
1811
|
+
function sortedUnique(values) {
|
|
1812
|
+
return Array.from(new Set(values.filter(Boolean))).sort();
|
|
1813
|
+
}
|
|
1814
|
+
function codeContextPack(database, query) {
|
|
1815
|
+
const normalized = query.trim();
|
|
1816
|
+
if (!normalized)
|
|
1817
|
+
return 'Code context pack: missing query; use --code-context-pack "path-or-symbol-or-route".';
|
|
1818
|
+
const like = containsLikePattern(normalized);
|
|
1819
|
+
const files = searchFiles(database, normalized, 12);
|
|
1820
|
+
const symbols = searchSymbols(database, normalized, 20);
|
|
1821
|
+
const routes = database.prepare("SELECT method, route, file_path, line, handler FROM routes WHERE route LIKE ? ESCAPE '\\' OR handler LIKE ? ESCAPE '\\' OR file_path LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 20").all(like, like, like);
|
|
1822
|
+
const imports = database.prepare("SELECT from_file, to_ref, imported, line, raw FROM imports WHERE from_file LIKE ? ESCAPE '\\' OR to_ref LIKE ? ESCAPE '\\' OR imported LIKE ? ESCAPE '\\' ORDER BY from_file, line LIMIT 30").all(like, like, like);
|
|
1823
|
+
const outgoingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE file_path LIKE ? ESCAPE '\\' OR source LIKE ? ESCAPE '\\' OR evidence LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 30").all(like, like, like);
|
|
1824
|
+
const incomingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE target LIKE ? ESCAPE '\\' OR evidence LIKE ? ESCAPE '\\' ORDER BY file_path, line LIMIT 30").all(like, like);
|
|
1825
|
+
const relatedFilePaths = sortedUnique([
|
|
1826
|
+
...files.map((row) => String(row.path ?? "")),
|
|
1827
|
+
...symbols.map((row) => String(row.file_path ?? "")),
|
|
1828
|
+
...routes.map((row) => String(row.file_path ?? "")),
|
|
1829
|
+
...imports.map((row) => String(row.from_file ?? "")),
|
|
1830
|
+
...outgoingEdges.map((row) => String(row.file_path ?? "")),
|
|
1831
|
+
...incomingEdges.map((row) => String(row.file_path ?? "")),
|
|
1832
|
+
]);
|
|
1833
|
+
const ownership = ownershipContext();
|
|
1834
|
+
const ownerRows = new Map();
|
|
1835
|
+
for (const filePath of relatedFilePaths) {
|
|
1836
|
+
const info = ownershipInfo(filePath, ownership);
|
|
1837
|
+
const current = ownerRows.get(info.owner) ?? { files: 0, owner: info.owner, owner_source: info.owner_source, sample_files: [] };
|
|
1838
|
+
current.files += 1;
|
|
1839
|
+
if (current.sample_files.length < 4)
|
|
1840
|
+
current.sample_files.push(filePath);
|
|
1841
|
+
ownerRows.set(info.owner, current);
|
|
1842
|
+
}
|
|
1843
|
+
const owners = Array.from(ownerRows.values()).sort((left, right) => right.files - left.files || left.owner.localeCompare(right.owner));
|
|
1844
|
+
const staleness = codeIndexStaleness(database);
|
|
1845
|
+
const coverage = evidenceCoverage(database);
|
|
1846
|
+
const staleLabel = staleness.stale
|
|
1847
|
+
? `STALE ${staleness.changed} changed, ${staleness.added} added, ${staleness.deleted} deleted`
|
|
1848
|
+
: "fresh";
|
|
1849
|
+
const lines = [
|
|
1850
|
+
`Code context pack "${normalized}": ${files.length} file matches, ${symbols.length} symbols, ${routes.length} routes, ${imports.length} imports, ${incomingEdges.length} incoming / ${outgoingEdges.length} outgoing edges; index ${staleLabel}; ${codeContextScaleLine(Number(coverage.files ?? 0))}.`,
|
|
1851
|
+
"Evidence is structural only: paths, lines, signatures, routes, imports, edges, and owners; no source snippets are included.",
|
|
1852
|
+
];
|
|
1853
|
+
pushBudgetedSection(lines, "Files:", files, 8, (row) => ` file-match ${String(row.path)} (${String(row.language)}, ${String(row.profile)}, ${Number(row.lines ?? 0)} lines)`);
|
|
1854
|
+
pushBudgetedSection(lines, "Symbols:", symbols, 12, (row) => ` symbol-match ${String(row.file_path)}:${String(row.line)} ${String(row.kind)} ${String(row.name)} - ${structuralSignature(row.signature)}`);
|
|
1855
|
+
pushBudgetedSection(lines, "Routes:", routes, 8, (row) => ` route-match ${String(row.method)} ${String(row.route)} -> ${String(row.handler)} (${String(row.file_path)}:${String(row.line)})`);
|
|
1856
|
+
pushBudgetedSection(lines, "Imports:", imports, 8, (row) => ` import-match ${String(row.from_file)}:${String(row.line)} -> ${String(row.to_ref)}${row.imported ? ` (${String(row.imported)})` : ""}`);
|
|
1857
|
+
pushBudgetedSection(lines, "Incoming edges:", incomingEdges, 8, (row) => ` edge-in ${String(row.kind)} ${String(row.source)} -> ${String(row.target)} (${String(row.file_path)}:${String(row.line)})`);
|
|
1858
|
+
pushBudgetedSection(lines, "Outgoing edges:", outgoingEdges, 8, (row) => ` edge-out ${String(row.kind)} ${String(row.source)} -> ${String(row.target)} (${String(row.file_path)}:${String(row.line)})`);
|
|
1859
|
+
pushBudgetedSection(lines, "Owners:", owners, 6, (row) => ` owner ${row.owner} (${row.owner_source}, ${row.files} files): ${row.sample_files.join(", ")}`);
|
|
1860
|
+
return finalizeCodeContextPack(lines.join("\n"));
|
|
1861
|
+
}
|
|
1862
|
+
function snapshotRows(database, sql) {
|
|
1863
|
+
return database.prepare(sql).all().map((row) => {
|
|
1864
|
+
const normalized = {};
|
|
1865
|
+
for (const key of Object.keys(row).sort()) {
|
|
1866
|
+
const value = row[key];
|
|
1867
|
+
normalized[key] = typeof value === "string" || typeof value === "number" || value === null ? value : String(value);
|
|
1868
|
+
}
|
|
1869
|
+
return normalized;
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
function codeIndexSnapshot(database) {
|
|
1873
|
+
return {
|
|
1874
|
+
configs: snapshotRows(database, "SELECT file_path, line, key, value FROM configs ORDER BY file_path, line, key, value"),
|
|
1875
|
+
edges: snapshotRows(database, "SELECT file_path, line, kind, source_kind, source, target_kind, target, evidence FROM edges ORDER BY file_path, line, kind, source, target, evidence"),
|
|
1876
|
+
files: snapshotRows(database, "SELECT path, language, profile, kind, lines, bytes FROM files ORDER BY path"),
|
|
1877
|
+
imports: snapshotRows(database, "SELECT from_file, line, to_ref, imported, raw FROM imports ORDER BY from_file, line, to_ref, imported, raw"),
|
|
1878
|
+
routes: snapshotRows(database, "SELECT file_path, line, method, route, handler FROM routes ORDER BY file_path, line, method, route, handler"),
|
|
1879
|
+
symbols: snapshotRows(database, "SELECT file_path, line, kind, name, signature FROM symbols ORDER BY file_path, line, kind, name, signature"),
|
|
1880
|
+
};
|
|
1646
1881
|
}
|
|
1647
1882
|
// Error thrown when the code-evidence index is missing or schema-incompatible.
|
|
1648
1883
|
// The MCP server catches this to return an isError tool result (tools/list still
|
|
@@ -1844,6 +2079,21 @@ function runCodeImpactMode() {
|
|
|
1844
2079
|
database.close();
|
|
1845
2080
|
}
|
|
1846
2081
|
}
|
|
2082
|
+
function runCodeContextPackMode() {
|
|
2083
|
+
if (!args_1.codeContextPackTarget.trim()) {
|
|
2084
|
+
console.error("missing context pack query: use --code-context-pack \"path-or-symbol-or-route\"");
|
|
2085
|
+
process.exit(1);
|
|
2086
|
+
}
|
|
2087
|
+
requireExistingIndex();
|
|
2088
|
+
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
2089
|
+
try {
|
|
2090
|
+
warnIfCodeIndexStale(database);
|
|
2091
|
+
console.log(codeContextPack(database, args_1.codeContextPackTarget.trim()));
|
|
2092
|
+
}
|
|
2093
|
+
finally {
|
|
2094
|
+
database.close();
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
1847
2097
|
function runCodeSearchSymbolMode() {
|
|
1848
2098
|
if (!args_1.codeSearchSymbol.trim()) {
|
|
1849
2099
|
console.error("missing symbol search term: use --code-search-symbol \"term\"");
|
|
@@ -1853,18 +2103,18 @@ function runCodeSearchSymbolMode() {
|
|
|
1853
2103
|
const database = openDatabase(codeEvidenceDatabasePath().absolutePath);
|
|
1854
2104
|
try {
|
|
1855
2105
|
warnIfCodeIndexStale(database);
|
|
1856
|
-
|
|
1857
|
-
printRows(database.prepare("SELECT name, kind, file_path, line, signature FROM symbols WHERE name LIKE ? OR signature LIKE ? ORDER BY file_path, line LIMIT 50").all(like, like));
|
|
2106
|
+
printRows(searchSymbols(database, args_1.codeSearchSymbol.trim()));
|
|
1858
2107
|
}
|
|
1859
2108
|
finally {
|
|
1860
2109
|
database.close();
|
|
1861
2110
|
}
|
|
1862
2111
|
}
|
|
1863
2112
|
function isCodeEvidenceMode() {
|
|
1864
|
-
return isCodeEvidenceModeFor({ codeFilesMode: args_1.codeFilesMode, codeImpactMode: args_1.codeImpactMode, codeIndexMode: args_1.codeIndexMode, codeQuerySql: args_1.codeQuerySql, codeReportMode: args_1.codeReportMode, codeSearchSymbol: args_1.codeSearchSymbol, codeStatusMode: args_1.codeStatusMode });
|
|
2113
|
+
return isCodeEvidenceModeFor({ codeContextPackTarget: args_1.codeContextPackTarget, codeFilesMode: args_1.codeFilesMode, codeImpactMode: args_1.codeImpactMode, codeIndexMode: args_1.codeIndexMode, codeQuerySql: args_1.codeQuerySql, codeReportMode: args_1.codeReportMode, codeSearchSymbol: args_1.codeSearchSymbol, codeStatusMode: args_1.codeStatusMode });
|
|
1865
2114
|
}
|
|
1866
2115
|
function isCodeEvidenceModeFor(flags) {
|
|
1867
|
-
return flags.
|
|
2116
|
+
return Boolean(flags.codeContextPackTarget)
|
|
2117
|
+
|| flags.codeIndexMode
|
|
1868
2118
|
|| Boolean(flags.codeQuerySql)
|
|
1869
2119
|
|| flags.codeReportMode
|
|
1870
2120
|
|| flags.codeStatusMode
|
|
@@ -7,13 +7,14 @@ const install_skill_1 = require("./install-skill");
|
|
|
7
7
|
const modes_1 = require("./modes");
|
|
8
8
|
const migration_1 = require("./migration");
|
|
9
9
|
const templates_1 = require("./templates");
|
|
10
|
+
const wiki_visualizer_1 = require("./wiki-visualizer");
|
|
10
11
|
const workspace_1 = require("./workspace");
|
|
11
12
|
function codeIndex() {
|
|
12
13
|
return require("./code-index");
|
|
13
14
|
}
|
|
14
15
|
function printUsage() {
|
|
15
16
|
console.log(`Usage:
|
|
16
|
-
project-librarian [init] [options]
|
|
17
|
+
project-librarian [init|update] [options]
|
|
17
18
|
project-librarian install-skill [--scope user|project] [--agents codex|claude|cursor|gemini|all]
|
|
18
19
|
project-librarian mcp
|
|
19
20
|
|
|
@@ -33,6 +34,8 @@ Options:
|
|
|
33
34
|
--issue-title <title> Override the generated issue draft title.
|
|
34
35
|
--query <terms> Search wiki paths, metadata, titles, and bodies (answer-shaped, capped output).
|
|
35
36
|
--wiki-impact <page-or-term> Show wiki backlinks, decision_ref citations, and router depth for matching pages.
|
|
37
|
+
--wiki-visualize Write a static wiki graph visualizer to .project-wiki/wiki-graph.html.
|
|
38
|
+
--wiki-visualize-out <path> With --wiki-visualize, write under a custom .project-wiki/ path.
|
|
36
39
|
--refresh-index Update the managed auto-discovered wiki index block.
|
|
37
40
|
--capture-inbox Append a candidate note with --title, --content, and optional --category.
|
|
38
41
|
--glossary-init Create and route the optional glossary page.
|
|
@@ -49,10 +52,12 @@ Options:
|
|
|
49
52
|
--code-report Print architecture and ownership summaries from the code evidence index.
|
|
50
53
|
--code-report-section <section> With --code-report, print one section: coverage, ownership, languages, parsers, workspaces, workspace-graph, routes, hotspots, configs, or edges.
|
|
51
54
|
--code-impact <term> Show file, symbol, route, import, and edge impact evidence for a term.
|
|
55
|
+
--code-context-pack <term> Print a budgeted first-pass code context pack for a path, symbol, route, or module term.
|
|
52
56
|
--code-search-symbol <term> Search indexed symbols.
|
|
53
57
|
|
|
54
58
|
Commands:
|
|
55
|
-
|
|
59
|
+
update Run the idempotent wiki/setup update path; rejects migration flags.
|
|
60
|
+
mcp Run the stdio MCP server exposing answer-shaped code-evidence tools (code_context_pack, code_impact, code_ownership, code_workspace_graph, code_search, code_status) over the existing .project-wiki index.
|
|
56
61
|
|
|
57
62
|
--help Show this help.`);
|
|
58
63
|
}
|
|
@@ -87,6 +92,10 @@ if (args_1.missingValueOptions.length > 0) {
|
|
|
87
92
|
printUsage();
|
|
88
93
|
process.exit(1);
|
|
89
94
|
}
|
|
95
|
+
if (args_1.command === "update" && args_1.migrateMode) {
|
|
96
|
+
console.error("update cannot be combined with --migrate or --adopt-existing; use project-librarian --migrate for migration.");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
90
99
|
if (args_1.fixMode && !args_1.doctorMode) {
|
|
91
100
|
console.error("--fix is only supported with --doctor.");
|
|
92
101
|
process.exit(1);
|
|
@@ -99,6 +108,10 @@ if (args_1.codeReportSection && !args_1.codeReportMode) {
|
|
|
99
108
|
console.error("--code-report-section is only supported with --code-report.");
|
|
100
109
|
process.exit(1);
|
|
101
110
|
}
|
|
111
|
+
if (args_1.wikiVisualizeOutput && !args_1.wikiVisualizeMode) {
|
|
112
|
+
console.error("--wiki-visualize-out is only supported with --wiki-visualize.");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
102
115
|
if (args_1.codeIndexIncrementalMode && !args_1.codeIndexMode) {
|
|
103
116
|
console.error("--incremental is only supported with --code-index.");
|
|
104
117
|
process.exit(1);
|
|
@@ -134,9 +147,9 @@ else {
|
|
|
134
147
|
runInitCommand();
|
|
135
148
|
}
|
|
136
149
|
function runInitCommand() {
|
|
137
|
-
const activeCodeModes = [args_1.codeQueryMode, args_1.codeReportMode, args_1.codeStatusMode, args_1.codeFilesMode, args_1.codeImpactMode, args_1.codeSearchSymbolMode, args_1.codeIndexMode].filter(Boolean).length;
|
|
150
|
+
const activeCodeModes = [args_1.codeQueryMode, args_1.codeReportMode, args_1.codeStatusMode, args_1.codeFilesMode, args_1.codeImpactMode, args_1.codeContextPackMode, args_1.codeSearchSymbolMode, args_1.codeIndexMode].filter(Boolean).length;
|
|
138
151
|
if (activeCodeModes > 1) {
|
|
139
|
-
console.error("Use one code evidence mode at a time: --code-index, --code-query, --code-report, --code-status, --code-files, --code-impact, or --code-search-symbol.");
|
|
152
|
+
console.error("Use one code evidence mode at a time: --code-index, --code-query, --code-report, --code-status, --code-files, --code-impact, --code-context-pack, or --code-search-symbol.");
|
|
140
153
|
process.exit(1);
|
|
141
154
|
}
|
|
142
155
|
if (args_1.codeQueryMode) {
|
|
@@ -164,6 +177,11 @@ function runInitCommand() {
|
|
|
164
177
|
exitAfterStdoutDrain(0);
|
|
165
178
|
return;
|
|
166
179
|
}
|
|
180
|
+
if (args_1.codeContextPackMode) {
|
|
181
|
+
codeIndex().runCodeContextPackMode();
|
|
182
|
+
exitAfterStdoutDrain(0);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
167
185
|
if (args_1.codeSearchSymbolMode) {
|
|
168
186
|
codeIndex().runCodeSearchSymbolMode();
|
|
169
187
|
exitAfterStdoutDrain(0);
|
|
@@ -178,6 +196,18 @@ function runInitCommand() {
|
|
|
178
196
|
exitAfterStdoutDrain(0);
|
|
179
197
|
return;
|
|
180
198
|
}
|
|
199
|
+
if (args_1.wikiVisualizeMode) {
|
|
200
|
+
try {
|
|
201
|
+
const output = (0, wiki_visualizer_1.writeWikiVisualizer)(args_1.wikiVisualizeOutput);
|
|
202
|
+
console.log(`Project wiki visualizer written: ${output}`);
|
|
203
|
+
exitAfterStdoutDrain(0);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
181
211
|
if (args_1.queryTerm) {
|
|
182
212
|
(0, modes_1.runQueryMode)();
|
|
183
213
|
exitAfterStdoutDrain(0);
|