project-librarian 0.4.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 CHANGED
@@ -85,7 +85,7 @@ npm run benchmark:release -- --allow-codex-run
85
85
  | 중형 | 53.4% 적음 | 4.4% 적음 | 68.5% 적음 |
86
86
  | 대형 | 71.6% 적음* | 12.8% 적음 | 66.0% 적음 |
87
87
 
88
- 최신 합성 위키 트랙 릴리스 후보는 2026-06-16에 `gpt-5.5`로 측정했으며, 42개 시나리오를 각 3회 측정하고 1회 예열했습니다. 전체 주장 게이트는 **실패**했습니다. 42개 중 41개 시나리오는 정확성 검사를 통과했지만, Project Librarian이 없는 대형 `decision_lookup` 대조군이 3회 중 2회만 정답이었습니다. 실패한 대조군 실행은 기대한 최신 benchmark evidence 결정인 `2026-06-10` decision log 항목 대신, dated history 파일의 `2026-05-04` 항목을 골랐습니다. 따라서 이 표는 공개 릴리스 주장이 아니라 진단 근거로만 보아야 하며, 깨끗한 릴리스 실행이 주장 게이트를 통과해야 공개 주장으로 쓸 수 있습니다. 숨기지 않는 한계도 그대로 남습니다. 소형 `aggregation`은 위키를 켰을 때 훨씬 비싸고, 소형 `multi_session`도 약간 더 비싸며, `aggregation`은 토큰 비용이 줄어드는 규모에서도 실행 시간은 매번 더 느립니다.
88
+ 최신 합성 위키 트랙 릴리스 후보는 2026-06-16에 `gpt-5.5`로 측정했으며, 42개 시나리오를 각 3회 측정하고 1회 예열했습니다. 전체 주장 게이트는 **실패**했습니다. 42개 중 41개 시나리오는 정확성 검사를 통과했지만, Project Librarian이 없는 대형 `decision_lookup` 대조군이 3회 중 2회만 정답이었습니다. 실패한 대조군 실행은 날짜가 붙은 히스토리 파일에서 `2026-05-04`를 골랐고, 기대값은 결정 로그의 최신 벤치마크 근거 결정인 `2026-06-10`이었습니다. 따라서 이 표는 공개 릴리스 주장이 아니라 진단 근거로만 보아야 하며, 깨끗한 릴리스 실행이 주장 게이트를 통과해야 공개 주장으로 쓸 수 있습니다. 숨기지 않는 한계도 그대로 남습니다. 소형 `aggregation`은 위키를 켰을 때 훨씬 비싸고, 소형 `multi_session`도 약간 더 비싸며, `aggregation`은 토큰 비용이 줄어드는 규모에서도 실행 시간은 매번 더 느립니다.
89
89
 
90
90
  ### 코드 그래프 트랙 (코드 근거 인덱스, 실제 저장소)
91
91
 
@@ -188,6 +188,7 @@ npx project-librarian install-skill --scope project --agents all
188
188
  | 증분 갱신 요구 | "Project Librarian 코드 근거 인덱스를 증분 갱신해줘." | `--code-index --incremental` |
189
189
  | 전체 재생성 강제 | "Project Librarian 코드 근거 인덱스를 전체 재생성해줘." | `--code-index --code-index-full` |
190
190
  | 선택적 Tree-sitter 백엔드 사용 | "Tree-sitter 파서로 Project Librarian 코드 근거를 만들어줘." | `--code-index --code-parser tree-sitter` |
191
+ | 캐시 호환성 진단 | "Project Librarian 코드 근거 캐시 health를 확인해줘." | `--code-index-health` |
191
192
  | 캐시 상태 보기 | "Project Librarian 코드 근거 상태를 보여줘." | `--code-status` |
192
193
  | 인덱싱된 파일 목록 | "Project Librarian 코드 근거에 인덱싱된 파일을 보여줘." | `--code-files` |
193
194
  | 아키텍처/소유권 보고서 출력 | "Project Librarian 코드 보고서를 보여줘." | `--code-report` |
@@ -240,7 +241,7 @@ Git 훅 파일:
240
241
  - `wiki/index.md`
241
242
  - `wiki/meta/document-taxonomy.md`
242
243
 
243
- `canonical/project-brief.md`, `canonical/open-questions.md`, `canonical/assumptions.md`, `canonical/risks.md`, ADR 템플릿처럼 실제 내용이 없는 빈 프로젝트 문서는 기본 생성하지 않습니다. 나중에 실제 내용이 생기면 문서를 만들고 `--refresh-index`로 라우팅할 수 있습니다. 마이그레이션 중 발견한 양식 전용 legacy 템플릿은 새 위키 페이지나 검토 row로 만들지 않고 `wiki/migration/inventory.md`에 스킵 사유만 남깁니다.
244
+ `canonical/project-brief.md`, `canonical/open-questions.md`, `canonical/assumptions.md`, `canonical/risks.md`, ADR 템플릿처럼 실제 내용이 없는 빈 프로젝트 문서는 기본 생성하지 않습니다. 나중에 실제 내용이 생기면 문서를 만들고 `--refresh-index`로 라우팅할 수 있습니다. 마이그레이션 중 발견한 양식 전용 기존 템플릿은 새 위키 페이지나 검토 행으로 만들지 않고 `wiki/migration/inventory.md`에 스킵 사유만 남깁니다.
244
245
 
245
246
  MCP 서버 등록 (`mcpServers`에 기존 항목 보존하며 병합):
246
247
 
@@ -276,12 +277,12 @@ codex mcp add project-librarian -- node .codex/skills/project-librarian/dist/ini
276
277
  6. `--refresh-index`는 새로 발견한 위키 페이지를 라우팅하며, route가 많으면 `wiki/indexes/auto-*.md` 범위별 라우터로 분리합니다.
277
278
  7. `--code-index`는 `.project-wiki/` 아래 폐기 가능한 SQLite 근거 캐시를 만듭니다.
278
279
  8. `--code-report`, `--code-impact`, `--code-context-pack`, `--code-search-symbol`, `--code-query`가 계획 갱신용 코드 근거를 제공합니다.
279
- 9. 읽기 전용 위키 소비자는 공통 concept read model을 사용해 canonical 위키 스키마를 다시 쓰지 않고 경로와 frontmatter에서 사용자용 페이지 유형을 파생합니다.
280
- 10. 위키 생산자는 기존 markdown/YAML 정본 스키마를 계속 작성하고, 진단·MCP·시각화 같은 읽기 전용 소비자는 원본 문서를 바꾸지 않는 파생 보기를 사용합니다.
281
- 11. `--wiki-visualize`는 데이터베이스나 서버를 추가하지 않고 기존 위키 그래프와 concept read model을 재사용해 `.project-wiki/` 아래 정적 그래프 산출물을 작성합니다.
280
+ 9. 읽기 전용 위키 소비자는 공통 개념 읽기 모델을 사용해 정본 위키 스키마를 다시 쓰지 않고 경로와 frontmatter에서 사용자용 페이지 유형을 파생합니다.
281
+ 10. 위키 생산자는 기존 Markdown/YAML 정본 스키마를 계속 작성하고, 진단·MCP·시각화 같은 읽기 전용 소비자는 원본 문서를 바꾸지 않는 파생 보기를 사용합니다.
282
+ 11. `--wiki-visualize`는 데이터베이스나 서버를 추가하지 않고 기존 위키 그래프와 개념 읽기 모델을 재사용해 `.project-wiki/` 아래 정적 그래프 산출물을 작성합니다.
282
283
  12. 진단은 깨진 링크, 중복 route, 고아 페이지, 오래된 페이지, 누락된 TL;DR, 근거 누락, 마이그레이션 정책 위반을 보고합니다.
283
284
 
284
- 마이그레이션은 검토를 우선합니다. `--migrate`는 기존 `wiki/`를 `wiki_legacy*`로 보존하고, 양식 전용 legacy 파일은 제외한 뒤, 여러 성격의 내용이 섞인 기존 페이지를 의미 단위로 나눕니다. 이후 각 단위를 문서 분류 체계에 따라 분류해 `wiki/migration/` 아래 검토 파일을 작성합니다.
285
+ 마이그레이션은 검토를 우선합니다. `--migrate`는 기존 `wiki/`를 `wiki_legacy*`로 보존하고, 양식 전용 기존 파일은 제외한 뒤, 여러 성격의 내용이 섞인 기존 페이지를 의미 단위로 나눕니다. 이후 각 단위를 문서 분류 체계에 따라 분류해 `wiki/migration/` 아래 검토 파일을 작성합니다.
285
286
 
286
287
  - `inventory.md`는 기존 마크다운 파일 목록과 파일 단위 분류를 기록합니다.
287
288
  - `unit-map.md`는 각 제목, 문단, 목록 항목, 표 행, 코드 블록의 권장 분류 영역과 대상 페이지를 기록합니다.
@@ -351,6 +352,7 @@ node .codex/skills/project-librarian/dist/init-project-wiki.js install-skill [--
351
352
  | `--review-migration`, `--semantic-migrate` | 마이그레이션 coverage와 수신함 상태를 마이그레이션 검토 파일에 동기화합니다. |
352
353
  | `--no-git-config` | `git core.hooksPath`를 바꾸지 않고 훅 파일을 설치합니다. |
353
354
  | `--code-index` | 폐기 가능한 코드 근거 인덱스를 빌드합니다. |
355
+ | `--code-index-health` | 코드 근거 캐시 호환성을 검사하고 쓰기 없이 재빌드 안내를 출력합니다. |
354
356
  | `--code-report` | 근거 인덱스에서 아키텍처/소유권 요약을 출력합니다. |
355
357
  | `--code-report-section <section>` | 한 섹션만 출력: `coverage`, `ownership`, `languages`, `parsers`, `workspaces`, `workspace-graph`, `routes`, `hotspots`, `configs`, `edges`. |
356
358
  | `--code-impact <term>` | 파일, 심볼, route, import, edge, 소유자 영향 근거를 보여줍니다. |
@@ -367,13 +369,26 @@ npm install
367
369
  npm run typecheck
368
370
  npm run build
369
371
  npm test
372
+ npm run benchmark:claim-ledger
373
+ npm run release:check
370
374
  npm pack --dry-run
371
375
  ```
372
376
 
373
377
  `src/` 아래 TypeScript를 수정할 때는 커밋 전에 빌드해 `dist/`를 최신 상태로 유지하세요.
374
378
 
379
+ `npm run release:check`는 로컬 전용 관리자 게이트입니다. 테스트, 벤치마크 파서 smoke, 벤치마크 release preview, 벤치마크 claim ledger 분류, package dry-run 검사, dist 실행 가능 여부, README 벤치마크 claim 경계 문구를 확인합니다. publish하지 않고 measured Codex 벤치마크도 실행하지 않습니다.
380
+
375
381
  관리자 벤치마크 명령은 [benchmarks/README.md](benchmarks/README.md)에 있습니다. 이 명령은 릴리스 근거와 공개 주장 검증을 위한 것이며, 일반 사용자 설정 절차가 아닙니다.
376
382
 
383
+ 코드 근거 런타임/스토리지 점검에는 `npm run perf:code-efficiency`를 사용합니다. 이 명령은 3k/10k/50k 픽스처를 생성하고 `benchmarks/reports/code-performance-efficiency/current.json`과 `.md`를 작성합니다. 명령 시간에는 CLI 시작과 freshness 확인이 포함되며, `query_groups` 섹션은 대표 file/symbol/route/import/edge 쿼리의 직접 DB 시간을 따로 보고합니다. 보고서에는 `mixed-monorepo`를 포함한 체크인 샘플 corpus도 합성 scale fixture와 분리해 포함됩니다.
384
+
385
+ 무시된 오래된 LLM 벤치마크 raw 출력은 격리 Codex home을 삭제하기 전에 dry-run-first 헬퍼로 먼저 감사할 수 있습니다.
386
+
387
+ ```bash
388
+ npm run benchmark:llm:prune-raw -- --older-than-days 14
389
+ npm run benchmark:llm:prune-raw -- --older-than-days 14 --execute
390
+ ```
391
+
377
392
  ## 영감
378
393
 
379
394
  이 프로젝트는 Andrej Karpathy의 [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) 패턴에서 영감을 받았습니다. 긴 대화 기록에서 프로젝트 상태를 재구성하는 대신, 지속되는 markdown 컨텍스트를 작업 가까이에 둡니다.
package/README.md CHANGED
@@ -351,6 +351,7 @@ Important options:
351
351
  | `--review-migration`, `--semantic-migrate` | Sync migration coverage and inbox statuses into migration review files. |
352
352
  | `--no-git-config` | Install hook files without changing `git core.hooksPath`. |
353
353
  | `--code-index` | Build the disposable code evidence index. |
354
+ | `--code-index-health` | Inspect code evidence cache compatibility and print rebuild guidance without writing. |
354
355
  | `--code-report` | Print architecture and ownership summaries from the evidence index. |
355
356
  | `--code-report-section <section>` | Print one section: `coverage`, `ownership`, `languages`, `parsers`, `workspaces`, `workspace-graph`, `routes`, `hotspots`, `configs`, or `edges`. |
356
357
  | `--code-impact <term>` | Show file, symbol, route, import, edge, and owner impact evidence. |
@@ -367,13 +368,26 @@ npm install
367
368
  npm run typecheck
368
369
  npm run build
369
370
  npm test
371
+ npm run benchmark:claim-ledger
372
+ npm run release:check
370
373
  npm pack --dry-run
371
374
  ```
372
375
 
373
376
  When editing TypeScript under `src/`, rebuild before committing so `dist/` stays current.
374
377
 
378
+ `npm run release:check` is a local-only maintainer gate: it runs tests, benchmark parser smoke, benchmark release preview, benchmark claim-ledger classification, package dry-run inspection, dist executable checks, and README benchmark-claim boundary checks. It never publishes and never launches a measured Codex benchmark.
379
+
375
380
  Maintainer benchmark commands live in [benchmarks/README.md](benchmarks/README.md). They are for release evidence and public claim validation, not normal end-user setup.
376
381
 
382
+ For code-evidence runtime/storage checks, `npm run perf:code-efficiency` generates 3k/10k/50k fixtures and writes `benchmarks/reports/code-performance-efficiency/current.json` plus `.md`. Command timings include CLI startup and freshness checks; the `query_groups` section reports direct DB timings for representative file/symbol/route/import/edge queries. The report also includes checked-in sample corpora, including `mixed-monorepo`, separately from synthetic scale fixtures.
383
+
384
+ Old ignored LLM benchmark raw output can be audited with the dry-run-first helper before deleting retained isolated Codex homes:
385
+
386
+ ```bash
387
+ npm run benchmark:llm:prune-raw -- --older-than-days 14
388
+ npm run benchmark:llm:prune-raw -- --older-than-days 14 --execute
389
+ ```
390
+
377
391
  ## Inspiration
378
392
 
379
393
  This project is inspired by Andrej Karpathy's [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern: keep persistent markdown context close to the work instead of reconstructing project state from long chat history.
package/dist/args.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.codeIndexOutput = exports.codeSearchSymbol = exports.codeReportSection = exports.codeQuerySql = exports.codeImpactTarget = exports.codeContextPackTarget = exports.wikiVisualizeOutput = exports.wikiVisualizeMode = exports.wikiImpactTarget = exports.wikiImpactMode = exports.queryTerm = exports.codeSearchSymbolMode = exports.codeQueryMode = exports.codeImpactMode = exports.codeParserMode = exports.codeContextPackMode = exports.codeFilesMode = exports.codeStatusMode = exports.codeReportMode = exports.codeIndexFullMode = exports.codeIndexIncrementalMode = exports.codeIndexMode = exports.acknowledgeSmallRepoMode = exports.noGitConfigMode = exports.reviewMigrationMode = exports.pruneCheckMode = exports.captureInboxMode = exports.refreshIndexMode = exports.issueDraftMode = exports.issueCreateMode = exports.glossaryMode = exports.fixMode = exports.doctorMode = exports.qualityCheckMode = exports.linkCheckMode = exports.migrationQualityCheckMode = exports.migrationLintMode = exports.migrationDoctorMode = exports.lintMode = exports.migrateMode = exports.missingValueOptions = exports.unexpectedValueOptions = exports.unknownOptions = exports.args = exports.commandArgs = exports.command = exports.unknownCommand = exports.helpMode = exports.parsedArgs = exports.rawArgs = void 0;
4
- exports.issueDraftTitle = exports.issueBodyFile = exports.captureCategory = exports.captureContent = exports.captureTitle = exports.codeIndexScopes = exports.codeParser = void 0;
3
+ exports.codeSearchSymbol = exports.codeReportSection = exports.codeQuerySql = exports.codeImpactTarget = exports.codeContextPackTarget = exports.wikiVisualizeOutput = exports.wikiVisualizeMode = exports.wikiImpactTarget = exports.wikiImpactMode = exports.queryTerm = exports.codeSearchSymbolMode = exports.codeQueryMode = exports.codeImpactMode = exports.codeParserMode = exports.codeContextPackMode = exports.codeFilesMode = exports.codeStatusMode = exports.codeReportMode = exports.codeIndexHealthMode = exports.codeIndexFullMode = exports.codeIndexIncrementalMode = exports.codeIndexMode = exports.acknowledgeSmallRepoMode = exports.noGitConfigMode = exports.reviewMigrationMode = exports.pruneCheckMode = exports.captureInboxMode = exports.refreshIndexMode = exports.issueDraftMode = exports.issueCreateMode = exports.glossaryMode = exports.fixMode = exports.doctorMode = exports.qualityCheckMode = exports.linkCheckMode = exports.migrationQualityCheckMode = exports.migrationLintMode = exports.migrationDoctorMode = exports.lintMode = exports.migrateMode = exports.missingValueOptions = exports.unexpectedValueOptions = exports.unknownOptions = exports.args = exports.commandArgs = exports.command = exports.unknownCommand = exports.helpMode = exports.parsedArgs = exports.rawArgs = void 0;
4
+ exports.issueDraftTitle = exports.issueBodyFile = exports.captureCategory = exports.captureContent = exports.captureTitle = exports.codeIndexScopes = exports.codeParser = exports.codeIndexOutput = void 0;
5
5
  exports.parseArgs = parseArgs;
6
6
  exports.argValue = argValue;
7
7
  exports.argValues = argValues;
@@ -18,6 +18,7 @@ const flagsWithoutValues = new Set([
18
18
  "--code-incremental",
19
19
  "--code-index",
20
20
  "--code-index-full",
21
+ "--code-index-health",
21
22
  "--code-index-incremental",
22
23
  "--code-evidence-index-full",
23
24
  "--code-evidence-index-incremental",
@@ -151,6 +152,7 @@ function parseArgs(argv) {
151
152
  codeImpactMode: hasFlag("--code-impact") || hasFlag("--code-evidence-impact"),
152
153
  codeImpactTarget,
153
154
  codeIndexFullMode: args.has("--code-index-full") || args.has("--code-evidence-index-full"),
155
+ codeIndexHealthMode: args.has("--code-index-health"),
154
156
  codeIndexIncrementalMode: args.has("--incremental") || args.has("--code-incremental") || args.has("--code-index-incremental") || args.has("--code-evidence-index-incremental"),
155
157
  codeIndexMode: args.has("--code-index") || args.has("--code-evidence-index"),
156
158
  codeIndexOutput: argValue("--code-index-out") || argValue("--code-evidence-out") || ".project-wiki/code-evidence.sqlite",
@@ -233,6 +235,7 @@ exports.acknowledgeSmallRepoMode = exports.parsedArgs.acknowledgeSmallRepoMode;
233
235
  exports.codeIndexMode = exports.parsedArgs.codeIndexMode;
234
236
  exports.codeIndexIncrementalMode = exports.parsedArgs.codeIndexIncrementalMode;
235
237
  exports.codeIndexFullMode = exports.parsedArgs.codeIndexFullMode;
238
+ exports.codeIndexHealthMode = exports.parsedArgs.codeIndexHealthMode;
236
239
  exports.codeReportMode = exports.parsedArgs.codeReportMode;
237
240
  exports.codeStatusMode = exports.parsedArgs.codeStatusMode;
238
241
  exports.codeFilesMode = exports.parsedArgs.codeFilesMode;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectCodeEvidence = collectCodeEvidence;
4
+ const ownership_1 = require("./ownership");
5
+ const search_1 = require("./search");
6
+ function sortedUnique(values) {
7
+ return Array.from(new Set(values.filter(Boolean))).sort();
8
+ }
9
+ function ownerRowsForFilePaths(filePaths, options) {
10
+ const ownership = (0, ownership_1.ownershipContext)();
11
+ const ownersByName = new Map();
12
+ for (const filePath of filePaths) {
13
+ const info = (0, ownership_1.ownershipInfo)(filePath, ownership);
14
+ const current = ownersByName.get(info.owner) ?? {
15
+ codeowners: new Set(),
16
+ files: 0,
17
+ owner: info.owner,
18
+ owner_source: info.owner_source,
19
+ sample_files: [],
20
+ };
21
+ current.files += 1;
22
+ if (current.sample_files.length < options.ownerSampleLimit)
23
+ current.sample_files.push(filePath);
24
+ if (options.includeOwnerCodeowners && info.codeowners) {
25
+ for (const owner of info.codeowners.split(", ").filter(Boolean))
26
+ current.codeowners.add(owner);
27
+ }
28
+ ownersByName.set(info.owner, current);
29
+ }
30
+ return Array.from(ownersByName.values()).map((owner) => {
31
+ const row = {
32
+ files: owner.files,
33
+ owner: owner.owner,
34
+ owner_source: owner.owner_source,
35
+ sample_files: owner.sample_files,
36
+ };
37
+ if (options.includeOwnerCodeowners)
38
+ row.codeowners = Array.from(owner.codeowners).sort().join(", ");
39
+ return row;
40
+ }).sort((left, right) => right.files - left.files || left.owner.localeCompare(right.owner));
41
+ }
42
+ function collectCodeEvidence(database, query, options) {
43
+ const normalized = query.trim();
44
+ const like = (0, search_1.containsLikePattern)(normalized);
45
+ const files = (0, search_1.searchFiles)(database, normalized, options.fileLimit);
46
+ const symbols = (0, search_1.searchSymbols)(database, normalized, options.symbolLimit);
47
+ 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 ?").all(like, like, like, options.routeLimit);
48
+ 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 ?").all(like, like, like, options.importLimit);
49
+ const outgoingEdges = options.includeEdgeEvidenceMatches
50
+ ? 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 ?").all(like, like, like, options.edgeLimit)
51
+ : 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 ?").all(like, like, options.edgeLimit);
52
+ const incomingEdges = options.includeEdgeEvidenceMatches
53
+ ? 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 ?").all(like, like, options.edgeLimit)
54
+ : 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 ?").all(like, options.edgeLimit);
55
+ const routeTargets = routes.map((row) => `${String(row.method)} ${String(row.route)}`);
56
+ const routeEdges = options.includeRouteEdges && routeTargets.length > 0
57
+ ? 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 ?`).all(...routeTargets, options.routeEdgeLimit)
58
+ : [];
59
+ const relatedFilePaths = sortedUnique([
60
+ ...files.map((row) => String(row.path ?? "")),
61
+ ...symbols.map((row) => String(row.file_path ?? "")),
62
+ ...routes.map((row) => String(row.file_path ?? "")),
63
+ ...imports.map((row) => String(row.from_file ?? "")),
64
+ ...outgoingEdges.map((row) => String(row.file_path ?? "")),
65
+ ...incomingEdges.map((row) => String(row.file_path ?? "")),
66
+ ...routeEdges.map((row) => String(row.file_path ?? "")),
67
+ ]);
68
+ return {
69
+ files,
70
+ imports,
71
+ incomingEdges,
72
+ owners: ownerRowsForFilePaths(relatedFilePaths, options),
73
+ relatedFilePaths,
74
+ outgoingEdges,
75
+ routeEdges,
76
+ routes,
77
+ symbols,
78
+ };
79
+ }
@@ -0,0 +1,58 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.indexConfigs = indexConfigs;
37
+ const path = __importStar(require("node:path"));
38
+ const shared_1 = require("./shared");
39
+ function indexConfigs(file, insertConfig) {
40
+ if (path.basename(file.path) === "package.json") {
41
+ try {
42
+ const parsed = JSON.parse(file.text);
43
+ for (const [name, value] of Object.entries(parsed.scripts ?? {}))
44
+ insertConfig.run(`script:${name}`, value, file.path, 1);
45
+ for (const [name, value] of Object.entries(parsed.dependencies ?? {}))
46
+ insertConfig.run(`dependency:${name}`, value, file.path, 1);
47
+ for (const [name, value] of Object.entries(parsed.devDependencies ?? {}))
48
+ insertConfig.run(`devDependency:${name}`, value, file.path, 1);
49
+ }
50
+ catch {
51
+ insertConfig.run("parse-error", "package.json is not valid JSON", file.path, 1);
52
+ }
53
+ return;
54
+ }
55
+ (0, shared_1.insertMatches)(file, /^\s*([A-Za-z0-9_.-]+)\s*[:=]\s*(.+)$/gm, (match, line) => {
56
+ insertConfig.run(match[1] ?? "", (match[2] ?? "").trim(), file.path, line);
57
+ });
58
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.indexPythonLight = indexPythonLight;
4
+ exports.indexGoLight = indexGoLight;
5
+ const shared_1 = require("./shared");
6
+ function indexPythonLight(file, statements) {
7
+ const symbolPatterns = [
8
+ [/^\s*def\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/gm, "function", (match) => `def ${match[1] ?? ""}(${match[2] ?? ""})`],
9
+ [/^\s*class\s+([A-Za-z_]\w*)/gm, "class", (match) => `class ${match[1] ?? ""}`],
10
+ ];
11
+ for (const [regex, kind, signature] of symbolPatterns) {
12
+ (0, shared_1.insertMatches)(file, regex, (match, line) => (0, shared_1.insertSymbol)(statements, match[1] ?? "", kind, file, line, signature(match)));
13
+ }
14
+ const importPatterns = [
15
+ [/^\s*from\s+([A-Za-z0-9_.$]+)\s+import\s+(.+)$/gm, (match) => [match[1] ?? "", match[2] ?? ""]],
16
+ [/^\s*import\s+([A-Za-z0-9_.$, \t]+)$/gm, (match) => [match[1] ?? "", ""]],
17
+ ];
18
+ for (const [regex, fields] of importPatterns) {
19
+ (0, shared_1.insertMatches)(file, regex, (match, line) => {
20
+ const [toRef, imported] = fields(match);
21
+ statements.insertImport.run(file.path, toRef, imported.trim(), line, match[0].trim());
22
+ (0, shared_1.insertEdge)(statements, "import", "file", file.path, "module", toRef, file, line, match[0].trim());
23
+ });
24
+ }
25
+ }
26
+ function indexGoLight(file, statements) {
27
+ const symbolPatterns = [
28
+ [/^\s*func\s*\(\s*[^)]*\)\s*([A-Za-z_]\w*)\s*\(([^)]*)\)/gm, "method", (match) => match[1] ?? "", (match) => `func (...) ${match[1] ?? ""}(${match[2] ?? ""})`],
29
+ [/^\s*func\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/gm, "function", (match) => match[1] ?? "", (match) => `func ${match[1] ?? ""}(${match[2] ?? ""})`],
30
+ [/^\s*type\s+([A-Za-z_]\w*)\s+(struct|interface)?/gm, "type", (match) => match[1] ?? "", (match) => `type ${match[1] ?? ""} ${match[2] ?? ""}`.trim()],
31
+ [/^\s*const\s+([A-Za-z_]\w*)\b/gm, "constant", (match) => match[1] ?? "", (match) => `const ${match[1] ?? ""}`],
32
+ [/^\s*var\s+([A-Za-z_]\w*)\b/gm, "variable", (match) => match[1] ?? "", (match) => `var ${match[1] ?? ""}`],
33
+ ];
34
+ for (const [regex, kind, name, signature] of symbolPatterns) {
35
+ (0, shared_1.insertMatches)(file, regex, (match, line) => (0, shared_1.insertSymbol)(statements, name(match), kind, file, line, signature(match)));
36
+ }
37
+ (0, shared_1.insertMatches)(file, /^\s*import\s+(?:(?:([A-Za-z_]\w*|[_.])\s+)?\"([^\"]+)\"|`([^`]+)`)/gm, (match, line) => {
38
+ const imported = match[1] ?? "";
39
+ const toRef = match[2] ?? match[3] ?? "";
40
+ (0, shared_1.insertGoImport)(file, statements, toRef, imported, line, match[0].trim());
41
+ });
42
+ (0, shared_1.insertMatches)(file, /^\s*import\s*\(([\s\S]*?)^\s*\)/gm, (blockMatch) => {
43
+ const block = blockMatch[1] ?? "";
44
+ const blockStart = blockMatch.index ?? 0;
45
+ for (const lineMatch of block.matchAll(/^\s*(?:([A-Za-z_]\w*|[_.])\s+)?\"([^\"]+)\"/gm)) {
46
+ const imported = lineMatch[1] ?? "";
47
+ const toRef = lineMatch[2] ?? "";
48
+ const line = (0, shared_1.lineNumber)(file.text, blockStart + (lineMatch.index ?? 0));
49
+ (0, shared_1.insertGoImport)(file, statements, toRef, imported, line, lineMatch[0].trim());
50
+ }
51
+ });
52
+ }
@@ -0,0 +1,137 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractionProfile = extractionProfile;
37
+ exports.createExtractionBackendRegistry = createExtractionBackendRegistry;
38
+ const path = __importStar(require("node:path"));
39
+ const config_1 = require("./config");
40
+ const light_languages_1 = require("./light-languages");
41
+ const tree_sitter_1 = require("./tree-sitter");
42
+ const typescript_1 = require("./typescript");
43
+ function treeSitterProfile(relativePath) {
44
+ const extension = path.extname(relativePath).toLowerCase();
45
+ if ([".ts", ".mts", ".cts"].includes(extension))
46
+ return "tree-sitter-typescript";
47
+ if (extension === ".tsx")
48
+ return "tree-sitter-tsx";
49
+ if ([".js", ".mjs", ".cjs", ".jsx"].includes(extension))
50
+ return "tree-sitter-javascript";
51
+ if (extension === ".py")
52
+ return "tree-sitter-python";
53
+ if (extension === ".go")
54
+ return "tree-sitter-go";
55
+ if ([".c", ".h"].includes(extension))
56
+ return "tree-sitter-c";
57
+ if ([".cc", ".cpp", ".cxx", ".hpp", ".hh", ".hxx"].includes(extension))
58
+ return "tree-sitter-cpp";
59
+ if (extension === ".cs")
60
+ return "tree-sitter-csharp";
61
+ if (extension === ".java")
62
+ return "tree-sitter-java";
63
+ if ([".kt", ".kts"].includes(extension))
64
+ return "tree-sitter-kotlin";
65
+ if (extension === ".php")
66
+ return "tree-sitter-php";
67
+ if (extension === ".rs")
68
+ return "tree-sitter-rust";
69
+ if (extension === ".swift")
70
+ return "tree-sitter-swift";
71
+ return "inventory-only";
72
+ }
73
+ function extractionProfile(relativePath, language, parserMode) {
74
+ if (language === "config")
75
+ return "config";
76
+ if (parserMode === "tree-sitter")
77
+ return treeSitterProfile(relativePath);
78
+ if (isJavaScriptLikeProfileInput(language))
79
+ return "typescript-ast";
80
+ if (language === "python")
81
+ return "python-light";
82
+ if (language === "go")
83
+ return "go-light";
84
+ return "inventory-only";
85
+ }
86
+ function isJavaScriptLikeProfileInput(language) {
87
+ return language === "javascript" || language === "typescript";
88
+ }
89
+ function createExtractionBackendRegistry(fail) {
90
+ const extractionBackends = [
91
+ {
92
+ id: "typescript-compiler",
93
+ index: typescript_1.indexJavaScriptLike,
94
+ label: "TypeScript compiler API",
95
+ profile: "typescript-ast",
96
+ strength: "structural",
97
+ },
98
+ {
99
+ id: "regex-light",
100
+ index: light_languages_1.indexPythonLight,
101
+ label: "Python lightweight regex",
102
+ profile: "python-light",
103
+ strength: "light",
104
+ },
105
+ {
106
+ id: "regex-light",
107
+ index: light_languages_1.indexGoLight,
108
+ label: "Go lightweight regex",
109
+ profile: "go-light",
110
+ strength: "light",
111
+ },
112
+ ...(0, tree_sitter_1.treeSitterBackends)(fail),
113
+ {
114
+ id: "config-key-value",
115
+ index: (file, statements) => (0, config_1.indexConfigs)(file, statements.insertConfig),
116
+ label: "Configuration key/value extractor",
117
+ profile: "config",
118
+ strength: "config",
119
+ },
120
+ {
121
+ id: "inventory-only",
122
+ index: () => undefined,
123
+ label: "Inventory-only file listing",
124
+ profile: "inventory-only",
125
+ strength: "inventory",
126
+ },
127
+ ];
128
+ const extractionBackendsByProfile = new Map(extractionBackends.map((backend) => [backend.profile, backend]));
129
+ return {
130
+ backendForProfile(profile) {
131
+ const backend = extractionBackendsByProfile.get(profile);
132
+ if (!backend)
133
+ fail(`missing extraction backend for profile: ${profile}`);
134
+ return backend;
135
+ },
136
+ };
137
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.oneLine = oneLine;
4
+ exports.lineNumber = lineNumber;
5
+ exports.insertSymbol = insertSymbol;
6
+ exports.insertEdge = insertEdge;
7
+ exports.insertMatches = insertMatches;
8
+ exports.insertGoImport = insertGoImport;
9
+ function oneLine(text) {
10
+ return text.replace(/\s+/g, " ").trim().slice(0, 240);
11
+ }
12
+ function lineNumber(text, index) {
13
+ return text.slice(0, index).split(/\r?\n/).length;
14
+ }
15
+ function insertSymbol(statements, name, kind, file, line, signature) {
16
+ if (!name)
17
+ return;
18
+ statements.insertSymbol.run(name, kind, file.path, line, signature);
19
+ statements.insertSymbolFts.run(name, kind, file.path, signature);
20
+ }
21
+ function insertEdge(statements, kind, sourceKind, source, targetKind, target, file, line, evidence) {
22
+ if (!target)
23
+ return;
24
+ statements.insertEdge.run(kind, sourceKind, source, targetKind, target, file.path, line, evidence);
25
+ }
26
+ function insertMatches(file, regex, insert) {
27
+ for (const match of file.text.matchAll(regex)) {
28
+ insert(match, lineNumber(file.text, match.index ?? 0));
29
+ }
30
+ }
31
+ function insertGoImport(file, statements, toRef, imported, line, raw) {
32
+ if (!toRef)
33
+ return;
34
+ statements.insertImport.run(file.path, toRef, imported, line, raw);
35
+ insertEdge(statements, "import", "file", file.path, "module", toRef, file, line, raw);
36
+ }