scip-cli 2.7.0__tar.gz → 2.8.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.7.0 → scip_cli-2.8.0}/PKG-INFO +2 -2
- scip_cli-2.8.0/docs/port-v28-board.md +32 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/SKILL.md +3 -1
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/__init__.py +1 -1
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/__main__.py +9 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/reindex.py +8 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/config.py +6 -0
- scip_cli-2.8.0/scip_cli/exclude.py +108 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/indexing/convert.py +8 -2
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/indexing/core.py +8 -1
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/indexing/languages.py +6 -6
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/indexing/orchestrate.py +3 -0
- scip_cli-2.8.0/scip_cli/indexing/postprocess.py +296 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/indexing/typescript.py +13 -3
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/e2e_harness.py +3 -1
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixture_catalog.py +8 -0
- scip_cli-2.8.0/tests/fixtures/typescript-project/src/__tests__/fixtureOnly.spec.ts +5 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_config.py +1 -0
- scip_cli-2.8.0/tests/test_exclude_globs.py +162 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_python_index_integration.py +1 -1
- scip_cli-2.7.0/scip_cli/indexing/postprocess.py +0 -152
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.agents/commands/migration-problems.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.agents/rules/agent.mdc +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.github/pull_request_template.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.github/workflows/ci.yml +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.gitignore +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/.pre-commit-config.yaml +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/AGENTS.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/CODE_OF_CONDUCT.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/CONTRIBUTING.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/LICENSE +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/README.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/SECURITY.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/port.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/conclusions.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/experiment-summary.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/go-experiment.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/go.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/plan.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/rust-experiment.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/rust.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/summary.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/zig-experiment.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/docs/ports/zig.md +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/pyproject.toml +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/__init__.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/common.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/file.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/graph.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/live.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/project.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/sections.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/symbol.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/analyze/targets.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/cache.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/cli_args.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/__init__.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/analyze.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/code.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/deps.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/members.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/rdeps.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/refs.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/search.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/skill.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/commands/symbols.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/debug.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/discover.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/indexing/__init__.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/indexing/constants.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/indexing/runners.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/merge.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/output.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/paths.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/project.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/queries.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/scip_tool.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/scope.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/session.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/source.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/sql.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/symbols.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/targets.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scip_cli/tsconfig.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scripts/bench.sh +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scripts/bench_postprocess.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scripts/build.sh +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scripts/publish.sh +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/scripts/test.sh +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/__init__.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/analyze_db.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/bench_db.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/conftest.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/package.json +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/app/handler.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/config.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/consumer.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/dead.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/domain/i18n/en.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/domain/i18n/index.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/domain/i18n/useLocale.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/domain/labels/index.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/events/streamTypes.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/gateway/index.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/helper.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/hooks/useHookA.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/hooks/useHookB.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/hooks/useItems.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/index.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/integrations/inferenceClient.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/lib/handler.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/loaders/useInference.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/models/MutationAggregate.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/pages/panelPage.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/rules/applyRule.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/types/a.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/types/b.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/ui/Button.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/ui/LazyPanel.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/ui/buttonConsumer.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/ui/menuModule.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/user.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/widget.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/widgets/OrphanWidget.ts +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/tsconfig.json +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/perf_util.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_analyze.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_analyze_graph.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_analyze_perf.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_bench_queries.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_cache.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_composability.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_discover.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_e2e.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_e2e_analyze_patterns.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_e2e_perf.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_index_batching.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_index_prune.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_indexer_env.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_indexer_fallback.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_merge.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_multi_symbol.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_pure_functions.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_qualified_symbols.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_reindex.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_scip_tool.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_scope.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_symbols_freq.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_targets.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_tsconfig.py +0 -0
- {scip_cli-2.7.0 → scip_cli-2.8.0}/tests/test_typescript_projects.py +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# v2.8.0 multi-port board
|
|
2
|
+
|
|
3
|
+
**Goal:** Ship Python v2.8.0 (exclude globs), then port full v2.6.0→v2.8.0 gap to go/rust/zig in `/tmp`, direct-to-main, smoke-tested. Mirror GitHub releases optional.
|
|
4
|
+
|
|
5
|
+
**Frozen reference:** `/tmp/scip-cli-2.8.0-freeze.patch` (pre-commit snapshot)
|
|
6
|
+
|
|
7
|
+
## Gap to port (per mirror)
|
|
8
|
+
|
|
9
|
+
| Release | Features | Manual port |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| v2.7.0 | `analyze --check`, `dead_files`, section FP warnings | analyze/*, commands/analyze, tests |
|
|
12
|
+
| v2.8.0 | `reindex --exclude`, `excludeGlobs`, post-process prune | exclude, indexing, config, reindex, tests |
|
|
13
|
+
|
|
14
|
+
`scripts/sync-upstream.sh --ref v2.8.0` covers SKILL, fixtures, templates only.
|
|
15
|
+
|
|
16
|
+
## Lanes
|
|
17
|
+
|
|
18
|
+
| Lane | Path | Status | Notes |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| P0 Python | `~/Code/scip-cli` | in progress | commit → v2.8.0 → push → release |
|
|
21
|
+
| G1-go | `/tmp/scip-cli-go` | pending | direct-to-main |
|
|
22
|
+
| G1-rust | `/tmp/scip-cli-rust` | pending | direct-to-main |
|
|
23
|
+
| G1-zig | `/tmp/scip-cli-zig` | pending | direct-to-main |
|
|
24
|
+
|
|
25
|
+
## Verify log
|
|
26
|
+
|
|
27
|
+
| Check | Python | Go | Rust | Zig |
|
|
28
|
+
|---|---|---|---|---|
|
|
29
|
+
| scripts/test.sh | | | | |
|
|
30
|
+
| exclude dogfood | | | | |
|
|
31
|
+
| analyze --check | n/a | | | |
|
|
32
|
+
| readonly /iterate review | | | | |
|
|
@@ -160,7 +160,9 @@ Directory detection uses the filesystem when present, otherwise an indexed path
|
|
|
160
160
|
### reindex
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
|
-
reindex [--path DIR ...] [--tsconfig FILE_OR_GLOB ...] [--with-external]
|
|
163
|
+
reindex [--path DIR ...] [--tsconfig FILE_OR_GLOB ...] [--exclude GLOB ...] [--with-external]
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
`--path` and `--tsconfig` cannot be combined (**TypeScript only**). `--tsconfig` takes `tsconfig*.json` files (repeatable; globs 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). Scope is saved as `index-scope.json` and reused until a full `reindex` with neither flag.
|
|
167
|
+
|
|
168
|
+
`--exclude GLOB` omits matching files from the SQLite index after conversion (repeatable; merged with `excludeGlobs` in `.scip-cli.json`). Persisted in `index-exclude.json` until a full `reindex` with no `--exclude`. 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.
|
|
@@ -166,6 +166,15 @@ def main() -> None:
|
|
|
166
166
|
"Cannot be combined with --path."
|
|
167
167
|
),
|
|
168
168
|
)
|
|
169
|
+
reindex_parser.add_argument(
|
|
170
|
+
"--exclude",
|
|
171
|
+
action="append",
|
|
172
|
+
metavar="GLOB",
|
|
173
|
+
help=(
|
|
174
|
+
"Omit indexed files matching GLOB from the SQLite index "
|
|
175
|
+
"(repeatable; merged with excludeGlobs in .scip-cli.json; persisted until full reindex)"
|
|
176
|
+
),
|
|
177
|
+
)
|
|
169
178
|
reindex_parser.add_argument(
|
|
170
179
|
"--with-external",
|
|
171
180
|
action="store_true",
|
|
@@ -9,6 +9,7 @@ from ..cache import (
|
|
|
9
9
|
index_db_path,
|
|
10
10
|
promote_next_index,
|
|
11
11
|
)
|
|
12
|
+
from ..exclude import save_persisted_exclude_globs
|
|
12
13
|
from ..indexing import index_project, log_index_complete
|
|
13
14
|
from ..paths import normalize_path_scope
|
|
14
15
|
from ..project import Language, find_project_root_and_language
|
|
@@ -24,6 +25,7 @@ def main(args):
|
|
|
24
25
|
|
|
25
26
|
path_args = getattr(args, "path", None) or []
|
|
26
27
|
tsconfig_args = getattr(args, "tsconfig", None) or []
|
|
28
|
+
exclude_args = getattr(args, "exclude", None) or []
|
|
27
29
|
if path_args and tsconfig_args:
|
|
28
30
|
print("Error: reindex --path and --tsconfig cannot be combined", file=sys.stderr)
|
|
29
31
|
sys.exit(1)
|
|
@@ -68,6 +70,12 @@ def main(args):
|
|
|
68
70
|
else:
|
|
69
71
|
save_index_scope(root, None)
|
|
70
72
|
|
|
73
|
+
if exclude_args:
|
|
74
|
+
save_persisted_exclude_globs(root, exclude_args)
|
|
75
|
+
print(f"Index exclude: {', '.join(exclude_args)}", file=sys.stderr)
|
|
76
|
+
else:
|
|
77
|
+
save_persisted_exclude_globs(root, None)
|
|
78
|
+
|
|
71
79
|
cache_dir = get_cache_dir(root)
|
|
72
80
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
73
81
|
|
|
@@ -16,6 +16,7 @@ class ProjectSettings:
|
|
|
16
16
|
max_heap_mb: int | None = None
|
|
17
17
|
index_roots: list[str] = field(default_factory=list)
|
|
18
18
|
only_index_roots: bool = False
|
|
19
|
+
exclude_globs: list[str] = field(default_factory=list)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def load_project_config(project_root: Path) -> ProjectSettings:
|
|
@@ -44,10 +45,15 @@ def load_project_config(project_root: Path) -> ProjectSettings:
|
|
|
44
45
|
if type(only_index_roots) is not bool:
|
|
45
46
|
raise RuntimeError(f"Invalid {CONFIG_FILENAME}: onlyIndexRoots must be a boolean")
|
|
46
47
|
|
|
48
|
+
exclude_globs = data.get("excludeGlobs", [])
|
|
49
|
+
if not isinstance(exclude_globs, list) or not all(isinstance(p, str) for p in exclude_globs):
|
|
50
|
+
raise RuntimeError(f"Invalid {CONFIG_FILENAME}: excludeGlobs must be a string array")
|
|
51
|
+
|
|
47
52
|
return ProjectSettings(
|
|
48
53
|
max_heap_mb=max_heap_mb,
|
|
49
54
|
index_roots=index_roots,
|
|
50
55
|
only_index_roots=only_index_roots,
|
|
56
|
+
exclude_globs=exclude_globs,
|
|
51
57
|
)
|
|
52
58
|
|
|
53
59
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Persisted index exclude globs and path matching."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path, PurePosixPath
|
|
8
|
+
|
|
9
|
+
from .config import load_project_config
|
|
10
|
+
|
|
11
|
+
EXCLUDE_FILENAME = "index-exclude.json"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _exclude_path(project_root: Path) -> Path:
|
|
15
|
+
from .cache import get_cache_dir
|
|
16
|
+
|
|
17
|
+
return get_cache_dir(project_root) / EXCLUDE_FILENAME
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_persisted_exclude_globs(project_root: Path) -> tuple[str, ...]:
|
|
21
|
+
"""Load exclude globs from the last scoped reindex --exclude, if any."""
|
|
22
|
+
path = _exclude_path(project_root)
|
|
23
|
+
if not path.is_file():
|
|
24
|
+
return ()
|
|
25
|
+
try:
|
|
26
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
27
|
+
except (OSError, json.JSONDecodeError):
|
|
28
|
+
return ()
|
|
29
|
+
if not isinstance(data, dict):
|
|
30
|
+
return ()
|
|
31
|
+
raw = data.get("globs")
|
|
32
|
+
if not raw or not isinstance(raw, list) or not all(isinstance(g, str) for g in raw):
|
|
33
|
+
return ()
|
|
34
|
+
return tuple(raw)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def save_persisted_exclude_globs(project_root: Path, globs: list[str] | None) -> None:
|
|
38
|
+
"""Persist or clear reindex --exclude globs for a project."""
|
|
39
|
+
path = _exclude_path(project_root)
|
|
40
|
+
if not globs:
|
|
41
|
+
if path.is_file():
|
|
42
|
+
path.unlink()
|
|
43
|
+
return
|
|
44
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
path.write_text(json.dumps({"globs": globs}, indent=2) + "\n", encoding="utf-8")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def resolve_exclude_globs(project_root: Path) -> tuple[str, ...]:
|
|
49
|
+
"""Merge .scip-cli.json excludeGlobs with persisted reindex --exclude globs."""
|
|
50
|
+
settings = load_project_config(project_root)
|
|
51
|
+
merged: list[str] = []
|
|
52
|
+
seen: set[str] = set()
|
|
53
|
+
for glob in (*settings.exclude_globs, *load_persisted_exclude_globs(project_root)):
|
|
54
|
+
if glob and glob not in seen:
|
|
55
|
+
seen.add(glob)
|
|
56
|
+
merged.append(glob)
|
|
57
|
+
return tuple(merged)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _glob_to_regex(pattern: str) -> re.Pattern[str]:
|
|
61
|
+
"""Translate a repo-relative glob to a regex (supports **, *, ?)."""
|
|
62
|
+
normalized = pattern.replace("\\", "/").lstrip("/")
|
|
63
|
+
parts: list[str] = []
|
|
64
|
+
index = 0
|
|
65
|
+
while index < len(normalized):
|
|
66
|
+
char = normalized[index]
|
|
67
|
+
if char == "*":
|
|
68
|
+
if index + 1 < len(normalized) and normalized[index + 1] == "*":
|
|
69
|
+
if index + 2 < len(normalized) and normalized[index + 2] == "/":
|
|
70
|
+
parts.append("(?:.+/)?")
|
|
71
|
+
index += 3
|
|
72
|
+
continue
|
|
73
|
+
parts.append(".*")
|
|
74
|
+
index += 2
|
|
75
|
+
continue
|
|
76
|
+
parts.append("[^/]*")
|
|
77
|
+
index += 1
|
|
78
|
+
continue
|
|
79
|
+
if char == "?":
|
|
80
|
+
parts.append("[^/]")
|
|
81
|
+
index += 1
|
|
82
|
+
continue
|
|
83
|
+
if char in ".^$+{}|()[]":
|
|
84
|
+
parts.append(re.escape(char))
|
|
85
|
+
else:
|
|
86
|
+
parts.append(char)
|
|
87
|
+
index += 1
|
|
88
|
+
return re.compile("^" + "".join(parts) + "$")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def path_matches_glob(relative_path: str, pattern: str) -> bool:
|
|
92
|
+
"""Return True when a repo-relative path matches an exclude glob.
|
|
93
|
+
|
|
94
|
+
Patterns without '/' match the basename only (e.g. ``*.test.ts``).
|
|
95
|
+
Patterns with '/' match the full posix path (``tests/**``, ``src/*.spec.ts``).
|
|
96
|
+
"""
|
|
97
|
+
path = relative_path.replace("\\", "/").lstrip("/")
|
|
98
|
+
glob = pattern.replace("\\", "/").lstrip("/")
|
|
99
|
+
if not glob:
|
|
100
|
+
return False
|
|
101
|
+
target = PurePosixPath(path).name if "/" not in glob else path
|
|
102
|
+
return bool(_glob_to_regex(glob).match(target))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def path_matches_any_glob(relative_path: str, patterns: tuple[str, ...] | list[str]) -> bool:
|
|
106
|
+
if not patterns:
|
|
107
|
+
return False
|
|
108
|
+
return any(path_matches_glob(relative_path, pattern) for pattern in patterns)
|
|
@@ -68,7 +68,13 @@ def prefix_document_paths(db_path: Path, prefix: str) -> None:
|
|
|
68
68
|
conn.close()
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def convert_scip_to_db(
|
|
71
|
+
def convert_scip_to_db(
|
|
72
|
+
scip_path,
|
|
73
|
+
db_path,
|
|
74
|
+
*,
|
|
75
|
+
document_path_prefix: Path | str | None = None,
|
|
76
|
+
exclude_globs: tuple[str, ...] | None = None,
|
|
77
|
+
):
|
|
72
78
|
"""Convert a SCIP protobuf file to a SQLite index at db_path."""
|
|
73
79
|
db_path = Path(db_path)
|
|
74
80
|
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -88,7 +94,7 @@ def convert_scip_to_db(scip_path, db_path, *, document_path_prefix: Path | str |
|
|
|
88
94
|
if not db_path.exists():
|
|
89
95
|
raise RuntimeError("Failed to convert index")
|
|
90
96
|
|
|
91
|
-
postprocess_index(db_path)
|
|
97
|
+
postprocess_index(db_path, exclude_globs=exclude_globs or ())
|
|
92
98
|
prefix = project_path_prefix(document_path_prefix)
|
|
93
99
|
if prefix is not None:
|
|
94
100
|
prefix_document_paths(db_path, prefix)
|
|
@@ -17,6 +17,7 @@ from ..cache import (
|
|
|
17
17
|
)
|
|
18
18
|
from ..config import load_project_config
|
|
19
19
|
from ..discover import discover_golang_modules, discover_python_projects, discover_rust_crates
|
|
20
|
+
from ..exclude import resolve_exclude_globs
|
|
20
21
|
from .constants import DEFAULT_MAX_HEAP_MB
|
|
21
22
|
from .languages import index_golang_module, index_python_project, index_rust_crate
|
|
22
23
|
from .orchestrate import index_discovered_projects
|
|
@@ -87,10 +88,13 @@ def index_project(root, lang, cache_dir, *, replace=False, log=True):
|
|
|
87
88
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
88
89
|
root = Path(root).resolve()
|
|
89
90
|
env = indexer_env(root)
|
|
91
|
+
exclude_globs = resolve_exclude_globs(root)
|
|
90
92
|
|
|
91
93
|
if lang == Language.TYPESCRIPT:
|
|
92
94
|
projects = typescript_projects(root)
|
|
93
|
-
output_db, _indexed, skipped, total = index_typescript(
|
|
95
|
+
output_db, _indexed, skipped, total = index_typescript(
|
|
96
|
+
root, cache_dir, projects, env, replace=replace, exclude_globs=exclude_globs
|
|
97
|
+
)
|
|
94
98
|
if log:
|
|
95
99
|
log_index_complete(
|
|
96
100
|
output_db,
|
|
@@ -110,6 +114,7 @@ def index_project(root, lang, cache_dir, *, replace=False, log=True):
|
|
|
110
114
|
replace=replace,
|
|
111
115
|
progress_noun="Python packages",
|
|
112
116
|
index_one=index_python_project,
|
|
117
|
+
exclude_globs=exclude_globs,
|
|
113
118
|
)
|
|
114
119
|
if log:
|
|
115
120
|
log_index_complete(
|
|
@@ -130,6 +135,7 @@ def index_project(root, lang, cache_dir, *, replace=False, log=True):
|
|
|
130
135
|
replace=replace,
|
|
131
136
|
progress_noun="Go modules",
|
|
132
137
|
index_one=index_golang_module,
|
|
138
|
+
exclude_globs=exclude_globs,
|
|
133
139
|
)
|
|
134
140
|
if log:
|
|
135
141
|
log_index_complete(
|
|
@@ -150,6 +156,7 @@ def index_project(root, lang, cache_dir, *, replace=False, log=True):
|
|
|
150
156
|
replace=replace,
|
|
151
157
|
progress_noun="Rust crates",
|
|
152
158
|
index_one=index_rust_crate,
|
|
159
|
+
exclude_globs=exclude_globs,
|
|
153
160
|
)
|
|
154
161
|
if log:
|
|
155
162
|
log_index_complete(
|
|
@@ -13,7 +13,7 @@ def _project_cwd(root: Path, project: Path) -> Path:
|
|
|
13
13
|
return root if project == Path(".") else root / project
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def index_python_project(root, project, work_dir, env, *, output_db=None):
|
|
16
|
+
def index_python_project(root, project, work_dir, env, *, output_db=None, exclude_globs=()):
|
|
17
17
|
"""Index one Python package directory into work_dir/index.db (or output_db when set)."""
|
|
18
18
|
work_dir = Path(work_dir)
|
|
19
19
|
work_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -31,13 +31,13 @@ def index_python_project(root, project, work_dir, env, *, output_db=None):
|
|
|
31
31
|
if result.returncode != 0:
|
|
32
32
|
return label, None, result.stderr.strip() or "indexing failed"
|
|
33
33
|
try:
|
|
34
|
-
convert_scip_to_db(part_scip, db_path, document_path_prefix=project)
|
|
34
|
+
convert_scip_to_db(part_scip, db_path, document_path_prefix=project, exclude_globs=exclude_globs)
|
|
35
35
|
finally:
|
|
36
36
|
part_scip.unlink(missing_ok=True)
|
|
37
37
|
return label, db_path, None
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def index_golang_module(root, module, work_dir, env, *, output_db=None):
|
|
40
|
+
def index_golang_module(root, module, work_dir, env, *, output_db=None, exclude_globs=()):
|
|
41
41
|
"""Index one Go module directory into work_dir/index.db (or output_db when set)."""
|
|
42
42
|
work_dir = Path(work_dir)
|
|
43
43
|
work_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -55,13 +55,13 @@ def index_golang_module(root, module, work_dir, env, *, output_db=None):
|
|
|
55
55
|
if result.returncode != 0:
|
|
56
56
|
return label, None, result.stderr.strip() or "indexing failed"
|
|
57
57
|
try:
|
|
58
|
-
convert_scip_to_db(part_scip, db_path, document_path_prefix=module)
|
|
58
|
+
convert_scip_to_db(part_scip, db_path, document_path_prefix=module, exclude_globs=exclude_globs)
|
|
59
59
|
finally:
|
|
60
60
|
part_scip.unlink(missing_ok=True)
|
|
61
61
|
return label, db_path, None
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def index_rust_crate(root, crate, work_dir, env, *, output_db=None):
|
|
64
|
+
def index_rust_crate(root, crate, work_dir, env, *, output_db=None, exclude_globs=()):
|
|
65
65
|
"""Index one Rust crate directory into work_dir/index.db (or output_db when set)."""
|
|
66
66
|
work_dir = Path(work_dir)
|
|
67
67
|
work_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -79,7 +79,7 @@ def index_rust_crate(root, crate, work_dir, env, *, output_db=None):
|
|
|
79
79
|
if result.returncode != 0:
|
|
80
80
|
return label, None, result.stderr.strip() or "indexing failed"
|
|
81
81
|
try:
|
|
82
|
-
convert_scip_to_db(part_scip, db_path, document_path_prefix=crate)
|
|
82
|
+
convert_scip_to_db(part_scip, db_path, document_path_prefix=crate, exclude_globs=exclude_globs)
|
|
83
83
|
finally:
|
|
84
84
|
part_scip.unlink(missing_ok=True)
|
|
85
85
|
return label, db_path, None
|
|
@@ -103,6 +103,7 @@ def index_discovered_projects(
|
|
|
103
103
|
replace: bool,
|
|
104
104
|
progress_noun: str,
|
|
105
105
|
index_one,
|
|
106
|
+
exclude_globs: tuple[str, ...] = (),
|
|
106
107
|
) -> tuple[Path, int, int, int]:
|
|
107
108
|
"""Index one SCIP unit per project path; merge when multiple part DBs."""
|
|
108
109
|
output_db = index_db_path(cache_dir, replace=replace)
|
|
@@ -132,6 +133,7 @@ def index_discovered_projects(
|
|
|
132
133
|
project,
|
|
133
134
|
tmpdir_path / f"part-{index}",
|
|
134
135
|
env,
|
|
136
|
+
exclude_globs=exclude_globs,
|
|
135
137
|
): (index, project)
|
|
136
138
|
for index, project in enumerate(projects, start=1)
|
|
137
139
|
}
|
|
@@ -160,6 +162,7 @@ def index_discovered_projects(
|
|
|
160
162
|
cache_dir if direct_output else tmpdir_path / f"part-{index}",
|
|
161
163
|
env,
|
|
162
164
|
output_db=direct_output,
|
|
165
|
+
exclude_globs=exclude_globs,
|
|
163
166
|
)
|
|
164
167
|
if db_path is None:
|
|
165
168
|
skipped += 1
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""Shrink converted SQLite indexes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sqlite3
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _replace_table(conn, old_name: str, new_name: str) -> None:
|
|
10
|
+
conn.execute(f"DROP TABLE {old_name}")
|
|
11
|
+
conn.execute(f"ALTER TABLE {new_name} RENAME TO {old_name}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _trim_unused_columns(conn, keep_external=False):
|
|
15
|
+
"""Remove columns we never use; omit variable symbols while rebuilding global_symbols.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
conn: SQLite connection
|
|
19
|
+
keep_external: If True, keep symbols without definitions (external libs)
|
|
20
|
+
"""
|
|
21
|
+
conn.execute("""
|
|
22
|
+
CREATE TABLE documents_new (
|
|
23
|
+
id INTEGER PRIMARY KEY,
|
|
24
|
+
relative_path TEXT NOT NULL UNIQUE
|
|
25
|
+
)
|
|
26
|
+
""")
|
|
27
|
+
conn.execute("INSERT INTO documents_new (id, relative_path) SELECT id, relative_path FROM documents")
|
|
28
|
+
_replace_table(conn, "documents", "documents_new")
|
|
29
|
+
|
|
30
|
+
from ..symbols import sql_exclude_variable_symbols
|
|
31
|
+
|
|
32
|
+
exclude = sql_exclude_variable_symbols("symbol")
|
|
33
|
+
|
|
34
|
+
# Build the WHERE clause: exclude variables AND optionally require definitions
|
|
35
|
+
where_clauses = [exclude]
|
|
36
|
+
if not keep_external:
|
|
37
|
+
# Keep symbols that are either:
|
|
38
|
+
# 1. Defined in the project (have defn_enclosing_ranges entries)
|
|
39
|
+
# 2. Structural symbols needed for type analysis (type literals, parameters)
|
|
40
|
+
# 3. Functions/methods (even without defs, they may be project code)
|
|
41
|
+
#
|
|
42
|
+
# SCIP symbol format patterns (language-agnostic):
|
|
43
|
+
# - %typeLiteral% : Type/interface fields (e.g., Options#typeLiteral0:verbose.)
|
|
44
|
+
# - %).(% : Function parameters (e.g., greet().(name))
|
|
45
|
+
# - %().% : Functions and methods (e.g., Widget.run()., greet().)
|
|
46
|
+
where_clauses.append(
|
|
47
|
+
"EXISTS (SELECT 1 FROM defn_enclosing_ranges der WHERE der.symbol_id = global_symbols.id) "
|
|
48
|
+
+ "OR symbol LIKE '%typeLiteral%' "
|
|
49
|
+
+ "OR symbol LIKE '%).(%' "
|
|
50
|
+
+ "OR symbol LIKE '%().%'"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
where_clause = " AND ".join(where_clauses)
|
|
54
|
+
|
|
55
|
+
conn.execute("""
|
|
56
|
+
CREATE TABLE global_symbols_new (
|
|
57
|
+
id INTEGER PRIMARY KEY,
|
|
58
|
+
symbol TEXT NOT NULL UNIQUE,
|
|
59
|
+
display_name TEXT,
|
|
60
|
+
kind INTEGER
|
|
61
|
+
)
|
|
62
|
+
""")
|
|
63
|
+
conn.execute(f"""
|
|
64
|
+
INSERT INTO global_symbols_new (id, symbol, display_name, kind)
|
|
65
|
+
SELECT id, symbol, display_name, kind FROM global_symbols
|
|
66
|
+
WHERE {where_clause}
|
|
67
|
+
""")
|
|
68
|
+
_replace_table(conn, "global_symbols", "global_symbols_new")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _trim_mentions_to_known_symbols(conn):
|
|
72
|
+
"""Copy mentions that reference retained symbols (drops variable refs without DELETE)."""
|
|
73
|
+
if not conn.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='mentions' LIMIT 1").fetchone():
|
|
74
|
+
return
|
|
75
|
+
conn.execute("""
|
|
76
|
+
CREATE TABLE mentions_new (
|
|
77
|
+
chunk_id INTEGER NOT NULL,
|
|
78
|
+
symbol_id INTEGER NOT NULL,
|
|
79
|
+
role INTEGER NOT NULL,
|
|
80
|
+
PRIMARY KEY (chunk_id, symbol_id, role)
|
|
81
|
+
)
|
|
82
|
+
""")
|
|
83
|
+
conn.execute("""
|
|
84
|
+
INSERT INTO mentions_new (chunk_id, symbol_id, role)
|
|
85
|
+
SELECT m.chunk_id, m.symbol_id, m.role
|
|
86
|
+
FROM mentions m
|
|
87
|
+
JOIN global_symbols g ON g.id = m.symbol_id
|
|
88
|
+
""")
|
|
89
|
+
_replace_table(conn, "mentions", "mentions_new")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _trim_defn_to_known_symbols(conn):
|
|
93
|
+
"""Copy defn rows for retained symbols only."""
|
|
94
|
+
if not conn.execute(
|
|
95
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='defn_enclosing_ranges' LIMIT 1"
|
|
96
|
+
).fetchone():
|
|
97
|
+
return
|
|
98
|
+
conn.execute("""
|
|
99
|
+
CREATE TABLE defn_enclosing_ranges_new (
|
|
100
|
+
id INTEGER PRIMARY KEY,
|
|
101
|
+
document_id INTEGER NOT NULL,
|
|
102
|
+
symbol_id INTEGER NOT NULL,
|
|
103
|
+
start_line INTEGER NOT NULL,
|
|
104
|
+
start_char INTEGER NOT NULL,
|
|
105
|
+
end_line INTEGER NOT NULL,
|
|
106
|
+
end_char INTEGER NOT NULL
|
|
107
|
+
)
|
|
108
|
+
""")
|
|
109
|
+
conn.execute("""
|
|
110
|
+
INSERT INTO defn_enclosing_ranges_new (
|
|
111
|
+
id, document_id, symbol_id, start_line, start_char, end_line, end_char
|
|
112
|
+
)
|
|
113
|
+
SELECT d.id, d.document_id, d.symbol_id, d.start_line, d.start_char, d.end_line, d.end_char
|
|
114
|
+
FROM defn_enclosing_ranges d
|
|
115
|
+
JOIN global_symbols g ON g.id = d.symbol_id
|
|
116
|
+
""")
|
|
117
|
+
_replace_table(conn, "defn_enclosing_ranges", "defn_enclosing_ranges_new")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _prune_excluded_documents(conn: sqlite3.Connection, exclude_globs: tuple[str, ...]) -> None:
|
|
121
|
+
"""Drop documents matching exclude globs and cascade to chunks, mentions, defs, symbols."""
|
|
122
|
+
from ..exclude import path_matches_any_glob
|
|
123
|
+
|
|
124
|
+
if not exclude_globs:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
rows = conn.execute("SELECT id, relative_path FROM documents").fetchall()
|
|
128
|
+
exclude_ids = {row[0] for row in rows if path_matches_any_glob(row[1], exclude_globs)}
|
|
129
|
+
if not exclude_ids:
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
conn.execute("""
|
|
133
|
+
CREATE TABLE documents_new (
|
|
134
|
+
id INTEGER PRIMARY KEY,
|
|
135
|
+
relative_path TEXT NOT NULL UNIQUE
|
|
136
|
+
)
|
|
137
|
+
""")
|
|
138
|
+
conn.execute(
|
|
139
|
+
"INSERT INTO documents_new (id, relative_path) SELECT id, relative_path FROM documents WHERE id NOT IN "
|
|
140
|
+
+ f"({','.join('?' * len(exclude_ids))})",
|
|
141
|
+
tuple(exclude_ids),
|
|
142
|
+
)
|
|
143
|
+
_replace_table(conn, "documents", "documents_new")
|
|
144
|
+
|
|
145
|
+
if conn.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='chunks' LIMIT 1").fetchone():
|
|
146
|
+
conn.execute("""
|
|
147
|
+
CREATE TABLE chunks_new (
|
|
148
|
+
id INTEGER PRIMARY KEY,
|
|
149
|
+
document_id INTEGER NOT NULL,
|
|
150
|
+
chunk_index INTEGER NOT NULL,
|
|
151
|
+
start_line INTEGER NOT NULL,
|
|
152
|
+
end_line INTEGER NOT NULL,
|
|
153
|
+
occurrences BLOB NOT NULL
|
|
154
|
+
)
|
|
155
|
+
""")
|
|
156
|
+
conn.execute(
|
|
157
|
+
"INSERT INTO chunks_new SELECT c.* FROM chunks c JOIN documents d ON c.document_id = d.id",
|
|
158
|
+
)
|
|
159
|
+
_replace_table(conn, "chunks", "chunks_new")
|
|
160
|
+
|
|
161
|
+
if conn.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='mentions' LIMIT 1").fetchone():
|
|
162
|
+
conn.execute("""
|
|
163
|
+
CREATE TABLE mentions_new (
|
|
164
|
+
chunk_id INTEGER NOT NULL,
|
|
165
|
+
symbol_id INTEGER NOT NULL,
|
|
166
|
+
role INTEGER NOT NULL,
|
|
167
|
+
PRIMARY KEY (chunk_id, symbol_id, role)
|
|
168
|
+
)
|
|
169
|
+
""")
|
|
170
|
+
conn.execute("""
|
|
171
|
+
INSERT INTO mentions_new (chunk_id, symbol_id, role)
|
|
172
|
+
SELECT m.chunk_id, m.symbol_id, m.role
|
|
173
|
+
FROM mentions m
|
|
174
|
+
JOIN chunks c ON c.id = m.chunk_id
|
|
175
|
+
""")
|
|
176
|
+
_replace_table(conn, "mentions", "mentions_new")
|
|
177
|
+
|
|
178
|
+
if conn.execute(
|
|
179
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='defn_enclosing_ranges' LIMIT 1"
|
|
180
|
+
).fetchone():
|
|
181
|
+
conn.execute("""
|
|
182
|
+
CREATE TABLE defn_enclosing_ranges_new (
|
|
183
|
+
id INTEGER PRIMARY KEY,
|
|
184
|
+
document_id INTEGER NOT NULL,
|
|
185
|
+
symbol_id INTEGER NOT NULL,
|
|
186
|
+
start_line INTEGER NOT NULL,
|
|
187
|
+
start_char INTEGER NOT NULL,
|
|
188
|
+
end_line INTEGER NOT NULL,
|
|
189
|
+
end_char INTEGER NOT NULL
|
|
190
|
+
)
|
|
191
|
+
""")
|
|
192
|
+
conn.execute("""
|
|
193
|
+
INSERT INTO defn_enclosing_ranges_new (
|
|
194
|
+
id, document_id, symbol_id, start_line, start_char, end_line, end_char
|
|
195
|
+
)
|
|
196
|
+
SELECT d.id, d.document_id, d.symbol_id, d.start_line, d.start_char, d.end_line, d.end_char
|
|
197
|
+
FROM defn_enclosing_ranges d
|
|
198
|
+
JOIN documents doc ON doc.id = d.document_id
|
|
199
|
+
""")
|
|
200
|
+
_replace_table(conn, "defn_enclosing_ranges", "defn_enclosing_ranges_new")
|
|
201
|
+
|
|
202
|
+
conn.execute("""
|
|
203
|
+
CREATE TABLE global_symbols_new (
|
|
204
|
+
id INTEGER PRIMARY KEY,
|
|
205
|
+
symbol TEXT NOT NULL UNIQUE,
|
|
206
|
+
display_name TEXT,
|
|
207
|
+
kind INTEGER
|
|
208
|
+
)
|
|
209
|
+
""")
|
|
210
|
+
conn.execute("""
|
|
211
|
+
INSERT INTO global_symbols_new (id, symbol, display_name, kind)
|
|
212
|
+
SELECT g.id, g.symbol, g.display_name, g.kind
|
|
213
|
+
FROM global_symbols g
|
|
214
|
+
WHERE EXISTS (SELECT 1 FROM defn_enclosing_ranges d WHERE d.symbol_id = g.id)
|
|
215
|
+
OR EXISTS (SELECT 1 FROM mentions m WHERE m.symbol_id = g.id)
|
|
216
|
+
""")
|
|
217
|
+
_replace_table(conn, "global_symbols", "global_symbols_new")
|
|
218
|
+
|
|
219
|
+
if conn.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='mentions' LIMIT 1").fetchone():
|
|
220
|
+
conn.execute("""
|
|
221
|
+
CREATE TABLE mentions_new (
|
|
222
|
+
chunk_id INTEGER NOT NULL,
|
|
223
|
+
symbol_id INTEGER NOT NULL,
|
|
224
|
+
role INTEGER NOT NULL,
|
|
225
|
+
PRIMARY KEY (chunk_id, symbol_id, role)
|
|
226
|
+
)
|
|
227
|
+
""")
|
|
228
|
+
conn.execute("""
|
|
229
|
+
INSERT INTO mentions_new (chunk_id, symbol_id, role)
|
|
230
|
+
SELECT m.chunk_id, m.symbol_id, m.role
|
|
231
|
+
FROM mentions m
|
|
232
|
+
JOIN global_symbols g ON g.id = m.symbol_id
|
|
233
|
+
""")
|
|
234
|
+
_replace_table(conn, "mentions", "mentions_new")
|
|
235
|
+
|
|
236
|
+
if conn.execute(
|
|
237
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='defn_enclosing_ranges' LIMIT 1"
|
|
238
|
+
).fetchone():
|
|
239
|
+
conn.execute("""
|
|
240
|
+
CREATE TABLE defn_enclosing_ranges_new (
|
|
241
|
+
id INTEGER PRIMARY KEY,
|
|
242
|
+
document_id INTEGER NOT NULL,
|
|
243
|
+
symbol_id INTEGER NOT NULL,
|
|
244
|
+
start_line INTEGER NOT NULL,
|
|
245
|
+
start_char INTEGER NOT NULL,
|
|
246
|
+
end_line INTEGER NOT NULL,
|
|
247
|
+
end_char INTEGER NOT NULL
|
|
248
|
+
)
|
|
249
|
+
""")
|
|
250
|
+
conn.execute("""
|
|
251
|
+
INSERT INTO defn_enclosing_ranges_new (
|
|
252
|
+
id, document_id, symbol_id, start_line, start_char, end_line, end_char
|
|
253
|
+
)
|
|
254
|
+
SELECT d.id, d.document_id, d.symbol_id, d.start_line, d.start_char, d.end_line, d.end_char
|
|
255
|
+
FROM defn_enclosing_ranges d
|
|
256
|
+
JOIN global_symbols g ON g.id = d.symbol_id
|
|
257
|
+
""")
|
|
258
|
+
_replace_table(conn, "defn_enclosing_ranges", "defn_enclosing_ranges_new")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _recreate_postprocess_indexes(conn: sqlite3.Connection) -> None:
|
|
262
|
+
"""expt-convert indexes are dropped when tables are rebuilt."""
|
|
263
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_global_symbols_symbol ON global_symbols(symbol)")
|
|
264
|
+
if conn.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='mentions' LIMIT 1").fetchone():
|
|
265
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_mentions_symbol_id_role ON mentions(symbol_id, role)")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def postprocess_index(db_path, keep_external=None, exclude_globs: tuple[str, ...] | None = None):
|
|
269
|
+
"""Shrink index: drop unused columns and prune external symbols by default.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
db_path: Path to the SQLite database
|
|
273
|
+
keep_external: If True, keep symbols without definitions (external libs).
|
|
274
|
+
If None, reads from SCIP_CLI_KEEP_EXTERNAL env var.
|
|
275
|
+
exclude_globs: Repo-relative globs; matching documents are removed entirely.
|
|
276
|
+
"""
|
|
277
|
+
from ..sql import configure_bulk_write_connection
|
|
278
|
+
|
|
279
|
+
# Default to False unless explicitly set via parameter or env var
|
|
280
|
+
if keep_external is None:
|
|
281
|
+
keep_external = os.environ.get("SCIP_CLI_KEEP_EXTERNAL", "0") == "1"
|
|
282
|
+
|
|
283
|
+
conn = sqlite3.connect(str(db_path))
|
|
284
|
+
try:
|
|
285
|
+
configure_bulk_write_connection(conn)
|
|
286
|
+
# Rebuild tables first; indexes are recreated once at the end (expt-convert indexes
|
|
287
|
+
# are dropped with table swaps — avoid maintaining them during bulk inserts).
|
|
288
|
+
if exclude_globs:
|
|
289
|
+
_prune_excluded_documents(conn, exclude_globs)
|
|
290
|
+
_trim_unused_columns(conn, keep_external=keep_external)
|
|
291
|
+
_trim_mentions_to_known_symbols(conn)
|
|
292
|
+
_trim_defn_to_known_symbols(conn)
|
|
293
|
+
_recreate_postprocess_indexes(conn)
|
|
294
|
+
conn.commit()
|
|
295
|
+
finally:
|
|
296
|
+
conn.close()
|