scip-cli 3.1.0__tar.gz → 3.2.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.
Files changed (274) hide show
  1. scip_cli-3.2.0/.agents/commands/dogfood.md +15 -0
  2. scip_cli-3.2.0/.agents/commands/release.md +69 -0
  3. {scip_cli-3.1.0 → scip_cli-3.2.0}/.agents/rules/agent.mdc +5 -3
  4. scip_cli-3.2.0/.agents/skills/new-command/SKILL.md +38 -0
  5. scip_cli-3.2.0/.agents/skills/sql-bench/SKILL.md +29 -0
  6. scip_cli-3.2.0/.agents/skills/test/SKILL.md +42 -0
  7. {scip_cli-3.1.0 → scip_cli-3.2.0}/.gitignore +0 -1
  8. scip_cli-3.2.0/.pre-commit-config.yaml +10 -0
  9. {scip_cli-3.1.0 → scip_cli-3.2.0}/CONTRIBUTING.md +2 -3
  10. {scip_cli-3.1.0 → scip_cli-3.2.0}/PKG-INFO +12 -5
  11. {scip_cli-3.1.0 → scip_cli-3.2.0}/README.md +11 -4
  12. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/SKILL.md +19 -2
  13. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/__init__.py +1 -1
  14. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/__main__.py +16 -1
  15. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/common.py +5 -0
  16. scip_cli-3.2.0/scip_cli/analyze/entrypoints/__init__.py +77 -0
  17. scip_cli-3.2.0/scip_cli/analyze/entrypoints/golang.py +28 -0
  18. scip_cli-3.2.0/scip_cli/analyze/entrypoints/paths.py +34 -0
  19. scip_cli-3.2.0/scip_cli/analyze/entrypoints/python.py +128 -0
  20. scip_cli-3.2.0/scip_cli/analyze/entrypoints/rust.py +49 -0
  21. scip_cli-3.2.0/scip_cli/analyze/entrypoints/typescript.py +143 -0
  22. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/file.py +52 -1
  23. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/project.py +59 -0
  24. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/sections.py +22 -1
  25. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/symbol.py +9 -1
  26. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/cache.py +1 -1
  27. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/analyze.py +12 -3
  28. scip_cli-3.2.0/scip_cli/commands/query.py +66 -0
  29. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/discover.py +15 -0
  30. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/constants.py +9 -1
  31. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/convert.py +12 -0
  32. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/core.py +6 -3
  33. scip_cli-3.2.0/scip_cli/indexing/import_mentions.py +201 -0
  34. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/languages.py +7 -1
  35. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/runners.py +1 -1
  36. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/ts_projects.py +1 -0
  37. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/session.py +2 -2
  38. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/source.py +13 -0
  39. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/sql.py +7 -0
  40. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/bench_incremental.py +17 -8
  41. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/bench_incremental_gate.py +18 -2
  42. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/publish.sh +15 -1
  43. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/test.sh +1 -2
  44. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/analyze_db.py +17 -0
  45. scip_cli-3.2.0/tests/conftest.py +35 -0
  46. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/e2e_harness.py +8 -1
  47. scip_cli-3.2.0/tests/fixture_cache.py +113 -0
  48. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixture_catalog.py +8 -0
  49. scip_cli-3.2.0/tests/fixtures/typescript-project/bin/cli-entry.ts +5 -0
  50. scip_cli-3.2.0/tests/fixtures/typescript-project/bin/npm-script-only.ts +3 -0
  51. scip_cli-3.2.0/tests/fixtures/typescript-project/package.json +11 -0
  52. scip_cli-3.2.0/tests/fixtures/typescript-project/src/heuristic/classifyLeaf.test.ts +5 -0
  53. scip_cli-3.2.0/tests/fixtures/typescript-project/src/heuristic/classifyLeaf.ts +3 -0
  54. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_analyze.py +58 -3
  55. scip_cli-3.2.0/tests/test_analyze_live.py +282 -0
  56. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_cache.py +2 -2
  57. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_discover.py +20 -0
  58. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_e2e.py +58 -1
  59. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_e2e_analyze_patterns.py +37 -0
  60. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_exclude_globs.py +1 -13
  61. scip_cli-3.2.0/tests/test_import_mentions.py +25 -0
  62. scip_cli-3.2.0/tests/test_incremental_equivalence.py +52 -0
  63. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_indexer_fallback.py +26 -1
  64. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_python_index_integration.py +9 -1
  65. scip_cli-3.1.0/.pre-commit-config.yaml +0 -20
  66. scip_cli-3.1.0/scripts/bench_incremental.sh +0 -27
  67. scip_cli-3.1.0/scripts/bench_incremental_gate.sh +0 -25
  68. scip_cli-3.1.0/tests/conftest.py +0 -25
  69. scip_cli-3.1.0/tests/fixtures/typescript-project/package.json +0 -5
  70. scip_cli-3.1.0/tests/test_analyze_live.py +0 -71
  71. scip_cli-3.1.0/tests/test_incremental_equivalence.py +0 -81
  72. {scip_cli-3.1.0 → scip_cli-3.2.0}/.agents/commands/migration-problems.md +0 -0
  73. {scip_cli-3.1.0 → scip_cli-3.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  74. {scip_cli-3.1.0 → scip_cli-3.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  75. {scip_cli-3.1.0 → scip_cli-3.2.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  76. {scip_cli-3.1.0 → scip_cli-3.2.0}/.github/pull_request_template.md +0 -0
  77. {scip_cli-3.1.0 → scip_cli-3.2.0}/.github/workflows/ci.yml +0 -0
  78. {scip_cli-3.1.0 → scip_cli-3.2.0}/AGENTS.md +0 -0
  79. {scip_cli-3.1.0 → scip_cli-3.2.0}/CODE_OF_CONDUCT.md +0 -0
  80. {scip_cli-3.1.0 → scip_cli-3.2.0}/LICENSE +0 -0
  81. {scip_cli-3.1.0 → scip_cli-3.2.0}/SECURITY.md +0 -0
  82. {scip_cli-3.1.0 → scip_cli-3.2.0}/pyproject.toml +0 -0
  83. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/__init__.py +0 -0
  84. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/graph.py +0 -0
  85. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/live.py +0 -0
  86. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/analyze/targets.py +0 -0
  87. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/cli_args.py +0 -0
  88. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/__init__.py +0 -0
  89. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/code.py +0 -0
  90. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/deps.py +0 -0
  91. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/members.py +0 -0
  92. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/rdeps.py +0 -0
  93. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/refs.py +0 -0
  94. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/reindex.py +0 -0
  95. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/search.py +0 -0
  96. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/skill.py +0 -0
  97. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/commands/symbols.py +0 -0
  98. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/config.py +0 -0
  99. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/debug.py +0 -0
  100. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/exclude.py +0 -0
  101. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/__init__.py +0 -0
  102. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/document_upsert.py +0 -0
  103. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/git_delta.py +0 -0
  104. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/github_npm.py +0 -0
  105. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/incremental.py +0 -0
  106. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/orchestrate.py +0 -0
  107. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/performance.py +0 -0
  108. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/postprocess.py +0 -0
  109. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/shard_files.py +0 -0
  110. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/shards.py +0 -0
  111. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/source_discovery.py +0 -0
  112. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/indexing/typescript.py +0 -0
  113. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/merge.py +0 -0
  114. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/metadata.py +0 -0
  115. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/output.py +0 -0
  116. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/paths.py +0 -0
  117. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/project.py +0 -0
  118. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/queries.py +0 -0
  119. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/scip_tool.py +0 -0
  120. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/scope.py +0 -0
  121. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/smoke_config.py +0 -0
  122. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/symbols.py +0 -0
  123. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/targets.py +0 -0
  124. {scip_cli-3.1.0 → scip_cli-3.2.0}/scip_cli/tsconfig.py +0 -0
  125. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/bench.sh +0 -0
  126. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/bench_fingerprint_smoke.py +0 -0
  127. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/bench_postprocess.py +0 -0
  128. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/build.sh +0 -0
  129. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/generate_incremental_bench_fixture.py +0 -0
  130. {scip_cli-3.1.0 → scip_cli-3.2.0}/scripts/smoke.local.example.json +0 -0
  131. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/__init__.py +0 -0
  132. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/bench_db.py +0 -0
  133. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/package.json +0 -0
  134. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/alpha/src/index.ts +0 -0
  135. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/alpha/src/util1.ts +0 -0
  136. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/alpha/src/util2.ts +0 -0
  137. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/alpha/src/util3.ts +0 -0
  138. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/alpha/tsconfig.json +0 -0
  139. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/beta/src/index.ts +0 -0
  140. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/beta/src/util1.ts +0 -0
  141. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/beta/src/util2.ts +0 -0
  142. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/beta/src/util3.ts +0 -0
  143. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/beta/tsconfig.json +0 -0
  144. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/index.ts +0 -0
  145. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/meta.ts +0 -0
  146. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_00.ts +0 -0
  147. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_01.ts +0 -0
  148. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_02.ts +0 -0
  149. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_03.ts +0 -0
  150. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_04.ts +0 -0
  151. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_05.ts +0 -0
  152. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_06.ts +0 -0
  153. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_07.ts +0 -0
  154. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_08.ts +0 -0
  155. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_09.ts +0 -0
  156. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_10.ts +0 -0
  157. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_11.ts +0 -0
  158. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_12.ts +0 -0
  159. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_13.ts +0 -0
  160. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_14.ts +0 -0
  161. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_15.ts +0 -0
  162. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_16.ts +0 -0
  163. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_17.ts +0 -0
  164. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_18.ts +0 -0
  165. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_19.ts +0 -0
  166. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_20.ts +0 -0
  167. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_21.ts +0 -0
  168. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_22.ts +0 -0
  169. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_23.ts +0 -0
  170. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_24.ts +0 -0
  171. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_25.ts +0 -0
  172. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_26.ts +0 -0
  173. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_27.ts +0 -0
  174. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_28.ts +0 -0
  175. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_29.ts +0 -0
  176. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_30.ts +0 -0
  177. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_31.ts +0 -0
  178. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_32.ts +0 -0
  179. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_33.ts +0 -0
  180. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_34.ts +0 -0
  181. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_35.ts +0 -0
  182. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_36.ts +0 -0
  183. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_37.ts +0 -0
  184. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_38.ts +0 -0
  185. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_39.ts +0 -0
  186. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_40.ts +0 -0
  187. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_41.ts +0 -0
  188. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_42.ts +0 -0
  189. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_43.ts +0 -0
  190. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_44.ts +0 -0
  191. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_45.ts +0 -0
  192. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_46.ts +0 -0
  193. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_47.ts +0 -0
  194. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_48.ts +0 -0
  195. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_49.ts +0 -0
  196. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_50.ts +0 -0
  197. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_51.ts +0 -0
  198. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_52.ts +0 -0
  199. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_53.ts +0 -0
  200. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_54.ts +0 -0
  201. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_55.ts +0 -0
  202. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_56.ts +0 -0
  203. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_57.ts +0 -0
  204. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_58.ts +0 -0
  205. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/src/modules/module_59.ts +0 -0
  206. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/bulk/tsconfig.json +0 -0
  207. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/gamma/src/index.ts +0 -0
  208. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/gamma/src/util1.ts +0 -0
  209. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/gamma/src/util2.ts +0 -0
  210. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/gamma/src/util3.ts +0 -0
  211. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/incremental-bench/packages/gamma/tsconfig.json +0 -0
  212. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/__tests__/fixtureOnly.spec.ts +0 -0
  213. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/app/handler.ts +0 -0
  214. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/config.ts +0 -0
  215. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/consumer.ts +0 -0
  216. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/dead.ts +0 -0
  217. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/domain/i18n/en.ts +0 -0
  218. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/domain/i18n/index.ts +0 -0
  219. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/domain/i18n/useLocale.ts +0 -0
  220. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/domain/labels/index.ts +0 -0
  221. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/events/streamTypes.ts +0 -0
  222. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/gateway/index.ts +0 -0
  223. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/helper.ts +0 -0
  224. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/hooks/useHookA.ts +0 -0
  225. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/hooks/useHookB.ts +0 -0
  226. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/hooks/useItems.ts +0 -0
  227. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/index.ts +0 -0
  228. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/integrations/inferenceClient.ts +0 -0
  229. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/lib/handler.ts +0 -0
  230. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/loaders/useInference.ts +0 -0
  231. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/models/MutationAggregate.ts +0 -0
  232. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/pages/panelPage.ts +0 -0
  233. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/rules/applyRule.ts +0 -0
  234. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/types/a.ts +0 -0
  235. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/types/b.ts +0 -0
  236. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/ui/Button.ts +0 -0
  237. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/ui/LazyPanel.ts +0 -0
  238. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/ui/buttonConsumer.ts +0 -0
  239. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/ui/menuModule.ts +0 -0
  240. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/user.ts +0 -0
  241. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/widget.ts +0 -0
  242. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/src/widgets/OrphanWidget.ts +0 -0
  243. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/fixtures/typescript-project/tsconfig.json +0 -0
  244. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/incremental_equivalence.py +0 -0
  245. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/perf_util.py +0 -0
  246. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_analyze_graph.py +0 -0
  247. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_analyze_perf.py +0 -0
  248. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_bench_queries.py +0 -0
  249. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_composability.py +0 -0
  250. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_config.py +0 -0
  251. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_document_upsert.py +0 -0
  252. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_e2e_perf.py +0 -0
  253. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_git_delta.py +0 -0
  254. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_incremental_shards.py +0 -0
  255. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_index_batching.py +0 -0
  256. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_index_performance.py +0 -0
  257. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_index_prune.py +0 -0
  258. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_indexer_env.py +0 -0
  259. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_indexer_failure_message.py +0 -0
  260. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_merge.py +0 -0
  261. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_metadata.py +0 -0
  262. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_multi_symbol.py +0 -0
  263. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_pure_functions.py +0 -0
  264. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_qualified_symbols.py +0 -0
  265. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_reindex.py +0 -0
  266. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_scip_tool.py +0 -0
  267. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_scope.py +0 -0
  268. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_shard_files.py +0 -0
  269. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_source_discovery.py +0 -0
  270. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_sql.py +0 -0
  271. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_symbols_freq.py +0 -0
  272. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_targets.py +0 -0
  273. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_tsconfig.py +0 -0
  274. {scip_cli-3.1.0 → scip_cli-3.2.0}/tests/test_typescript_projects.py +0 -0
@@ -0,0 +1,15 @@
1
+ # Dogfood analyze on this repo
2
+
3
+ Human-invoked pass before broad review. From repo root with editable `scip-cli` on PATH:
4
+
5
+ ```bash
6
+ scip-cli reindex
7
+ scip-cli analyze --limit 25
8
+ scip-cli analyze --priority high --limit 25
9
+ scip-cli analyze scip_cli --limit 15
10
+ scip-cli analyze scip_cli/queries.py --limit 20
11
+ ```
12
+
13
+ Interpret README § *Finding easy wins with analyze* (cycles → dead exports → same-file → stale types). Fix or extend `analyze` per `agent.mdc` § Analyze feedback loop before spawning review subagents.
14
+
15
+ Do not commit unless the user asks.
@@ -0,0 +1,69 @@
1
+ ---
2
+ argument-hint: "[major | minor | patch]"
3
+ ---
4
+
5
+ # Release scip-cli (tag + GitHub)
6
+
7
+ Bump level: **`$ARGUMENTS`** — must be exactly `major`, `minor`, or `patch`. Compute the next semver from `scip_cli/__init__.py` (same rules as `scripts/publish.sh`: patch +1; minor +1 and patch 0; major +1 and minor/patch 0). Tag and release **`vX.Y.Z`** for that computed version.
8
+
9
+ ## Commit messages (this repo)
10
+
11
+ - **Day-to-day:** sentence case, verb first — e.g. `Add entrypoint fixtures`, `Pass indexer binary name to npx fallback`. **Not** Conventional Commits (`feat(scope):`, `chore:`, …).
12
+ - **Version bump commit:** subject **only** `vX.Y.Z` (same string as the tag), e.g. `v3.2.0`.
13
+
14
+ Do not commit, tag, or push unless the user invoked this release command.
15
+
16
+ ## Preflight
17
+
18
+ 1. Repo root; working tree clean before the bump commit.
19
+ 2. `./scripts/test.sh`
20
+ 3. If `$ARGUMENTS` is missing or not `major`/`minor`/`patch`, stop and ask.
21
+ 4. After bump, `git rev-parse "vX.Y.Z"` must fail (tag must not exist yet).
22
+
23
+ ## Bump version
24
+
25
+ Use the bump logic in `scripts/publish.sh` (or run that script’s Python block with `BUMP_LEVEL=$ARGUMENTS`). Then:
26
+
27
+ ```bash
28
+ git add scip_cli/__init__.py
29
+ git commit -m "vX.Y.Z"
30
+ ```
31
+
32
+ ## Tag (non-interactive — never open the editor)
33
+
34
+ Always pass **both** tag name and message on the command line:
35
+
36
+ ```bash
37
+ git tag -a "vX.Y.Z" -m "vX.Y.Z"
38
+ ```
39
+
40
+ Do **not** run bare `git tag vX.Y.Z` (Git may open an editor for the annotation).
41
+
42
+ Push branch and tag:
43
+
44
+ ```bash
45
+ git push origin HEAD "vX.Y.Z"
46
+ ```
47
+
48
+ ## GitHub release (`gh`)
49
+
50
+ Read `~/.claude/skills/gh/references/releases.md`. Use the repo scripts:
51
+
52
+ ```bash
53
+ GH_SCRIPTS="${GH_SCRIPTS:-$HOME/.claude/skills/gh/scripts}"
54
+ REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo flesler/scip-cli)"
55
+
56
+ "$GH_SCRIPTS/release-notes.sh" X.Y.Z --install '```bash
57
+ pip install scip-cli=={{VERSION}}
58
+ ```'
59
+
60
+ "$GH_SCRIPTS/sync-github-release.sh" "$REPO" X.Y.Z \
61
+ --install '```bash
62
+ pip install scip-cli=={{VERSION}}
63
+ ```' \
64
+ --execute
65
+ ```
66
+
67
+ **Do not** run `gh release create` by hand unless the user rejects the script path. **Do not** upload PyPI (`twine`) or run full `./scripts/publish.sh` unless the user explicitly asks.
68
+
69
+ Report: bump level, new version, tag pushed, release URL, PyPI skipped unless requested.
@@ -7,6 +7,8 @@ alwaysApply: true
7
7
 
8
8
  **Work from repo root.** CLI usage, flags, and user gotchas: `scip-cli skill` (source: `scip_cli/SKILL.md`). Read once per task — do not duplicate SKILL here.
9
9
 
10
+ **Cursor:** Skills in `.cursor/skills/` (`test`, `sql-bench`, `new-command`) — folder name = frontmatter `name:`; agent reads when relevant. Slash commands in `.cursor/commands/` only for workflows you invoke (`dogfood`, `migration-problems`, `release`); never pair the same workflow as both.
11
+
10
12
  **Almost always:** bare `scip-cli` on PATH must be **this checkout live** (`pip install -e ".[dev]"`). All examples use `scip-cli` — never `.venv/bin/scip-cli`, `python -m scip_cli`, or other paths. Only swap to PyPI briefly to smoke-test a release.
11
13
 
12
14
  ## Bootstrap
@@ -34,11 +36,11 @@ Node.js + `npx` required for integration tests (`scip-typescript`). Optional: `S
34
36
  |Full suite|`pytest`|
35
37
  |E2e loop|`pytest tests/test_e2e.py tests/test_e2e_analyze_patterns.py`|
36
38
  |Lint / format|`ruff check .` / `ruff format .`|
37
- |Typecheck|`basedpyright scip_cli/`|
39
+ |Typecheck|`basedpyright --warnings scip_cli/ scripts/`|
38
40
 
39
- Pre-commit: ruff + ruff-format + basedpyright + full pytest. E2e before commit on command changes.
41
+ Pre-commit, CI, and publish: `./scripts/test.sh` (same gate). No pre-push hook. E2e before commit on command changes.
40
42
 
41
- **Use `scripts/test.sh`** before committing to catch all issues (lint, format, types, tests). This matches what pre-commit runs.
43
+ **Use `scripts/test.sh`** before committing to catch all issues (lint, format, types, tests). This matches what pre-commit and CI run.
42
44
 
43
45
  ## Improving this repo (do this before review subagents)
44
46
 
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: new-command
3
+ description: Read when adding or changing a scip-cli CLI subcommand.
4
+ ---
5
+
6
+ # New or changed CLI command
7
+
8
+ User-facing reference lives in `scip_cli/SKILL.md` + `README.md` (must match `scip_cli/__main__.py` flags). Run `scip-cli skill` to verify dump.
9
+
10
+ ## Checklist
11
+
12
+ 1. **`scip_cli/commands/<name>.py`** — `main(args)`; thin handler. Use `session.setup()` for index DB unless the command is `reindex` / `skill`.
13
+ 2. **`scip_cli/__main__.py`** — import; `add_parser`; wire `dispatch["<name>"] = <module>.main`.
14
+ 3. **Shared flags** — reuse `scip_cli/cli_args.py` (`add_path_argument`, `add_limit_argument`, …) when siblings use them.
15
+ 4. **SQL** — keep queries in `scip_cli/queries.py` or `scip_cli/analyze/`, not fat command modules.
16
+ 5. **Docs** — quick-guide row + `### <name>` section in `scip_cli/SKILL.md`; bullet in README § Commands.
17
+ 6. **Tests** — `tests/test_e2e.py` (or `test_e2e_analyze_patterns.py` for analyze-only); add `scip_cli.commands.<name>.setup` to `tests/e2e_harness.py` `COMMAND_SETUP_PATHS`.
18
+ 7. **Verify** — follow skill `test`; run narrowed pytest then e2e loop.
19
+
20
+ ## Patterns
21
+
22
+ - **Read-only DB:** `db, project_root = setup()` in `try` / `finally: db.close()`. Connection already has `configure_read_connection` via `get_db`.
23
+ - **Errors:** raise `RuntimeError` or let `sqlite3.Error` bubble — `__main__.main` maps them to stderr + exit code.
24
+ - **Output:** stdout = records; stderr = warnings (match existing commands).
25
+
26
+ ## Map
27
+
28
+ |Concern|Path|
29
+ |---|---|
30
+ |Entry / argparse|`scip_cli/__main__.py`|
31
+ |Handlers|`scip_cli/commands/`|
32
+ |Index open|`scip_cli/session.py`, `scip_cli/indexing/core.py`|
33
+ |User skill install|`scip-cli skill <path>` copies `scip_cli/SKILL.md`|
34
+
35
+ ## Cursor repo conventions
36
+
37
+ - **Skills** (`.cursor/skills/<name>/`, frontmatter `name:` = folder name): agent reads when the task matches — e.g. `test`, `sql-bench`, `new-command`.
38
+ - **Commands** (`.cursor/commands/`): only workflows **you** invoke with slash — e.g. `dogfood`, `migration-problems`. Never duplicate the same workflow as a skill and a command.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: sql-bench
3
+ description: Read when changing queries.py, analyze SQL, symbols SQL, or sql.py.
4
+ ---
5
+
6
+ # SQL / query performance
7
+
8
+ Small fixture DB hides regressions on 40MB+ indexes. CI perf tests only catch gross mistakes on the tiny project.
9
+
10
+ ## Required before commit
11
+
12
+ Touching `scip_cli/queries.py`, `scip_cli/analyze/*`, `scip_cli/symbols.py` SQL, or `scip_cli/sql.py`:
13
+
14
+ ```bash
15
+ scripts/bench.sh --baseline # once per change series
16
+ # … edit …
17
+ scripts/bench.sh --compare
18
+ ```
19
+
20
+ Synthetic scale: `tests/bench_db.py` + `tests/test_bench_queries.py`. Lines `BENCH:<name>:<ms>`; compare table ratio >1.0 = regression.
21
+
22
+ ## Correctness vs speed
23
+
24
+ - Behavior / SCIP shapes → fixture e2e (skill `test`).
25
+ - Graph plumbing only → `analyze_db` unit tests.
26
+
27
+ ## Optional: real cached index
28
+
29
+ Replace `<project-slug>` under `~/.cache/scip-cli/projects/` — snippet in `agent.mdc` § Large cached index benchmark.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: test
3
+ description: Read when running or writing scip-cli tests (pytest, e2e, CI parity).
4
+ ---
5
+
6
+ # scip-cli tests
7
+
8
+ Repo root only. Bootstrap: `.cursor/rules/agent.mdc` (editable install, Node for fixture index).
9
+
10
+ ## Pick a suite
11
+
12
+ |Goal|Command|
13
+ |---|---|
14
+ |Pre-commit / CI / publish|`scripts/test.sh`|
15
+ |Full|`pytest`|
16
+ |CLI + fixture index|`pytest tests/test_e2e.py tests/test_e2e_analyze_patterns.py`|
17
+ |One class / test|`pytest tests/test_e2e.py::TestQuery -q`|
18
+ |Analyze unit / graph|`pytest tests/test_analyze_graph.py tests/test_analyze.py`|
19
+ |SQL perf (not correctness)|`scripts/bench.sh` — see skill `sql-bench`|
20
+
21
+ `scripts/test.sh` uses `.venv/bin/{ruff,basedpyright,pytest}`; create venv + `pip install -e ".[dev]"` if missing.
22
+
23
+ ## E2e rules
24
+
25
+ - In-process only: `tests/e2e_harness.py` (`CliRunner`, `run_cli`) patches `scip_cli.commands.<cmd>.setup` — **never** subprocess `scip-cli` in tests.
26
+ - **Never** call `reindex` in e2e (mutates `~/.cache/scip-cli`).
27
+ - Session fixture `indexed_fixture` indexes `tests/fixtures/typescript-project/` once (real `scip-typescript`).
28
+ - Stable symbols/paths: add constants to `tests/fixture_catalog.py`; import in tests — do not hardcode fixture paths in test bodies.
29
+ - New CLI command: append `scip_cli.commands.<cmd>.setup` to `COMMAND_SETUP_PATHS` in `e2e_harness.py`.
30
+
31
+ ## Fixture vs in-memory DB
32
+
33
+ |Need|Layer|
34
+ |---|---|
35
+ |SCIP symbol shapes, analyze false positives, CLI output|Fixture e2e (`test_e2e*.py`)|
36
+ |Tarjan, section wiring, one-off SQL plumbing|`tests/analyze_db.py` + `test_analyze_graph.py`|
37
+
38
+ Prefer fixture e2e for indexer-dependent behavior. Details: `agent.mdc` § TDD.
39
+
40
+ ## After command / flag changes
41
+
42
+ Run e2e loop at minimum before commit; `scripts/test.sh` before push.
@@ -45,7 +45,6 @@ Thumbs.db
45
45
  # Project specific
46
46
  TODO.md
47
47
  todo.md
48
- dogfood.md
49
48
 
50
49
  # Local-only dev scripts (repo-specific smoke harnesses)
51
50
  scripts/deep-smoke.sh
@@ -0,0 +1,10 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: ci-gate
5
+ name: CI gate (scripts/test.sh)
6
+ entry: bash scripts/test.sh
7
+ language: system
8
+ pass_filenames: false
9
+ always_run: true
10
+ stages: [pre-commit]
@@ -48,11 +48,10 @@ cd scip-cli
48
48
  # Install with dev dependencies
49
49
  pip install -e ".[dev]"
50
50
 
51
- # Install pre-commit hooks
51
+ # Install pre-commit hook (same command as CI — no pre-push hook)
52
52
  pre-commit install
53
53
 
54
- # Run tests
55
- pytest
54
+ ./scripts/test.sh
56
55
 
57
56
  # Run linter
58
57
  ruff check .
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scip-cli
3
- Version: 3.1.0
3
+ Version: 3.2.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
@@ -26,6 +26,8 @@ Description-Content-Type: text/markdown
26
26
 
27
27
  Python CLI for AI agents. Queries SCIP indexes for precise refs, definitions, and repo health analysis — TypeScript/JavaScript, Python, Go, Rust.
28
28
 
29
+ **Related (optional):** [scip-atlas](https://github.com/flesler/scip-atlas) builds `atlas.db` (git paths, commits, owners) from this cache's `index.db`. [scip-atlas-web](https://github.com/flesler/scip-atlas-web) packs both into a slim DB and a single-file browser explorer. You do not need them for CLI or agent workflows.
30
+
29
31
  ## Why
30
32
 
31
33
  AI agents waste tokens on grep and file scanning. scip-cli gives them precise, type-aware code navigation in milliseconds — and `analyze` surfaces dead code, cycles, and coupling so agents (and humans) can fix real problems fast.
@@ -91,7 +93,7 @@ python -m venv .venv && source .venv/bin/activate
91
93
  pip install -e ".[dev]"
92
94
  ```
93
95
 
94
- Tests and lint: `pytest`, `ruff check .`, `basedpyright scip_cli/` from the same environment (or `scripts/test.sh` for the publish gate).
96
+ Tests and lint: `scripts/test.sh` (same as CI and pre-commit), or run `pytest`, `ruff check .`, `basedpyright --warnings scip_cli/ scripts/` from `.venv`.
95
97
 
96
98
  ### 2. Install prerequisites (optional)
97
99
 
@@ -155,6 +157,7 @@ scip-cli <command> [arguments]
155
157
  - `rdeps <file>` - Find files that depend on a file (`--path`)
156
158
  - `deps <symbol|file>` - Find outbound dependencies (what a symbol or file calls) (`--path`, `--paths-only`)
157
159
  - `members <symbol>` - List members of a class/interface (`--path`)
160
+ - `query <sql>` - SQL against the project index (`--format tsv|csv|json`, default tsv; read-only unless `--write`)
158
161
  - `analyze [target]` - SQL health dashboards (`--limit`, `--per-check-limit`, `--priority`, `--check`, `--include-tests`). No target: project-wide; directory or file path; symbol name. See [Finding easy wins with `analyze`](#finding-easy-wins-with-analyze).
159
162
  - `reindex` - Force re-indexing (`--path`, `--tsconfig`, `--exclude`, `--fresh`, `--no-incremental`; incremental by default in git TS repos)
160
163
  - `skill [path]` - Install or dump the SKILL.md
@@ -192,6 +195,10 @@ scip-cli members Widget
192
195
  # Project health dashboard (or: scip-cli analyze src/foo.ts / scip-cli analyze greet)
193
196
  scip-cli analyze
194
197
 
198
+ # List index tables / columns (read-only SQL)
199
+ scip-cli query "SELECT name FROM sqlite_schema WHERE type='table' ORDER BY 1"
200
+ scip-cli query "PRAGMA table_info(global_symbols)"
201
+
195
202
  # Install skill file
196
203
  scip-cli skill ~/.claude/skills/scip-cli/SKILL.md
197
204
  ```
@@ -227,7 +234,7 @@ scip-cli deps greet --paths-only | sort -u
227
234
  6. Compacts the database after each reindex (`VACUUM` + WAL checkpoint)
228
235
  7. Subsequent queries are SQLite lookups against that cache (not re-indexing)
229
236
 
230
- **Incremental reindex (TypeScript, v3.0+):** plain `scip-cli reindex` in a git worktree is incremental by default — reuses unchanged tsconfig shards via `shards/manifest.json` (`git_commit` + per-shard `tsconfig_digest`). Dirty shards reindex only changed files (git delta → importer closure → fork `--files`) and upsert into the live `index.db`. Use `--no-incremental` or `--fresh` for a full rebuild; `--unversioned` and non-git projects always full-reindex. Bench locally with `scripts/bench_incremental_gate.sh --branch fixture`.
237
+ **Incremental reindex (TypeScript, v3.0+):** plain `scip-cli reindex` in a git worktree is incremental by default — reuses unchanged tsconfig shards via `shards/manifest.json` (`git_commit` + per-shard `tsconfig_digest`). Dirty shards reindex only changed files (git delta → importer closure → fork `--files`) and upsert into the live `index.db`. Use `--no-incremental` or `--fresh` for a full rebuild; `--unversioned` and non-git projects always full-reindex. Bench locally with `python scripts/bench_incremental_gate.py --branch fixture`.
231
238
 
232
239
  ## Configuration
233
240
 
@@ -303,7 +310,7 @@ Sections are tagged `[high]`, `[medium]`, `[low]` and listed in that order.
303
310
  |---|---|---|
304
311
  |**high**|Cycles, dead exports, dead files, stale types (unreferenced is omitted when dead exports is on)|Nuke or fix cycles; delete unused; `_` prefix|
305
312
  |**medium**|Same-file only, change surface (file target)|Module-private by usage|
306
- |**low**|Test-only consumers, coupling, bottlenecks, hotspots|Noisy on Python (index omits many same-file calls); verify with `rg`|
313
+ |**low**|Single-importer files (`[test-only]` when sole importer is tests), test-only consumers, coupling, bottlenecks, hotspots|Noisy on Python (index omits many same-file calls); verify with `rg`|
307
314
 
308
315
  Use `--priority high` for a quick gate; `--priority high,medium` adds context. File drill-down adds change surface and unused imports.
309
316
 
@@ -329,7 +336,7 @@ scip-cli analyze scip_cli --limit 15 # directory: scoped project +
329
336
 
330
337
  **Defaults:** project-wide and directory analyze skip `tests/`, `*.test.*`, `*.spec.*`, `conftest.py`, and `__tests__/`. Pass `--include-tests` to include them. File-target analyze always includes that file.
331
338
 
332
- **Limits:** "Dead export" means no cross-file mentions in the index — not unreachable code. Same-file private helpers are expected. Re-run `reindex` after large changes; the index is a snapshot.
339
+ **Limits:** "Dead export" means no cross-file mentions in the index — not unreachable code. Same-file private helpers are expected. Shebang lines and manifest entrypoints (`package.json` main/bin/scripts with dist→src mapping for TypeScript; Python `pyproject`/`setup.py`/`setup.cfg` scripts) are excluded from project-level dead files and dead exports. Re-run `reindex` after large changes; the index is a snapshot.
333
340
 
334
341
  ## Performance
335
342
 
@@ -5,6 +5,8 @@
5
5
 
6
6
  Python CLI for AI agents. Queries SCIP indexes for precise refs, definitions, and repo health analysis — TypeScript/JavaScript, Python, Go, Rust.
7
7
 
8
+ **Related (optional):** [scip-atlas](https://github.com/flesler/scip-atlas) builds `atlas.db` (git paths, commits, owners) from this cache's `index.db`. [scip-atlas-web](https://github.com/flesler/scip-atlas-web) packs both into a slim DB and a single-file browser explorer. You do not need them for CLI or agent workflows.
9
+
8
10
  ## Why
9
11
 
10
12
  AI agents waste tokens on grep and file scanning. scip-cli gives them precise, type-aware code navigation in milliseconds — and `analyze` surfaces dead code, cycles, and coupling so agents (and humans) can fix real problems fast.
@@ -70,7 +72,7 @@ python -m venv .venv && source .venv/bin/activate
70
72
  pip install -e ".[dev]"
71
73
  ```
72
74
 
73
- Tests and lint: `pytest`, `ruff check .`, `basedpyright scip_cli/` from the same environment (or `scripts/test.sh` for the publish gate).
75
+ Tests and lint: `scripts/test.sh` (same as CI and pre-commit), or run `pytest`, `ruff check .`, `basedpyright --warnings scip_cli/ scripts/` from `.venv`.
74
76
 
75
77
  ### 2. Install prerequisites (optional)
76
78
 
@@ -134,6 +136,7 @@ scip-cli <command> [arguments]
134
136
  - `rdeps <file>` - Find files that depend on a file (`--path`)
135
137
  - `deps <symbol|file>` - Find outbound dependencies (what a symbol or file calls) (`--path`, `--paths-only`)
136
138
  - `members <symbol>` - List members of a class/interface (`--path`)
139
+ - `query <sql>` - SQL against the project index (`--format tsv|csv|json`, default tsv; read-only unless `--write`)
137
140
  - `analyze [target]` - SQL health dashboards (`--limit`, `--per-check-limit`, `--priority`, `--check`, `--include-tests`). No target: project-wide; directory or file path; symbol name. See [Finding easy wins with `analyze`](#finding-easy-wins-with-analyze).
138
141
  - `reindex` - Force re-indexing (`--path`, `--tsconfig`, `--exclude`, `--fresh`, `--no-incremental`; incremental by default in git TS repos)
139
142
  - `skill [path]` - Install or dump the SKILL.md
@@ -171,6 +174,10 @@ scip-cli members Widget
171
174
  # Project health dashboard (or: scip-cli analyze src/foo.ts / scip-cli analyze greet)
172
175
  scip-cli analyze
173
176
 
177
+ # List index tables / columns (read-only SQL)
178
+ scip-cli query "SELECT name FROM sqlite_schema WHERE type='table' ORDER BY 1"
179
+ scip-cli query "PRAGMA table_info(global_symbols)"
180
+
174
181
  # Install skill file
175
182
  scip-cli skill ~/.claude/skills/scip-cli/SKILL.md
176
183
  ```
@@ -206,7 +213,7 @@ scip-cli deps greet --paths-only | sort -u
206
213
  6. Compacts the database after each reindex (`VACUUM` + WAL checkpoint)
207
214
  7. Subsequent queries are SQLite lookups against that cache (not re-indexing)
208
215
 
209
- **Incremental reindex (TypeScript, v3.0+):** plain `scip-cli reindex` in a git worktree is incremental by default — reuses unchanged tsconfig shards via `shards/manifest.json` (`git_commit` + per-shard `tsconfig_digest`). Dirty shards reindex only changed files (git delta → importer closure → fork `--files`) and upsert into the live `index.db`. Use `--no-incremental` or `--fresh` for a full rebuild; `--unversioned` and non-git projects always full-reindex. Bench locally with `scripts/bench_incremental_gate.sh --branch fixture`.
216
+ **Incremental reindex (TypeScript, v3.0+):** plain `scip-cli reindex` in a git worktree is incremental by default — reuses unchanged tsconfig shards via `shards/manifest.json` (`git_commit` + per-shard `tsconfig_digest`). Dirty shards reindex only changed files (git delta → importer closure → fork `--files`) and upsert into the live `index.db`. Use `--no-incremental` or `--fresh` for a full rebuild; `--unversioned` and non-git projects always full-reindex. Bench locally with `python scripts/bench_incremental_gate.py --branch fixture`.
210
217
 
211
218
  ## Configuration
212
219
 
@@ -282,7 +289,7 @@ Sections are tagged `[high]`, `[medium]`, `[low]` and listed in that order.
282
289
  |---|---|---|
283
290
  |**high**|Cycles, dead exports, dead files, stale types (unreferenced is omitted when dead exports is on)|Nuke or fix cycles; delete unused; `_` prefix|
284
291
  |**medium**|Same-file only, change surface (file target)|Module-private by usage|
285
- |**low**|Test-only consumers, coupling, bottlenecks, hotspots|Noisy on Python (index omits many same-file calls); verify with `rg`|
292
+ |**low**|Single-importer files (`[test-only]` when sole importer is tests), test-only consumers, coupling, bottlenecks, hotspots|Noisy on Python (index omits many same-file calls); verify with `rg`|
286
293
 
287
294
  Use `--priority high` for a quick gate; `--priority high,medium` adds context. File drill-down adds change surface and unused imports.
288
295
 
@@ -308,7 +315,7 @@ scip-cli analyze scip_cli --limit 15 # directory: scoped project +
308
315
 
309
316
  **Defaults:** project-wide and directory analyze skip `tests/`, `*.test.*`, `*.spec.*`, `conftest.py`, and `__tests__/`. Pass `--include-tests` to include them. File-target analyze always includes that file.
310
317
 
311
- **Limits:** "Dead export" means no cross-file mentions in the index — not unreachable code. Same-file private helpers are expected. Re-run `reindex` after large changes; the index is a snapshot.
318
+ **Limits:** "Dead export" means no cross-file mentions in the index — not unreachable code. Same-file private helpers are expected. Shebang lines and manifest entrypoints (`package.json` main/bin/scripts with dist→src mapping for TypeScript; Python `pyproject`/`setup.py`/`setup.cfg` scripts) are excluded from project-level dead files and dead exports. Re-run `reindex` after large changes; the index is a snapshot.
312
319
 
313
320
  ## Performance
314
321
 
@@ -19,6 +19,7 @@ TypeScript/JavaScript, Python, Go, Rust — not GraphQL, CSS, or other files. Ru
19
19
  |What does this use?|`deps target`|Outbound deps (symbol or file)|
20
20
  |Class members|`members Class`|Methods/fields + line ranges|
21
21
  |Health / risk|`analyze`|SQL dashboard — project, dir, file, or symbol|
22
+ |Raw SQL|`query "SELECT …"`|Rows from `index.db` (`--format tsv|csv|json`)|
22
23
 
23
24
  Default `--limit` is **10** on query commands unless noted. Stdout = records; stderr = warnings/progress. Pipe with `--paths-only` / `--names-only`.
24
25
 
@@ -103,6 +104,22 @@ members [--limit N] [--path PATH] [--names-only] <symbol>
103
104
 
104
105
  `start:end kind name`. Prefix match under parent; missing ranges filled from source when needed.
105
106
 
107
+ ### query
108
+
109
+ ```bash
110
+ query [--format tsv|csv|json] [--write] <sql> [...]
111
+ ```
112
+
113
+ SQL against the cached project `index.db`. **Read-only by default** (`PRAGMA query_only`); `--write` allows mutations (commits after each statement). Default `--format` is **tsv** (header row + tab-separated values). `csv` and `json` (array of objects) are also supported. Prefer `reindex` over hand-editing the cache.
114
+
115
+ Introspect schema (SQLite catalog + `PRAGMA`):
116
+
117
+ ```bash
118
+ query "SELECT name FROM sqlite_schema WHERE type='table' ORDER BY 1"
119
+ query "PRAGMA table_info(mentions)"
120
+ query "SELECT m.name AS table_name, p.name AS column_name, p.type FROM sqlite_schema m JOIN pragma_table_info(m.name) p WHERE m.type='table' AND m.name NOT LIKE 'sqlite_%' ORDER BY 1, p.cid"
121
+ ```
122
+
106
123
  ### analyze
107
124
 
108
125
  ```bash
@@ -116,9 +133,9 @@ analyze [--limit N] [--per-check-limit N] [--path PATH] [--include-tests] [--pri
116
133
  |file|File dashboards + top symbols by external consumers|
117
134
  |symbol|Pressure, consumers, deps, affected|
118
135
 
119
- Priority **high → medium → low**. `--limit` = total finding rows (default 20); headers/truncation don't count. `--per-check-limit` caps each section. Agents: `--limit 40 --per-check-limit 5`. `--priority high` or `high,medium` (`1`/`2`/`3`) skips lower tiers. `--check NAME` (repeatable/comma) ANDs with priority.
136
+ Priority **high → medium → low**. `[high]` cycles, dead exports, dead files, stale types (project unreferenced omitted when dead exports runs; file unreferenced omitted when dead-in-file runs). `[medium]` same-file-only, change surface (file). `[low]` single-importer files (`[test-only]` tag), test-only consumers, coupling, bottlenecks, hotspots. `--limit` = total finding rows (default 20); headers/truncation don't count. `--per-check-limit` caps each section. Agents: `--limit 40 --per-check-limit 5`. `--priority high` or `high,medium` (`1`/`2`/`3`) skips lower tiers. `--check NAME` (repeatable/comma) ANDs with priority.
120
137
 
121
- **Easy wins:** cycles, dead exports, dead files (production). Stale types = types with no external refs. Dead = no cross-file refs in index, not `vulture`. Empty `rdeps` on a hit is often a SCIP miss — verify with `rg -F STEM` (basename in `(...)`, same-file hits ignored).
138
+ **Easy wins:** cycles, dead exports, dead files (production). Stale types = types with no external refs. Dead = no cross-file refs in index, not `vulture`. Shebang plus manifest entrypoints (`package.json` main/bin/scripts, dist→src mapping; each Python package for pyproject/setup/setup.cfg) are never project-level dead files/exports. `single_importer_files` rows with only test importers are tagged `[test-only]`. File-scoped `analyze path/to/cli.ts` still reports unused symbols in that file. Empty `rdeps` on a hit is often a SCIP miss — verify with `rg -F STEM` (basename in `(...)`, same-file hits ignored).
122
139
 
123
140
  ### reindex
124
141
 
@@ -1,3 +1,3 @@
1
1
  """scip-cli: Fast code intelligence via SCIP indexes."""
2
2
 
3
- __version__ = "3.1.0"
3
+ __version__ = "3.2.0"
@@ -18,7 +18,7 @@ from .cli_args import (
18
18
  add_paths_only_argument,
19
19
  positive_int,
20
20
  )
21
- from .commands import analyze, code, deps, members, rdeps, refs, reindex, search, skill, symbols
21
+ from .commands import analyze, code, deps, members, query, rdeps, refs, reindex, search, skill, symbols
22
22
  from .symbols import SymbolKind
23
23
 
24
24
  # Set up debug logging based on SCIP_CLI_DEBUG env var
@@ -122,6 +122,20 @@ def main() -> None:
122
122
  members_parser.add_argument("symbol", help="Symbol name")
123
123
 
124
124
  # skill
125
+ query_parser = subparsers.add_parser("query", help="Run SQL against the project index database")
126
+ query_parser.add_argument("sql", nargs="+", help="SQL to execute")
127
+ query_parser.add_argument(
128
+ "--format",
129
+ choices=("tsv", "csv", "json"),
130
+ default="tsv",
131
+ help="Output format (default: tsv)",
132
+ )
133
+ query_parser.add_argument(
134
+ "--write",
135
+ action="store_true",
136
+ help="Allow INSERT/UPDATE/DELETE (default: read-only)",
137
+ )
138
+
125
139
  skill_parser = subparsers.add_parser("skill", help="Install or dump the scip-cli SKILL.md")
126
140
  skill_parser.add_argument("path", nargs="?", help="Optional file path to write to (creates dirs)")
127
141
 
@@ -234,6 +248,7 @@ def main() -> None:
234
248
  "rdeps": rdeps.main,
235
249
  "deps": deps.main,
236
250
  "members": members.main,
251
+ "query": query.main,
237
252
  "skill": skill.main,
238
253
  "analyze": analyze.main,
239
254
  "reindex": reindex.main,
@@ -161,6 +161,11 @@ def is_test_path(relative_path: str) -> bool:
161
161
  return name == "conftest.py"
162
162
 
163
163
 
164
+ def is_package_init_module(relative_path: str) -> bool:
165
+ """Python package __init__.py — import surface, not a nuke candidate for dead_files."""
166
+ return relative_path.replace("\\", "/").endswith("__init__.py")
167
+
168
+
164
169
  def is_dynamic_loader_path(relative_path: str) -> bool:
165
170
  """True for migration files typically loaded by filename; SCIP has no import edge."""
166
171
  p = relative_path.replace("\\", "/").lower()
@@ -0,0 +1,77 @@
1
+ """CLI entrypoints that SCIP never links via import edges."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextvars import ContextVar
6
+ from pathlib import Path
7
+
8
+ from ...project import Language, detect_language
9
+ from ...source import file_has_shebang
10
+ from . import golang as golang_entrypoints
11
+ from . import python as python_entrypoints
12
+ from . import rust as rust_entrypoints
13
+ from . import typescript as typescript_entrypoints
14
+ from .paths import canonical_rel
15
+
16
+ _DECLARED_LOADERS = {
17
+ Language.TYPESCRIPT: typescript_entrypoints.declared_entrypoint_paths,
18
+ Language.PYTHON: python_entrypoints.declared_entrypoint_paths,
19
+ Language.GOLANG: golang_entrypoints.declared_entrypoint_paths,
20
+ Language.RUST: rust_entrypoints.declared_entrypoint_paths,
21
+ }
22
+
23
+
24
+ class EntrypointIndex:
25
+ """Declared CLI entrypoints (manifest + shebang) for one analyze pass."""
26
+
27
+ _root: Path | None
28
+ _declared_paths: set[str]
29
+ _shebang_cache: dict[str, bool]
30
+
31
+ def __init__(self, project_root: Path | None) -> None:
32
+ self._root = project_root.resolve() if project_root is not None else None
33
+ self._declared_paths = set()
34
+ self._shebang_cache = {}
35
+ if self._root is not None:
36
+ lang = detect_language(self._root)
37
+ if lang is not None:
38
+ loader = _DECLARED_LOADERS.get(lang)
39
+ if loader is not None:
40
+ self._declared_paths.update(loader(self._root))
41
+
42
+ def is_entrypoint(self, relative_path: str) -> bool:
43
+ if self._root is None:
44
+ return False
45
+ canonical = canonical_rel(relative_path)
46
+ if canonical in self._declared_paths:
47
+ return True
48
+ if python_entrypoints.is_python_entrypoint_file(relative_path):
49
+ return True
50
+ if canonical in self._shebang_cache:
51
+ return self._shebang_cache[canonical]
52
+ has_shebang = file_has_shebang(self._root, relative_path)
53
+ self._shebang_cache[canonical] = has_shebang
54
+ return has_shebang
55
+
56
+
57
+ _CURRENT: ContextVar[EntrypointIndex | None] = ContextVar("scip_cli_entrypoint_index", default=None)
58
+
59
+
60
+ def bind_entrypoints(project_root: Path | str | None) -> object:
61
+ root = Path(project_root) if project_root is not None else None
62
+ return _CURRENT.set(EntrypointIndex(root))
63
+
64
+
65
+ def reset_entrypoints(token) -> None:
66
+ _CURRENT.reset(token)
67
+
68
+
69
+ def is_entrypoint_path(relative_path: str) -> bool:
70
+ index = _CURRENT.get()
71
+ if index is None:
72
+ return False
73
+ return index.is_entrypoint(relative_path)
74
+
75
+
76
+ def entrypoints_bound() -> bool:
77
+ return _CURRENT.get() is not None
@@ -0,0 +1,28 @@
1
+ """Entrypoints for Go modules (main.go and cmd/*/main.go)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from ...discover import discover_golang_modules
8
+ from .paths import path_in_repo
9
+
10
+
11
+ def declared_entrypoint_paths(project_root: Path) -> set[str]:
12
+ repo_root = project_root.resolve()
13
+ paths: set[str] = set()
14
+ for mod_rel in discover_golang_modules(repo_root):
15
+ mod_root = repo_root / mod_rel
16
+ if (mod_root / "main.go").is_file():
17
+ paths.add(path_in_repo(repo_root, mod_rel, "main.go"))
18
+ cmd_dir = mod_root / "cmd"
19
+ if not cmd_dir.is_dir():
20
+ continue
21
+ for child in cmd_dir.iterdir():
22
+ if not child.is_dir():
23
+ continue
24
+ main_go = child / "main.go"
25
+ if main_go.is_file():
26
+ rel = main_go.relative_to(mod_root).as_posix()
27
+ paths.add(path_in_repo(repo_root, mod_rel, rel))
28
+ return paths
@@ -0,0 +1,34 @@
1
+ """Shared path normalization for entrypoint manifests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def canonical_rel(relative_path: str) -> str:
9
+ return relative_path.replace("\\", "/").lstrip("./")
10
+
11
+
12
+ def path_in_repo(_repo_root: Path, package_rel: Path, path_in_package: str) -> str:
13
+ """Map a path relative to a package dir to a repo-relative path."""
14
+ rel = path_in_package.replace("\\", "/").lstrip("./")
15
+ if not package_rel.parts:
16
+ return canonical_rel(rel)
17
+ prefix = package_rel.as_posix().strip("/")
18
+ return canonical_rel(f"{prefix}/{rel}")
19
+
20
+
21
+ def normalize_path_under_root(project_root: Path, raw: str, *, extensions: tuple[str, ...]) -> str | None:
22
+ text = raw.strip().strip("'\"")
23
+ if not text:
24
+ return None
25
+ if not any(text.endswith(ext) for ext in extensions):
26
+ return None
27
+ root = project_root.resolve()
28
+ path = Path(text)
29
+ if path.is_absolute():
30
+ try:
31
+ return canonical_rel(str(path.resolve().relative_to(root)))
32
+ except ValueError:
33
+ return None
34
+ return canonical_rel(text)