sql-code-graph 1.2.2__tar.gz → 1.3.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 (429) hide show
  1. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/ARCHITECTURE_REVIEW.md +133 -2
  2. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/PKG-INFO +1 -1
  3. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/docs/cli.md +14 -1
  4. sql_code_graph-1.3.0/plan/fix_issue29_live_test_followups.md +442 -0
  5. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/pyproject.toml +1 -1
  6. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/__init__.py +1 -1
  7. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/db.py +23 -0
  8. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/git.py +11 -4
  9. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/index.py +167 -4
  10. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/mcp.py +70 -3
  11. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/reindex.py +146 -76
  12. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/kuzu_backend.py +10 -6
  13. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/metrics/store.py +48 -0
  14. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/server.py +165 -70
  15. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/tools.py +155 -14
  16. sql_code_graph-1.3.0/src/sqlcg/server/writer.py +634 -0
  17. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_mcp_lifecycle.py +29 -9
  18. sql_code_graph-1.3.0/tests/integration/test_dialect_auto_resolution.py +178 -0
  19. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_read_via_server.py +7 -1
  20. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_reindex_via_server.py +60 -28
  21. sql_code_graph-1.3.0/tests/integration/test_single_writer_queue.py +382 -0
  22. sql_code_graph-1.3.0/tests/unit/test_BugB_escalation_uses_init_path.py +101 -0
  23. sql_code_graph-1.3.0/tests/unit/test_BugC_hook_upgrade.py +184 -0
  24. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_git_hooks_notify.py +29 -0
  25. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_kuzu_lock.py +4 -4
  26. sql_code_graph-1.3.0/tests/unit/test_writer_queue.py +323 -0
  27. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/uv.lock +1 -1
  28. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/api-documenter.md +0 -0
  29. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/architect-planner.md +0 -0
  30. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/architect-reviewer.md +0 -0
  31. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/code-reviewer.md +0 -0
  32. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/developer.md +0 -0
  33. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/plan-reviewer.md +0 -0
  34. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.claude/agents/sprint-planner.md +0 -0
  35. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  36. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  37. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  38. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/workflows/benchmark.yml +0 -0
  39. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/workflows/e2e-tests.yml +0 -0
  40. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/workflows/release.yml +0 -0
  41. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.github/workflows/test.yml +0 -0
  42. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.gitignore +0 -0
  43. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.pre-commit-config.yaml +0 -0
  44. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/.sqlcgignore +0 -0
  45. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/CHANGELOG.md +0 -0
  46. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/CLAUDE.md +0 -0
  47. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/README.md +0 -0
  48. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/docs/AIRBNB_PARSE_REPORT.md +0 -0
  49. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/e2e_firstuser_report.md +0 -0
  50. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/e2e_run_20260528_101413.output +0 -0
  51. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/main.py +0 -0
  52. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/WORKFLOW.md +0 -0
  53. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/blueprint.md +0 -0
  54. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/bundle_claude_skill.md +0 -0
  55. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/feature_34_unused_presentation_segregation.md +0 -0
  56. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/feature_35_external_downstream_injection.md +0 -0
  57. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/feature_F2_bundle_claude_skill.md +0 -0
  58. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_downstream_sink_location.md +0 -0
  59. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_dynamic_table_parsing.md +0 -0
  60. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_expand_qualify_perf.md +0 -0
  61. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_firstuser_findings.md +0 -0
  62. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/fix_schema_case_mismatch.md +0 -0
  63. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/hygiene_config_path_and_survivors.md +0 -0
  64. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/investigation_e5_e4.md +0 -0
  65. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/living_codebase_resync.md +0 -0
  66. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/schema_comparison_with_schema.json +0 -0
  67. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/schema_comparison_without_schema.json +0 -0
  68. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/sprint_08_changelogs_fullindex.json +0 -0
  69. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/sprint_08_fullcorpus_index.json +0 -0
  70. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/measurements/sprint_pool_300s_plan.json +0 -0
  71. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/parse_diagnostics.md +0 -0
  72. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/parsing_errors_experiment.md +0 -0
  73. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/snowflake_en_test_suite.md +0 -0
  74. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_01.md +0 -0
  75. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_01_deployment_pypi.md +0 -0
  76. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_02.md +0 -0
  77. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_02_v0.3.0_core.md +0 -0
  78. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_03.md +0 -0
  79. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_03_v0.3.1_postmortem.md +0 -0
  80. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_04_column_lineage.md +0 -0
  81. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_04_column_lineage_fix.md +0 -0
  82. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_05_star_resolution.md +0 -0
  83. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_06_lineage_coverage.md +0 -0
  84. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_07_open_ecodes.md +0 -0
  85. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_07_perf_and_live_test.md +0 -0
  86. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_08_perf_upsert.md +0 -0
  87. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_09_lineage_coverage.md +0 -0
  88. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_10_anchor_tools.md +0 -0
  89. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_11_v1.0.2_bugfix.md +0 -0
  90. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_12_v1.1.0.md +0 -0
  91. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_13_v1.1.0_cluster_b.md +0 -0
  92. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sprint_3.1_postmortem.md +0 -0
  93. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/sqlcg.md +0 -0
  94. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/trust_layer.md +0 -0
  95. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1.1.0_cluster_b_provenance_trust.md +0 -0
  96. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1.1.0_live_graph_freshness.md +0 -0
  97. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1.1.1_batch_upsert_perf.md +0 -0
  98. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1_1_2_bugfix.md +0 -0
  99. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1_1_3_union_cte_star.md +0 -0
  100. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1_2_0_read_proxy.md +0 -0
  101. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/plan/v1_2_1_bugfix.md +0 -0
  102. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/profile.html +0 -0
  103. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/pyrightconfig.json +0 -0
  104. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/scripts/collect_parse_errors.py +0 -0
  105. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/scripts/generate_cli_docs.sh +0 -0
  106. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/__main__.py +0 -0
  107. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/__init__.py +0 -0
  108. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/__init__.py +0 -0
  109. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/analyze.py +0 -0
  110. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/find.py +0 -0
  111. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/gain.py +0 -0
  112. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/install.py +0 -0
  113. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/report.py +0 -0
  114. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/uninstall.py +0 -0
  115. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/commands/watch.py +0 -0
  116. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/cli/main.py +0 -0
  117. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/__init__.py +0 -0
  118. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/config.py +0 -0
  119. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/freshness.py +0 -0
  120. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/graph_db.py +0 -0
  121. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/jobs.py +0 -0
  122. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/neo4j_backend.py +0 -0
  123. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/queries.cypher +0 -0
  124. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/queries.py +0 -0
  125. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/schema.cypher +0 -0
  126. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/core/schema.py +0 -0
  127. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/__init__.py +0 -0
  128. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/dbt_adapter.py +0 -0
  129. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/error_classify.py +0 -0
  130. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/git_delta.py +0 -0
  131. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/indexer.py +0 -0
  132. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/pool.py +0 -0
  133. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/walker.py +0 -0
  134. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/indexer/watcher.py +0 -0
  135. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/lineage/__init__.py +0 -0
  136. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/lineage/aggregator.py +0 -0
  137. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/lineage/schema_resolver.py +0 -0
  138. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/metrics/__init__.py +0 -0
  139. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/__init__.py +0 -0
  140. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/ansi_parser.py +0 -0
  141. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/base.py +0 -0
  142. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/bigquery_parser.py +0 -0
  143. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/postgres_parser.py +0 -0
  144. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/registry.py +0 -0
  145. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/snowflake_parser.py +0 -0
  146. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/parsers/tsql_parser.py +0 -0
  147. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/__init__.py +0 -0
  148. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/control.py +0 -0
  149. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/exceptions.py +0 -0
  150. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/models.py +0 -0
  151. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/noise_filter.py +0 -0
  152. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/read_client.py +0 -0
  153. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/server/skill.py +0 -0
  154. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/utils/__init__.py +0 -0
  155. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/utils/hashing.py +0 -0
  156. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/utils/ignore.py +0 -0
  157. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/src/sqlcg/utils/logging.py +0 -0
  158. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/__init__.py +0 -0
  159. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/__init__.py +0 -0
  160. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/adversarial/200_join.sql +0 -0
  161. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/adversarial/500_union.sql +0 -0
  162. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/bench_indexer.py +0 -0
  163. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/conftest.py +0 -0
  164. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/case_normalization.sql +0 -0
  165. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/colon_cast.sql +0 -0
  166. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/colon_reserved_word.sql +0 -0
  167. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/copy_into.sql +0 -0
  168. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/create_procedure.sql +0 -0
  169. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/identifier_dynamic.sql +0 -0
  170. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/lateral_flatten.sql +0 -0
  171. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/qualify.sql +0 -0
  172. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/scripting_block.sql +0 -0
  173. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/golden_corpus/snowflake/three_part.sql +0 -0
  174. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q01.sql +0 -0
  175. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q02.sql +0 -0
  176. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q03.sql +0 -0
  177. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q04.sql +0 -0
  178. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/benchmarks/tpch/q05.sql +0 -0
  179. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/__init__.py +0 -0
  180. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/conftest.py +0 -0
  181. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_F2_skill_install_e2e.py +0 -0
  182. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_airbnb_e2e.py +0 -0
  183. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_cli_index.py +0 -0
  184. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_git_hook_install.py +0 -0
  185. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_golden_lineage.py +0 -0
  186. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_mcp_tools.py +0 -0
  187. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_parse_diagnostics_cli.py +0 -0
  188. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_star_resolution_e2e.py +0 -0
  189. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/e2e/test_watch.py +0 -0
  190. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/dim_hosts_cleansed.sql +0 -0
  191. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/dim_listings_cleansed.sql +0 -0
  192. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/fct_reviews.sql +0 -0
  193. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/mart_fullmoon_reviews.sql +0 -0
  194. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/raw_hosts.sql +0 -0
  195. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/raw_listings.sql +0 -0
  196. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/raw_reviews.sql +0 -0
  197. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/src_hosts.sql +0 -0
  198. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/src_listings.sql +0 -0
  199. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/airbnb/src_reviews.sql +0 -0
  200. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/bigquery/.gitkeep +0 -0
  201. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/jaffle_shop/customers.sql +0 -0
  202. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/jaffle_shop/orders.sql +0 -0
  203. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/jaffle_shop/raw_orders.sql +0 -0
  204. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/snowflake/base_tables.sql +0 -0
  205. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/snowflake/reports.sql +0 -0
  206. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/snowflake/views.sql +0 -0
  207. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/star_corpus/ddl_src.sql +0 -0
  208. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/star_corpus/ddl_tgt.sql +0 -0
  209. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/star_corpus/etl_alias_star.sql +0 -0
  210. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/star_corpus/etl_star.sql +0 -0
  211. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/synthetic/base_tables.sql +0 -0
  212. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/synthetic/reports.sql +0 -0
  213. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/fixtures/synthetic/views.sql +0 -0
  214. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/__init__.py +0 -0
  215. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/snowflake/__init__.py +0 -0
  216. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/snowflake/test_insert_select.py +0 -0
  217. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_T34_presentation_segregation.py +0 -0
  218. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_T35_external_consumers.py +0 -0
  219. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_anchor_tools.py +0 -0
  220. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_bulk_upsert.py +0 -0
  221. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_column_lineage_e2e.py +0 -0
  222. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_cross_file_lineage.py +0 -0
  223. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_cte_recall_guard.py +0 -0
  224. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_cte_schema_alias_guard.py +0 -0
  225. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_cte_source_node_invariant.py +0 -0
  226. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_dialect_matrix.py +0 -0
  227. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_e36_xfile_regression_guard.py +0 -0
  228. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_freshness_mcp.py +0 -0
  229. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_hygiene_config_root_reconciliation.py +0 -0
  230. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_indexer_batching.py +0 -0
  231. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_indexer_commits.py +0 -0
  232. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_indexer_to_graph.py +0 -0
  233. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_live_anchors.py +0 -0
  234. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_parse_diagnostics.py +0 -0
  235. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_pr1_confidence_reason.py +0 -0
  236. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_pr2_source_location.py +0 -0
  237. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_pr3_kind_tagging.py +0 -0
  238. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_readonly_under_lock.py +0 -0
  239. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_resync.py +0 -0
  240. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_star_resolution.py +0 -0
  241. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_temp_table_lineage.py +0 -0
  242. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_union_cte_star_recall_guard.py +0 -0
  243. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/integration/test_user_surface_recall_guard.py +0 -0
  244. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/perf/__init__.py +0 -0
  245. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/perf/test_perf.py +0 -0
  246. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E10/__init__.py +0 -0
  247. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E11/__init__.py +0 -0
  248. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E12/__init__.py +0 -0
  249. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E12/e12_json_path.sql +0 -0
  250. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E12/e12_lateral_flatten.sql +0 -0
  251. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E12/test_e12.py +0 -0
  252. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E13/__init__.py +0 -0
  253. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E14/__init__.py +0 -0
  254. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E15/__init__.py +0 -0
  255. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E16/__init__.py +0 -0
  256. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E16/e16_merge.sql +0 -0
  257. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E16/e16_merge_delete.sql +0 -0
  258. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E16/test_e16.py +0 -0
  259. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E17/__init__.py +0 -0
  260. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/__init__.py +0 -0
  261. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/e18_decode.sql +0 -0
  262. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/e18_iff_decode.sql +0 -0
  263. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/e18_nvl2.sql +0 -0
  264. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E18/test_e18.py +0 -0
  265. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E19/__init__.py +0 -0
  266. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/__init__.py +0 -0
  267. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/e2_expr_alias.sql +0 -0
  268. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/e2_function_alias.sql +0 -0
  269. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/e2_multiply_alias.sql +0 -0
  270. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E2/test_e2.py +0 -0
  271. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E20/__init__.py +0 -0
  272. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E21/__init__.py +0 -0
  273. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E21/e21_alias_forward_ref.sql +0 -0
  274. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E21/e21_three_level_chain.sql +0 -0
  275. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E21/test_e21.py +0 -0
  276. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E22/__init__.py +0 -0
  277. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E23/__init__.py +0 -0
  278. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E23/e23_stored_proc.sql +0 -0
  279. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E23/e23_stored_proc_multi.sql +0 -0
  280. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E23/test_e23.py +0 -0
  281. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E24/__init__.py +0 -0
  282. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/__init__.py +0 -0
  283. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/e25_cross_db.sql +0 -0
  284. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/e25_two_part.sql +0 -0
  285. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/test_e25.py +0 -0
  286. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E25/test_e25_full_id.py +0 -0
  287. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E26/__init__.py +0 -0
  288. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E27/__init__.py +0 -0
  289. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E27/e27_nested_udf.sql +0 -0
  290. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E27/e27_udf.sql +0 -0
  291. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E27/test_e27.py +0 -0
  292. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E28/__init__.py +0 -0
  293. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E29/__init__.py +0 -0
  294. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/__init__.py +0 -0
  295. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/e3_alter_table.sql +0 -0
  296. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/e3_create_sequence.sql +0 -0
  297. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/e3_ddl_only.sql +0 -0
  298. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E3/test_e3.py +0 -0
  299. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E30/__init__.py +0 -0
  300. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E31/__init__.py +0 -0
  301. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E32/__init__.py +0 -0
  302. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E33/__init__.py +0 -0
  303. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E34/__init__.py +0 -0
  304. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E35/__init__.py +0 -0
  305. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/__init__.py +0 -0
  306. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/e36_temp_multi_use.sql +0 -0
  307. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/e36_temp_table.sql +0 -0
  308. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/test_e36.py +0 -0
  309. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E36/test_e36_xfile.py +0 -0
  310. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E37/__init__.py +0 -0
  311. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E38/__init__.py +0 -0
  312. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/__init__.py +0 -0
  313. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/e4_execute_immediate.sql +0 -0
  314. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/e4_if_not_exists.sql +0 -0
  315. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/e4_unexpected_token.sql +0 -0
  316. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/e4_unpivot.sql +0 -0
  317. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E4/test_e4.py +0 -0
  318. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/__init__.py +0 -0
  319. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/e5_cte_missing_source.sql +0 -0
  320. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/e5_multi_cte.sql +0 -0
  321. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/e5_nested_cte.sql +0 -0
  322. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/test_e5.py +0 -0
  323. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E5/test_e5_cte_projection.py +0 -0
  324. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/__init__.py +0 -0
  325. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/e8_dynamic_sources.sql +0 -0
  326. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/e8_seq_nextval.sql +0 -0
  327. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/e8_uuid.sql +0 -0
  328. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E8/test_e8.py +0 -0
  329. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E9/__init__.py +0 -0
  330. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/__init__.py +0 -0
  331. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/fixture_sum_absent_cross_schema.sql +0 -0
  332. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/fixture_sum_case_when.sql +0 -0
  333. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/fixture_sum_present_source.sql +0 -0
  334. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_aggregates/test_e_aggregates.py +0 -0
  335. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/__init__.py +0 -0
  336. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/fixture_date_aliased.sql +0 -0
  337. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/fixture_date_unaliased.sql +0 -0
  338. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/fixture_datediff_unaliased.sql +0 -0
  339. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/fixture_year_unaliased.sql +0 -0
  340. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/E_date_functions/test_e_date_functions.py +0 -0
  341. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/README.md +0 -0
  342. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/__init__.py +0 -0
  343. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/__init__.py +0 -0
  344. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/fixture_etl.sql +0 -0
  345. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/fixture_omloopsnelheid.sql +0 -0
  346. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/fixture_semantic.sql +0 -0
  347. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/fixture_source.sql +0 -0
  348. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/test_anchor_ma_aantal_op_order.py +0 -0
  349. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/anchors/test_anchor_omloopsnelheid.py +0 -0
  350. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/conftest.py +0 -0
  351. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/snowflake/test_plan_review_gates.py +0 -0
  352. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/__init__.py +0 -0
  353. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/snowflake/__init__.py +0 -0
  354. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/snowflake/test_scripting_noise.py +0 -0
  355. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_F2_install_skill.py +0 -0
  356. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_F2_skill_render.py +0 -0
  357. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_F2_uninstall_skill.py +0 -0
  358. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T09_01_qualify_once.py +0 -0
  359. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T09_02_ddl_skip.py +0 -0
  360. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T09_04_subprocess_isolate.py +0 -0
  361. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T09_06_log_verbosity.py +0 -0
  362. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_T35_config_external_consumers.py +0 -0
  363. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_aggregator.py +0 -0
  364. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_aggregator_skip.py +0 -0
  365. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_base_parser.py +0 -0
  366. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_branch_monitor.py +0 -0
  367. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_bulk_upsert_invariant.py +0 -0
  368. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_canonical_target_resolution.py +0 -0
  369. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_cli.py +0 -0
  370. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_cli_help.py +0 -0
  371. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_closure_depth.py +0 -0
  372. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_column_lineage_wiring.py +0 -0
  373. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_config.py +0 -0
  374. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_data_models.py +0 -0
  375. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_db_info.py +0 -0
  376. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_dominant_cause.py +0 -0
  377. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_find_cmd.py +0 -0
  378. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_firstuser_findings.py +0 -0
  379. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_freshness_helper.py +0 -0
  380. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_gain_ratio.py +0 -0
  381. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_git_delta.py +0 -0
  382. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_git_hooks.py +0 -0
  383. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_graph_backend.py +0 -0
  384. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_hard_kill_pool.py +0 -0
  385. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_hygiene_config_warning.py +0 -0
  386. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_include_working_tree.py +0 -0
  387. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_index_cmd.py +0 -0
  388. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_index_flags.py +0 -0
  389. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_index_progress.py +0 -0
  390. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_indexer_progress.py +0 -0
  391. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_indexer_quality.py +0 -0
  392. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_install.py +0 -0
  393. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_install_message.py +0 -0
  394. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_jobs.py +0 -0
  395. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_judgement.py +0 -0
  396. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_kuzu_backend.py +0 -0
  397. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_lineage_conversion.py +0 -0
  398. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_literal_column_skip.py +0 -0
  399. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_mcp_best_practices.py +0 -0
  400. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_mcp_control.py +0 -0
  401. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_mcp_stdio_smoke.py +0 -0
  402. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_metrics.py +0 -0
  403. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_noise_filter.py +0 -0
  404. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_parse_file_dependency_filter.py +0 -0
  405. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_parse_quality.py +0 -0
  406. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_parser.py +0 -0
  407. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_perf_scaling_guard.py +0 -0
  408. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_pr07_observability.py +0 -0
  409. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_queries_loader.py +0 -0
  410. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_read_client.py +0 -0
  411. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_resolve_pass2_passes_dependency_filter.py +0 -0
  412. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_schema_resolver.py +0 -0
  413. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_server.py +0 -0
  414. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_snowflake_strip_alter_set_tag.py +0 -0
  415. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_sprint_06_t04_t05.py +0 -0
  416. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_star_resolution_unit.py +0 -0
  417. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_star_schema_unit.py +0 -0
  418. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_submit_feedback.py +0 -0
  419. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_subprocess_isolate.py +0 -0
  420. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_t02_expression_name_extraction.py +0 -0
  421. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_t03_ddl_skip.py +0 -0
  422. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_timeout_cancel.py +0 -0
  423. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_tools_hints.py +0 -0
  424. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_tools_warnings.py +0 -0
  425. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_uninstall.py +0 -0
  426. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_unqualified_fallback.py +0 -0
  427. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_upsert_batch_invariant.py +0 -0
  428. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_walker.py +0 -0
  429. {sql_code_graph-1.2.2 → sql_code_graph-1.3.0}/tests/unit/test_watcher.py +0 -0
@@ -3026,8 +3026,13 @@ Both are now load-bearing; recorded so a future refactor does not silently undo
3026
3026
  re-verification (80→47 `cte_insert` islands) recorded in the plan; the surviving 47
3027
3027
  are likely legitimately source-less computed measures, not a regression. Shape B of
3028
3028
  the #40 guard now asserts a computed-measure projection so a real gap there reds the guard.
3029
- - **#28 server read-only-by-default + reindex escalation** — out of 1.2.1; recommended
3030
- as a separate minor (1.3.0). The read half is already handled by the v1.2.0 routing proxy.
3029
+ - **#28 server read-only-by-default + reindex escalation** — **IMPLEMENTED in 1.3.0**
3030
+ (#29 single-writer queue + RO→RW escalation; plan
3031
+ [`fix_issue_29_single_writer_queue.md`](plan/fix_issue_29_single_writer_queue.md)).
3032
+ The read half was already handled by the v1.2.0 routing proxy; 1.3.0 adds the write half:
3033
+ the server serves RO after a startup RW schema-ensure window, and escalates RO→RW only for
3034
+ the duration of a single-writer drain. See §20 for the compliance postmortem and two
3035
+ accepted/flagged deviations.
3031
3036
  - **`connected_clients` fidelity** — best-effort `1` today; no real counter.
3032
3037
 
3033
3038
  ## 19. v1.2.1 live DWH re-verification — two residual findings (2026-06-02)
@@ -3145,3 +3150,129 @@ The fix held for v1.2.2 (the helper mirrors the shipped query), but the divergen
3145
3150
  structural. **Future hardening:** replace the string-reconstruction helper with a
3146
3151
  `CliRunner`-based guard that runs the real `analyze downstream` command and asserts the
3147
3152
  rendered output. LOW priority; below §19.1 and the open §3.x items. No plan stub drafted.
3153
+
3154
+ ## 20. v1.3.0 — Single-Writer Queue + RO→RW Escalation (#29 / #28) (2026-06-04)
3155
+
3156
+ Implements the long-deferred §18.4 "#28 server read-only-by-default + reindex escalation"
3157
+ item. Plan: [`fix_issue_29_single_writer_queue.md`](plan/fix_issue_29_single_writer_queue.md).
3158
+ Branch `feat/fix-issue-29-single-writer-queue`, 8 commits (T-01…T-08). Test suite
3159
+ 1009 passed / 7 skipped / 1 xfailed; pyright clean; ruff clean.
3160
+
3161
+ ### 20.1 What shipped (plan-compliance verdict)
3162
+
3163
+ The five phases landed as planned. The hard invariants were honoured **in the
3164
+ control-socket / drain path**:
3165
+
3166
+ - **B1 (no co-existence window)** — Steps 2.2 (drain task + escalation primitive) and 2.3
3167
+ (retire the inline `reindex` op) landed in **one commit** (`62c19cd`). The `reindex` and
3168
+ `index` socket-op handlers in [`server.py`](src/sqlcg/server/server.py) `_control_socket_task`
3169
+ enqueue onto `WriterQueue` and never call `backend_ref()`; the drain task is the sole
3170
+ backend consumer, resolving the backend under `backend_lock` via `escalate_to_rw`.
3171
+ - **B2 (shutdown vs. drain ordering)** — `_stop_watcher`
3172
+ ([`server.py`](src/sqlcg/server/server.py) L394-410) sets `shutdown_requested` then
3173
+ acquires `backend_lock` **before** `shutdown_backend()`; `de_escalate_to_ro`
3174
+ ([`writer.py`](src/sqlcg/server/writer.py)) skips the RO reopen when the event is set.
3175
+ Both guarantees (a)+(b) present.
3176
+ - **B3 (status framing atomic)** — server-side `status` framing (Step 4.1) and the CLI
3177
+ recv-exactly parse (Step 4.2) shipped in one commit (`af2c85d`); `mcp_stop`'s
3178
+ `s.recv(128)` and the unframed `stop` reply are untouched.
3179
+ - **W3 (`from=null`)** — the `reindex` op stores `from_sha=None`; the drain resolves the
3180
+ stored SHA via `rw.get_indexed_sha()` and HEAD via `git rev-parse`, with an actionable
3181
+ "no prior index" error on a never-indexed DB. The CLI refusal was removed.
3182
+ - **W5 (`done:true` sentinel)** — both server emit and CLI recv loops key off
3183
+ `done == true`, not EOF; failure relays `ok:false, done:true, error:…`.
3184
+ - **W6 (`find_lock_holder` public)** — promoted in `kuzu_backend.py`, in-module call site
3185
+ updated, imported by `writer.py`.
3186
+ - **W7 (drain exception handler)** — the drain's `except Exception` clears `_active`
3187
+ (`mark_active_failed`), relays an `ok:false,done:true` terminal frame to waiters, and the
3188
+ loop survives; `EscalationLockError` keeps its dedicated C3 path with the ERROR log.
3189
+
3190
+ ### 20.2 Deviation 1 — MCP `index_repo` tool inline escalation (FLAGGED — follow-up required)
3191
+
3192
+ **What the developer added (not in the plan):** a `_serving_ro` flag +
3193
+ `_get_or_escalate_rw` / `_de_escalate_to_ro_from_tool` helpers in
3194
+ [`tools.py`](src/sqlcg/server/tools.py) so the MCP `index_repo` **tool** (invoked over the
3195
+ stdio transport, distinct from the CLI `index` socket op) escalates RO→RW inline when the
3196
+ server is serving RO.
3197
+
3198
+ **Verdict: the intent is consistent with the single-writer model, but the implementation
3199
+ violates the plan's central invariant and leaks the write lock on failure. Track as a
3200
+ v1.3.x follow-up, not a release blocker (the happy path works and tests are green), but
3201
+ it must be fixed before it is relied on under concurrency.**
3202
+
3203
+ Three concrete problems:
3204
+
3205
+ 1. **Escalation outside `backend_lock` (breaks B1/OD-7).** The plan's load-bearing invariant
3206
+ is "once escalation exists, **no op may operate on a backend resolved outside
3207
+ `backend_lock`**; the only backend consumer is the drain task." The MCP `index_repo` tool
3208
+ escalates and writes with **no lock held** — a second path that can swap
3209
+ `tools._backend` concurrently with a `query` op or a drain. This is exactly the class of
3210
+ bug B1 was written to prevent.
3211
+ 2. **RW-lock leak on the failure path.** `_de_escalate_to_ro_from_tool` is called only on the
3212
+ success branch ([`tools.py`](src/sqlcg/server/tools.py) L613-614), **not in a `finally`**.
3213
+ Any exception during `index_repo` / `upsert_node` / `run_read` / `upsert_edge` re-raises
3214
+ (L634) with the backend still RW — the exclusive write lock stays held, blocking all
3215
+ subsequent routed reads until the server is restarted. (Contrast the drain path, which
3216
+ de-escalates in a `finally`.)
3217
+ 3. **Dead `_set_backend_lock` / `_backend_lock`.** `_set_backend_lock`
3218
+ ([`tools.py`](src/sqlcg/server/tools.py) L117) and the `_backend_lock` global are
3219
+ **defined but never called** — a zero-value stub that looks like the intended lock plumbing
3220
+ for the tool escalation but was never wired. This is the CLAUDE.md "every new method must
3221
+ have a grep-confirmed call site" rule unmet, and the "function defined but never invoked"
3222
+ smell.
3223
+
3224
+ **Follow-up (recommended):** route the MCP `index_repo` tool write through the same
3225
+ `WriterQueue`/drain as the CLI `index` op (so there is genuinely one writer), or — minimally —
3226
+ acquire `backend_lock` for the escalation and move the de-escalation into a `finally`. There
3227
+ is **no test** for this path today (no test references `_get_or_escalate_rw` / `_serving_ro`),
3228
+ so the leak and the lock-bypass are unguarded. A regression test asserting (a) the tool
3229
+ escalates under the lock and (b) a raising `index_repo` de-escalates back to RO is required
3230
+ with the fix.
3231
+
3232
+ ### 20.3 Deviation 2 — Step 4.3 "queued behind N" display (ACCEPTED, minor)
3233
+
3234
+ The plan's Step 4.3 specified that a client queued behind other work prints
3235
+ `queued behind: <op> (done/total files) — position N` before attaching to live progress.
3236
+ The developer implemented the `done:true` terminal sentinel and the progress-bar attach
3237
+ fully, but **omitted the "queued behind … position N" pre-active display**: the CLI receives
3238
+ the `{queued:true, position:N}` frame and the subsequent progress frames, but the queued
3239
+ frame is silently consumed (it carries no `files_total`, so the progress loop renders
3240
+ nothing for it) — see [`index.py`](src/sqlcg/cli/commands/index.py) L307-311.
3241
+
3242
+ **Verdict: ACCEPTED as a cosmetic shortfall.** The functional contract (enqueue, attach,
3243
+ stream progress, terminate on `done:true`, exit non-zero on `ok:false`) is intact; only the
3244
+ human-readable "you are behind N others" hint is missing. The `position` is already on the
3245
+ wire, so completing it is a one-line render in the existing loop. Track as a polish item,
3246
+ not a correctness gap.
3247
+
3248
+ ### 20.4 Test-coverage gaps vs. the plan's Test Strategy (FLAGGED)
3249
+
3250
+ The plan's Test Strategy enumerated several **deterministic** integration tests that were
3251
+ not implemented as test functions, even though
3252
+ [`test_single_writer_queue.py`](tests/integration/test_single_writer_queue.py)'s module
3253
+ docstring lists B2 / W3 among its coverage (the docstring overstates what is asserted):
3254
+
3255
+ - **B2 stop-mid-drain** (in-flight write commits; `tools._backend is None` after shutdown,
3256
+ no reopen-after-shutdown) — listed in the docstring, **no test function**.
3257
+ - **W3** arg-level assertion (`resync_changed(from=stored SHA, to=HEAD)`) and the
3258
+ never-indexed "no prior index" error — **no test** (the drain code is present and exercised
3259
+ indirectly by Scenario A, but the W3-specific arg/branch assertions are missing).
3260
+ - **W7** drain-body-raises → `_active` cleared + `ok:false,done:true` terminal frame + loop
3261
+ survives — **no test**.
3262
+ - **Reads block during a drain** (OD-1, deterministic `anyio.Event` ordering) — **no test**.
3263
+ - **B1 structural guard** (the `reindex` op body contains no pre-lock `backend_ref()`,
3264
+ mirroring the perf-invariant guard discipline) — **no test**.
3265
+ - **Escalation transition on real Kuzu** (second in-process RO holder forces
3266
+ `EscalationLockError`) — **no test** (only the injected-`opener` unit tests cover retry).
3267
+
3268
+ What **is** covered: fresh-DB init, stale-schema refusal (Step 1.4), `db reset` refusal
3269
+ (Step 3.4), framed `status` + `writer_queue` shape (Steps 4.1/4.2), metrics persisted +
3270
+ `SQLCG_METRICS=0` suppression (Step 4.5), the escalation retry/backoff + ERROR-log unit tests
3271
+ (Steps 1.2/4.4), the coalescing rules (Step 2.1), and the drain-runs-`resync_changed`-once
3272
+ invariant guard (Scenario A in `test_reindex_via_server.py`).
3273
+
3274
+ **Verdict:** these are real plan deviations (the named deterministic guards were specified
3275
+ and not delivered), but the shipped behaviour is exercised indirectly and the suite is green,
3276
+ so they are **follow-up hardening**, not a ship blocker. They should be filed alongside the
3277
+ Deviation-1 fix — the B2/W7/reads-block tests in particular guard the exact concurrency
3278
+ contract that Deviation 1 currently bypasses.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-code-graph
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: SQL code graph analyzer and lineage tracer
5
5
  Project-URL: Homepage, https://github.com/Warhorze/sql-code-graph
6
6
  Project-URL: Repository, https://github.com/Warhorze/sql-code-graph
@@ -62,6 +62,13 @@ sqlcg index [OPTIONS] PATH
62
62
 
63
63
  Index SQL files in a directory.
64
64
 
65
+ When a server is live on this DB, the index is routed through the server's
66
+ control socket so the DB is never opened directly (avoids lock contention).
67
+ Use --detach to enqueue and return immediately (fire-and-forget).
68
+
69
+ With no server live, falls back to the direct-write path unchanged
70
+ (zero-config small-repo invariant).
71
+
65
72
  Schema aliases (staging schema → canonical schema) can be configured in
66
73
  .sqlcg.toml under sqlcg.schema_aliases, e.g. da_tmp = "da".
67
74
 
@@ -80,6 +87,7 @@ Schema aliases (staging schema → canonical schema) can be configured in
80
87
  | --debug | BOOLEAN | No | No | False | Show detailed log output during indexing |
81
88
  | --profile / --no-profile | BOOLEAN | No | No | False | Emit per-stage timing after indexing |
82
89
  | --include-working-tree | BOOLEAN | No | No | False | Index the working tree including uncommitted changes. Marks freshness as 'indexed with working-tree changes'. |
90
+ | --detach | BOOLEAN | No | No | False | When routing through a live server, return immediately after enqueueing (fire-and-forget). Default is to wait for the index to complete. |
83
91
 
84
92
  ## `sqlcg reindex`
85
93
 
@@ -548,7 +556,12 @@ sqlcg mcp status [OPTIONS]
548
556
  Print server status JSON (connects to control socket).
549
557
 
550
558
  Returns JSON with fields: running, pid, db_path, indexed_sha, head_sha,
551
- stale_by_commits, connected_clients, uptime when a server is live.
559
+ stale_by_commits, connected_clients, uptime, writer_queue when a server
560
+ is live.
561
+
562
+ The status response is length-prefixed framed (v1.3.0, B3) so large
563
+ writer_queue payloads are received in full — the client uses the
564
+ recv-exactly makefile+readline+read(n) pattern, NOT a single recv(4096).
552
565
 
553
566
  When no server is found: {"running": false}.
554
567
  When the PID file exists with a live process but the socket is unavailable:
@@ -0,0 +1,442 @@
1
+ # Feature Plan: #29 Live-Test Follow-up Fixes (fold into PR #51 / v1.3.0)
2
+
3
+ ## Summary
4
+ Three correctness bugs (plus one cosmetic message fix) surfaced during live-DWH
5
+ testing of the open PR #51 branch `feat/fix-issue-29-single-writer-queue`
6
+ (v1.3.0 — single-writer queue + RO→RW escalation). These fold into the **unreleased**
7
+ PR #51; the version stays **1.3.0** (no separate bump — see Version note below).
8
+
9
+ Source of findings: GitHub issue [#29](https://github.com/Warhorze/sql-code-graph/issues/29)
10
+ live-test comment. Bug B is already documented as an architecture follow-up in
11
+ `ARCHITECTURE_REVIEW.md` §20.2 ("Deviation 1 — MCP `index_repo` tool inline escalation").
12
+
13
+ ## Version note (RESOLVED — stays 1.3.0)
14
+ These are bug fixes to code that has **not yet shipped** (PR #51 has no reviews and is
15
+ not merged). Per CLAUDE.md SemVer, the version stays **1.3.0** — there is no released
16
+ 1.3.0 to patch against, so no `1.3.1`. Do **not** bump `pyproject.toml` / `__init__.py`
17
+ / `uv.lock`. Decision confirmed (see Resolved Decisions §D1): fold into PR #51, version
18
+ unchanged.
19
+
20
+ ## Scope
21
+
22
+ ### In Scope
23
+ - **Bug A** — resolve `--dialect auto` (and any repo-config-derived client-side value)
24
+ **before** building the socket WriterRequest in `reindex.py` and `index.py`.
25
+ - **Bug B** — store the path `init_backend(db_path)` received in a module-level variable
26
+ and use it for all escalation/de-escalation in `tools.py`; audit every `get_db_path()`
27
+ call in `tools.py` / `writer.py` / `server.py` for the same default-path leak.
28
+ - **Bug C** — `install-hooks` upgrades an sqlcg-owned hook in place when the rendered
29
+ content differs from the current template; prints `Upgraded git hook: …`. True
30
+ idempotency (identical rendered content) stays a silent skip. Foreign-hook warning
31
+ path unchanged.
32
+ - **Minor D** — reword the hook fallback message from "server busy/locked" to a
33
+ failure-agnostic "reindex failed".
34
+ - Regression tests for A, B, C, D that assert **observable output** (payload contents,
35
+ default-path untouched, stdout message, hook file content).
36
+
37
+ ### Non-Goals
38
+ - **The kuzu 0.11.3 `KU_UNREACHABLE` crash** (csr_node_group.cpp:411 on a second
39
+ consecutive reindex). It reproduces on the direct no-server path on a fresh DB →
40
+ upstream/pre-existing, tracked separately in the issue comment. **No kuzu upgrade,
41
+ no DB-suspect guard in this plan.**
42
+ - The **deeper §20.2 escalation-architecture fixes** (escalation outside `backend_lock`,
43
+ RW-lock leak on the `index_repo` failure path, the dead `_set_backend_lock` /
44
+ `_backend_lock` stub). Bug B as scoped by the maintainer is *narrow*: "use the init
45
+ path, audit `get_db_path`". §20.2 demands more. See "Design — Bug B" for the boundary
46
+ and the explicit decision; the full §20.2 remediation is **out of scope here** and
47
+ remains a tracked v1.3.x follow-up (resolved decision D3).
48
+ - No version bump (see Version note).
49
+ - **Performance invariants are untouched.** None of these fixes go near
50
+ [`base.py`](src/sqlcg/parsers/base.py) or [`indexer.py`](src/sqlcg/indexer/indexer.py)
51
+ hot paths. The diff is confined to two CLI commands, `tools.py`, `git.py`, and tests.
52
+ The developer must confirm `git diff --stat` shows zero changes to `base.py` and to
53
+ `indexer.py`'s `_extract_column_lineage` / `_upsert_parsed_file` / `_build_file_rows`
54
+ / `_flush_row_batch`.
55
+
56
+ ## Design
57
+
58
+ ### Bug A — `--dialect auto` leaks through the routed reindex/index
59
+
60
+ **Root cause (verified in code):**
61
+ - [`reindex.py`](src/sqlcg/cli/commands/reindex.py) calls
62
+ `_try_route_reindex_via_server(... dialect=dialect ...)` at **lines 86-93**, which sends
63
+ `payload["dialect"] = dialect` (line 227). The `if dialect == "auto": dialect = get_dialect(path)`
64
+ resolution is at **lines 98-99 — AFTER** the routing call. So when a server is live the
65
+ WriterRequest carries the literal `"auto"`.
66
+ - [`index.py`](src/sqlcg/cli/commands/index.py) has the **same ordering bug**:
67
+ `_try_route_index_via_server(... dialect=dialect ...)` at **lines 113-118**
68
+ (payload at line 230) runs **before** the `if dialect == "auto"` resolution at
69
+ **lines 155-156**. The routed full index *appeared* correct in live testing only
70
+ because the synthetic DDL happened to parse acceptably; the code order is still wrong
71
+ and must be fixed for parity.
72
+
73
+ **Server-side confirmation (no fix needed there):** the drain loop
74
+ ([`writer.py`](src/sqlcg/server/writer.py) lines 528-605) passes `req.dialect`
75
+ verbatim to `index_repo` / `resync_changed`. There is no server-side `auto` resolution
76
+ — the server cannot resolve `auto` because `get_dialect(path)` reads the repo's
77
+ `.sqlcg.toml` and that is a client-side concern. The fix **must** be client-side.
78
+ `"Unknown dialect 'auto'"` is raised downstream by sqlglot's dialect lookup once the
79
+ literal `"auto"` reaches the parser.
80
+
81
+ **Fix:** in **both** `reindex.py` and `index.py`, move the
82
+ `if dialect == "auto": dialect = get_dialect(path)` resolution **above** the
83
+ `_try_route_*_via_server(...)` call so the routing payload always carries a concrete
84
+ dialect (or an explicit `None`/named dialect the user passed). After the move:
85
+ - `reindex.py`: resolve dialect right after `path = path.resolve()` (line 78), before
86
+ line 86. `get_dialect` is already imported in the function-local import at line 73.
87
+ - `index.py`: resolve dialect right after `path = path.resolve()` (line 110), before
88
+ line 113. `get_dialect` is already imported at module level (line 19).
89
+ - The duplicate resolution further down (reindex.py 98-99, index.py 155-156) is then
90
+ **removed** (it becomes dead — the value is already resolved). Confirm via grep that
91
+ exactly one `if dialect == "auto"` remains per file, and it is before the route call.
92
+
93
+ **Boundary note:** "auto" is the only repo-config-derived value carried in the payload
94
+ today (`root`, `from`, `to`, `wait` are not config-derived). No other client-side
95
+ `.sqlcg.toml` lookup needs hoisting. Document this so the developer does not over-reach.
96
+
97
+ ### Bug B — RW escalation ignores the path `init_backend()` received
98
+
99
+ **Root cause (verified in code):**
100
+ - [`tools.py`](src/sqlcg/server/tools.py) `init_backend(db_path)` (line 128) opens the
101
+ backend on `path = db_path or str(get_db_path())` (line 151) but stores **only**
102
+ `_backend` and `_serving_ro` (lines 168-169). The `path` is **not** retained.
103
+ - The MCP tool `index_repo` (line 547) recomputes `db_path_str = str(get_db_path())`
104
+ (**line 579**) and passes that to `_get_or_escalate_rw` (line 581) and
105
+ `_de_escalate_to_ro_from_tool` (line 614). So escalation opens whatever
106
+ `get_db_path()` returns — i.e. the default `~/.sqlcg/graph.db` — **not** the DB
107
+ `init_backend` actually opened.
108
+
109
+ **Live impact (reproduced):** `tests/e2e/test_mcp_tools.py` does
110
+ `init_backend(str(tmp_path / "test.db"))` (line 30) then `index_repo(...)` (line 33).
111
+ The escalation opened the user's real `~/.sqlcg/graph.db`, wrote
112
+ `tests/fixtures/synthetic` into it during a pytest run, and held the live DB lock —
113
+ blocking a real server start.
114
+
115
+ **Fix:**
116
+ 1. Add a module-level variable `_init_db_path: str | None = None` next to `_backend`
117
+ / `_serving_ro` (tools.py lines 101-114).
118
+ 2. In `init_backend`, set `_init_db_path = path` (the resolved `db_path or
119
+ str(get_db_path())`) under the existing `global` declaration (line 150 — add
120
+ `_init_db_path` to it).
121
+ 3. In `shutdown_backend` (line 181), reset `_init_db_path = None` alongside the other
122
+ globals (add to the `global` at line 187).
123
+ 4. Add a private accessor `_escalation_db_path() -> str` that returns `_init_db_path`
124
+ when set, else `str(get_db_path())` (preserves the no-`init_backend` direct-call
125
+ path used by some unit tests). **This is a new function — it MUST have a grep-confirmed
126
+ call site before the PR opens** (the `index_repo` tool is its call site; see step).
127
+ 5. In `index_repo` (line 579), replace `db_path_str = str(get_db_path())` with
128
+ `db_path_str = _escalation_db_path()`. The downstream `_get_or_escalate_rw(db_path_str, …)`
129
+ (581) and `_de_escalate_to_ro_from_tool(db_path_str)` (614) then use the init path.
130
+
131
+ **Audit of every `get_db_path()` call (verified list):**
132
+ | File:line | Context | Verdict |
133
+ |---|---|---|
134
+ | `tools.py:14` | import | n/a |
135
+ | `tools.py:151` | `init_backend` fallback when `db_path is None` | **correct** — this is the source of truth; capture it into `_init_db_path` |
136
+ | `tools.py:579` | `index_repo` escalation | **LEAK — fix** (step 5) |
137
+ | `writer.py` `escalate_to_rw` / `de_escalate_to_ro` | take `db_path: str` as a parameter; the drain passes the `drain_loop(db_path=…)` argument (writer.py 510-511, 628-629) | **correct** — drain path threads the real db_path; do not change |
138
+ | `server.py:140,221` | status reply `db_path` display | display-only; reflects `db_path or _get_db_path()` from the server CLI arg — **correct** |
139
+ | `server.py:509,525` | `_run_with_control` resolves `_db_path_obj = Path(db_path) if db_path else get_db_path()` and threads it into `drain_loop` | **correct** — the drain receives the server's actual db_path |
140
+
141
+ So the **only** leak is `tools.py:579` (the MCP `index_repo` tool). The drain/CLI-op
142
+ path is already correct because `db_path` is threaded as a parameter, not re-fetched.
143
+
144
+ **§20.2 boundary (explicit):** this fix corrects the *path* used by the existing
145
+ `index_repo` tool escalation. It does **not** move escalation under `backend_lock`, does
146
+ **not** add a `finally`-based de-escalation, and does **not** wire/remove the dead
147
+ `_set_backend_lock` / `_backend_lock` stub. Those are the §20.2 remediation, scoped out
148
+ here (resolved decision D3). The Bug B fix makes the tool target the correct DB; it does not claim
149
+ to make the tool concurrency-safe.
150
+
151
+ ### Bug C — `install-hooks` silently refuses to upgrade old sqlcg hooks
152
+
153
+ **Root cause (verified in code):** [`git.py`](src/sqlcg/cli/commands/git.py)
154
+ `_install_single_hook` (line 91): when the file exists and `spec.sentinel in existing_content`
155
+ (line 103), it returns silently (line 105). The sentinel string (e.g.
156
+ `# sqlcg post-checkout hook`) is **identical across versions**, so a v1.2.x hook (no
157
+ `--notify`, blocks `git checkout` ~2 min against a live server) survives a v1.3.0
158
+ `install-hooks` with exit 0 and no output.
159
+
160
+ **Fix:** the sentinel proves sqlcg ownership. When the sentinel is present:
161
+ - Render the current template: `script = spec.script_template.format(sqlcg_bin=sqlcg_bin)`
162
+ (this already happens at line 99). The rendered script embeds the resolved binary path,
163
+ so **the comparison MUST be against the fully rendered `script`**, not the raw template.
164
+ - If `existing_content == script` → identical → **silent skip** (true idempotency, R9).
165
+ - If `existing_content != script` → sqlcg-owned but stale → **overwrite** (`write_text` +
166
+ `chmod(0o755)`) and print `[green]Upgraded git hook:[/green] .git/hooks/<filename>`.
167
+
168
+ When the sentinel is **absent** (foreign hook) → the existing warning + manual-append
169
+ path (lines 106-118) is **unchanged**.
170
+
171
+ **Comparison subtlety:** `existing_content` is the raw file text; `script` is the
172
+ rendered template. A hook installed by a different sqlcg version differs in the command
173
+ line (e.g. missing `--notify`, or the old "server busy/locked" message — see Minor D),
174
+ so the `!=` will fire and trigger the upgrade. A hook installed by the *current* binary
175
+ from the *same* resolved path is byte-identical → silent skip. Note the binary path is
176
+ embedded, so two installs from different binary locations will (correctly) re-render and
177
+ upgrade.
178
+
179
+ ### Minor D — misleading hook fallback message
180
+
181
+ **Root cause (verified in code):** both hook templates in `_HOOKS`
182
+ ([`git.py`](src/sqlcg/cli/commands/git.py) lines 36-37 post-checkout, lines 53 & 56
183
+ post-merge) print `sqlcg: graph not updated (server busy/locked) -- run 'sqlcg mcp status'`
184
+ on **any** reindex non-zero exit, including a KuzuDB storage crash (KU_UNREACHABLE), which
185
+ has nothing to do with the server being busy.
186
+
187
+ **Fix:** reword all three occurrences to a failure-agnostic message:
188
+ `sqlcg: graph not updated (reindex failed) -- run 'sqlcg mcp status'`.
189
+
190
+ **Interaction with Bug C:** changing the template text changes the **rendered** content,
191
+ so every previously-installed hook (with the old message) now differs from the new
192
+ template → Bug C's upgrade path will (correctly) overwrite it on the next `install-hooks`.
193
+ This is the intended behaviour and is exactly why C and D ship together. The
194
+ `test_git_hooks_notify.py` content guards
195
+ ([`tests/unit/test_git_hooks_notify.py`](tests/unit/test_git_hooks_notify.py)) assert the
196
+ old "server busy/locked" string — they MUST be updated to the new wording (see Test
197
+ Strategy; not a regression, a deliberate text change).
198
+
199
+ ### Docs impact
200
+ `docs/cli.md` is auto-generated by
201
+ [`scripts/generate_cli_docs.sh`](scripts/generate_cli_docs.sh) from **command docstrings
202
+ and option metadata only**. Verified: it does **not** embed `install-hooks` stdout
203
+ messages nor the hook template text (the `install-hooks` section at docs/cli.md:635-653
204
+ is the docstring + the `--repo` option). Bug C changes only **stdout** (a new
205
+ `console.print`), and Minor D changes only the **hook template string** — neither touches
206
+ a docstring or an option. **No `docs/cli.md` regeneration is required.** The developer
207
+ must confirm by diffing: `bash scripts/generate_cli_docs.sh` then `git diff docs/cli.md`
208
+ must show **no change**. (If it does, a docstring was inadvertently touched — investigate.)
209
+
210
+ ## Implementation Steps
211
+
212
+ ### Phase 1: Bug A — client-side dialect resolution before routing
213
+ **Step 1.1**: In `reindex.py`, move `if dialect == "auto": dialect = get_dialect(path)`
214
+ to immediately after `path = path.resolve()` (after line 78, before the route call at
215
+ line 86). Remove the now-dead resolution at lines 98-99.
216
+ - Files: [`reindex.py`](src/sqlcg/cli/commands/reindex.py)
217
+ - Acceptance: `_try_route_reindex_via_server` receives a resolved dialect; grep shows
218
+ exactly one `if dialect == "auto"` in the file, positioned before line ~86.
219
+
220
+ **Step 1.2**: In `index.py`, move `if dialect == "auto": dialect = get_dialect(path)`
221
+ to immediately after `path = path.resolve()` (after line 110, before the route call at
222
+ line 113). Remove the now-dead resolution at lines 155-156.
223
+ - Files: [`index.py`](src/sqlcg/cli/commands/index.py)
224
+ - Acceptance: `_try_route_index_via_server` receives a resolved dialect; grep shows
225
+ exactly one `if dialect == "auto"` in the file, positioned before line ~113.
226
+
227
+ ### Phase 2: Bug B — escalation uses the init path
228
+ **Step 2.1**: Add `_init_db_path: str | None = None` module global (tools.py ~line 114);
229
+ set it in `init_backend` (add to `global` at 150; assign after computing `path` at 151);
230
+ reset to `None` in `shutdown_backend` (add to `global` at 187).
231
+ - Files: [`tools.py`](src/sqlcg/server/tools.py)
232
+ - Acceptance: `init_backend(p)` then reading the module global returns `p`;
233
+ `shutdown_backend()` clears it.
234
+
235
+ **Step 2.2**: Add `_escalation_db_path() -> str` returning `_init_db_path or str(get_db_path())`.
236
+ Replace `db_path_str = str(get_db_path())` at line 579 with `db_path_str = _escalation_db_path()`.
237
+ - Files: [`tools.py`](src/sqlcg/server/tools.py)
238
+ - Acceptance: grep shows `_escalation_db_path` defined once and called at exactly one
239
+ site (`index_repo`); no behavioural change when `init_backend` was never called
240
+ (falls back to `get_db_path()`).
241
+
242
+ ### Phase 3: Bug C + Minor D — hook upgrade + reworded message
243
+ **Step 3.1 (Minor D)**: Reword the fallback message in all three template occurrences
244
+ to `sqlcg: graph not updated (reindex failed) -- run 'sqlcg mcp status'`.
245
+ - Files: [`git.py`](src/sqlcg/cli/commands/git.py) (lines 36-37, 53, 56)
246
+ - Acceptance: no occurrence of `server busy/locked` remains in `git.py`.
247
+
248
+ **Step 3.2 (Bug C)**: In `_install_single_hook`, replace the silent-skip branch (lines
249
+ 103-105) with: if sentinel present and `existing_content == script` → silent return; if
250
+ sentinel present and content differs → `write_text(script)` + `chmod(0o755)` + print
251
+ `Upgraded git hook: .git/hooks/<filename>`. Foreign-hook branch unchanged.
252
+ - Files: [`git.py`](src/sqlcg/cli/commands/git.py)
253
+ - Acceptance: identical rendered content → no output, file unchanged; sentinel-present
254
+ stale content → file overwritten with current `script`, "Upgraded git hook:" printed;
255
+ no-sentinel file → unchanged warning path.
256
+
257
+ ### Phase 4: Tests + verification
258
+ **Step 4.1**: Bug A regression — assert the WriterRequest never carries `"auto"`.
259
+ **Step 4.2**: Bug B regression — `init_backend(tmp)` + escalating `index_repo` must not
260
+ touch the default path.
261
+ **Step 4.3**: Bug C/D tests — upgrade overwrites stale hook + prints; idempotent skip;
262
+ foreign warning unchanged; new message present.
263
+ **Step 4.4**: In `test_git_hooks_notify.py`, add a new negative assertion that
264
+ `"server busy/locked"` does NOT appear in any hook template. No existing guard
265
+ asserts the old wording (current tests cover `--notify`, `>&2`, and `|| true`
266
+ absence only), so nothing needs updating — only the new negative assertion is added.
267
+ Also add a positive assertion that all templates contain `"(reindex failed)"`.
268
+ **Step 4.5**: Run perf invariant guards + full suite + ruff + pyright; confirm
269
+ `git diff --stat` excludes `base.py` and indexer hot paths; confirm
270
+ `generate_cli_docs.sh` produces no `docs/cli.md` diff.
271
+
272
+ ## Test Strategy
273
+
274
+ ### Unit tests
275
+ - **Bug A** (`tests/integration/`, new file `test_dialect_auto_resolution.py` — these
276
+ tests require a live Unix-socket thread to capture the on-wire payload and belong in
277
+ `tests/integration/`, not `tests/unit/`): call
278
+ `reindex_cmd(..., dialect="auto")` and `index_cmd(..., dialect="auto")` with a live
279
+ fake socket server (reuse the in-process accept pattern from
280
+ [`tests/integration/test_reindex_via_server.py`](tests/integration/test_reindex_via_server.py)
281
+ lines 114-255 — a thread that accepts, reads the framed payload, decodes JSON). Assert
282
+ `json.loads(payload)["dialect"] != "auto"` and equals `get_dialect(repo_root)` (use a
283
+ fixture repo with a `.sqlcg.toml` declaring e.g. `dialect = "postgres"` so the assertion
284
+ is non-vacuous — `assert payload["dialect"] == "postgres"`). Add the symmetric index
285
+ case. **Observable assertion: the exact dialect string on the wire.**
286
+ - **Bug B** (`tests/unit/` or `tests/e2e/test_mcp_tools.py`): `init_backend(str(tmp_path /
287
+ "test.db"))`, then call `index_repo(fixture)`. Assert the default path is never touched.
288
+ Two complementary observable checks (use both):
289
+ 1. `monkeypatch` `sqlcg.server.tools.get_db_path` to a sentinel that **raises** if called
290
+ during escalation (proves `_escalation_db_path` used `_init_db_path`, not
291
+ `get_db_path()`). Note `init_backend` itself calls `get_db_path` only on the
292
+ `db_path is None` branch — since we pass a path, it must not be called at all in this
293
+ test → the raising sentinel is safe across the whole call.
294
+ 2. Record the default `~/.sqlcg/graph.db` mtime (or its non-existence) before and after;
295
+ assert unchanged / still-absent. Guards the live-observed symptom directly.
296
+ **Teardown requirement**: call `shutdown_backend()` after `index_repo` returns (e.g.
297
+ in a `finally` block or pytest fixture teardown) so the `_init_db_path` module global
298
+ is reset between tests. Without this, a later `init_backend(None)` call in a subsequent
299
+ test inherits the stale path and produces a misleading pass.
300
+ - **Bug C** (`tests/unit/test_git_hooks.py` — existing, extend it): three cases —
301
+ (a) pre-write a stale sqlcg hook (sentinel + old "server busy/locked" text), run
302
+ `install-hooks`, assert file now equals the current rendered template AND stdout contains
303
+ `Upgraded git hook:`; (b) write the current rendered hook, run again, assert no output and
304
+ byte-identical file (idempotency); (c) write a foreign hook (no sentinel), assert the
305
+ warning path fires and the file is unchanged. **Observable: file content + captured stdout.**
306
+ - **Minor D** (`tests/unit/test_git_hooks_notify.py` — existing): add a new
307
+ negative assertion that no hook template contains `"server busy/locked"`. No
308
+ existing guard asserts the old wording (current tests only check `--notify`, `>&2`,
309
+ and absence of `|| true`), so nothing needs updating — only two assertions are added:
310
+ one asserting all templates contain `"(reindex failed)"` and one asserting none
311
+ contain `"server busy/locked"`.
312
+
313
+ ### Integration / e2e
314
+ - The existing [`tests/integration/test_reindex_via_server.py`](tests/integration/test_reindex_via_server.py)
315
+ and `tests/e2e/test_mcp_tools.py` must stay green; `test_mcp_tools.py` is the file that
316
+ surfaced Bug B live, so its green state after the fix is itself a guard.
317
+
318
+ ### Invariant guards (must stay green, untouched)
319
+ [`test_perf_scaling_guard.py`](tests/unit/test_perf_scaling_guard.py),
320
+ [`test_T09_01_qualify_once.py`](tests/unit/test_T09_01_qualify_once.py),
321
+ [`test_bulk_upsert_invariant.py`](tests/unit/test_bulk_upsert_invariant.py),
322
+ [`test_upsert_batch_invariant.py`](tests/unit/test_upsert_batch_invariant.py).
323
+
324
+ ## Acceptance Criteria
325
+ - [ ] **A**: With a live server, `sqlcg reindex --dialect auto <repo>` sends a WriterRequest
326
+ whose `dialect` is the resolved dialect (never `"auto"`); same for `sqlcg index
327
+ --dialect auto`. Regression test asserts the on-wire payload.
328
+ - [ ] **A**: Each of `reindex.py` / `index.py` has exactly one `if dialect == "auto"`
329
+ block, positioned before the socket-route call.
330
+ - [ ] **B**: `init_backend(tmp)` followed by an escalating `index_repo` never opens,
331
+ writes, or locks the default `~/.sqlcg/graph.db`. Regression test proves it via a
332
+ raising-sentinel monkeypatch AND an mtime/absence check.
333
+ - [ ] **B**: `_escalation_db_path` has exactly one grep-confirmed call site.
334
+ - [ ] **B**: every `get_db_path()` call in `tools.py`/`writer.py`/`server.py` is either
335
+ the captured init source (tools.py:151), a display value, or threaded as a parameter
336
+ — the audit table holds.
337
+ - [ ] **C**: `install-hooks` over a stale sqlcg-owned hook overwrites it and prints
338
+ `Upgraded git hook: .git/hooks/<name>`; over an identical hook it is silent and the
339
+ file is byte-unchanged; over a foreign (no-sentinel) hook it prints the unchanged
340
+ warning and leaves the file untouched.
341
+ - [ ] **C**: the upgrade comparison is against the fully **rendered** template (binary
342
+ path embedded), not the raw template.
343
+ - [ ] **D**: no hook template prints "server busy/locked"; all three print
344
+ `(reindex failed)`. `test_git_hooks_notify.py` updated accordingly.
345
+ - [ ] Version stays 1.3.0 (no bump to `pyproject.toml` / `__init__.py` / `uv.lock`).
346
+ - [ ] `git diff --stat` shows no change to `base.py` or to `indexer.py`'s hot-path methods.
347
+ - [ ] `bash scripts/generate_cli_docs.sh` yields no `docs/cli.md` diff.
348
+ - [ ] Full suite + ruff + pyright clean; the four invariant guards green.
349
+
350
+ ## Risks and Mitigations
351
+ | Risk | Mitigation |
352
+ |---|---|
353
+ | Bug B fix mistaken for full §20.2 remediation | Plan explicitly scopes Bug B to "use init path + audit"; §20.2 lock/finally/stub work is out of scope (resolved decision D3). Acceptance criteria do not claim concurrency safety. |
354
+ | Bug C upgrade compares raw template, not rendered → false "identical" or false "upgrade" | Step 3.2 + acceptance criterion mandate comparing the rendered `script`; binary path is embedded. |
355
+ | Minor D text change unexpectedly fails existing hook-content tests | Step 4.4 updates `test_git_hooks_notify.py` deliberately; this is an intended text change, not a regression. |
356
+ | Bug A: removing the second `auto` resolution leaves a stale path on a code path that bypasses routing | The route call returns early when handled; the direct path now relies on the hoisted resolution. Both reindex.py and index.py keep exactly one resolution that runs unconditionally before routing — verified the route call does not consume/clear `dialect`. |
357
+ | Hoisting `get_dialect` in `reindex.py` uses the function-local import | `get_dialect` is already in the line-73 import tuple; the hoisted call sits after that import. Confirm the import precedes the new call site. |
358
+
359
+ ## Rollout / Rollback
360
+ - All four fixes fold into PR #51 (no separate PR, no separate version). Per MEMORY
361
+ "Separate PRs, don't fold": this PR has **no reviews yet**, so folding follow-up fixes
362
+ for the same unreleased feature into it is appropriate (the rule targets folding new
363
+ work into an *already-reviewed* PR).
364
+ - Rollback: revert the four commits; the v1.3.0 single-writer feature is unaffected
365
+ (these are additive corrections).
366
+
367
+ ### Resolved Decisions (2026-06-04 — shepherd, user-approved)
368
+ All three pre-implementation questions are resolved. None blocks implementation.
369
+
370
+ - **D1 — Version stays 1.3.0, no bump.** These fixes fold into the unreleased v1.3.0
371
+ PR #51 (no reviews yet). Do not touch `pyproject.toml` / `__init__.py` / `uv.lock`.
372
+ See Version note above.
373
+ - **D2 — Bug C overwrite prints a line (not silent).** On the sentinel-present /
374
+ content-differs upgrade path, print `Upgraded git hook: .git/hooks/<name>`. Silent
375
+ upgrades of executable hooks are worse than one line of output. True idempotency
376
+ (identical rendered content) remains a silent skip.
377
+ - **D3 — §20.2 escalation-architecture remediation stays OUT of scope.** This plan
378
+ covers bugs A-D only. The deeper `ARCHITECTURE_REVIEW.md` §20.2 work — (a) escalation
379
+ outside `backend_lock`, (b) RW-lock leak on the `index_repo` failure path (no
380
+ `finally`), (c) the dead `_set_backend_lock` / `_backend_lock` stub — remains a tracked
381
+ separate v1.3.x follow-up. The §20.2 cross-reference is retained in Non-Goals and
382
+ Design — Bug B so the linkage is not lost.
383
+
384
+ ## Plan Compliance — 2026-06-04 — PASS
385
+
386
+ Compliance check run by architect-planner against commits `8e1e09b` (Bug A),
387
+ `45643c8` (Bug B), `29d21ee` (Bug C + Minor D), with test setup in `1d4db7a`.
388
+ Diff base `daa7389..HEAD`, excluding plan-docs commits (`5dce5d4`, `05ac62d`,
389
+ `7936818`).
390
+
391
+ **Verdict: PASS — all steps implemented as planned; no deviations.**
392
+
393
+ ### Step-by-step verification
394
+
395
+ - **Step 1.1 / 1.2 (Bug A)** — PASS. `if dialect == "auto": dialect = get_dialect(path)`
396
+ hoisted above the route call in both files; the duplicate lower resolution removed.
397
+ Grep confirms exactly one `auto` block per file, positioned before the route call
398
+ (`reindex.py:84`, `index.py:116`).
399
+ - **Step 2.1 (Bug B globals)** — PASS. `_init_db_path: str | None = None` added; set in
400
+ `init_backend` (added to `global`, assigned after `path = db_path or str(get_db_path())`);
401
+ reset to `None` in `shutdown_backend` (added to `global`).
402
+ - **Step 2.2 (Bug B accessor)** — PASS. `_escalation_db_path()` defined once, returns
403
+ `_init_db_path or str(get_db_path())`; called at exactly one site (`tools.py:607` in
404
+ `index_repo`), replacing `str(get_db_path())`. Grep-confirmed single call site.
405
+ - **Step 3.1 (Minor D)** — PASS. All three template occurrences reworded to
406
+ `(reindex failed)`; zero `server busy/locked` remain in `git.py`.
407
+ - **Step 3.2 (Bug C)** — PASS. Silent-skip branch replaced: identical rendered `script`
408
+ → silent return; sentinel-present + differing content → `write_text(script)` +
409
+ `chmod(0o755)` + `Upgraded git hook:` print. Comparison is against the fully rendered
410
+ `script` (binary path embedded), per D2. Foreign-hook warning path unchanged.
411
+ - **Steps 4.1–4.4 (tests)** — PASS. All acceptance tests assert observable output, not
412
+ weakened:
413
+ - Bug A: asserts exact on-wire `payload["dialect"]` (`postgres` / `snowflake`) — non-vacuous.
414
+ - Bug B: raising-sentinel monkeypatch AND default-DB mtime/absence check, with
415
+ `shutdown_backend()` teardown to reset the global (per plan).
416
+ - Bug C: three cases (stale→upgrade+print, identical→silent+byte-unchanged,
417
+ foreign→warning+unchanged) assert file content + captured stdout.
418
+ - Minor D: negative (`no "server busy/locked"`) + positive (`"(reindex failed)"`)
419
+ assertions added in both `test_BugC_hook_upgrade.py` and `test_git_hooks_notify.py`.
420
+ - **Step 4.5 (verification)** — PASS:
421
+ - Full suite (`--ignore=tests/benchmarks`): 1026 passed, 7 skipped, 3 xfailed, 0 failures.
422
+ - 9 acceptance tests: all green (2 Bug A + 2 Bug B + 5 Bug C/D).
423
+ - ruff: clean. pyright: 0 errors / 0 warnings.
424
+ - `bash scripts/generate_cli_docs.sh` → `docs/cli.md`: no diff.
425
+ - `git diff --stat`: `base.py` and `indexer.py` UNTOUCHED.
426
+
427
+ ### Resolved-decision verification
428
+
429
+ - **D1 (version stays 1.3.0)** — HONORED. No diff to `pyproject.toml`,
430
+ `src/sqlcg/__init__.py`, or `uv.lock`.
431
+ - **D2 (Bug C prints, not silent; idempotency stays silent)** — HONORED. Upgrade prints
432
+ `Upgraded git hook:`; identical rendered content is a silent skip.
433
+ - **D3 (§20.2 remediation out of scope)** — HONORED. Bug B fix changes only the escalation
434
+ *path*; it does not move escalation under `backend_lock`, does not add a `finally`-based
435
+ de-escalation, and does not wire or remove the dead `_set_backend_lock` /
436
+ `_backend_lock` stub (still present, untouched).
437
+
438
+ ### Scope
439
+
440
+ In-scope files only: `git.py`, `index.py`, `reindex.py`, `tools.py`, and tests
441
+ (`test_dialect_auto_resolution.py`, `test_BugB_escalation_uses_init_path.py`,
442
+ `test_BugC_hook_upgrade.py`, `test_git_hooks_notify.py`). No out-of-scope source touched.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sql-code-graph"
7
- version = "1.2.2"
7
+ version = "1.3.0"
8
8
  description = "SQL code graph analyzer and lineage tracer"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"