graphite-code 1.1.0__tar.gz → 1.2.0__tar.gz

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