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.
- {graphite_code-1.1.0 → graphite_code-1.2.0}/CHANGELOG.md +432 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/PKG-INFO +2 -2
- {graphite_code-1.1.0 → graphite_code-1.2.0}/README.md +1 -1
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/__init__.py +1 -1
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/agent_hooks.py +373 -73
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/agent_settings.py +20 -3
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/answer_contract.py +148 -11
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/channel.py +145 -12
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/cli.py +29 -7
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/context.py +16 -6
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/daemon_health.py +4 -1
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/doctor.py +6 -22
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/doctor_probes.py +9 -1
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/extract/ast.py +300 -152
- graphite_code-1.2.0/src/graphite/extract/pyscope.py +398 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/git.py +8 -29
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/health.py +12 -5
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/hookinstall.py +6 -1
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/init.py +18 -9
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/mcp_server.py +34 -7
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/natural_query.py +12 -8
- graphite_code-1.2.0/src/graphite/programs.py +190 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/query.py +107 -29
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/process_runner.py +6 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/service.py +24 -12
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/ts_bridge.py +16 -1
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/windows_startup.py +6 -1
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/windows_task.py +12 -4
- graphite_code-1.2.0/tests/test_agent_hooks.py +1398 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_agent_settings.py +76 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_answer_contract.py +156 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_call_graph.py +5 -2
- graphite_code-1.2.0/tests/test_case_ambiguous_answer.py +316 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_core.py +258 -0
- graphite_code-1.2.0/tests/test_channel_kind.py +249 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_lock.py +55 -1
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_log_integrity.py +44 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_mcp.py +26 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_register.py +26 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon_health.py +31 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_dispatch_evidence.py +43 -10
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_doctor.py +72 -3
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_documentation.py +36 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_hookinstall.py +32 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_init.py +36 -4
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_init_activation_doctrine.py +38 -2
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_method_dispatch_scope.py +45 -9
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_mutation_tests.py +116 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_natural_query.py +32 -3
- graphite_code-1.2.0/tests/test_programs.py +403 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_published_schemas.py +27 -1
- graphite_code-1.2.0/tests/test_pyscope.py +307 -0
- graphite_code-1.2.0/tests/test_pyscopeoracle.py +261 -0
- graphite_code-1.2.0/tests/test_python_import_bindings.py +80 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_python_resolver.py +8 -8
- graphite_code-1.2.0/tests/test_python_scope_identity.py +539 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_query_plan.py +133 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_process_runner.py +21 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_service.py +111 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_ts_bridge.py +63 -5
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_windows_startup.py +21 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_windows_task.py +42 -2
- graphite_code-1.1.0/tests/test_agent_hooks.py +0 -699
- {graphite_code-1.1.0 → graphite_code-1.2.0}/.gitignore +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/ARCHITECTURE.md +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/CONTRIBUTING.md +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/LICENSE +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/RELEASING.md +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/pyproject.toml +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/__main__.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/_cleanup_worker.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/_win32_ctypes.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/activation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/analyze.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/bootstrap.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/buildlock.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/cache.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/cluster.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/config.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/daemon.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/daemon_launch.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/debt.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/dependency_install.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/detach.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/engine_identity.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/export/__init__.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/export/html.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/export/json.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/export/md.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/extract/__init__.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/freshness.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/graph.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/graph_io.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/hook_entry.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/hookshim.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/incident_ledger.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/ingest.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/io.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/launchd_agent.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/listing.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/llm.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/llm_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/mcp.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/overlays.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/probe_process.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/probe_workspace.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/process_contracts.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/provider_observer.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/py.typed +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/query_plan.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/replacement_audit.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/resolve.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/review.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/__init__.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/approval.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/classifier.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/claude_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/claude_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/cli_identity.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/codex_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/codex_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/context_builder.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/contracts.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/diff_policy.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/edit_apply.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/effort.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/lifecycle.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/lifecycle_operator.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/lifecycle_service.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/lifecycle_storage.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/ollama_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/ollama_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/openrouter_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/openrouter_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/policy.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/probe_runner.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/profiles.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/prompt.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/registry.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/route_pool.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/route_pool_execution.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/schema_validation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/settings.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/shadow.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/storage.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/telemetry.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/worktree.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/zai_edit.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/zai_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/routing/zai_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/savings.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/systemd_unit.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/ts_resolver.mjs +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/typescript_activation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/usage_ledger.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/validation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/watch.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/src/graphite/windows_job.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/conftest.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/data/sqloracle/blindspots.py.txt +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/data/sqloracle/round69.py.txt +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/fake_clis/fake_cli.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/fixtures/provider_lifecycle_schema_v1.sql +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/fixtures/routing_schema_v2_ca77600.sql +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/fixtures/routing_schema_v3_94eb333.sql +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/fixtures/routing_schema_v4_lifecycle_migration.sql +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test__win32_ctypes.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_activation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_activation_backstop.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_agent_hook_activation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_arrow_and_constructor_binding.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_atomic_write.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_bootstrap.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_build_detach.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_buildlock.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cache_engine_identity.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cache_entry_pruning.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cache_file_set.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cache_pruning.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_attribution.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_cli.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_report.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_channel_status.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_class_field_arrow_binding.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cli_channel.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cli_channel_attribution.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cli_reference.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cli_version.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cluster.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_compare_dist.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_configuration_reference.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_context.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_context_builder.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cwd_relative_config.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_cycle_search.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon_activation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon_build_lock.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon_child_lock.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon_engine_staleness.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon_health_activation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon_launch.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_daemon_service_cli.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_debt.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_dependency_install.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_destructured_binding.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_determinism.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_doctor_agent_hooks.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_doctor_hooks.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_doctor_managed_docs.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_doctor_probes.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_edit_apply.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_engine_identity.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_exit_codes_reference.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_external_calls.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_git_security.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_go_rust.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_graph_io.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_graph_provider_isolation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_hardening.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_health.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_hook_entry.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_hook_template.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_hookshim.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_html_security.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_incident_ledger.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_init_hooks.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_init_validation_exit.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_launchd_agent.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_lifecycle_operator.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_listing.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_listing_surfaces.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_llm.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_llm_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_mcp.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_mcp_server.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_mcp_server_channel_attribution.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_monorepo.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_node_identity.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_ollama_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_overlays.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_path_leak_fixture.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_probe_diagnostics.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_probe_process.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_probe_workspace.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_process_contracts.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_claude_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_codex_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_lifecycle.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_lifecycle_service.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_lifecycle_storage.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_observer.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_ollama_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_openrouter_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_provider_probe_runner.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_public_surface.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_reliability.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_replacement_audit.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_resolve.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_review.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_route_pool.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_route_pool_execution.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_approval.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_benchmark.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_classifier.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_claude_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_cli.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_cli_contracts.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_cli_recovery.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_codex_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_context.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_contracts.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_diff_policy.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_openrouter_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_policy.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_profiles.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_registry.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_schema_validation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_security.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_shadow.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_storage.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_telemetry.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_worktree.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_zai_executor.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_routing_zai_probe.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_savings.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_search.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_smoke.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_sqloracle.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_synthetic_repo.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_systemd_unit.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_typescript_activation.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_typescript_resolver.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_usage_ledger.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_verify_artifact.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_verify_published_release.py +0 -0
- {graphite_code-1.1.0 → graphite_code-1.2.0}/tests/test_watch.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
41
|
+
__version__ = "1.2.0"
|