project-librarian 0.5.4 → 0.5.6

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.
Files changed (46) hide show
  1. package/CONTRIBUTING.md +36 -0
  2. package/README.ko.md +57 -360
  3. package/README.md +56 -359
  4. package/dist/args.js +6 -1
  5. package/dist/code-index/extractors/light-languages.js +285 -0
  6. package/dist/code-index/extractors/registry.js +12 -0
  7. package/dist/code-index/extractors/shared.js +18 -1
  8. package/dist/code-index/extractors/typescript.js +30 -16
  9. package/dist/code-index/modes.js +136 -32
  10. package/dist/code-index/native-helper-matrix.js +99 -0
  11. package/dist/code-index/native-helper.js +292 -0
  12. package/dist/code-index/ownership.js +8 -6
  13. package/dist/code-index/schema.js +72 -13
  14. package/dist/code-index/search.js +1 -1
  15. package/dist/code-index-db.js +20 -12
  16. package/dist/code-index-file-policy.js +17 -11
  17. package/dist/code-index.js +365 -13
  18. package/dist/hooks.js +5 -5
  19. package/dist/init-project-wiki.js +7 -1
  20. package/dist/install-skill.js +99 -6
  21. package/dist/mcp-server.js +4 -4
  22. package/dist/migration.js +27 -2
  23. package/dist/native/darwin-arm64/project-librarian-indexer +0 -0
  24. package/dist/native/darwin-x64/project-librarian-indexer +0 -0
  25. package/dist/native/linux-arm64/project-librarian-indexer +0 -0
  26. package/dist/native/linux-arm64-musl/project-librarian-indexer +0 -0
  27. package/dist/native/linux-x64/project-librarian-indexer +0 -0
  28. package/dist/native/linux-x64-musl/project-librarian-indexer +0 -0
  29. package/dist/native/project-librarian-indexer-manifest.json +70 -0
  30. package/dist/native/win32-arm64/project-librarian-indexer.exe +0 -0
  31. package/dist/native/win32-x64/project-librarian-indexer.exe +0 -0
  32. package/dist/templates.js +4 -3
  33. package/dist/workspace.js +137 -10
  34. package/docs/README.md +11 -0
  35. package/docs/benchmarks.md +64 -0
  36. package/docs/cli-reference.md +60 -0
  37. package/docs/code-evidence.md +87 -0
  38. package/docs/ko/README.md +13 -0
  39. package/docs/ko/benchmarks.md +64 -0
  40. package/docs/ko/cli-reference.md +60 -0
  41. package/docs/ko/code-evidence.md +87 -0
  42. package/docs/ko/maintainer.md +76 -0
  43. package/docs/ko/usage.md +167 -0
  44. package/docs/maintainer.md +76 -0
  45. package/docs/usage.md +175 -0
  46. package/package.json +13 -2
@@ -52,8 +52,10 @@ function pathOwnerKey(filePath) {
52
52
  return parts[0] ?? ".";
53
53
  }
54
54
  function readJsonObject(relativePath) {
55
+ if (!(0, workspace_1.containedProjectFileStat)(relativePath))
56
+ return null;
55
57
  try {
56
- const parsed = JSON.parse(fs.readFileSync((0, workspace_1.abs)(relativePath), "utf8"));
58
+ const parsed = JSON.parse((0, workspace_1.read)(relativePath));
57
59
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
58
60
  }
59
61
  catch {
@@ -83,19 +85,19 @@ function workspacePatternCandidates(pattern) {
83
85
  const suffix = normalized.slice(starIndex + 1).replace(/^\/+/, "");
84
86
  const base = prefix || ".";
85
87
  const basePath = (0, workspace_1.abs)(base);
86
- if (!fs.existsSync(basePath) || !fs.statSync(basePath).isDirectory())
88
+ if (!(0, workspace_1.containedProjectDirectoryStat)(base))
87
89
  return [];
88
90
  return fs.readdirSync(basePath, { withFileTypes: true })
89
91
  .filter((entry) => entry.isDirectory())
90
92
  .map((entry) => (0, workspace_1.normalizePath)(path.join(base, entry.name, suffix)))
91
- .filter((candidate) => fs.existsSync((0, workspace_1.abs)(candidate)) && fs.statSync((0, workspace_1.abs)(candidate)).isDirectory());
93
+ .filter((candidate) => (0, workspace_1.containedProjectDirectoryStat)(candidate));
92
94
  }
93
95
  function workspacePackages() {
94
96
  const packages = new Map();
95
97
  for (const pattern of workspacePatternsFromRootPackage()) {
96
98
  for (const candidate of workspacePatternCandidates(pattern)) {
97
99
  const packageJsonPath = (0, workspace_1.normalizePath)(path.join(candidate, "package.json"));
98
- if (!fs.existsSync((0, workspace_1.abs)(packageJsonPath)))
100
+ if (!(0, workspace_1.containedProjectFileStat)(packageJsonPath))
99
101
  continue;
100
102
  const packageJson = readJsonObject(packageJsonPath);
101
103
  const packageName = typeof packageJson?.name === "string" ? packageJson.name : candidate;
@@ -119,9 +121,9 @@ function codeownerRules() {
119
121
  const files = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"];
120
122
  const rules = [];
121
123
  for (const filePath of files) {
122
- if (!fs.existsSync((0, workspace_1.abs)(filePath)))
124
+ if (!(0, workspace_1.containedProjectFileStat)(filePath))
123
125
  continue;
124
- const lines = fs.readFileSync((0, workspace_1.abs)(filePath), "utf8").split(/\r?\n/);
126
+ const lines = (0, workspace_1.read)(filePath).split(/\r?\n/);
125
127
  lines.forEach((lineText, index) => {
126
128
  const trimmed = lineText.trim();
127
129
  if (!trimmed || trimmed.startsWith("#"))
@@ -1,6 +1,41 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.codeIndexSchemaVersion = void 0;
37
+ exports.fileFtsRowid = fileFtsRowid;
38
+ exports.createSecondaryIndexes = createSecondaryIndexes;
4
39
  exports.setupDatabase = setupDatabase;
5
40
  exports.createIndexStatements = createIndexStatements;
6
41
  exports.removeIndexedFile = removeIndexedFile;
@@ -10,14 +45,44 @@ exports.indexedScopes = indexedScopes;
10
45
  exports.indexedParserMode = indexedParserMode;
11
46
  exports.incrementalCompatibility = incrementalCompatibility;
12
47
  exports.codeIndexSnapshot = codeIndexSnapshot;
48
+ const crypto = __importStar(require("node:crypto"));
13
49
  const workspace_1 = require("../workspace");
14
- exports.codeIndexSchemaVersion = "4";
15
- function setupDatabase(database) {
50
+ exports.codeIndexSchemaVersion = "5";
51
+ function stableFtsRowid(parts) {
52
+ const hash = crypto.createHash("sha256");
53
+ for (const part of parts) {
54
+ hash.update(part);
55
+ hash.update("\0");
56
+ }
57
+ const digest = hash.digest();
58
+ let value = 0;
59
+ for (let index = 0; index < 6; index += 1)
60
+ value = value * 256 + digest[index];
61
+ return value + 1;
62
+ }
63
+ function fileFtsRowid(filePath) {
64
+ return stableFtsRowid(["file", filePath]);
65
+ }
66
+ const secondaryIndexSql = `
67
+ CREATE INDEX idx_symbols_file ON symbols(file_path);
68
+ CREATE INDEX idx_symbols_name ON symbols(name);
69
+ CREATE INDEX idx_imports_from ON imports(from_file);
70
+ CREATE INDEX idx_routes_path ON routes(route);
71
+ CREATE INDEX idx_configs_file ON configs(file_path);
72
+ CREATE INDEX idx_edges_source ON edges(source_kind, source);
73
+ CREATE INDEX idx_edges_target ON edges(target_kind, target);
74
+ CREATE INDEX idx_edges_kind ON edges(kind);
75
+ `;
76
+ function createSecondaryIndexes(database) {
77
+ database.exec(secondaryIndexSql);
78
+ }
79
+ function setupDatabase(database, options = {}) {
16
80
  database.exec(`
17
81
  PRAGMA journal_mode = WAL;
18
82
  CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
19
83
  CREATE TABLE files (
20
84
  path TEXT PRIMARY KEY,
85
+ fts_rowid INTEGER NOT NULL UNIQUE,
21
86
  language TEXT NOT NULL,
22
87
  profile TEXT NOT NULL,
23
88
  kind TEXT NOT NULL,
@@ -71,30 +136,24 @@ function setupDatabase(database) {
71
136
  );
72
137
  CREATE VIRTUAL TABLE files_fts USING fts5(path, language, profile, content);
73
138
  CREATE VIRTUAL TABLE symbols_fts USING fts5(name, kind, file_path, signature);
74
- CREATE INDEX idx_symbols_file ON symbols(file_path);
75
- CREATE INDEX idx_symbols_name ON symbols(name);
76
- CREATE INDEX idx_imports_from ON imports(from_file);
77
- CREATE INDEX idx_routes_path ON routes(route);
78
- CREATE INDEX idx_configs_file ON configs(file_path);
79
- CREATE INDEX idx_edges_source ON edges(source_kind, source);
80
- CREATE INDEX idx_edges_target ON edges(target_kind, target);
81
- CREATE INDEX idx_edges_kind ON edges(kind);
82
139
  `);
140
+ if (options.secondaryIndexes ?? true)
141
+ createSecondaryIndexes(database);
83
142
  }
84
143
  function createIndexStatements(database) {
85
144
  return {
86
145
  deleteConfig: database.prepare("DELETE FROM configs WHERE file_path = ?"),
87
146
  deleteEdge: database.prepare("DELETE FROM edges WHERE file_path = ?"),
88
147
  deleteFile: database.prepare("DELETE FROM files WHERE path = ?"),
89
- deleteFileFts: database.prepare("DELETE FROM files_fts WHERE path = ?"),
148
+ deleteFileFts: database.prepare("DELETE FROM files_fts WHERE rowid = (SELECT fts_rowid FROM files WHERE path = ?)"),
90
149
  deleteImport: database.prepare("DELETE FROM imports WHERE from_file = ?"),
91
150
  deleteRoute: database.prepare("DELETE FROM routes WHERE file_path = ?"),
92
151
  deleteSymbol: database.prepare("DELETE FROM symbols WHERE file_path = ?"),
93
152
  deleteSymbolFts: database.prepare("DELETE FROM symbols_fts WHERE file_path = ?"),
94
153
  insertConfig: database.prepare("INSERT INTO configs (key, value, file_path, line) VALUES (?, ?, ?, ?)"),
95
154
  insertEdge: database.prepare("INSERT INTO edges (kind, source_kind, source, target_kind, target, file_path, line, evidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"),
96
- insertFile: database.prepare("INSERT INTO files (path, language, profile, kind, bytes, lines, hash, mtime_ms, size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
97
- insertFileFts: database.prepare("INSERT INTO files_fts (path, language, profile, content) VALUES (?, ?, ?, ?)"),
155
+ insertFile: database.prepare("INSERT INTO files (path, fts_rowid, language, profile, kind, bytes, lines, hash, mtime_ms, size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
156
+ insertFileFts: database.prepare("INSERT INTO files_fts (rowid, path, language, profile, content) VALUES (?, ?, ?, ?, ?)"),
98
157
  insertImport: database.prepare("INSERT INTO imports (from_file, to_ref, imported, line, raw) VALUES (?, ?, ?, ?, ?)"),
99
158
  insertMeta: database.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)"),
100
159
  insertRoute: database.prepare("INSERT INTO routes (method, route, file_path, line, handler) VALUES (?, ?, ?, ?, ?)"),
@@ -82,7 +82,7 @@ function searchFiles(database, term, limit = 25) {
82
82
  const ftsRows = database.prepare(`
83
83
  SELECT files.path, files.language, files.profile, files.lines, files.bytes
84
84
  FROM files_fts
85
- JOIN files ON files.path = files_fts.path
85
+ JOIN files ON files.fts_rowid = files_fts.rowid
86
86
  WHERE files_fts MATCH ?
87
87
  ORDER BY bm25(files_fts, 8.0, 1.0, 1.0, 0.25), files.path
88
88
  LIMIT ?
@@ -4,17 +4,27 @@ exports.codeEvidenceNodeRuntimeRequirement = void 0;
4
4
  exports.loadDatabaseSync = loadDatabaseSync;
5
5
  exports.openDatabase = openDatabase;
6
6
  exports.codeEvidenceNodeRuntimeRequirement = "Node.js 22.13+ or 24+; node:sqlite was added in Node.js 22.5.0 and became available without --experimental-sqlite in Node.js 22.13.0";
7
+ function warningType(option) {
8
+ if (typeof option === "string")
9
+ return option;
10
+ if (typeof option !== "object" || option === null || !("type" in option))
11
+ return "";
12
+ const value = option.type;
13
+ return typeof value === "string" ? value : "";
14
+ }
15
+ function isSqliteExperimentalWarning(warning, options) {
16
+ const message = warning instanceof Error ? warning.message : typeof warning === "string" ? warning : "";
17
+ const type = warning instanceof Error ? warning.name : warningType(options[0]);
18
+ return type === "ExperimentalWarning" && message.includes("SQLite");
19
+ }
7
20
  function loadDatabaseSync(fail) {
8
- const previousListeners = process.listeners("warning");
9
- const suppressExperimentalSqliteWarning = (warning) => {
10
- if (warning.name !== "ExperimentalWarning" || !warning.message.includes("SQLite")) {
11
- for (const listener of previousListeners)
12
- listener.call(process, warning);
13
- }
14
- };
21
+ const previousEmitWarning = process.emitWarning;
15
22
  try {
16
- process.removeAllListeners("warning");
17
- process.on("warning", suppressExperimentalSqliteWarning);
23
+ process.emitWarning = ((warning, ...options) => {
24
+ if (isSqliteExperimentalWarning(warning, options))
25
+ return;
26
+ previousEmitWarning.call(process, warning, ...options);
27
+ });
18
28
  const sqlite = require("node:sqlite");
19
29
  return sqlite.DatabaseSync;
20
30
  }
@@ -23,9 +33,7 @@ function loadDatabaseSync(fail) {
23
33
  return fail(`code evidence index requires Node.js 22.13+ because it uses node:sqlite without experimental flags; current Node is ${process.version}. Runtime policy: ${exports.codeEvidenceNodeRuntimeRequirement}. Error: ${message}`);
24
34
  }
25
35
  finally {
26
- process.removeAllListeners("warning");
27
- for (const listener of previousListeners)
28
- process.on("warning", listener);
36
+ process.emitWarning = previousEmitWarning;
29
37
  }
30
38
  }
31
39
  function openDatabase(databasePath, fail) {
@@ -38,6 +38,7 @@ exports.fileLanguage = fileLanguage;
38
38
  exports.isJavaScriptLike = isJavaScriptLike;
39
39
  exports.shouldIndexFile = shouldIndexFile;
40
40
  exports.isIgnoredCodePath = isIgnoredCodePath;
41
+ exports.cachedDiscoveredCodeFileStat = cachedDiscoveredCodeFileStat;
41
42
  exports.discoverCodeFiles = discoverCodeFiles;
42
43
  exports.smallRepoCodeIndexGate = smallRepoCodeIndexGate;
43
44
  const childProcess = __importStar(require("node:child_process"));
@@ -76,6 +77,7 @@ const languageByExtension = {
76
77
  };
77
78
  const configExtensions = new Set([".json", ".yaml", ".yml", ".toml"]);
78
79
  exports.maxIndexedBytes = 1024 * 1024;
80
+ const discoveredCodeFileStats = new Map();
79
81
  function fileLanguage(relativePath) {
80
82
  if (path.basename(relativePath) === ".env.example")
81
83
  return "config";
@@ -92,6 +94,8 @@ function isBlockedSensitiveConfigFile(relativePath) {
92
94
  const base = path.basename(relativePath).toLowerCase();
93
95
  if (base === ".env.example")
94
96
  return false;
97
+ if (base.startsWith("."))
98
+ return true;
95
99
  return /(^|[._-])(secret|secrets|credential|credentials|token|tokens|private|key|keys)([._-]|$)/i.test(base);
96
100
  }
97
101
  function isJavaScriptLike(relativePath) {
@@ -130,7 +134,9 @@ function walkCodeFiles(relativePath, files = []) {
130
134
  const target = (0, workspace_1.abs)(relativePath);
131
135
  if (!fs.existsSync(target))
132
136
  return files;
133
- const stat = fs.statSync(target);
137
+ const stat = fs.lstatSync(target);
138
+ if (stat.isSymbolicLink())
139
+ return files.sort();
134
140
  if (stat.isFile()) {
135
141
  if (stat.size <= exports.maxIndexedBytes && shouldIndexFile(relativePath))
136
142
  files.push(relativePath);
@@ -143,8 +149,8 @@ function walkCodeFiles(relativePath, files = []) {
143
149
  walkCodeFiles(child, files);
144
150
  }
145
151
  else if (entry.isFile() && shouldIndexFile(child)) {
146
- const childStat = fs.statSync((0, workspace_1.abs)(child));
147
- if (childStat.size <= exports.maxIndexedBytes)
152
+ const childStat = (0, workspace_1.containedProjectFileStat)(child);
153
+ if (childStat && childStat.size <= exports.maxIndexedBytes)
148
154
  files.push(child);
149
155
  }
150
156
  }
@@ -164,15 +170,13 @@ function gitTrackedAndUnignoredFiles(scopes) {
164
170
  }
165
171
  }
166
172
  function indexableFileStat(file) {
167
- try {
168
- const stat = fs.statSync((0, workspace_1.abs)(file));
169
- return stat.isFile() ? stat : null;
170
- }
171
- catch {
172
- return null;
173
- }
173
+ return (0, workspace_1.containedProjectFileStat)(file);
174
+ }
175
+ function cachedDiscoveredCodeFileStat(relativePath) {
176
+ return discoveredCodeFileStats.get(relativePath);
174
177
  }
175
178
  function discoverCodeFiles(scopes) {
179
+ discoveredCodeFileStats.clear();
176
180
  const gitFiles = gitTrackedAndUnignoredFiles(scopes);
177
181
  const candidates = gitFiles ?? scopes.flatMap((scope) => walkCodeFiles(scope));
178
182
  const files = [];
@@ -180,8 +184,10 @@ function discoverCodeFiles(scopes) {
180
184
  if (isIgnoredCodePath(file) || !shouldIndexFile(file))
181
185
  continue;
182
186
  const stat = indexableFileStat(file);
183
- if (stat && stat.size <= exports.maxIndexedBytes)
187
+ if (stat && stat.size <= exports.maxIndexedBytes) {
188
+ discoveredCodeFileStats.set(file, { absolutePath: (0, workspace_1.abs)(file), stat });
184
189
  files.push(file);
190
+ }
185
191
  }
186
192
  return files.sort();
187
193
  }