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.
@@ -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 like = `%${target}%`;
1581
- const fileMatches = database.prepare("SELECT path, language, profile, lines, bytes FROM files WHERE path LIKE ? ORDER BY path LIMIT 25").all(like);
1582
- const symbolMatches = database.prepare("SELECT name, kind, file_path, line, signature FROM symbols WHERE name LIKE ? OR signature LIKE ? OR file_path LIKE ? ORDER BY file_path, line LIMIT 50").all(like, like, like);
1583
- const routeMatches = database.prepare("SELECT method, route, file_path, line, handler FROM routes WHERE route LIKE ? OR handler LIKE ? OR file_path LIKE ? ORDER BY file_path, line LIMIT 50").all(like, like, like);
1584
- const importMatches = database.prepare("SELECT from_file, to_ref, imported, line, raw FROM imports WHERE from_file LIKE ? OR to_ref LIKE ? OR imported LIKE ? ORDER BY from_file, line LIMIT 75").all(like, like, like);
1585
- const outgoingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE file_path LIKE ? OR source LIKE ? ORDER BY file_path, line LIMIT 100").all(like, like);
1586
- const incomingEdges = database.prepare("SELECT kind, source_kind, source, target_kind, target, file_path, line, evidence FROM edges WHERE target LIKE ? ORDER BY file_path, line LIMIT 100").all(like);
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
- // Symbol search reusing the exact SELECT behind --code-search-symbol so the MCP
1642
- // code_search tool returns identical rows without duplicating the query string.
1643
- function searchSymbols(database, term) {
1644
- const like = `%${term}%`;
1645
- return 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);
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
- const like = `%${args_1.codeSearchSymbol}%`;
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.codeIndexMode
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
- mcp Run the stdio MCP server exposing answer-shaped code-evidence tools (code_impact, code_ownership, code_workspace_graph, code_search, code_status) over the existing .project-wiki index.
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);