graphite-code 1.1.1__tar.gz → 1.2.1__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 (300) hide show
  1. {graphite_code-1.1.1 → graphite_code-1.2.1}/CHANGELOG.md +392 -0
  2. {graphite_code-1.1.1 → graphite_code-1.2.1}/PKG-INFO +2 -2
  3. {graphite_code-1.1.1 → graphite_code-1.2.1}/README.md +1 -1
  4. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/__init__.py +1 -1
  5. graphite_code-1.2.1/src/graphite/agent_hooks.py +978 -0
  6. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/agent_settings.py +20 -3
  7. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/answer_contract.py +148 -11
  8. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/channel.py +48 -2
  9. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/cli.py +29 -7
  10. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/context.py +16 -6
  11. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/extract/ast.py +300 -152
  12. graphite_code-1.2.1/src/graphite/extract/pyscope.py +398 -0
  13. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/health.py +12 -5
  14. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/ingest.py +16 -3
  15. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/init.py +14 -8
  16. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/mcp_server.py +22 -3
  17. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/natural_query.py +12 -8
  18. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/query.py +107 -29
  19. graphite_code-1.2.1/tests/test_agent_hooks.py +1717 -0
  20. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_agent_settings.py +76 -0
  21. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_answer_contract.py +156 -0
  22. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_call_graph.py +5 -2
  23. graphite_code-1.2.1/tests/test_case_ambiguous_answer.py +316 -0
  24. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_core.py +34 -0
  25. graphite_code-1.2.1/tests/test_channel_kind.py +249 -0
  26. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_dispatch_evidence.py +43 -10
  27. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_documentation.py +36 -0
  28. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_hardening.py +17 -0
  29. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_init.py +4 -4
  30. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_init_activation_doctrine.py +38 -2
  31. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_method_dispatch_scope.py +45 -9
  32. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_natural_query.py +32 -3
  33. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_published_schemas.py +27 -1
  34. graphite_code-1.2.1/tests/test_pyscope.py +307 -0
  35. graphite_code-1.2.1/tests/test_pyscopeoracle.py +261 -0
  36. graphite_code-1.2.1/tests/test_python_import_bindings.py +80 -0
  37. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_python_resolver.py +8 -8
  38. graphite_code-1.2.1/tests/test_python_scope_identity.py +539 -0
  39. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_query_plan.py +133 -0
  40. graphite_code-1.1.1/src/graphite/agent_hooks.py +0 -577
  41. graphite_code-1.1.1/tests/test_agent_hooks.py +0 -699
  42. {graphite_code-1.1.1 → graphite_code-1.2.1}/.gitignore +0 -0
  43. {graphite_code-1.1.1 → graphite_code-1.2.1}/ARCHITECTURE.md +0 -0
  44. {graphite_code-1.1.1 → graphite_code-1.2.1}/CONTRIBUTING.md +0 -0
  45. {graphite_code-1.1.1 → graphite_code-1.2.1}/LICENSE +0 -0
  46. {graphite_code-1.1.1 → graphite_code-1.2.1}/RELEASING.md +0 -0
  47. {graphite_code-1.1.1 → graphite_code-1.2.1}/pyproject.toml +0 -0
  48. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/__main__.py +0 -0
  49. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/_cleanup_worker.py +0 -0
  50. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/_win32_ctypes.py +0 -0
  51. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/activation.py +0 -0
  52. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/analyze.py +0 -0
  53. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/bootstrap.py +0 -0
  54. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/buildlock.py +0 -0
  55. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/cache.py +0 -0
  56. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/cluster.py +0 -0
  57. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/config.py +0 -0
  58. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/daemon.py +0 -0
  59. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/daemon_health.py +0 -0
  60. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/daemon_launch.py +0 -0
  61. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/debt.py +0 -0
  62. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/dependency_install.py +0 -0
  63. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/detach.py +0 -0
  64. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/doctor.py +0 -0
  65. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/doctor_probes.py +0 -0
  66. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/engine_identity.py +0 -0
  67. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/export/__init__.py +0 -0
  68. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/export/html.py +0 -0
  69. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/export/json.py +0 -0
  70. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/export/md.py +0 -0
  71. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/extract/__init__.py +0 -0
  72. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/freshness.py +0 -0
  73. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/git.py +0 -0
  74. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/graph.py +0 -0
  75. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/graph_io.py +0 -0
  76. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/hook_entry.py +0 -0
  77. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/hookinstall.py +0 -0
  78. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/hookshim.py +0 -0
  79. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/incident_ledger.py +0 -0
  80. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/io.py +0 -0
  81. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/launchd_agent.py +0 -0
  82. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/listing.py +0 -0
  83. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/llm.py +0 -0
  84. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/llm_probe.py +0 -0
  85. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/mcp.py +0 -0
  86. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/overlays.py +0 -0
  87. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/probe_process.py +0 -0
  88. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/probe_workspace.py +0 -0
  89. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/process_contracts.py +0 -0
  90. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/programs.py +0 -0
  91. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/provider_observer.py +0 -0
  92. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/py.typed +0 -0
  93. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/query_plan.py +0 -0
  94. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/replacement_audit.py +0 -0
  95. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/resolve.py +0 -0
  96. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/review.py +0 -0
  97. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/__init__.py +0 -0
  98. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/approval.py +0 -0
  99. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/classifier.py +0 -0
  100. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/claude_executor.py +0 -0
  101. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/claude_probe.py +0 -0
  102. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/cli_identity.py +0 -0
  103. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/codex_executor.py +0 -0
  104. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/codex_probe.py +0 -0
  105. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/context_builder.py +0 -0
  106. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/contracts.py +0 -0
  107. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/diff_policy.py +0 -0
  108. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/edit_apply.py +0 -0
  109. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/effort.py +0 -0
  110. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/lifecycle.py +0 -0
  111. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/lifecycle_operator.py +0 -0
  112. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/lifecycle_service.py +0 -0
  113. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/lifecycle_storage.py +0 -0
  114. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/ollama_executor.py +0 -0
  115. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/ollama_probe.py +0 -0
  116. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/openrouter_executor.py +0 -0
  117. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/openrouter_probe.py +0 -0
  118. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/policy.py +0 -0
  119. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/probe_runner.py +0 -0
  120. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/process_runner.py +0 -0
  121. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/profiles.py +0 -0
  122. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/prompt.py +0 -0
  123. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/registry.py +0 -0
  124. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/route_pool.py +0 -0
  125. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/route_pool_execution.py +0 -0
  126. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/schema_validation.py +0 -0
  127. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/service.py +0 -0
  128. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/settings.py +0 -0
  129. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/shadow.py +0 -0
  130. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/storage.py +0 -0
  131. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/telemetry.py +0 -0
  132. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/worktree.py +0 -0
  133. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/zai_edit.py +0 -0
  134. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/zai_executor.py +0 -0
  135. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/routing/zai_probe.py +0 -0
  136. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/savings.py +0 -0
  137. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/systemd_unit.py +0 -0
  138. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/ts_bridge.py +0 -0
  139. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/ts_resolver.mjs +0 -0
  140. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/typescript_activation.py +0 -0
  141. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/usage_ledger.py +0 -0
  142. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/validation.py +0 -0
  143. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/watch.py +0 -0
  144. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/windows_job.py +0 -0
  145. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/windows_startup.py +0 -0
  146. {graphite_code-1.1.1 → graphite_code-1.2.1}/src/graphite/windows_task.py +0 -0
  147. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/conftest.py +0 -0
  148. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/data/sqloracle/blindspots.py.txt +0 -0
  149. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/data/sqloracle/round69.py.txt +0 -0
  150. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/fake_clis/fake_cli.py +0 -0
  151. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/fixtures/provider_lifecycle_schema_v1.sql +0 -0
  152. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/fixtures/routing_schema_v2_ca77600.sql +0 -0
  153. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/fixtures/routing_schema_v3_94eb333.sql +0 -0
  154. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/fixtures/routing_schema_v4_lifecycle_migration.sql +0 -0
  155. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test__win32_ctypes.py +0 -0
  156. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_activation.py +0 -0
  157. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_activation_backstop.py +0 -0
  158. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_agent_hook_activation.py +0 -0
  159. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_arrow_and_constructor_binding.py +0 -0
  160. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_atomic_write.py +0 -0
  161. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_bootstrap.py +0 -0
  162. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_build_detach.py +0 -0
  163. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_buildlock.py +0 -0
  164. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cache_engine_identity.py +0 -0
  165. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cache_entry_pruning.py +0 -0
  166. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cache_file_set.py +0 -0
  167. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cache_pruning.py +0 -0
  168. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_attribution.py +0 -0
  169. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_cli.py +0 -0
  170. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_lock.py +0 -0
  171. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_log_integrity.py +0 -0
  172. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_mcp.py +0 -0
  173. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_register.py +0 -0
  174. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_report.py +0 -0
  175. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_channel_status.py +0 -0
  176. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_class_field_arrow_binding.py +0 -0
  177. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cli_channel.py +0 -0
  178. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cli_channel_attribution.py +0 -0
  179. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cli_reference.py +0 -0
  180. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cli_version.py +0 -0
  181. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cluster.py +0 -0
  182. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_compare_dist.py +0 -0
  183. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_configuration_reference.py +0 -0
  184. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_context.py +0 -0
  185. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_context_builder.py +0 -0
  186. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cwd_relative_config.py +0 -0
  187. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_cycle_search.py +0 -0
  188. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon.py +0 -0
  189. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon_activation.py +0 -0
  190. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon_build_lock.py +0 -0
  191. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon_child_lock.py +0 -0
  192. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon_engine_staleness.py +0 -0
  193. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon_health.py +0 -0
  194. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon_health_activation.py +0 -0
  195. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon_launch.py +0 -0
  196. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_daemon_service_cli.py +0 -0
  197. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_debt.py +0 -0
  198. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_dependency_install.py +0 -0
  199. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_destructured_binding.py +0 -0
  200. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_determinism.py +0 -0
  201. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_doctor.py +0 -0
  202. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_doctor_agent_hooks.py +0 -0
  203. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_doctor_hooks.py +0 -0
  204. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_doctor_managed_docs.py +0 -0
  205. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_doctor_probes.py +0 -0
  206. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_edit_apply.py +0 -0
  207. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_engine_identity.py +0 -0
  208. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_exit_codes_reference.py +0 -0
  209. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_external_calls.py +0 -0
  210. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_git_security.py +0 -0
  211. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_go_rust.py +0 -0
  212. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_graph_io.py +0 -0
  213. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_graph_provider_isolation.py +0 -0
  214. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_health.py +0 -0
  215. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_hook_entry.py +0 -0
  216. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_hook_template.py +0 -0
  217. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_hookinstall.py +0 -0
  218. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_hookshim.py +0 -0
  219. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_html_security.py +0 -0
  220. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_incident_ledger.py +0 -0
  221. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_init_hooks.py +0 -0
  222. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_init_validation_exit.py +0 -0
  223. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_launchd_agent.py +0 -0
  224. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_lifecycle_operator.py +0 -0
  225. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_listing.py +0 -0
  226. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_listing_surfaces.py +0 -0
  227. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_llm.py +0 -0
  228. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_llm_probe.py +0 -0
  229. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_mcp.py +0 -0
  230. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_mcp_server.py +0 -0
  231. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_mcp_server_channel_attribution.py +0 -0
  232. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_monorepo.py +0 -0
  233. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_mutation_tests.py +0 -0
  234. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_node_identity.py +0 -0
  235. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_ollama_executor.py +0 -0
  236. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_overlays.py +0 -0
  237. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_path_leak_fixture.py +0 -0
  238. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_probe_diagnostics.py +0 -0
  239. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_probe_process.py +0 -0
  240. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_probe_workspace.py +0 -0
  241. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_process_contracts.py +0 -0
  242. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_programs.py +0 -0
  243. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_claude_probe.py +0 -0
  244. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_codex_probe.py +0 -0
  245. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_lifecycle.py +0 -0
  246. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_lifecycle_service.py +0 -0
  247. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_lifecycle_storage.py +0 -0
  248. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_observer.py +0 -0
  249. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_ollama_probe.py +0 -0
  250. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_openrouter_probe.py +0 -0
  251. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_provider_probe_runner.py +0 -0
  252. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_public_surface.py +0 -0
  253. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_reliability.py +0 -0
  254. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_replacement_audit.py +0 -0
  255. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_resolve.py +0 -0
  256. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_review.py +0 -0
  257. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_route_pool.py +0 -0
  258. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_route_pool_execution.py +0 -0
  259. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_approval.py +0 -0
  260. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_benchmark.py +0 -0
  261. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_classifier.py +0 -0
  262. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_claude_executor.py +0 -0
  263. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_cli.py +0 -0
  264. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_cli_contracts.py +0 -0
  265. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_cli_recovery.py +0 -0
  266. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_codex_executor.py +0 -0
  267. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_context.py +0 -0
  268. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_contracts.py +0 -0
  269. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_diff_policy.py +0 -0
  270. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_executor.py +0 -0
  271. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_openrouter_executor.py +0 -0
  272. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_policy.py +0 -0
  273. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_process_runner.py +0 -0
  274. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_profiles.py +0 -0
  275. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_registry.py +0 -0
  276. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_schema_validation.py +0 -0
  277. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_security.py +0 -0
  278. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_service.py +0 -0
  279. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_shadow.py +0 -0
  280. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_storage.py +0 -0
  281. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_telemetry.py +0 -0
  282. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_worktree.py +0 -0
  283. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_zai_executor.py +0 -0
  284. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_routing_zai_probe.py +0 -0
  285. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_savings.py +0 -0
  286. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_search.py +0 -0
  287. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_smoke.py +0 -0
  288. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_sqloracle.py +0 -0
  289. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_synthetic_repo.py +0 -0
  290. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_systemd_unit.py +0 -0
  291. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_ts_bridge.py +0 -0
  292. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_typescript_activation.py +0 -0
  293. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_typescript_resolver.py +0 -0
  294. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_usage_ledger.py +0 -0
  295. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_verify_artifact.py +0 -0
  296. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_verify_published_release.py +0 -0
  297. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_watch.py +0 -0
  298. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_windows_startup.py +0 -0
  299. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_windows_task.py +0 -0
  300. {graphite_code-1.1.1 → graphite_code-1.2.1}/tests/test_zai_edit.py +0 -0
@@ -15,6 +15,398 @@ machine-checkable identity; the version is for humans.
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [1.2.1] — 2026-10-11
19
+
20
+ A patch release. It carries one fix, to the agent hooks (#80). No stable
21
+ surface in `docs/compatibility.md` changes: nothing under `docs/reference/` or
22
+ `docs/schemas/` moves, `graphite agent-hook` still always exits 0, and the
23
+ wording of a hook's message is not a stable surface. The graph itself does not
24
+ change: on this repository, measured against the deployed 1.2.0 engine,
25
+ 8581 of 8581 node ids survive, 23612 of 23612 edge triples are unchanged,
26
+ every relation count is equal, and `analysis` is identical. Only the engine
27
+ fingerprint moves, because graphite's own bytes did.
28
+
29
+ ### Fixed
30
+
31
+ **The agent hooks find the repository from a subfolder (#80).** A hook payload
32
+ carries the session's directory, and in Claude Code that follows a `cd` in the
33
+ shell. All three hooks took it for the repository root. One directory below the
34
+ root, with the graph fresh at the root:
35
+
36
+ - `session-start` said `Graph status: missing`, the status that licenses a full
37
+ fallback from graph-first. The remedy it printed, `python -m graphite build .`,
38
+ is refused there ("nested Git roots are unsupported").
39
+ - `pre-tool-use` wrote nothing: no refusal in strict mode and no reminder. A
40
+ `cd` into any subfolder switched the gate off.
41
+ - `stop` created a `.graphite/local/` in the subfolder for its cursor and looked
42
+ for the turn's usage there.
43
+ - The subfolder was registered as an open project. The daemon cannot build
44
+ one, so it logged `project_snapshot_failed` for it at every discovery cycle,
45
+ and the repository's own marker was no longer renewed.
46
+
47
+ The repository is now the nearest directory upward that holds `.git`, the test
48
+ the build already uses to refuse a nested path (`ingest.enclosing_git_root`).
49
+ It is taken only where graphite is set up: a `graph-out/graph.json`, or the
50
+ `GRAPHITE.md` that `init` commits. In a subfolder of any other checkout, and in
51
+ a directory outside every checkout, the hooks read the session's directory as
52
+ they did. A repository inside the repository, with its own `.git`, stays its
53
+ own root.
54
+
55
+ One reading changes inside a checkout that is set up. A folder there that has
56
+ its own `GRAPHITE.md` or `graph-out/` but no `.git` was read as a project of
57
+ its own: `Graph status: missing`, or `unknown` over a leftover graph, with a
58
+ `build .` that is refused there. It is now read as part of the checkout.
59
+
60
+ Below the root:
61
+
62
+ - The graph, the freshness check, the usage ledger and the activation marker
63
+ are the repository's.
64
+ - A search's relative paths, and a search that names no path, belong to the
65
+ directory the shell is in. `grep <symbol> core.py` beside the file is one
66
+ file. A search with no path in a `docs/` that holds no code is text. `..` and
67
+ `../src` are judged as the directories they are.
68
+ - Once the command line has run a `cd` the hook does not follow, or any command
69
+ it does not model, the shell may be anywhere in the repository. A search then
70
+ keeps an absolute path, and a relative one that names a file in the session's
71
+ directory. With a directory, or no path, it is judged as a search of the
72
+ whole repository: from a `docs/` with no code, `cd ../src ; grep -rn <symbol> .`
73
+ is refused. At the root this case resolves paths against the root, as before.
74
+ - Every status, refusal and reminder ends by naming the root: "This session is
75
+ below the repository root: run graphite from `<root>`." The CLI still looks
76
+ for the graph in its own working directory (#81), so the commands those
77
+ messages print work from the root only.
78
+
79
+ The hooks no longer register a directory below a checkout's root with the
80
+ daemon, whether or not graphite is set up in that checkout.
81
+
82
+ Nothing changes for a session at the repository root. Measured on the 1.2.0
83
+ wheel and on this change: 32 strict-hook arms from the #72 work (25 refused, 7
84
+ allowed) and 12 hook arms on a checkout with `.git` and `GRAPHITE.md` read the
85
+ same on both.
86
+
87
+ ## [1.2.0] — 2026-10-10
88
+
89
+ A minor release. Query and search results, channel rounds and Python
90
+ definition nodes gain fields, which are additions under
91
+ `docs/compatibility.md`. No field is removed or renamed, and nothing under
92
+ `docs/reference/` changes. Python node ids change, which that document allows
93
+ when the CHANGELOG says so: **do not persist node ids across this upgrade.**
94
+
95
+ The graph changes in this release. On this repository, measured against the
96
+ deployed 1.1.1 engine:
97
+
98
+ - 4,776 of 5,716 definition ids keep both their id and their definition. 937
99
+ are gone and 1,322 are new. No id names a different definition. 3 survive
100
+ as strings and now name the placeholder for a call made through a local
101
+ name (`poll = getattr(process, "poll", None)`, then `poll()`), which 1.1.1
102
+ bound to the method of that name;
103
+ - all 408 file ids survive, and 2,046 of 2,049 placeholder ids;
104
+ - nodes go from 8,173 to 8,555 and edges from 23,073 to 23,363. `calls` goes
105
+ from 14,758 to 14,994 and `contains` from 6,042 to 6,096; `imports` (2,310)
106
+ and `inherits` (118) are unchanged. 18,983 of 23,228 edge triples are
107
+ unchanged;
108
+ - `analysis.cycles` goes from 20 to 19, and the community count from 170 to
109
+ 171;
110
+ - the Python `calls` ratio goes from 0.978 to 0.975.
111
+
112
+ Two builds of this tree are byte-identical (`graph.json`, `GRAPH_REPORT.md`,
113
+ `graph.html`). The parser version plays no part in these figures: each engine
114
+ gives the same graph under tree-sitter 0.25.2 and 0.26.0.
115
+
116
+ The entries below quote figures measured when each change was made, on earlier
117
+ commits of this tree, so they differ slightly from the ones above.
118
+
119
+ `graphite init` now writes `DOC_VERSION` 15. Re-run it in each repository to
120
+ refresh the managed files.
121
+
122
+ ### Changed
123
+
124
+ **Consumers use graphite only as a tool, and the agent channel carries only bug
125
+ reports and recommended improvements.** A consumer has the tool, never the
126
+ agent behind it, so anything that needs an agent to answer cannot exist in a
127
+ real installation. The files `graphite init` writes (`DOC_VERSION` 14 -> 15:
128
+ `GRAPHITE.md`, the `CLAUDE.md` / `AGENTS.md` pointer, the Cursor rule) now
129
+ say so, and stop telling agents to ask other agents. Version 14 told them "If
130
+ you need a fact from another repo, ask for it" and to "ask the owning agent to
131
+ look". Facts about a tool come from the tool: its CLI, its MCP tools and its
132
+ documentation. The `graphite_channel_post` description states the same rule
133
+ when an agent posts. `graphite channel --help` states it for an agent that
134
+ has the CLI but not the MCP tools. Re-run `graphite init` to pick it up; the
135
+ sentence "The channel carries only bug reports and recommended improvements."
136
+ marks a refreshed file.
137
+
138
+ ### Added
139
+
140
+ **Channel rounds carry a `kind`: `bug_report` or `improvement`.**
141
+ `graphite_channel_post` takes it, and the round records it. These surfaces
142
+ show it:
143
+ - `graphite_channel_list` and `graphite_channel_read`;
144
+ - `graphite channel list` and `graphite channel report`, as text and as
145
+ `--json`;
146
+ - `graphite channel show`.
147
+
148
+ The text views tag a classified round as `[bug_report]` or `[improvement]`
149
+ before its title. JSON carries `null` for a round posted without one, which is
150
+ every round before this release. Any other value is refused with
151
+ `invalid_kind` before a round number is spent.
152
+
153
+ **Two Python call-binding blind spots are now declared on every Python
154
+ `callers` / `calls` answer**, the day they were confirmed (#70, #71):
155
+
156
+ - `python-nested-name-shared-id`: nested functions and methods share one node
157
+ per file and short name, so a call to or from one may be attributed to
158
+ another definition of the same name;
159
+ - `python-bare-call-ignores-scope`: a bare call through a parameter or other
160
+ local binding, or inside a method body, may be bound to a same-named module
161
+ function or sibling method.
162
+
163
+ Both produce wrong callers at `decision_grade`. Measured on graphite's own
164
+ code with the real extractor against CPython `symtable`: 118 of 12,082
165
+ bare-name calls bound to the wrong definition, and 1,345 call sites credited
166
+ to the wrong caller; on Django 5.2.7, 76 and 6,614. They are scope disclosures,
167
+ present on empty and non-empty answers alike, because the wrong binding is
168
+ made during extraction and no single answer can tell whether it is affected.
169
+ Both are retired in this same release by the fix under "Fixed".
170
+
171
+ **A new Python `calls` caveat, `python-member-call-over-dispatch-cap`, declared
172
+ the day it was measured (#73).** A member call (`obj.m()`, `self.m()`,
173
+ `super().m()`) whose method name has more than three definitions in the
174
+ caller's file and the files it imports gets no edge. So a `callers` or
175
+ `impact` result on a method may be missing real callers. It is a scope
176
+ disclosure, present on every Python `calls` answer; the measurement is under
177
+ "Fixed".
178
+
179
+ **Query by qualified name.** A dotted Python qualified name (`Worker.run`,
180
+ `test_one.fake_build`) now selects that definition in `query`, `context` and
181
+ `impact`, as match type `qualname`, tried after `exact-id` and before `name`.
182
+ Among same-named definitions at equal path depth, a bare name picks the
183
+ module-level one, then a method, then a nested definition. Two published
184
+ schemas gain an optional field, an additive change:
185
+ - `search-result` rows carry `qualname` when it differs from `name`;
186
+ - `query-result` `resolution[]` items carry `alternates_total`, the number of
187
+ alternates. `alternates` still lists at most three (four for `path-suffix`).
188
+
189
+ ### Deprecated
190
+
191
+ **Posting a channel round without `kind`.** It still lands, with a
192
+ `kind_missing` warning in the result naming the replacement, and becomes an
193
+ error in the next major release (`docs/compatibility.md`: the channel
194
+ protocol is a stable surface).
195
+
196
+ ### Fixed
197
+
198
+ **Python methods and nested definitions get their own node, and bare-name
199
+ calls bind the way Python resolves them (#70, #71).**
200
+
201
+ - Every `def` and `class` that is not at module scope now has a scope-qualified
202
+ id, built from its qualified name (`Worker.run`, `test_one.fake_build`), so
203
+ same-named methods and nested helpers stop sharing one node.
204
+ - Module-scope ids are unchanged.
205
+ - Every Python definition node carries `qualname`.
206
+ - A bare call now resolves in the scope it is made in: parameters,
207
+ assignments, loop and `with` targets, `except` and `match` captures,
208
+ comprehension targets, function-local imports, `global` and `nonlocal`.
209
+ Enclosing class scopes are skipped from inside a method.
210
+ - A call through a local name is kept as an unbound edge rather than bound to
211
+ a same-named definition.
212
+ - A call on a module name bound by two different imports is kept as an unbound
213
+ edge rather than guessed by method name. Examples: `import pkg` beside
214
+ `import pkg.sub`, or an in-repo module with an external fallback. 1.1.1 bound
215
+ the first shape through its file-wide alias map; neither corpus has one.
216
+ - A file's Python node ids are distinct by construction. A module-level def
217
+ spelled like a scoped id (`def worker_run_7c6b1a()`) no longer merges into
218
+ `Worker.run`; the scoped id is re-salted instead. Real names never collide.
219
+
220
+ Measured with `scripts/pyscopeoracle.py` against CPython's `symtable`:
221
+ - wrong-target bare-name calls went from 118 to 0 on graphite and from 77 to 0 on
222
+ Django 5.2.7;
223
+ - 1,075 call sites on graphite (4,411 on Django) were credited to a different caller
224
+ before this change.
225
+
226
+ The oracle grades bare-name calls only; member calls (`obj.m()`) are not
227
+ graded. Its figures differ slightly from the census quoted under "Added":
228
+ - Django's 77 is the census's 76, plus two generator-target sites the census
229
+ missed, minus one site the oracle reports as rebound and does not score;
230
+ - 1,075 (4,411) counts only call sites that produce an edge, while 1,345
231
+ (6,614) also counts calls that produce none, such as `len()`.
232
+
233
+ Python node ids changed:
234
+ - 4,505 of 5,420 definition ids survive with the same meaning on graphite;
235
+ - 8,959 of 34,609 survive on Django (the whole sdist, tests included);
236
+ - id strings that now name a different definition: 0 on graphite and 6 on
237
+ Django. Each of the 6 is a module-level function whose id a same-named method
238
+ had taken.
239
+
240
+ Do not persist node ids across this upgrade. graphite's own node-id holders
241
+ were audited: the incident ledger, daemon manifest, `check` records, rollback
242
+ store, usage ledger, overlays and extraction cache. None reads a node id back
243
+ across an engine change.
244
+
245
+ The Python `calls` ratio moved from 0.977 to 0.975 on graphite (0.704 to 0.700
246
+ on Django). The change was not broken down by cause. The candidates are:
247
+ - local-value calls kept as unbound edges;
248
+ - split nodes no longer sharing deduplicated edges;
249
+ - over-cap member edges leaving the denominator.
250
+
251
+ The retired caveats `python-nested-name-shared-id` and
252
+ `python-bare-call-ignores-scope` stay in the registry, marked retired.
253
+
254
+ Member calls on a commonly reused method name now lose their edge more often,
255
+ though method dispatch itself is unchanged. A member call with more than
256
+ three candidate methods gets no edge, counting candidates in the caller's own
257
+ file and the files it imports. Same-named methods in one file used to be one
258
+ candidate; now each is its own. On Django 5.2.7:
259
+ - over-limit member-call sites rose from 2,889 to 3,817;
260
+ - dropped member-call sites rose from 7,149 to 8,518.
261
+
262
+ Many of the lost edges were wrong, such as `super().__init__()` bound to the
263
+ class's own `__init__`. Some were right but imprecise.
264
+
265
+ `graphite impact <file>` is unchanged for every file on graphite and on
266
+ Django, both impacted files and likely tests, measured over the whole
267
+ population. `impact` and `callers` on a method can lose callers: 8 of 1,803
268
+ definitions on graphite and 236 of 8,439 on Django lost at least one likely
269
+ test. This is disclosed as the caveat `python-member-call-over-dispatch-cap`.
270
+ Class-aware `self.`/`super()` dispatch is #73.
271
+
272
+ **`graphite init` no longer moves its hooks to the end of
273
+ `.claude/settings.json` when they are already current.** It used to strip and
274
+ re-append them on every run, so in a repo where another tool's hook came after
275
+ graphite's, each `init` re-run reordered the file. That showed up as a change
276
+ to a committed file although nothing had changed: Claude Code runs matching
277
+ hooks in parallel, so their order has no effect. A current entry now stays
278
+ where it is. A stale, duplicated or legacy graphite entry is still replaced,
279
+ and a missing one is still appended.
280
+
281
+ **A name typed in its exact case now picks the definition spelled that way.**
282
+ `graphite query "callers Verdict"` could answer about a function named
283
+ `verdict`, and grade that answer like any other. The whole query string was
284
+ lowercased before the matcher ranked same-named definitions, so the tie
285
+ between `Verdict` and `verdict` fell to id order. Now only the verb is folded.
286
+ Where two matches rank equally by path depth and scope, the one spelled as
287
+ typed comes first. The same holds for a dotted qualname (`Worker.run`), and
288
+ for `context` and `impact`. Matching still ignores case: `callers verdict`
289
+ still finds a class `Verdict` when nothing is spelled `verdict`.
290
+
291
+ A query plan (`query --plan-only`) and each `resolution[].input` now carry a
292
+ target as it was typed. They used to carry it lowercased.
293
+
294
+ Path depth still outranks case: a shallower `verdict` is still chosen over a
295
+ deeper `Verdict`. The next entry is what such an answer now says about itself.
296
+
297
+ **An answer about a different spelling than the one typed is no longer graded
298
+ `decision_grade` (#77, channel round 317).** When a name matched definitions in more
299
+ than one spelling and the node chosen is not spelled as typed, the answer may
300
+ be about another symbol. `callers Verdict` could list the callers of a function
301
+ `verdict` at `decision_grade`, with the class named only in `alternates`. Now:
302
+
303
+ - the `resolution[]` entry carries `case_ambiguous: true`, and so does the
304
+ `matched` entry in `context`. It is an optional field of the `query-result`
305
+ schema, an additive change;
306
+ - the answer grades `advisory` at most, in `query`, `context` and `impact`;
307
+ - the answer carries a new caveat, `target-resolved-to-another-spelling`. It is
308
+ conditional: present only where that pick was made, and listed first;
309
+ - `impact` and `context` print their `answer health:` and `known limits:`
310
+ lines for such an answer even when it is not empty;
311
+ - an empty listing reads "none found — UNVERIFIED: the name matched more than
312
+ one spelling ...".
313
+
314
+ A name with one spelling is not affected, however it is typed and however many
315
+ definitions share it. Those are listed in `alternates` as before. Ask again by
316
+ node id for an answer about exactly one node.
317
+
318
+ `query --natural` now keeps the case of the question. It used to lowercase it,
319
+ so "Who calls Verdict?" reached the matcher as `verdict`. The grammar itself
320
+ still ignores case. A suggested `impact` or `context` command now names the
321
+ path as typed. Search-fallback terms are still lowercased.
322
+
323
+ Ranking exact case before path depth was measured and not taken. Over every
324
+ name with more than one spelling:
325
+
326
+ | | graphite | Django 5.2.7 |
327
+ |---|---|---|
328
+ | names with more than one spelling | 5 of 5,264 | 163 of 28,868 |
329
+ | queries, each spelling typed exactly | 10 | 330 |
330
+ | answered about another spelling | 2 | 128 |
331
+ | the same, with exact case ranked first | 0 | 0 |
332
+ | the same names typed in lowercase | 5 | 163 |
333
+ | of those, resolving to a different node with exact case ranked first | 1 | 78 |
334
+
335
+ Lowercase is how a name is usually typed, and those queries rely on a bare name
336
+ meaning the shallowest definition. On Django, 20 of the 78 would move from one
337
+ test definition to another. The grade change moves no query to another node. It
338
+ applies to the 2 and the 128 exact-case queries above, and to 1 of the 5 and 88
339
+ of the 163 lowercase ones.
340
+
341
+ Not changed: a `reaches` or `path` result of "no path" is an error with no
342
+ `answer` block, so it has no grade to lower. Its message names the node ids it
343
+ used. That such a result is never graded, on a degraded graph included, is #76.
344
+ Two published sentences said otherwise and are corrected: the integration guide
345
+ called a `no_path` with `truncated: false` proof of absence, and the
346
+ `query-result` schema described the `answer` block as present on `no_path`.
347
+
348
+ **The strict graph-first hook no longer refuses searches for directory names,
349
+ filenames and plain words (#72).** It refused a cross-file search whenever one
350
+ fragment of the pattern matched a node in the graph. `graph-out`,
351
+ `graph\.json` and the word `round` under `docs/` were all turned away, against
352
+ the hook's own "literal text and filename searches are fine". Four things
353
+ changed:
354
+
355
+ - **A placeholder is not a symbol.** `json` is in the graph because a file
356
+ imports it, not because the repo defines it. About a quarter of the nodes in
357
+ graphite's own graph are placeholders.
358
+ - **A directory with no code is searched as text.** When no source file the
359
+ graph models (Python, TypeScript, JavaScript, Go, Rust) lies under any
360
+ directory the search names, the search is allowed. Every named path must
361
+ exist: a glob or a shell variable reaches the hook unexpanded, so it gets no
362
+ exemption. A directory the repository lies under, such as `..`, holds all of
363
+ its code.
364
+ - **The symbol check ignores case only when the search does.** `DONE` in a log
365
+ line is not the function `done`. The Grep tool without `-i` is compared
366
+ case-exactly. So are `grep`, `git grep` and `rg`, unless they carry `-i`,
367
+ `-y`, `--ignore-case`, `-S` or `--smart-case`. An inline `(?i)` in the
368
+ pattern folds on every route. Every other tool (`Select-String`, `ag`,
369
+ `ack`, `findstr`) is still treated as ignoring case. So is `grep` while
370
+ `GREP_OPTIONS` is set, and `rg` while `RIPGREP_CONFIG_PATH` is set: either
371
+ can turn folding on without showing in the command. The Grep tool is built
372
+ on ripgrep and is treated the same way while `RIPGREP_CONFIG_PATH` is set.
373
+ Whether it reads that file was not measured, so the hook does not assume it
374
+ matches case exactly.
375
+ - **A piece of a longer literal is not judged alone.** A name joined to its
376
+ neighbour by a hyphen (`graph-out`, `--no-verify`) or a slash (`src/pkg`), or
377
+ followed by a file extension (`graph\.json`, `query.py`), is skipped.
378
+ `name-?`, `name->` and member access such as `self\.run` are still judged.
379
+
380
+ Still refused: the name of a real definition, searched across a directory that
381
+ holds code. `dependents` over `src/` is the control.
382
+
383
+ **The strict hook resolves a search's paths where the shell will.**
384
+ `cd <scratch> && grep check ci.log` reads one file outside the repository. The
385
+ hook resolved `ci.log` against the repository root, found no such file, and
386
+ refused the search for a symbol named `check`. A `cd` or `pushd` is now
387
+ followed when the directory exists and it is joined to the search by `&&`,
388
+ with nothing between them but another `cd` or another search. A search that
389
+ then names no path searches that directory. After `;`, `||`, `&` or a pipe the
390
+ search can run without the `cd` having worked, so paths resolve against the
391
+ repository root as before. The same goes for a directory spelled with syntax
392
+ only the shell can expand (`$VAR`, `~`, `-` or a glob), for any other command
393
+ in between (`cd docs && ls && grep ...`: the hook cannot tell `ls` from a
394
+ function that changes directory), and while `CDPATH` is set.
395
+
396
+ Four kinds of search are now refused that used to pass:
397
+
398
+ - `cd docs && grep -rn <symbol> ..`, where `..` is the repository root. The
399
+ path was resolved against the root itself and read as leading outside.
400
+ - On Windows, a Git Bash drive path into the repository, such as
401
+ `grep -rn <symbol> /f/Projects/repo/src`. Read by Python it was a path on
402
+ the current drive. The Bash tool's `/c/...` paths are now read as the drive.
403
+ - A search rooted at a directory the repository lies under, such as
404
+ `grep -rn <symbol> ..`. It was read as a search outside the repository,
405
+ although it covers all of it.
406
+ - A search behind another search. Only the first search of a command line was
407
+ judged, so `grep x notes.md ; grep -rn <symbol> src/` passed. Every search in
408
+ the command is judged now, and one refusal refuses the command.
409
+
18
410
  ## [1.1.1] — 2026-10-03
19
411
 
20
412
  A patch release. Security fixes ship in the next patch release of the current
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: graphite-code
3
- Version: 1.1.1
3
+ Version: 1.2.1
4
4
  Summary: Local-first, zero-LLM knowledge graph extraction for codebases.
5
5
  Project-URL: Homepage, https://github.com/jared0565/graphite
6
6
  Project-URL: Repository, https://github.com/jared0565/graphite
@@ -48,7 +48,7 @@ Description-Content-Type: text/markdown
48
48
 
49
49
  Local-first, deterministic knowledge graph extraction for codebases — zero-LLM, daemon-maintained, agent-agnostic.
50
50
 
51
- **Status: 1.1.1, production/stable.** Published on PyPI as
51
+ **Status: 1.2.1, production/stable.** Published on PyPI as
52
52
  [`graphite-code`](https://pypi.org/project/graphite-code/) with PEP 740
53
53
  attestations; supported on Windows, Linux and macOS with Python 3.11–3.14
54
54
  (every cell gates CI). What 1.x promises — CLI, JSON outputs, `graph.json`,
@@ -2,7 +2,7 @@
2
2
 
3
3
  Local-first, deterministic knowledge graph extraction for codebases — zero-LLM, daemon-maintained, agent-agnostic.
4
4
 
5
- **Status: 1.1.1, production/stable.** Published on PyPI as
5
+ **Status: 1.2.1, production/stable.** Published on PyPI as
6
6
  [`graphite-code`](https://pypi.org/project/graphite-code/) with PEP 740
7
7
  attestations; supported on Windows, Linux and macOS with Python 3.11–3.14
8
8
  (every cell gates CI). What 1.x promises — CLI, JSON outputs, `graph.json`,
@@ -38,4 +38,4 @@ __all__ = ["__version__"]
38
38
  #: Coarse and hand-maintained by construction. It is a release label, not
39
39
  #: evidence that a given fix is present -- to answer that, survey for the marker
40
40
  #: the fix introduced, or compare `graphite --version` fingerprints.
41
- __version__ = "1.1.1"
41
+ __version__ = "1.2.1"