scip-cli 2.10.0__tar.gz → 2.11.0__tar.gz
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.
- {scip_cli-2.10.0 → scip_cli-2.11.0}/PKG-INFO +2 -1
- {scip_cli-2.10.0 → scip_cli-2.11.0}/README.md +1 -0
- scip_cli-2.11.0/docs/incremental-reindex-roadmap.md +42 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/SKILL.md +3 -1
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/__init__.py +1 -1
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/__main__.py +7 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/reindex.py +18 -1
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/core.py +8 -2
- scip_cli-2.11.0/scip_cli/indexing/shards.py +195 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/typescript.py +153 -4
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/tsconfig.py +12 -0
- scip_cli-2.11.0/tests/test_incremental_shards.py +227 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_reindex.py +67 -2
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.agents/commands/migration-problems.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.agents/rules/agent.mdc +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.github/pull_request_template.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.github/workflows/ci.yml +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.gitignore +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/.pre-commit-config.yaml +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/AGENTS.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/CODE_OF_CONDUCT.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/CONTRIBUTING.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/LICENSE +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/SECURITY.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/port.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/conclusions.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/experiment-summary.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/go-experiment.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/go.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/plan.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/rust-experiment.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/rust.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/summary.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/zig-experiment.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/docs/ports/zig.md +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/pyproject.toml +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/__init__.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/common.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/file.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/graph.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/live.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/project.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/sections.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/symbol.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/analyze/targets.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/cache.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/cli_args.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/__init__.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/analyze.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/code.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/deps.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/members.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/rdeps.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/refs.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/search.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/skill.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/commands/symbols.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/config.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/debug.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/discover.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/exclude.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/__init__.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/constants.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/convert.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/languages.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/orchestrate.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/postprocess.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/indexing/runners.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/merge.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/metadata.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/output.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/paths.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/project.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/queries.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/scip_tool.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/scope.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/session.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/source.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/sql.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/symbols.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scip_cli/targets.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scripts/bench.sh +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scripts/bench_postprocess.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scripts/build.sh +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scripts/publish.sh +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/scripts/test.sh +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/__init__.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/analyze_db.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/bench_db.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/conftest.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/e2e_harness.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixture_catalog.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/package.json +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/__tests__/fixtureOnly.spec.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/app/handler.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/config.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/consumer.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/dead.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/domain/i18n/en.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/domain/i18n/index.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/domain/i18n/useLocale.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/domain/labels/index.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/events/streamTypes.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/gateway/index.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/helper.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/hooks/useHookA.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/hooks/useHookB.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/hooks/useItems.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/index.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/integrations/inferenceClient.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/lib/handler.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/loaders/useInference.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/models/MutationAggregate.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/pages/panelPage.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/rules/applyRule.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/types/a.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/types/b.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/ui/Button.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/ui/LazyPanel.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/ui/buttonConsumer.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/ui/menuModule.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/user.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/widget.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/src/widgets/OrphanWidget.ts +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/fixtures/typescript-project/tsconfig.json +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/perf_util.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_analyze.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_analyze_graph.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_analyze_live.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_analyze_perf.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_bench_queries.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_cache.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_composability.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_config.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_discover.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_e2e.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_e2e_analyze_patterns.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_e2e_perf.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_exclude_globs.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_index_batching.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_index_prune.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_indexer_env.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_indexer_fallback.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_merge.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_metadata.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_multi_symbol.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_pure_functions.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_python_index_integration.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_qualified_symbols.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_scip_tool.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_scope.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_symbols_freq.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_targets.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_tsconfig.py +0 -0
- {scip_cli-2.10.0 → scip_cli-2.11.0}/tests/test_typescript_projects.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: scip-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.11.0
|
|
4
4
|
Summary: Fast code intelligence via SCIP indexes
|
|
5
5
|
Project-URL: Homepage, https://github.com/flesler/scip-cli
|
|
6
6
|
Author: Ariel Flesler
|
|
@@ -266,6 +266,7 @@ scip-cli reindex --tsconfig tsconfig.app.json --tsconfig tsconfig.spec.json
|
|
|
266
266
|
scip-cli reindex --exclude '**/*.test.ts' '**/*.spec.ts'
|
|
267
267
|
scip-cli reindex --exclude # clear persisted excludes
|
|
268
268
|
scip-cli reindex --fresh # full index; clear metadata.json
|
|
269
|
+
scip-cli reindex --incremental # reuse unchanged project shards (see SKILL for language support)
|
|
269
270
|
```
|
|
270
271
|
|
|
271
272
|
`--path` limits which discovered tsconfig **directories** are indexed (prefix match, same idea as query `--path`). `--tsconfig` skips discovery and indexes those `tsconfig*.json` **files** (repeatable; globs are expanded inside the tool). File-based runs default to one `scip-typescript` process per file so each gets its own heap (`SCIP_CLI_TS_INDEX_BATCH_SIZE` still overrides). Cannot combine `--path` and `--tsconfig`. **TypeScript only.** Scope and exclude defaults are saved in `metadata.json` next to `index.db` and reused on later `reindex` runs; use `reindex --fresh` to clear them and restore a full index. `--exclude` updates the persisted exclude list; a lone bare `--exclude` clears it.
|
|
@@ -245,6 +245,7 @@ scip-cli reindex --tsconfig tsconfig.app.json --tsconfig tsconfig.spec.json
|
|
|
245
245
|
scip-cli reindex --exclude '**/*.test.ts' '**/*.spec.ts'
|
|
246
246
|
scip-cli reindex --exclude # clear persisted excludes
|
|
247
247
|
scip-cli reindex --fresh # full index; clear metadata.json
|
|
248
|
+
scip-cli reindex --incremental # reuse unchanged project shards (see SKILL for language support)
|
|
248
249
|
```
|
|
249
250
|
|
|
250
251
|
`--path` limits which discovered tsconfig **directories** are indexed (prefix match, same idea as query `--path`). `--tsconfig` skips discovery and indexes those `tsconfig*.json` **files** (repeatable; globs are expanded inside the tool). File-based runs default to one `scip-typescript` process per file so each gets its own heap (`SCIP_CLI_TS_INDEX_BATCH_SIZE` still overrides). Cannot combine `--path` and `--tsconfig`. **TypeScript only.** Scope and exclude defaults are saved in `metadata.json` next to `index.db` and reused on later `reindex` runs; use `reindex --fresh` to clear them and restore a full index. `--exclude` updates the persisted exclude list; a lone bare `--exclude` clears it.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Incremental reindex roadmap
|
|
2
|
+
|
|
3
|
+
Phased plan to avoid full `scip-typescript` runs on every `reindex`. Shard reuse first; file-level and upstream TSC incremental follow.
|
|
4
|
+
|
|
5
|
+
## Phase 1 — scip-cli shard reuse (shipped)
|
|
6
|
+
|
|
7
|
+
Per-tsconfig shard cache with content fingerprints. **Shippable:** unit + integration tests in-repo; large monorepos can be validated manually outside the repo (not checked in).
|
|
8
|
+
|
|
9
|
+
- [x] Roadmap doc
|
|
10
|
+
- [x] `shards/manifest.json` in cache dir (fingerprint + part DB path per tsconfig project)
|
|
11
|
+
- [x] `compute_shard_fingerprint()` — tsconfig chain + included source file content hashes + exclude globs
|
|
12
|
+
- [x] `reindex --incremental` — per-project shards (batch size 1), reuse unchanged part DBs
|
|
13
|
+
- [x] Persist shard part DBs under `cache/shards/*.db`
|
|
14
|
+
- [x] Full `reindex` clears shard cache (avoid stale reuse after non-incremental rebuild)
|
|
15
|
+
- [x] `reindex --fresh` clears shard cache
|
|
16
|
+
- [x] Unit tests for fingerprint, manifest merge, and warm `index_typescript` reuse
|
|
17
|
+
- [x] Reindex CLI flag tests (`--incremental`, rejects with `--fresh` / Python)
|
|
18
|
+
- [x] Stderr stats: `Incremental: N shard(s) reused, M reindexed`
|
|
19
|
+
- [x] Update `scip_cli/SKILL.md` and README flag list
|
|
20
|
+
|
|
21
|
+
## Phase 2 — scip-typescript fork: TSC incremental program
|
|
22
|
+
|
|
23
|
+
- [ ] Fork `sourcegraph/scip-typescript`
|
|
24
|
+
- [ ] `--incremental` / `--ts-build-info-file` CLI flags
|
|
25
|
+
- [ ] `createIncrementalProgram` + `createIncrementalCompilerHost` in `ProjectIndexer`
|
|
26
|
+
- [ ] Default `tsBuildInfoFile` under scip-cli cache dir (not repo root)
|
|
27
|
+
- [ ] scip-cli passes `--ts-build-info-file` when shard is dirty
|
|
28
|
+
- [ ] Benchmark dirty shard: program build time before/after
|
|
29
|
+
|
|
30
|
+
## Phase 3 — File-level incremental inside a shard
|
|
31
|
+
|
|
32
|
+
- [ ] Document-level upsert in SQLite (replace rows for changed paths; merge is insert-only today)
|
|
33
|
+
- [ ] `reindex --incremental` accepts optional changed-file list (content-hash manifest in cache)
|
|
34
|
+
- [ ] Invalidation: changed files ∪ direct importers (from existing index `mentions`)
|
|
35
|
+
- [ ] scip-typescript fork: skip unchanged files in `ProjectIndexer` loop (`--files` or internal dirty set)
|
|
36
|
+
- [ ] Optional git input via env / scip-atlas (`last_index_commit`, `git diff --name-only`) — not a hard scip-cli dependency
|
|
37
|
+
|
|
38
|
+
## Phase 4 — Polish
|
|
39
|
+
|
|
40
|
+
- [ ] Dogfood on this repo + optional large-monorepo validation (manual, outside CI)
|
|
41
|
+
- [ ] Submit scip-typescript fork as upstream PR (phase 2, then 3 indexer pieces)
|
|
42
|
+
- [ ] `scripts/bench.sh` scenario for incremental reindex
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scip-cli
|
|
3
|
-
description: Read when needing
|
|
3
|
+
description: Read when needing symbol lookup or SQL health dashboards in code.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
TypeScript/JavaScript (.ts, .tsx, .js, .jsx), Python (.py), Go (.go), and Rust (.rs) — not GraphQL, CSS, or other files.
|
|
@@ -168,3 +168,5 @@ reindex [--path DIR ...] [--tsconfig FILE_OR_GLOB ...] [--exclude [GLOB ...]] [-
|
|
|
168
168
|
`--exclude GLOB` omits matching files from the SQLite index after conversion (repeatable; merged with `excludeGlobs` in `.scip-cli.json`). Passing `--exclude` updates the persisted exclude list; a lone bare `--exclude` (no globs on that flag) clears it — `--exclude foo --exclude` keeps `foo`. Patterns without `/` match basenames (`*.test.ts`); patterns with `/` match repo-relative paths (`tests/**`, `**/__tests__/**`). Indexers still parse excluded files when production code imports them — post-process removal is authoritative.
|
|
169
169
|
|
|
170
170
|
`--fresh` ignores persisted `metadata.json` and clears it before indexing unless `--path`, `--tsconfig`, or `--exclude` are set on the same command (use `reindex --fresh` to restore a full index).
|
|
171
|
+
|
|
172
|
+
`--incremental` reuses cached per-project part DBs under `shards/` in the project cache dir (`~/.cache/scip-cli/projects/<slug>/`, alongside `index.db` and `metadata.json`) when inputs are unchanged. **Currently TypeScript only** (Python reindex rejects the flag). Forces one indexer run per discovered project. A plain `reindex` clears the incremental cache; run `reindex --incremental` after an initial full index to benefit. Cannot combine with `--fresh`.
|
|
@@ -195,6 +195,13 @@ def main() -> None:
|
|
|
195
195
|
action="store_true",
|
|
196
196
|
help="Ignore persisted metadata.json; clear it unless scope/exclude flags are set on this run",
|
|
197
197
|
)
|
|
198
|
+
reindex_parser.add_argument(
|
|
199
|
+
"--incremental",
|
|
200
|
+
action="store_true",
|
|
201
|
+
help=(
|
|
202
|
+
"Reuse cached per-project index parts when inputs are unchanged (full reindex clears the incremental cache)"
|
|
203
|
+
),
|
|
204
|
+
)
|
|
198
205
|
reindex_parser.add_argument(
|
|
199
206
|
"--with-external",
|
|
200
207
|
action="store_true",
|
|
@@ -11,6 +11,7 @@ from ..cache import (
|
|
|
11
11
|
promote_next_index,
|
|
12
12
|
)
|
|
13
13
|
from ..indexing import index_project, log_index_complete
|
|
14
|
+
from ..indexing.shards import clear_shard_cache
|
|
14
15
|
from ..metadata import UNSET, apply_metadata_updates
|
|
15
16
|
from ..paths import normalize_path_scope
|
|
16
17
|
from ..project import Language, find_project_root_and_language
|
|
@@ -27,6 +28,13 @@ def main(args):
|
|
|
27
28
|
tsconfig_args = getattr(args, "tsconfig", None) or []
|
|
28
29
|
exclude_groups = getattr(args, "exclude", None)
|
|
29
30
|
fresh = getattr(args, "fresh", False)
|
|
31
|
+
incremental = getattr(args, "incremental", False)
|
|
32
|
+
if incremental and fresh:
|
|
33
|
+
print("Error: reindex --incremental and --fresh cannot be combined", file=sys.stderr)
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
if incremental and lang is not None and lang != Language.TYPESCRIPT:
|
|
36
|
+
print("Error: reindex --incremental is only supported for TypeScript projects", file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
30
38
|
if path_args and tsconfig_args:
|
|
31
39
|
print("Error: reindex --path and --tsconfig cannot be combined", file=sys.stderr)
|
|
32
40
|
sys.exit(1)
|
|
@@ -92,6 +100,8 @@ def main(args):
|
|
|
92
100
|
exclude_globs=exclude_update,
|
|
93
101
|
)
|
|
94
102
|
cleanup_in_progress_index(cache_dir)
|
|
103
|
+
if fresh or not incremental:
|
|
104
|
+
clear_shard_cache(cache_dir)
|
|
95
105
|
try:
|
|
96
106
|
# Pass --with-external flag to indexer via environment
|
|
97
107
|
if getattr(args, "with_external", False):
|
|
@@ -100,7 +110,14 @@ def main(args):
|
|
|
100
110
|
os.environ["SCIP_CLI_KEEP_EXTERNAL"] = "1"
|
|
101
111
|
|
|
102
112
|
started = time.perf_counter()
|
|
103
|
-
_output_db, skipped, total = index_project(
|
|
113
|
+
_output_db, skipped, total = index_project(
|
|
114
|
+
root,
|
|
115
|
+
lang,
|
|
116
|
+
cache_dir,
|
|
117
|
+
replace=True,
|
|
118
|
+
log=False,
|
|
119
|
+
incremental=incremental,
|
|
120
|
+
)
|
|
104
121
|
elapsed_seconds = time.perf_counter() - started
|
|
105
122
|
except RuntimeError as e:
|
|
106
123
|
cleanup_in_progress_index(cache_dir)
|
|
@@ -97,7 +97,7 @@ def indexer_env(project_root=None):
|
|
|
97
97
|
return env
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
def index_project(root, lang, cache_dir, *, replace=False, log=True):
|
|
100
|
+
def index_project(root, lang, cache_dir, *, replace=False, log=True, incremental=False):
|
|
101
101
|
"""Run the language-specific indexer and convert to DB."""
|
|
102
102
|
from ..project import Language
|
|
103
103
|
|
|
@@ -110,7 +110,13 @@ def index_project(root, lang, cache_dir, *, replace=False, log=True):
|
|
|
110
110
|
if lang == Language.TYPESCRIPT:
|
|
111
111
|
projects = typescript_projects(root)
|
|
112
112
|
output_db, _indexed, skipped, total = index_typescript(
|
|
113
|
-
root,
|
|
113
|
+
root,
|
|
114
|
+
cache_dir,
|
|
115
|
+
projects,
|
|
116
|
+
env,
|
|
117
|
+
replace=replace,
|
|
118
|
+
exclude_globs=exclude_globs,
|
|
119
|
+
incremental=incremental,
|
|
114
120
|
)
|
|
115
121
|
if log:
|
|
116
122
|
log_index_complete(
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Per-tsconfig shard fingerprints and cached part DBs for incremental reindex."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from ..exclude import path_matches_glob
|
|
11
|
+
from ..tsconfig import resolved_exclude, resolved_include_or_files, tsconfig_for_project, walk_tsconfig_chain
|
|
12
|
+
|
|
13
|
+
MANIFEST_VERSION = 1
|
|
14
|
+
MANIFEST_FILENAME = "manifest.json"
|
|
15
|
+
SHARDS_DIRNAME = "shards"
|
|
16
|
+
|
|
17
|
+
_SOURCE_SUFFIXES = frozenset({".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def shards_dir(cache_dir: Path) -> Path:
|
|
21
|
+
return Path(cache_dir) / SHARDS_DIRNAME
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def manifest_path(cache_dir: Path) -> Path:
|
|
25
|
+
return shards_dir(cache_dir) / MANIFEST_FILENAME
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def shard_key(project: Path) -> str:
|
|
29
|
+
"""Stable manifest key for a TypeScript project path."""
|
|
30
|
+
return project.as_posix()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def shard_db_filename(project: Path) -> str:
|
|
34
|
+
stem = shard_key(project).replace("/", "__").replace("\\", "__")
|
|
35
|
+
return f"{stem}.db"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def shard_db_path(cache_dir: Path, project: Path) -> Path:
|
|
39
|
+
return shards_dir(cache_dir) / shard_db_filename(project)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def clear_shard_cache(cache_dir: Path) -> None:
|
|
43
|
+
"""Remove persisted shard part DBs and manifest."""
|
|
44
|
+
root = shards_dir(cache_dir)
|
|
45
|
+
if root.is_dir():
|
|
46
|
+
shutil.rmtree(root)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_shard_manifest(cache_dir: Path) -> dict[str, dict[str, str]]:
|
|
50
|
+
"""Return shard key → {fingerprint, part_db} from manifest.json."""
|
|
51
|
+
path = manifest_path(cache_dir)
|
|
52
|
+
if not path.is_file():
|
|
53
|
+
return {}
|
|
54
|
+
try:
|
|
55
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
56
|
+
except (OSError, json.JSONDecodeError):
|
|
57
|
+
return {}
|
|
58
|
+
if not isinstance(data, dict):
|
|
59
|
+
return {}
|
|
60
|
+
raw_shards = data.get("shards")
|
|
61
|
+
if not isinstance(raw_shards, dict):
|
|
62
|
+
return {}
|
|
63
|
+
out: dict[str, dict[str, str]] = {}
|
|
64
|
+
for key, entry in raw_shards.items():
|
|
65
|
+
if not isinstance(key, str) or not isinstance(entry, dict):
|
|
66
|
+
continue
|
|
67
|
+
fingerprint = entry.get("fingerprint")
|
|
68
|
+
part_db = entry.get("part_db")
|
|
69
|
+
if isinstance(fingerprint, str) and isinstance(part_db, str):
|
|
70
|
+
out[key] = {"fingerprint": fingerprint, "part_db": part_db}
|
|
71
|
+
return out
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def save_shard_manifest(cache_dir: Path, shards: dict[str, dict[str, str]]) -> None:
|
|
75
|
+
"""Merge shard entries into manifest.json (full reindex clears stale entries)."""
|
|
76
|
+
root = shards_dir(cache_dir)
|
|
77
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
merged = {**load_shard_manifest(cache_dir), **shards}
|
|
79
|
+
payload = {
|
|
80
|
+
"version": MANIFEST_VERSION,
|
|
81
|
+
"shards": {
|
|
82
|
+
key: {"fingerprint": entry["fingerprint"], "part_db": entry["part_db"]}
|
|
83
|
+
for key, entry in sorted(merged.items())
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
manifest_path(cache_dir).write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _hash_file(path: Path) -> str:
|
|
90
|
+
digest = hashlib.sha256()
|
|
91
|
+
with path.open("rb") as handle:
|
|
92
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
93
|
+
digest.update(chunk)
|
|
94
|
+
return digest.hexdigest()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _glob_source_files(base_dir: Path, patterns: list[str]) -> list[Path]:
|
|
98
|
+
found: set[Path] = set()
|
|
99
|
+
for pattern in patterns:
|
|
100
|
+
if Path(pattern).is_absolute():
|
|
101
|
+
candidate = Path(pattern)
|
|
102
|
+
if candidate.is_file() and candidate.suffix.lower() in _SOURCE_SUFFIXES:
|
|
103
|
+
found.add(candidate.resolve())
|
|
104
|
+
continue
|
|
105
|
+
for match in base_dir.glob(pattern):
|
|
106
|
+
if match.is_file() and match.suffix.lower() in _SOURCE_SUFFIXES:
|
|
107
|
+
found.add(match.resolve())
|
|
108
|
+
return sorted(found)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _tsconfig_excludes_path(tsconfig: Path, file_path: Path) -> bool:
|
|
112
|
+
"""Return True when a file is excluded by the leaf tsconfig exclude list."""
|
|
113
|
+
exclude_patterns = resolved_exclude(tsconfig)
|
|
114
|
+
if not exclude_patterns:
|
|
115
|
+
return False
|
|
116
|
+
try:
|
|
117
|
+
relative = file_path.relative_to(tsconfig.parent).as_posix()
|
|
118
|
+
except ValueError:
|
|
119
|
+
return False
|
|
120
|
+
return any(path_matches_glob(relative, pattern) for pattern in exclude_patterns)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def list_project_source_files(root: Path, project: Path) -> list[Path]:
|
|
124
|
+
"""Source files that contribute to a shard fingerprint (tsconfig include/files)."""
|
|
125
|
+
root = Path(root).resolve()
|
|
126
|
+
tsconfig = tsconfig_for_project(root, project)
|
|
127
|
+
if tsconfig is None:
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
include, files = resolved_include_or_files(tsconfig)
|
|
131
|
+
base_dir = tsconfig.parent
|
|
132
|
+
if files is not None:
|
|
133
|
+
candidates = _glob_source_files(base_dir, files)
|
|
134
|
+
elif include is not None:
|
|
135
|
+
candidates = _glob_source_files(base_dir, include)
|
|
136
|
+
else:
|
|
137
|
+
candidates = _glob_source_files(base_dir, ["**/*"])
|
|
138
|
+
return [path for path in candidates if not _tsconfig_excludes_path(tsconfig, path)]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def compute_shard_fingerprint(
|
|
142
|
+
root: Path,
|
|
143
|
+
project: Path,
|
|
144
|
+
*,
|
|
145
|
+
exclude_globs: tuple[str, ...] = (),
|
|
146
|
+
) -> str:
|
|
147
|
+
"""Hash tsconfig chain + indexed sources + exclude globs for one project shard."""
|
|
148
|
+
root = Path(root).resolve()
|
|
149
|
+
tsconfig = tsconfig_for_project(root, project)
|
|
150
|
+
digest = hashlib.sha256()
|
|
151
|
+
digest.update(shard_key(project).encode())
|
|
152
|
+
digest.update(b"\0")
|
|
153
|
+
|
|
154
|
+
if tsconfig is not None:
|
|
155
|
+
for path, _data in walk_tsconfig_chain(tsconfig):
|
|
156
|
+
digest.update(path.as_posix().encode())
|
|
157
|
+
digest.update(b"\0")
|
|
158
|
+
digest.update(path.read_bytes())
|
|
159
|
+
digest.update(b"\0")
|
|
160
|
+
|
|
161
|
+
for path in list_project_source_files(root, project):
|
|
162
|
+
try:
|
|
163
|
+
relative = path.relative_to(root).as_posix()
|
|
164
|
+
except ValueError:
|
|
165
|
+
relative = path.as_posix()
|
|
166
|
+
digest.update(relative.encode())
|
|
167
|
+
digest.update(b"\0")
|
|
168
|
+
digest.update(_hash_file(path).encode())
|
|
169
|
+
digest.update(b"\0")
|
|
170
|
+
|
|
171
|
+
for glob in exclude_globs:
|
|
172
|
+
digest.update(glob.encode())
|
|
173
|
+
digest.update(b"\0")
|
|
174
|
+
|
|
175
|
+
return digest.hexdigest()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def resolve_cached_shard_db(
|
|
179
|
+
cache_dir: Path,
|
|
180
|
+
project: Path,
|
|
181
|
+
fingerprint: str,
|
|
182
|
+
manifest: dict[str, dict[str, str]],
|
|
183
|
+
) -> Path | None:
|
|
184
|
+
"""Return a reusable shard part DB when fingerprint and file both match."""
|
|
185
|
+
key = shard_key(project)
|
|
186
|
+
entry = manifest.get(key)
|
|
187
|
+
if entry is None or entry.get("fingerprint") != fingerprint:
|
|
188
|
+
return None
|
|
189
|
+
candidate = shards_dir(cache_dir) / Path(entry["part_db"]).name
|
|
190
|
+
if candidate.is_file():
|
|
191
|
+
return candidate
|
|
192
|
+
fallback = shard_db_path(cache_dir, project)
|
|
193
|
+
if fallback.is_file():
|
|
194
|
+
return fallback
|
|
195
|
+
return None
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import shutil
|
|
6
7
|
import sys
|
|
7
8
|
import tempfile
|
|
8
9
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
@@ -24,6 +25,16 @@ from .orchestrate import (
|
|
|
24
25
|
ts_batch_limit_display,
|
|
25
26
|
)
|
|
26
27
|
from .runners import run_indexer_with_fallback
|
|
28
|
+
from .shards import (
|
|
29
|
+
SHARDS_DIRNAME,
|
|
30
|
+
compute_shard_fingerprint,
|
|
31
|
+
load_shard_manifest,
|
|
32
|
+
resolve_cached_shard_db,
|
|
33
|
+
save_shard_manifest,
|
|
34
|
+
shard_db_filename,
|
|
35
|
+
shard_db_path,
|
|
36
|
+
shard_key,
|
|
37
|
+
)
|
|
27
38
|
|
|
28
39
|
|
|
29
40
|
def typescript_projects(root: Path) -> list[Path]:
|
|
@@ -125,15 +136,60 @@ def index_ts_projects(
|
|
|
125
136
|
return label, db_path, None
|
|
126
137
|
|
|
127
138
|
|
|
128
|
-
def
|
|
139
|
+
def _persist_shard_db(cache_dir: Path, project: Path, source_db: Path) -> Path:
|
|
140
|
+
"""Copy a part DB into the shard cache for incremental reuse."""
|
|
141
|
+
dest = shard_db_path(cache_dir, project)
|
|
142
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
143
|
+
shutil.copy2(source_db, dest)
|
|
144
|
+
return dest
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _index_shard_batch(
|
|
148
|
+
root: Path,
|
|
149
|
+
cache_dir: Path,
|
|
150
|
+
batch: list[Path],
|
|
151
|
+
work_dir: Path,
|
|
152
|
+
env,
|
|
153
|
+
*,
|
|
154
|
+
exclude_globs: tuple[str, ...],
|
|
155
|
+
output_db: Path | None,
|
|
156
|
+
) -> tuple[str, Path | None, str | None]:
|
|
157
|
+
label, db_path, error = index_ts_projects(
|
|
158
|
+
root,
|
|
159
|
+
batch,
|
|
160
|
+
work_dir,
|
|
161
|
+
env,
|
|
162
|
+
output_db=output_db,
|
|
163
|
+
exclude_globs=exclude_globs,
|
|
164
|
+
)
|
|
165
|
+
if db_path is None:
|
|
166
|
+
return label, None, error
|
|
167
|
+
if len(batch) == 1:
|
|
168
|
+
_persist_shard_db(cache_dir, batch[0], db_path)
|
|
169
|
+
return label, db_path, None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def index_typescript(
|
|
173
|
+
root,
|
|
174
|
+
cache_dir,
|
|
175
|
+
projects,
|
|
176
|
+
env,
|
|
177
|
+
*,
|
|
178
|
+
replace=False,
|
|
179
|
+
exclude_globs: tuple[str, ...] = (),
|
|
180
|
+
incremental: bool = False,
|
|
181
|
+
):
|
|
129
182
|
"""Index one or more TypeScript projects and write the merged index.db."""
|
|
130
183
|
root = Path(root)
|
|
131
184
|
cache_dir = Path(cache_dir)
|
|
132
185
|
output_db = index_db_path(cache_dir, replace=replace)
|
|
133
186
|
workers = index_workers()
|
|
134
|
-
batch_size = effective_ts_batch_size(projects)
|
|
187
|
+
batch_size = 1 if incremental else effective_ts_batch_size(projects)
|
|
135
188
|
batches = batch_projects(projects, batch_size)
|
|
136
189
|
use_parallel = len(batches) > 1 and workers > 1
|
|
190
|
+
manifest = load_shard_manifest(cache_dir) if incremental else {}
|
|
191
|
+
updated_shards: dict[str, dict[str, str]] = {}
|
|
192
|
+
reused = 0
|
|
137
193
|
|
|
138
194
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
139
195
|
tmpdir_path = Path(tmpdir)
|
|
@@ -142,14 +198,99 @@ def index_typescript(root, cache_dir, projects, env, *, replace=False, exclude_g
|
|
|
142
198
|
total = len(projects)
|
|
143
199
|
show_progress = total > PROGRESS_LOG_MIN_PROJECTS
|
|
144
200
|
|
|
145
|
-
if
|
|
201
|
+
if incremental and show_progress:
|
|
202
|
+
print(f"Incremental reindex: {total} project shard(s)...", file=sys.stderr)
|
|
203
|
+
|
|
204
|
+
if show_progress and use_parallel and not incremental:
|
|
146
205
|
batch_desc = ts_batch_limit_display(batch_size, total)
|
|
147
206
|
print(
|
|
148
207
|
f"Indexing {total} TypeScript projects ({workers} workers, {batch_desc}; merge is serial)...",
|
|
149
208
|
file=sys.stderr,
|
|
150
209
|
)
|
|
151
210
|
|
|
152
|
-
|
|
211
|
+
def _record_shard(project: Path, fingerprint: str) -> None:
|
|
212
|
+
relative = shard_db_filename(project)
|
|
213
|
+
updated_shards[shard_key(project)] = {
|
|
214
|
+
"fingerprint": fingerprint,
|
|
215
|
+
"part_db": f"{SHARDS_DIRNAME}/{relative}",
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
def _try_reuse_shard(project: Path) -> Path | None:
|
|
219
|
+
nonlocal reused
|
|
220
|
+
fingerprint = compute_shard_fingerprint(root, project, exclude_globs=exclude_globs)
|
|
221
|
+
cached = resolve_cached_shard_db(cache_dir, project, fingerprint, manifest)
|
|
222
|
+
if cached is None:
|
|
223
|
+
return None
|
|
224
|
+
reused += 1
|
|
225
|
+
_record_shard(project, fingerprint)
|
|
226
|
+
if show_progress:
|
|
227
|
+
print(f"Reused shard: {shard_key(project)}", file=sys.stderr)
|
|
228
|
+
return cached
|
|
229
|
+
|
|
230
|
+
if incremental:
|
|
231
|
+
ordered_parts: list[tuple[int, Path]] = []
|
|
232
|
+
pending: list[tuple[int, list[Path]]] = []
|
|
233
|
+
for index, batch in enumerate(batches, start=1):
|
|
234
|
+
project = batch[0]
|
|
235
|
+
cached = _try_reuse_shard(project)
|
|
236
|
+
if cached is not None:
|
|
237
|
+
ordered_parts.append((index, cached))
|
|
238
|
+
continue
|
|
239
|
+
pending.append((index, batch))
|
|
240
|
+
|
|
241
|
+
if pending:
|
|
242
|
+
if use_parallel and len(pending) > 1:
|
|
243
|
+
with ThreadPoolExecutor(max_workers=workers) as pool:
|
|
244
|
+
futures = {
|
|
245
|
+
pool.submit(
|
|
246
|
+
_index_shard_batch,
|
|
247
|
+
root,
|
|
248
|
+
cache_dir,
|
|
249
|
+
batch,
|
|
250
|
+
tmpdir_path / f"part-{index}",
|
|
251
|
+
env,
|
|
252
|
+
exclude_globs=exclude_globs,
|
|
253
|
+
output_db=None,
|
|
254
|
+
): (index, batch)
|
|
255
|
+
for index, batch in pending
|
|
256
|
+
}
|
|
257
|
+
for future in as_completed(futures):
|
|
258
|
+
batch_index, batch = futures[future]
|
|
259
|
+
label, db_path, error = future.result()
|
|
260
|
+
if db_path is None:
|
|
261
|
+
skipped += len(batch)
|
|
262
|
+
print(f"Warning: skipped {label}: {error}", file=sys.stderr)
|
|
263
|
+
else:
|
|
264
|
+
project = batch[0]
|
|
265
|
+
fingerprint = compute_shard_fingerprint(root, project, exclude_globs=exclude_globs)
|
|
266
|
+
_record_shard(project, fingerprint)
|
|
267
|
+
ordered_parts.append((batch_index, db_path))
|
|
268
|
+
if show_progress:
|
|
269
|
+
print(f"Indexed shard: {shard_key(project)}", file=sys.stderr)
|
|
270
|
+
else:
|
|
271
|
+
for index, batch in pending:
|
|
272
|
+
project = batch[0]
|
|
273
|
+
if show_progress:
|
|
274
|
+
print(f"Indexing shard: {shard_key(project)}", file=sys.stderr)
|
|
275
|
+
label, db_path, error = _index_shard_batch(
|
|
276
|
+
root,
|
|
277
|
+
cache_dir,
|
|
278
|
+
batch,
|
|
279
|
+
tmpdir_path / f"part-{index}",
|
|
280
|
+
env,
|
|
281
|
+
exclude_globs=exclude_globs,
|
|
282
|
+
output_db=None,
|
|
283
|
+
)
|
|
284
|
+
if db_path is None:
|
|
285
|
+
skipped += len(batch)
|
|
286
|
+
print(f"Warning: skipped {label}: {error}", file=sys.stderr)
|
|
287
|
+
continue
|
|
288
|
+
fingerprint = compute_shard_fingerprint(root, project, exclude_globs=exclude_globs)
|
|
289
|
+
_record_shard(project, fingerprint)
|
|
290
|
+
ordered_parts.append((index, db_path))
|
|
291
|
+
|
|
292
|
+
part_dbs = [db for _, db in sorted(ordered_parts, key=lambda item: item[0])]
|
|
293
|
+
elif use_parallel:
|
|
153
294
|
completed = 0
|
|
154
295
|
with ThreadPoolExecutor(max_workers=workers) as pool:
|
|
155
296
|
futures = {
|
|
@@ -204,4 +345,12 @@ def index_typescript(root, cache_dir, projects, env, *, replace=False, exclude_g
|
|
|
204
345
|
|
|
205
346
|
finalize_part_dbs(part_dbs, output_db)
|
|
206
347
|
|
|
348
|
+
if incremental:
|
|
349
|
+
save_shard_manifest(cache_dir, updated_shards)
|
|
350
|
+
if show_progress or reused:
|
|
351
|
+
print(
|
|
352
|
+
f"Incremental: {reused} shard(s) reused, {len(updated_shards) - reused} reindexed",
|
|
353
|
+
file=sys.stderr,
|
|
354
|
+
)
|
|
355
|
+
|
|
207
356
|
return output_db, len(part_dbs), skipped, total
|
|
@@ -140,6 +140,18 @@ def resolved_allow_js(path: Path) -> bool:
|
|
|
140
140
|
return False
|
|
141
141
|
|
|
142
142
|
|
|
143
|
+
def resolved_exclude(path: Path) -> list[str]:
|
|
144
|
+
"""`exclude` from the leaf tsconfig (first entry in the extends chain)."""
|
|
145
|
+
chain = walk_tsconfig_chain(path.resolve())
|
|
146
|
+
if not chain:
|
|
147
|
+
return []
|
|
148
|
+
_file, data = chain[0]
|
|
149
|
+
exclude = data.get("exclude")
|
|
150
|
+
if isinstance(exclude, list) and all(isinstance(item, str) for item in exclude):
|
|
151
|
+
return list(exclude)
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
|
|
143
155
|
def resolved_include_or_files(path: Path) -> tuple[list[str] | None, list[str] | None]:
|
|
144
156
|
"""First `include` or `files` found walking leaf → base. The other is None."""
|
|
145
157
|
for _file, data in walk_tsconfig_chain(path):
|