graphite-code 0.4.0__tar.gz → 0.5.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-0.4.0 → graphite_code-0.5.0}/CHANGELOG.md +770 -664
- {graphite_code-0.4.0 → graphite_code-0.5.0}/PKG-INFO +1 -1
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/__init__.py +41 -41
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/extract/ast.py +206 -29
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_arrow_and_constructor_binding.py +172 -172
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_cache_file_set.py +110 -110
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_call_graph.py +932 -932
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_class_field_arrow_binding.py +130 -130
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_destructured_binding.py +183 -183
- graphite_code-0.5.0/tests/test_dispatch_evidence.py +277 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_external_calls.py +548 -539
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_go_rust.py +73 -32
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_hardening.py +12 -12
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_method_dispatch_scope.py +294 -290
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_monorepo.py +199 -199
- graphite_code-0.5.0/tests/test_node_identity.py +139 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_python_resolver.py +22 -22
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_typescript_resolver.py +274 -274
- {graphite_code-0.4.0 → graphite_code-0.5.0}/.gitignore +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/ARCHITECTURE.md +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/CONTRIBUTING.md +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/LICENSE +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/README.md +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/RELEASING.md +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/pyproject.toml +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/__main__.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/_cleanup_worker.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/activation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/agent_hooks.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/agent_settings.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/analyze.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/answer_contract.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/bootstrap.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/buildlock.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/cache.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/channel.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/cli.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/cluster.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/config.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/context.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/daemon.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/daemon_health.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/debt.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/dependency_install.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/detach.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/doctor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/doctor_probes.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/engine_identity.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/export/__init__.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/export/html.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/export/json.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/export/md.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/extract/__init__.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/freshness.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/git.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/graph.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/graph_io.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/health.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/hook_entry.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/hookinstall.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/hookshim.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/incident_ledger.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/ingest.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/init.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/io.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/listing.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/llm.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/llm_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/mcp.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/mcp_server.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/natural_query.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/overlays.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/probe_process.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/probe_workspace.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/process_contracts.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/provider_observer.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/query.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/query_plan.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/replacement_audit.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/resolve.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/review.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/__init__.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/approval.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/classifier.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/claude_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/claude_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/cli_identity.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/codex_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/codex_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/context_builder.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/contracts.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/diff_policy.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/edit_apply.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/effort.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/lifecycle.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/lifecycle_operator.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/lifecycle_service.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/lifecycle_storage.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/ollama_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/ollama_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/openrouter_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/openrouter_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/policy.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/probe_runner.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/process_runner.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/profiles.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/prompt.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/registry.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/route_pool.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/route_pool_execution.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/schema_validation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/service.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/settings.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/shadow.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/storage.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/telemetry.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/worktree.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/zai_edit.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/zai_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/routing/zai_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/savings.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/ts_bridge.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/ts_resolver.mjs +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/typescript_activation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/usage_ledger.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/validation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/watch.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/windows_job.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/windows_startup.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/src/graphite/windows_task.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/conftest.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/data/sqloracle/blindspots.py.txt +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/data/sqloracle/round69.py.txt +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/fake_clis/fake_cli.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/fixtures/provider_lifecycle_schema_v1.sql +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/fixtures/routing_schema_v2_ca77600.sql +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/fixtures/routing_schema_v3_94eb333.sql +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/fixtures/routing_schema_v4_lifecycle_migration.sql +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_activation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_activation_backstop.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_agent_hook_activation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_agent_hooks.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_agent_settings.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_answer_contract.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_bootstrap.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_build_detach.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_buildlock.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_cache_engine_identity.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_cache_pruning.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_channel_cli.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_channel_core.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_channel_lock.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_channel_mcp.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_channel_register.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_channel_report.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_channel_status.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_cli_channel.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_cli_version.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_context.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_cwd_relative_config.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_daemon.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_daemon_activation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_daemon_build_lock.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_daemon_engine_staleness.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_daemon_health.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_daemon_health_activation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_debt.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_determinism.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_doctor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_doctor_agent_hooks.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_doctor_hooks.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_doctor_managed_docs.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_documentation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_edit_apply.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_engine_identity.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_git_security.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_graph_io.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_graph_provider_isolation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_health.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_hook_entry.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_hook_template.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_hookinstall.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_hookshim.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_html_security.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_incident_ledger.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_init.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_init_activation_doctrine.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_init_hooks.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_init_validation_exit.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_lifecycle_operator.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_listing.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_listing_surfaces.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_llm.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_mcp.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_natural_query.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_overlays.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_path_leak_fixture.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_probe_diagnostics.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_probe_workspace.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_claude_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_codex_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_lifecycle.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_lifecycle_service.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_lifecycle_storage.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_observer.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_ollama_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_openrouter_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_provider_probe_runner.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_public_surface.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_published_schemas.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_query_plan.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_reliability.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_replacement_audit.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_resolve.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_review.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_route_pool.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_approval.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_benchmark.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_classifier.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_claude_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_cli.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_cli_contracts.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_cli_recovery.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_codex_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_context.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_contracts.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_diff_policy.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_openrouter_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_policy.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_process_runner.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_profiles.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_registry.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_schema_validation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_security.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_service.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_shadow.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_storage.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_telemetry.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_worktree.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_zai_executor.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_routing_zai_probe.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_savings.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_search.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_smoke.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_sqloracle.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_typescript_activation.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_usage_ledger.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_verify_artifact.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_verify_published_release.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_watch.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_windows_startup.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_windows_task.py +0 -0
- {graphite_code-0.4.0 → graphite_code-0.5.0}/tests/test_zai_edit.py +0 -0
|
@@ -1,664 +1,770 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
Notable changes to graphite. Format follows [Keep a Changelog]; versioning is
|
|
4
|
-
semver, pre-1.0 (minor versions may break things).
|
|
5
|
-
|
|
6
|
-
**A version number here is a coarse release label, not a fix marker.** Several
|
|
7
|
-
behavioural fixes have shipped with no version change at all — the `-P`
|
|
8
|
-
agent-hook fix went out under an unchanged `DOC_VERSION`. To answer "does this
|
|
9
|
-
install have fix X", survey for the marker X introduced, or compare
|
|
10
|
-
`graphite --version` fingerprints between installs. The fingerprint is the
|
|
11
|
-
machine-checkable identity; the version is for humans.
|
|
12
|
-
|
|
13
|
-
[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/
|
|
14
|
-
|
|
15
|
-
## [0.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
**
|
|
106
|
-
`
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
`
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
`
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
`
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
case
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
`
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
`
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
`
|
|
385
|
-
|
|
386
|
-
`
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
`
|
|
491
|
-
is
|
|
492
|
-
`
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
**
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
**
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to graphite. Format follows [Keep a Changelog]; versioning is
|
|
4
|
+
semver, pre-1.0 (minor versions may break things).
|
|
5
|
+
|
|
6
|
+
**A version number here is a coarse release label, not a fix marker.** Several
|
|
7
|
+
behavioural fixes have shipped with no version change at all — the `-P`
|
|
8
|
+
agent-hook fix went out under an unchanged `DOC_VERSION`. To answer "does this
|
|
9
|
+
install have fix X", survey for the marker X introduced, or compare
|
|
10
|
+
`graphite --version` fingerprints between installs. The fingerprint is the
|
|
11
|
+
machine-checkable identity; the version is for humans.
|
|
12
|
+
|
|
13
|
+
[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/
|
|
14
|
+
|
|
15
|
+
## [0.5.0] — 2026-08-26
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
**Distinct definitions collapsed into one node id** (#57, #58). Two independent
|
|
20
|
+
mechanisms, one silent symptom: the losing definition vanished from the graph
|
|
21
|
+
entirely, every edge naming either landed on whichever survived, and nothing in
|
|
22
|
+
the answer signalled it — a contaminated `callers` list grades exactly like a
|
|
23
|
+
clean one, and an empty one for the vanished name reads as a trustworthy absence.
|
|
24
|
+
|
|
25
|
+
`_make_id` had **five** lossy operations — `strip("_.")` per part, `[^\w]+ -> _`,
|
|
26
|
+
`_+ -> _`, `casefold()` and truncation — so within one file `Path`, `path` and
|
|
27
|
+
`_path` were one id. Measured on this repo: **5 collisions, 6 definitions absent**.
|
|
28
|
+
`routing/storage.py` defines `initialize` (L950) and `_initialize` (L3965); the
|
|
29
|
+
graph kept `initialize`, and `self._initialize()` was recorded as a call to it.
|
|
30
|
+
|
|
31
|
+
Removing a normalisation does not fix this, which is worth knowing before trying:
|
|
32
|
+
drop the strip and `path`/`_path` are *still* merged, because the `_+` collapse
|
|
33
|
+
puts them back; drop both and `path`/`Path` remain merged by the casefold. An
|
|
34
|
+
ambiguous input now gets a short **hex** discriminator — the one alphabet that
|
|
35
|
+
survives both. A canonical input keeps its plain, readable id.
|
|
36
|
+
|
|
37
|
+
`_file_node_id` built its id from `path.stem`, **discarding the extension**, so
|
|
38
|
+
`index.ts` and `index.js`, or a component and its stylesheet, were one node — the
|
|
39
|
+
shape that most affects the TypeScript consumers. It also inherited the
|
|
40
|
+
sanitisation collisions: `src/graphite/init.py` (791 lines) and
|
|
41
|
+
`src/graphite/__init__.py` (41) were one node, so the larger module had no file
|
|
42
|
+
node at all and its 21 symbols hung off the smaller one. Both are fixed; file ids
|
|
43
|
+
now carry the full filename.
|
|
44
|
+
|
|
45
|
+
**Every node id in the graph changes.** Consumers re-extract on the engine
|
|
46
|
+
fingerprint anyway, so the rebuild costs nothing extra — but any node id written
|
|
47
|
+
down in a document or a script must be re-read from the new graph. Both
|
|
48
|
+
docstrings on `_file_node_id` claimed the old scheme kept every file distinct;
|
|
49
|
+
both were false and are rewritten rather than appended to.
|
|
50
|
+
|
|
51
|
+
Known residual, measured at zero occurrences here: a path separator and a literal
|
|
52
|
+
underscore are still indistinguishable, so `a/b.py` and `a_b.py` would collide.
|
|
53
|
+
Closing it would mean discriminating every path containing an underscore.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
**Method dispatch overruled the call classifier, in every language** (#56).
|
|
57
|
+
`_call_confidence` tags an edge `EXTERNAL_CALL` only when the call *provably*
|
|
58
|
+
leaves the repo — an attributable receiver root bound by an import that did not
|
|
59
|
+
resolve in-repo, or an `_EXTERNAL_GLOBALS` name no in-repo binding shadows.
|
|
60
|
+
`_resolve_method_dispatch` then re-pointed that same edge to an in-repo definition
|
|
61
|
+
by bare method name alone, producing an edge whose `confidence` said the call left
|
|
62
|
+
the repo and whose `target` was a function inside it.
|
|
63
|
+
|
|
64
|
+
The #54 reachability gate cannot catch these: the import that makes the definition
|
|
65
|
+
"reachable" is real and irrelevant, because the receiver is `os`. Externality is
|
|
66
|
+
evidence that gate never consults.
|
|
67
|
+
|
|
68
|
+
Measured on this repo, two builds through one interpreter differing only in this
|
|
69
|
+
pass: **42 `calls` edges removed, every one `EXTERNAL_CALL`, none `LOCAL_CALL`.**
|
|
70
|
+
Every removed edge read at its source line is a standard-library call —
|
|
71
|
+
`os.close`/`os.open`/`os.write`/`os.kill` (24), `subprocess.run` (8),
|
|
72
|
+
`threading.Event` (4), Node's `path.resolve` (2), `sys.stdin.read` (1).
|
|
73
|
+
`os.kill(pid, 0)` in one test was bound to **three** different in-repo `kill`s.
|
|
74
|
+
|
|
75
|
+
callers resolve 5 decision_grade → 3 decision_grade
|
|
76
|
+
callers kill 4 decision_grade → 1 decision_grade
|
|
77
|
+
callers event 6 decision_grade → 2 decision_grade
|
|
78
|
+
callers run 3 decision_grade → 0 ADVISORY
|
|
79
|
+
|
|
80
|
+
The last line is the honest-answer contract working: the answer is now empty *and
|
|
81
|
+
says so*, rather than confidently naming three wrong callers.
|
|
82
|
+
|
|
83
|
+
It is a **refusal to re-point, not a deletion**. The edge stays where it already
|
|
84
|
+
pointed, so `health.py` can go on excluding it from the resolution denominator —
|
|
85
|
+
it can only exclude what is present. **The ratio therefore does not move**
|
|
86
|
+
(`calls.python` 0.977 → 0.977, total 9427 → 9387, external 3044 → 3076): under
|
|
87
|
+
schema 3 these edges were already excluded as external, so the fix shifts them
|
|
88
|
+
from `total` to `external` in the same step. Grading this on health would have
|
|
89
|
+
read as "no effect" — the trap #54's first acceptance criterion fell into,
|
|
90
|
+
arriving from the other direction.
|
|
91
|
+
|
|
92
|
+
**Dispatch could cross a language boundary** (#56). No FFI is modelled, so a
|
|
93
|
+
JavaScript call site cannot reach a Python `def` — but the name match ran across
|
|
94
|
+
the whole node set with no notion of language, and did:
|
|
95
|
+
`path.resolve(input.root)` in `src/graphite/ts_resolver.mjs` bound to a `resolve`
|
|
96
|
+
defined in `tests/test_git_security.py`, at `decision_grade`. Dispatch is now
|
|
97
|
+
confined to one interop **family** (`javascript`/`typescript`/`jsx`/`tsx` together;
|
|
98
|
+
every other language its own), never to the raw `LANGUAGE_BY_EXT` label — `.ts`
|
|
99
|
+
and `.tsx` are different labels and one ecosystem. Unlike the #54 gate this is an
|
|
100
|
+
exact invariant rather than a proxy, so it needs no per-language census: it removes
|
|
101
|
+
only bindings that are impossible. **Zero marginal effect on this repo** — both
|
|
102
|
+
crossings here were also `EXTERNAL_CALL` and already gone — which is why its
|
|
103
|
+
fixture makes the crossing a `LOCAL_CALL`.
|
|
104
|
+
|
|
105
|
+
**A Rust call on an unnameable receiver was a false external** (#56).
|
|
106
|
+
`_simple_rust_value` returns `None` for anything but an identifier or `self`, so
|
|
107
|
+
`build().format()` reached `_call_confidence` as the bare name `format`, which is
|
|
108
|
+
in `_EXTERNAL_GLOBALS`. The Rust site passed no `attributable=`, so the edge came
|
|
109
|
+
out `EXTERNAL_CALL` — excused from the health denominator without ever being shown
|
|
110
|
+
to leave the repo. That is #14 mechanism A, which Python and TS/JS were threaded
|
|
111
|
+
for and Rust was not. Go is deliberately unchanged: its `selector_expression`
|
|
112
|
+
branch builds the call name from the operand's raw source text, so nothing here
|
|
113
|
+
reaches its bare-name fallback, and a guard nothing can trip is not a guard.
|
|
114
|
+
|
|
115
|
+
### Still open
|
|
116
|
+
|
|
117
|
+
The #54 reachability gate remains Python-only. That is a separate, weaker
|
|
118
|
+
question — bindings that are *possible but unlikely* in duck-typed code — and it
|
|
119
|
+
still owes the per-language measurement `_dispatch_is_gated` describes.
|
|
120
|
+
|
|
121
|
+
## [0.4.0] — 2026-08-14
|
|
122
|
+
|
|
123
|
+
**A minor bump because consumer graphs change, not because an API did.** Both fixes
|
|
124
|
+
below remove or add graph edges, so every consumer that rebuilds will see different
|
|
125
|
+
`callers`/`context` answers and a slightly different Python `calls` ratio. Nothing in
|
|
126
|
+
the CLI or the JSON schema changed. Under this project's pre-1.0 rule — minor versions
|
|
127
|
+
may break things — a patch number would have understated a release that silently
|
|
128
|
+
changes what the graph says.
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
**Python method calls bound to any same-named definition in the repo, however
|
|
133
|
+
unreachable** (#54). `_resolve_method_dispatch` re-pointed `recv.method()` to any
|
|
134
|
+
`is_method` node sharing the name, justified in its own docstring by "method names
|
|
135
|
+
are almost always globally unique" — true of domain names, false of every name a
|
|
136
|
+
standard library already owns. On graphite's own graph `query "callers resolve"`
|
|
137
|
+
returned **232 callers graded `decision_grade`**, every one an ordinary
|
|
138
|
+
`pathlib.Path(...).resolve()` re-pointed to a test double's `resolve` in
|
|
139
|
+
`tests/test_git_security.py`; `src/graphite/io.py::atomic_write_text` was recorded
|
|
140
|
+
as calling a `write` defined in `tests/test_doctor.py`. The mis-bindings counted as
|
|
141
|
+
`bound`, so the health ratio rose as the defect got worse.
|
|
142
|
+
|
|
143
|
+
A Python call now dispatches only to a definition in the calling file or in a file
|
|
144
|
+
it directly imports. Filtering happens **before** the ambiguity cap, because
|
|
145
|
+
reachability is the disambiguation that cap was standing in for — so a common name
|
|
146
|
+
with one reachable definition now resolves instead of being abandoned as "too
|
|
147
|
+
generic to guess".
|
|
148
|
+
|
|
149
|
+
Measured on this repo: `callers resolve` 232 → 5, `callers get` 87 → 0,
|
|
150
|
+
`callers write` 30 → 0; `calls` edges 13498 → 12497 (−1428, +427); `calls.python`
|
|
151
|
+
0.980 → 0.977. Every one of the 1428 dropped edges buckets as
|
|
152
|
+
`src|scripts|tests → tests` or `src → src`, and every sampled member was another
|
|
153
|
+
false binding (`sqlite3.connect`, `Thread.start`, a file handle's `write`).
|
|
154
|
+
|
|
155
|
+
**Expect consumer graphs to lose call edges and report a slightly lower Python
|
|
156
|
+
`calls` ratio.** That is honest accounting, not a regression — the same shape as the
|
|
157
|
+
constructor-edge change that moved some javascript cells down. Do not "fix" it back.
|
|
158
|
+
|
|
159
|
+
The gate resolves language through `LANGUAGE_BY_EXT`, the same table `collect_files`
|
|
160
|
+
uses to choose an extractor, so it cannot disagree with the walk that produced the
|
|
161
|
+
edge. Verified to reach a **warm** cache too: a graph built by the old code, rebuilt
|
|
162
|
+
in place by the new code, loses the false binding — the dispatch pass runs after
|
|
163
|
+
`cache.write`, so a stale partition cannot preserve it.
|
|
164
|
+
|
|
165
|
+
Scoped to **Python only, on evidence**. The gate's premise — "the caller does not
|
|
166
|
+
import the definer" standing in for "the caller cannot be holding one" — is never
|
|
167
|
+
exact in a duck-typed language. TypeScript/JavaScript stay ungated because
|
|
168
|
+
`test_member_call_ambiguous_small_set_links_to_all` pins the documented fan-out on
|
|
169
|
+
an `x: any` receiver with no import at all, a shape the gate would delete; Rust
|
|
170
|
+
because this repo has none to measure. Go could not be gated at all: its in-repo
|
|
171
|
+
imports target a synthesized package id that never equals any file's node id.
|
|
172
|
+
Residual: the same defect survives in ungated JS/TS.
|
|
173
|
+
|
|
174
|
+
**`import pkg.sub` emitted no import edge to `pkg/__init__.py`** (#55), though that
|
|
175
|
+
statement binds the name `pkg` and executes the package. Every dotted importer was
|
|
176
|
+
therefore invisible as a dependent of that `__init__.py` — the file where re-exports
|
|
177
|
+
and shared constants live, so a high-traffic edit target whose blast radius was
|
|
178
|
+
understated. Silently: a missing edge cannot lower the imports ratio, which is
|
|
179
|
+
computed over the edges that *were* emitted.
|
|
180
|
+
|
|
181
|
+
Found because the #54 gate refused `pkg.build()` and a test caught it. The gate
|
|
182
|
+
initially compensated with its own ancestor expansion; with the real edges emitted
|
|
183
|
+
that compensation became unfalsifiable — removing it changed no test — so it was
|
|
184
|
+
deleted rather than left looking like protection.
|
|
185
|
+
`test_a_dotted_import_reaches_the_package_it_binds` now guards the coupling.
|
|
186
|
+
|
|
187
|
+
Measured on this repo: import edges 1890 → 1920 (+30, −0), imports health 740/740 →
|
|
188
|
+
770/770 with `ratio` 1.0 and `external` **unchanged** at 1148 — purely additive, no
|
|
189
|
+
reclassification. `impact src/graphite/routing/__init__.py` went from 1 likely test
|
|
190
|
+
to **8**.
|
|
191
|
+
|
|
192
|
+
Scoped to `import a.b.c`, not `from a.b import c`. Both execute the package, but only
|
|
193
|
+
the first binds `a`, so only the first can carry a `a.something()` call to attribute;
|
|
194
|
+
the from-import spelling has two existing specifications pinning one edge per module,
|
|
195
|
+
and expanding it would have added ~234 further edges here with no measured demand.
|
|
196
|
+
Ancestors are edged only when they resolve in-repo, so a PEP 420 namespace package
|
|
197
|
+
(no `__init__.py`) and an external `import os.path` both emit nothing extra.
|
|
198
|
+
|
|
199
|
+
## [0.3.0] — 2026-08-12
|
|
200
|
+
|
|
201
|
+
**The first release consumers install as a built wheel.** Through 0.2.1 every
|
|
202
|
+
consumer on the development machine imported graphite from one shared editable
|
|
203
|
+
install pointed at the source tree, so a saved file was live everywhere with no
|
|
204
|
+
build, no review and no version boundary. That is why `__version__` could not
|
|
205
|
+
distinguish two states of the tree, and why "which fix does this install have"
|
|
206
|
+
was answerable only by surveying for markers. A wheel install writes source and
|
|
207
|
+
metadata together, so from 0.3.0 the version means something again.
|
|
208
|
+
|
|
209
|
+
### Fixed
|
|
210
|
+
|
|
211
|
+
**Daemon discovery only worked on one machine, and the wheel shipped that path.**
|
|
212
|
+
`_default_daemon_base` compared each parent directory against a single hardcoded
|
|
213
|
+
absolute path — the maintainer's own layout, present since the initial commit.
|
|
214
|
+
Two separate defects in one line. Functionally, discovery succeeded on exactly
|
|
215
|
+
one machine and silently fell back to the project root everywhere else, so
|
|
216
|
+
`daemon-health` and `bootstrap` reported no daemon for every other user.
|
|
217
|
+
Distribution-wise, the wheel carried an absolute developer path, which
|
|
218
|
+
`RELEASING.md` forbids in a release archive.
|
|
219
|
+
|
|
220
|
+
The value is only ever used to reach `base/.graphite-daemon/status.json`, so it
|
|
221
|
+
now searches upward for that marker instead of matching a name. Behaviour is
|
|
222
|
+
unchanged where the old literal applied, and correct everywhere else.
|
|
223
|
+
`GRAPHITE_PROJECTS_ROOT` still takes precedence, and the directory name is now a
|
|
224
|
+
single named constant shared with the reader of that file.
|
|
225
|
+
|
|
226
|
+
Worth recording *why this survived two releases*: 0.2.0 and 0.2.1 both ran a
|
|
227
|
+
disclosure scan and both recorded zero developer-path hits, because both searched
|
|
228
|
+
the **compressed** archive bytes, where a plaintext pattern cannot match. A clean
|
|
229
|
+
tree and a leaking one were indistinguishable — the scan could not fail. The
|
|
230
|
+
0.2.1 wheel in fact shipped this path in three modules and twelve times in
|
|
231
|
+
`METADATA`. `tests/test_hardening.py` now scans the packaged sources directly,
|
|
232
|
+
docstrings included, and is mutation-proven against the original line.
|
|
233
|
+
|
|
234
|
+
**The engine fingerprint could not see the parsers (#52).** `engine_identity`
|
|
235
|
+
digested `cache_version`, `schema_version`, `graphite.__version__` and the files
|
|
236
|
+
under `src/graphite/` — everything except the tree-sitter grammars that actually
|
|
237
|
+
produce the ASTs. Upgrading `tree-sitter-python` therefore changed every
|
|
238
|
+
extracted graph while the fingerprint stayed byte-identical.
|
|
239
|
+
|
|
240
|
+
Three mechanisms this repo deliberately built were defeated by that. The
|
|
241
|
+
extraction cache partitions on `{cache_version}-{engine[:16]}` (#21), so a
|
|
242
|
+
grammar upgrade served parses produced by the OLD parser — precisely the
|
|
243
|
+
staleness partitioning was introduced to prevent. The daemon queues a rebuild
|
|
244
|
+
when a project's recorded engine identity differs from the current one (#18), and
|
|
245
|
+
a grammar upgrade produced no difference, so supervised repos kept graphs that
|
|
246
|
+
disagreed with their source. And `metadata.engine.fingerprint` is documented as
|
|
247
|
+
the engine that produced the content, which across a parser change it was not.
|
|
248
|
+
|
|
249
|
+
Not hypothetical: every runtime dependency is lower-bound only, and the installed
|
|
250
|
+
`tree-sitter` was already 0.25.2 against a declared floor of `>=0.23`.
|
|
251
|
+
|
|
252
|
+
`engine_identity` now folds a `parsers` record into the digest, and
|
|
253
|
+
`metadata.engine` reports it, so a reader can see *which* grammars produced a
|
|
254
|
+
graph rather than only that something moved:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
"parsers": "tree-sitter=0.25.2,tree-sitter-go=0.25.0,..."
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
An absent grammar is recorded as `absent` rather than omitted — "go was
|
|
261
|
+
installed" and "go was not" are different engines and must not share a
|
|
262
|
+
fingerprint — and a missing grammar degrades that one language without making
|
|
263
|
+
identity itself fail. `ENGINE_SCHEMA_VERSION` is now `2`: a fingerprint computed
|
|
264
|
+
under schema 1 covered graphite's own files only, so the two are not comparable.
|
|
265
|
+
|
|
266
|
+
**Expect a one-time rebuild everywhere.** Every fingerprint changes, so the first
|
|
267
|
+
run after upgrading re-extracts and re-partitions its cache. That is the correct
|
|
268
|
+
signal, not a side effect.
|
|
269
|
+
|
|
270
|
+
**A transport failure now says what it observed (#51).** The deep MCP probe's
|
|
271
|
+
diagnostic read every field off a `ProbeProcessResult`, and a transport failure
|
|
272
|
+
never produces one — `run_bounded_process` raises instead of returning. So on a
|
|
273
|
+
timeout, the one failure the diagnostic exists to explain, it printed `<none>`
|
|
274
|
+
for every field. A real CI sighting was consequently uninterpretable.
|
|
275
|
+
|
|
276
|
+
`ProbeProcessError` now carries `elapsed_seconds`, `budget_seconds`,
|
|
277
|
+
`stdout_bytes` and `stderr_bytes`. Counts and timings only: the error type is
|
|
278
|
+
contractually free of process data, and tests pin that a child's output cannot
|
|
279
|
+
reach `str(exc)`. Numbers are safe to carry there for the same reason
|
|
280
|
+
`os_error` already was.
|
|
281
|
+
|
|
282
|
+
Read `elapsed_s` against `budget_s`. At or below budget, the deadline fired on
|
|
283
|
+
time and the child did not answer within it — note a normal timeout lands
|
|
284
|
+
*below* budget, because the runner reserves up to 40% of it for cleanup and
|
|
285
|
+
enforces the earlier execution deadline. **Above** budget means our own deadline
|
|
286
|
+
was late, i.e. the process was starved of CPU — the load hypothesis, which
|
|
287
|
+
nothing in the log could previously express. `stdout_bytes` splits the first
|
|
288
|
+
case: zero means the child never produced a byte, non-zero means it was alive
|
|
289
|
+
and progressing.
|
|
290
|
+
|
|
291
|
+
Live output, same probe, two failures:
|
|
292
|
+
|
|
293
|
+
deep_mcp output_limit: elapsed_s=0.170 | budget_s=0.35 | stdout_bytes=1048577
|
|
294
|
+
deep_mcp timeout: elapsed_s=0.325 | budget_s=0.35 | stdout_bytes=0
|
|
295
|
+
|
|
296
|
+
### Added
|
|
297
|
+
|
|
298
|
+
**`graphite debt` reports declared blind spots and their age.** A blind spot that
|
|
299
|
+
is DECLARED is working as designed; an undeclared one is the failure. The command
|
|
300
|
+
prints open and retired entries with time-to-retire, so the count is auditable
|
|
301
|
+
rather than a claim. Retirement is recorded against the fix that earned it.
|
|
302
|
+
|
|
303
|
+
**The public API surface is declared.** `docs/` now states what consumers may
|
|
304
|
+
depend on and what they may not. Six repos import this package from one install,
|
|
305
|
+
so "it happened to work" was the only contract they had; anything not listed is
|
|
306
|
+
explicitly not a promise.
|
|
307
|
+
|
|
308
|
+
**Release verification targets the BUILT DISTRIBUTION, not the source tree.**
|
|
309
|
+
Checks that passed against the working tree could not see what the wheel actually
|
|
310
|
+
contained — the two differ precisely where packaging bugs live.
|
|
311
|
+
|
|
312
|
+
**CommonJS is modelled (#49).** `require('<literal>')` now emits a real
|
|
313
|
+
`imports` edge — resolved in-repo, or `EXTERNAL_IMPORT` for a bare package,
|
|
314
|
+
exactly as the ESM equivalent does. Four call shapes that previously landed on
|
|
315
|
+
a same-file phantom now bind to their definition:
|
|
316
|
+
|
|
317
|
+
| shape | before | after |
|
|
318
|
+
|---|---|---|
|
|
319
|
+
| `const { f } = require('./x')` → `f()` | phantom | binds |
|
|
320
|
+
| `const m = require('./x')` → `m.f()` | phantom | binds |
|
|
321
|
+
| `module.exports.f = f` → `m.f()` | phantom | binds |
|
|
322
|
+
| `import * as ns from './x'` → `ns.f()` | phantom | binds |
|
|
323
|
+
|
|
324
|
+
`_ImportBindings` gained `namespaces`, mapping a whole-module local name to the
|
|
325
|
+
file it stands for, and `_resolve_call` turns `m.f()` into that file's `f`.
|
|
326
|
+
This is a mirror of what Python's `alias_map` has always done for `import x` +
|
|
327
|
+
`x.attr()`, not a new design. Detection of the `require()` shape lives in one
|
|
328
|
+
predicate used by both the binding collector and the edge-emitting walk, so the
|
|
329
|
+
two cannot drift.
|
|
330
|
+
|
|
331
|
+
Measured on a four-file fixture, JavaScript and TypeScript both: calls
|
|
332
|
+
**1/2 → 5/5**, imports **1/1 → 2/2**, placeholder share **0.143 → 0.077** as the
|
|
333
|
+
`m.f` phantoms stopped being invented, and the graph moved unhealthy → healthy.
|
|
334
|
+
Note the calls denominator *grew* while the ratio rose — newly bound sites are
|
|
335
|
+
sites that were previously never counted. `imported-by src/mod.js` now answers
|
|
336
|
+
`consumer.js` at `decision_grade` instead of answering nothing at that grade.
|
|
337
|
+
|
|
338
|
+
One interaction worth recording: `_resolve_method_dispatch` re-points any call
|
|
339
|
+
edge carrying `_member` by method NAME alone, so a namespace-resolved `m.f()`
|
|
340
|
+
would have been stolen back by any same-named class method elsewhere. The walk
|
|
341
|
+
now omits `_member` when the namespace map resolved the target — the post-pass
|
|
342
|
+
exists for edges that are "only a file-scoped phantom", which these no longer
|
|
343
|
+
are.
|
|
344
|
+
|
|
345
|
+
Guarded against a false positive the fix itself introduced. The binding maps are
|
|
346
|
+
file-level while calls are walked per scope, so an inner `const m = ...`, a
|
|
347
|
+
parameter named `m`, or a second destructure of the same name is
|
|
348
|
+
indistinguishable from the module binding at resolution time — and `m.real()`
|
|
349
|
+
would have claimed the module's definition, putting a caller in `callers real`
|
|
350
|
+
that does not exist. A wrong edge is worse than a missing one.
|
|
351
|
+
`_rebound_local_names` distrusts any require-bound name that is bound more than
|
|
352
|
+
once anywhere in the file: deliberately blunt rather than modelling JavaScript
|
|
353
|
+
scope, because it **fails closed**, giving up an edge instead of inventing one.
|
|
354
|
+
Measured cost — in a file that rebinds the name, every `m.x()` in it loses
|
|
355
|
+
binding, which is exactly the pre-#49 behaviour for that file and no worse.
|
|
356
|
+
Applied only to names CommonJS introduced; ESM binding forms are
|
|
357
|
+
statement-level and were never re-derived from a declarator.
|
|
358
|
+
|
|
359
|
+
Also measured and correct without change: a module-object call to a member the
|
|
360
|
+
module does not export (`m.notExported()`) produces an unbound placeholder and
|
|
361
|
+
LOWERS the ratio rather than fabricating a binding.
|
|
362
|
+
|
|
363
|
+
Three caveats retired on re-measured evidence — `ts-destructured-locals-unbound`
|
|
364
|
+
(declared 2026-07-27), `js-require-emits-no-import-edge` and
|
|
365
|
+
`js-module-object-calls-unbound` (both declared that morning). One added:
|
|
366
|
+
`js-dynamic-module-load-unmodelled`, because `require(expr)` and `import()`
|
|
367
|
+
expressions still emit nothing, measured the same day — note the second has a
|
|
368
|
+
string literal, so "non-literal argument" is not the test; `import()` is an
|
|
369
|
+
expression rather than an import statement. And one more,
|
|
370
|
+
`js-shadowed-module-local-unbound`, for the shadowing subset above. **Both
|
|
371
|
+
non-detection classes are narrowed, not gone**, so `imports` stays in
|
|
372
|
+
`NON_DETECTION_RELATIONS` for JavaScript and TypeScript. Retiring either
|
|
373
|
+
predecessor without its successor would have removed the honest grade from a
|
|
374
|
+
class of absence that is still not proof — the tidy-registry mistake, made once
|
|
375
|
+
and caught, then nearly made again one entry over.
|
|
376
|
+
|
|
377
|
+
### Fixed
|
|
378
|
+
|
|
379
|
+
**`imported-by` reported a confident false absence for CommonJS.** A
|
|
380
|
+
`require('./mod')` is a call expression, not an import statement, so the import
|
|
381
|
+
extractor never sees it and no candidate edge is emitted. A missing *site*
|
|
382
|
+
cannot lower a ratio computed over sites, so the metric graded its own blind
|
|
383
|
+
spot healthy. Measured on a two-file fixture where `consumer.js` requires
|
|
384
|
+
`./mod` **twice**: the graph held exactly one import edge (the unrelated ESM
|
|
385
|
+
one), the imports cell read `total 1, bound 1, ratio 1.0`, and `imported-by
|
|
386
|
+
src/mod.js` answered nothing at **`decision_grade`** — the grade whose contract
|
|
387
|
+
is "an empty result is a trustworthy absence".
|
|
388
|
+
|
|
389
|
+
This is round 55's defect in the relation that had been excused from it. That
|
|
390
|
+
round added `NON_DETECTION_RELATIONS` for `calls`, because a callback-registered
|
|
391
|
+
caller emits no edge, and recorded `imports` as exempt: "an import is a
|
|
392
|
+
syntactic construct that extraction either sees or does not… add a relation only
|
|
393
|
+
with a measured non-detection case, not on suspicion." CommonJS is that measured
|
|
394
|
+
case. **A resolution metric cannot underwrite a coverage claim**, in any
|
|
395
|
+
relation.
|
|
396
|
+
|
|
397
|
+
`imports` now joins the non-detection set, **scoped by language** — Rust `use`
|
|
398
|
+
and Go imports have no dynamic form graphite models, so their absences are still
|
|
399
|
+
evidence and are not downgraded to buy a fix for JavaScript. The empty-listing
|
|
400
|
+
line also names the construct that went undetected, since the reader's next
|
|
401
|
+
action is a grep and which one depends on whether the missing edge is a callback
|
|
402
|
+
registration or a `require()`.
|
|
403
|
+
|
|
404
|
+
Two blind spots declared the day they were measured, per the caveat process:
|
|
405
|
+
`js-require-emits-no-import-edge` (imports) and `js-module-object-calls-unbound`
|
|
406
|
+
(calls — `const m = require('./x'); m.f()` and `import * as ns; ns.f()`, neither
|
|
407
|
+
covered by the existing destructuring entry). Python already binds this shape
|
|
408
|
+
via `alias_map`; JavaScript has no equivalent. Extraction is unchanged — these
|
|
409
|
+
are declarations, not fixes.
|
|
410
|
+
|
|
411
|
+
**A failed Git version probe told the operator to upgrade a working Git.**
|
|
412
|
+
`GitUnsupportedVersionError` carries three unrelated conditions, and the one it
|
|
413
|
+
is named for is the rarest: the other two are a `--version` probe that timed out
|
|
414
|
+
or could not be launched. All three raised the literal "Git 2.38 or newer is
|
|
415
|
+
required" — a sanitized message, and a false statement in the two cases where no
|
|
416
|
+
version was ever read. It does not merely fail to help; it names a specific
|
|
417
|
+
remedy, and that remedy is wrong. `review` repeated the same literal one layer
|
|
418
|
+
up, `from None`, so the line a user actually sees on their terminal carried it
|
|
419
|
+
too.
|
|
420
|
+
|
|
421
|
+
Messages now come from a per-`reason` table of module constants
|
|
422
|
+
(`git_version_failure_message`). "Git 2.38 or newer is required" survives for
|
|
423
|
+
`too_old`, where it is exactly right; a timeout says it timed out; an
|
|
424
|
+
unrecognised reason says the version could not be verified rather than
|
|
425
|
+
inheriting a remedy. `review` looks up the same table instead of hardcoding a
|
|
426
|
+
literal — and deliberately does not pass `str(exc)` through, because that
|
|
427
|
+
message can carry Git's own output and keeping it off a terminal is what the
|
|
428
|
+
hardcoded literal was protecting. Mutation-proven: passing the exception's text
|
|
429
|
+
through fails three tests.
|
|
430
|
+
|
|
431
|
+
This is diagnosis, not a fix for #37 — the flake it makes readable has not
|
|
432
|
+
recurred in 40 CI runs since `b3ae61a`, and absence of a sighting is not
|
|
433
|
+
evidence of a fix.
|
|
434
|
+
|
|
435
|
+
**A package manager that printed anything was reported as not installed.**
|
|
436
|
+
`run_manager_version` gave `<manager> --version` a 64-byte output budget, and
|
|
437
|
+
`run_bounded_process` applies its budget **per stream** — so whatever the child
|
|
438
|
+
wrote to stderr competed with a limit sized for the version string on stdout.
|
|
439
|
+
Overflow raises `output_limit`, which the probe reported as
|
|
440
|
+
`manager_unavailable`: the same answer it gives when the manager is absent, so
|
|
441
|
+
TypeScript activation declined to proceed and named the wrong cause.
|
|
442
|
+
`_minimal_node_environment` forwards only locale variables and a PATH, so
|
|
443
|
+
nothing silences npm notices or Node deprecation warnings, and either clears 64
|
|
444
|
+
bytes on its own. The budget is now `MANAGER_VERSION_OUTPUT_LIMIT` (8 KiB) —
|
|
445
|
+
still a hard flood bound, three orders of magnitude under the install budget,
|
|
446
|
+
and the only pathological one: a sweep of every `max_output_bytes` call site
|
|
447
|
+
found the next smallest at 4 KiB.
|
|
448
|
+
|
|
449
|
+
Found from the other side, as #48: Python 3.14 added a `site.py` check that
|
|
450
|
+
warns when `sys.prefix` disagrees with the `pyvenv.cfg` layout, and the POSIX
|
|
451
|
+
activation fixture wrote that landmark beside the copied interpreter rather
|
|
452
|
+
than one level above its directory. 287 bytes on stderr, two red legs on ubuntu
|
|
453
|
+
and macOS, reported as a missing package manager. Reproduced locally under WSL
|
|
454
|
+
on CPython 3.14.7 — a different distribution from the runner's — and attributed
|
|
455
|
+
with a 2×2: **either fix alone clears it**, the budget because the probe stops
|
|
456
|
+
caring what the child says, the fixture because the child stops saying it. Both
|
|
457
|
+
shipped; the fixture was wrong on its own terms, and the budget defect was
|
|
458
|
+
never about 3.14.
|
|
459
|
+
|
|
460
|
+
**A version probe would not say what it had refused.** `run_manager_version`
|
|
461
|
+
flattened the provenance revalidation result into `manager_unavailable`, while
|
|
462
|
+
`run_install` had always returned it as-is — one test asserted both, side by
|
|
463
|
+
side, on a single command. "Your toolchain changed under us and graphite
|
|
464
|
+
refused to launch it" and "there is no package manager here" are opposite
|
|
465
|
+
operator situations, and they arrived as one string from a check whose whole
|
|
466
|
+
job is to be believed. The reason is now returned unflattened
|
|
467
|
+
(`executable_changed` / `command_changed`). The user-visible activation reason
|
|
468
|
+
is unchanged: the mapping in `typescript_activation` already defaults unknown
|
|
469
|
+
reasons to `manager_version_unavailable`.
|
|
470
|
+
|
|
471
|
+
**Every bounded subprocess reported a failed containment on macOS.**
|
|
472
|
+
`run_bounded_process` holds an exited child as an unreaped zombie on purpose, so
|
|
473
|
+
its pgid cannot be recycled under the signals that follow. On darwin that makes
|
|
474
|
+
the process group unsignalable and `killpg` answers **EPERM** — where Linux
|
|
475
|
+
answers success for the identical state — so cleanup called every successful
|
|
476
|
+
probe a failure. That is 46 of the 62 macOS test failures in #46, and in
|
|
477
|
+
ordinary use it made `doctor` and every routing probe unusable on macOS.
|
|
478
|
+
|
|
479
|
+
Measured on macos-latest 3.12.10 with ubuntu-latest as the control, four process
|
|
480
|
+
states each. A live descendant in the same group makes darwin answer OK, which
|
|
481
|
+
is what licenses reading EPERM as "nothing left to signal" rather than "not
|
|
482
|
+
allowed to signal"; the transport also creates the group itself via `setsid()`
|
|
483
|
+
from its own uid, so a member it may not signal is not reachable. The reading is
|
|
484
|
+
gated on the leader having exited — on the timeout path the leader is alive and
|
|
485
|
+
EPERM stays a failure. Linux behaviour is untouched.
|
|
486
|
+
|
|
487
|
+
**A failed cleanup overwrote the diagnosis it was called to follow.** Every
|
|
488
|
+
recheck after cleanup was guarded by "only if nothing failed yet"; the cleanup
|
|
489
|
+
assignment itself was not, so a run that had already determined `timeout`,
|
|
490
|
+
`output_limit` or `input_failed` reported `cleanup_failed` instead. Precedence
|
|
491
|
+
is now explicit — a transport failure, then the child's own non-zero exit, then
|
|
492
|
+
`cleanup_failed` only when there is nothing else to report — and a failed
|
|
493
|
+
containment rides on `ProbeProcessError.cleanup_failed` rather than replacing
|
|
494
|
+
the code. It is still raised, never returned as success.
|
|
495
|
+
|
|
496
|
+
**The generated daemon launcher ran a wrapper instead of the interpreter.**
|
|
497
|
+
`daemon_task_command` built its command from `resolve_graphite_executable()` —
|
|
498
|
+
whatever `graphite` resolved to on PATH, or `~/.local/bin/graphite.cmd` — and
|
|
499
|
+
launched it with the supervised projects root as the working directory, hidden,
|
|
500
|
+
at every login. It now emits `<interpreter> -P -m graphite daemon …`, and an
|
|
501
|
+
explicit `--graphite-executable` naming a console script is refused rather than
|
|
502
|
+
silently accepted.
|
|
503
|
+
|
|
504
|
+
⚠️ **The commit subject for that change (`ff34b4f`) states the mechanism
|
|
505
|
+
incorrectly**, and a published subject cannot be amended. `3c5304f` corrects the
|
|
506
|
+
source; this entry is the version a `git log --oneline` reader should trust.
|
|
507
|
+
|
|
508
|
+
A console script is **not** cwd-shadowable: running a script puts the script's
|
|
509
|
+
own directory on `sys.path[0]`, and only `-m` puts the CWD there. The hazard is
|
|
510
|
+
an `-m` **inside a wrapper** — which a generator can neither see into nor add
|
|
511
|
+
`-P` to. Measured from a directory holding a hostile `graphite.py`:
|
|
512
|
+
|
|
513
|
+
| launch | result |
|
|
514
|
+
|---|---|
|
|
515
|
+
| `python -m graphite` | shadow ran |
|
|
516
|
+
| `python -P -m graphite` | real graphite |
|
|
517
|
+
| `.cmd` wrapper → `python -B -m graphite` | shadow ran |
|
|
518
|
+
| `.cmd` wrapper → `python -B -P -m graphite` | real graphite |
|
|
519
|
+
|
|
520
|
+
So scope a shadowing sweep by *"does anything in this chain reach `-m` with a
|
|
521
|
+
repo root as its working directory"* — not by artifact kind, and not by whether
|
|
522
|
+
the head of the command looks like an interpreter.
|
|
523
|
+
|
|
524
|
+
**Fixing the generator does not fix the launcher it already wrote.** An existing
|
|
525
|
+
install keeps the old command until `graphite daemon-install-startup-windows`
|
|
526
|
+
(or `daemon-install-windows`) is re-run — the same marker-not-version rule this
|
|
527
|
+
file opens with.
|
|
528
|
+
|
|
529
|
+
**The distribution is now named `graphite-code`, and the name it is looked up by
|
|
530
|
+
is pinned.** PyPI's `graphite` belongs to another project. The import package is
|
|
531
|
+
still `graphite`; only the distribution name changed. A test pins the lookup name
|
|
532
|
+
because `importlib.metadata` fails silently on a mismatch — which is exactly how
|
|
533
|
+
the CLI came to report no version at all.
|
|
534
|
+
|
|
535
|
+
**The CLI reported nothing instead of an unresolvable distribution.** A failed
|
|
536
|
+
metadata lookup fell through to silence, so a broken install and a working one
|
|
537
|
+
were indistinguishable at the one command a consumer would use to tell them
|
|
538
|
+
apart. It now says which distribution it could not resolve.
|
|
539
|
+
|
|
540
|
+
**One machine's drive layout was welded into a published tool.** Default paths
|
|
541
|
+
and documentation carried this checkout's own absolute paths, which would have
|
|
542
|
+
shipped to every user of a release — and to PyPI's rendered README. Fixed in the
|
|
543
|
+
config defaults and the docs, with a check that keeps them gone.
|
|
544
|
+
|
|
545
|
+
**Six sqlite connections were left to the garbage collector.** `_connect`
|
|
546
|
+
orphaned its handle when configuration failed; the routing and lifecycle stores
|
|
547
|
+
left connections unclosed on several paths; and the test fixtures leaked the same
|
|
548
|
+
handle the source did, which hid the defect from the suite that should have
|
|
549
|
+
caught it. On Windows an unclosed handle blocks the file, so recovery depended on
|
|
550
|
+
GC timing. `conftest` now fails the suite when a handle is never closed — the
|
|
551
|
+
instrument that found the rest.
|
|
552
|
+
|
|
553
|
+
**A running interpreter is trusted by identity, not by its path.** Path-based
|
|
554
|
+
comparison misidentified the active interpreter when the same binary was reachable
|
|
555
|
+
by more than one path, which is normal under virtualenvs and symlinks.
|
|
556
|
+
|
|
557
|
+
**A test could pass while the thing it checked never happened (#50).** Sixteen
|
|
558
|
+
wall-clock assertions were demoted to named hang guards: a stopwatch bound beside
|
|
559
|
+
a structural assertion adds flake surface and no correctness. Two orphan checks
|
|
560
|
+
were worse than flaky — their reveal window was too short, so a surviving orphan
|
|
561
|
+
could go unseen and the test would pass. Each fix is mutation-proven.
|
|
562
|
+
|
|
563
|
+
**A failed Git step did not say which step failed (refs #37).** Git errors are now
|
|
564
|
+
attributed to the operation and carry the OS error text, so a CI sighting is
|
|
565
|
+
interpretable without a local repro.
|
|
566
|
+
|
|
567
|
+
## [0.2.1] — 2026-08-09
|
|
568
|
+
|
|
569
|
+
A portability release. `0.2.0` listed Linux and macOS as unverified; the suite
|
|
570
|
+
now runs green end to end on Linux. Two of the defects hiding behind that gap
|
|
571
|
+
were real, and the rest were tests that had never executed on a POSIX machine at
|
|
572
|
+
all.
|
|
573
|
+
|
|
574
|
+
### Added
|
|
575
|
+
|
|
576
|
+
- MIT license, declared with PEP 639 (`license = "MIT"` plus `license-files`)
|
|
577
|
+
and shipped in both the wheel and the sdist. The `v0.2.0` tag predates the
|
|
578
|
+
license commit, so that tagged tree carries no LICENSE file.
|
|
579
|
+
- `resolve_trusted_file(..., follow_launcher=True)`: POSIX launcher-aware
|
|
580
|
+
resolution, which keeps trust anchored in the resolved target while preserving
|
|
581
|
+
the caller's own spelling for execution.
|
|
582
|
+
|
|
583
|
+
### Fixed
|
|
584
|
+
|
|
585
|
+
**Virtual environments on POSIX.** Two call sites canonicalised `sys.executable`
|
|
586
|
+
before launching it. `.venv/bin/python` is a symlink there, and Python locates a
|
|
587
|
+
virtual environment from the executable it was *invoked as*, so the resolved path
|
|
588
|
+
started the base installation instead — which cannot `import graphite` at all.
|
|
589
|
+
Both now judge the resolved target and launch the path they were given.
|
|
590
|
+
Containment is unchanged: the rejection still tests the resolved path, so a
|
|
591
|
+
symlink outside the workspace pointing at a workspace-controlled binary is still
|
|
592
|
+
refused. Windows never saw this, because its virtual-environment interpreters are
|
|
593
|
+
copies rather than symlinks.
|
|
594
|
+
|
|
595
|
+
**A test froze the global clock and hung every POSIX CI leg** (#45).
|
|
596
|
+
`monkeypatch.setattr(probes.time, "monotonic", ...)` reads as module-scoped and
|
|
597
|
+
is not — `probes.time` *is* the stdlib `time` module — so the patch reached
|
|
598
|
+
`probe_process`'s POSIX grace loop, whose exit condition became unreachable and
|
|
599
|
+
which then spun on a real `time.sleep`. Every POSIX leg was killed at the
|
|
600
|
+
45-minute timeout. Fixed in the test by offsetting a real clock rather than
|
|
601
|
+
freezing one: an advancing `time.monotonic()` is the function's contract, so a
|
|
602
|
+
production guard would defend a condition that cannot occur.
|
|
603
|
+
|
|
604
|
+
### Changed
|
|
605
|
+
|
|
606
|
+
- The residual TOCTOU in `_canonical_executable` is now named where it lives.
|
|
607
|
+
Judging the resolved target while executing the given path moves the race from
|
|
608
|
+
"swap the file" to "re-point the symlink". It stays accepted — no path check
|
|
609
|
+
closes it, only an fd-based exec does — and the obvious narrowing, refusing
|
|
610
|
+
group- or world-writable launcher directories, would reject Homebrew's
|
|
611
|
+
`/usr/local/bin` on exactly the platforms the fix exists to support.
|
|
612
|
+
|
|
613
|
+
### Known limitations
|
|
614
|
+
|
|
615
|
+
As in `0.2.0`, except:
|
|
616
|
+
|
|
617
|
+
- **Linux is now verified**: 2835 passed, zero failures, zero timeouts, on
|
|
618
|
+
Ubuntu under WSL2 with CPython 3.12.13. That is one machine, one distribution
|
|
619
|
+
and one interpreter build — it means "no longer failing here", not "portable".
|
|
620
|
+
- **macOS remains unverified** (#46). Linux evidence is evidence about Linux.
|
|
621
|
+
- **CI has not started a job since 2026-08-05.** Every push since is refused
|
|
622
|
+
with a GitHub billing/spending-limit error before the job begins, so the local
|
|
623
|
+
gate and the WSL runs are currently the only signal.
|
|
624
|
+
|
|
625
|
+
## [0.2.0] — 2026-08-07
|
|
626
|
+
|
|
627
|
+
The first tagged release, covering everything since the initial import on
|
|
628
|
+
2026-07-10 — 662 commits in total. `0.1.0` was the version the repo was created
|
|
629
|
+
at; it was never tagged or released, so there is no earlier baseline to diff
|
|
630
|
+
against. Grouped by theme rather than enumerated; `git log` has the detail.
|
|
631
|
+
|
|
632
|
+
### Added
|
|
633
|
+
|
|
634
|
+
**Answers that grade themselves.** Every query result carries an `answer` block
|
|
635
|
+
scoped to the relations and languages that answer actually used, graded
|
|
636
|
+
`decision_grade` / `advisory` / `inconclusive`, with a registry of named
|
|
637
|
+
caveats. On a `decision_grade` answer an empty result is a trustworthy absence.
|
|
638
|
+
Human output prints `answer health:` and `known limits:` lines only when the
|
|
639
|
+
answer is empty or degraded. Published JSON schemas under `docs/schemas/` with
|
|
640
|
+
compatibility tests.
|
|
641
|
+
|
|
642
|
+
**Resolution health as a first-class signal**, now at schema 3: `calls` and
|
|
643
|
+
`imports` cells each carry an `external` count, and `total` already excludes it,
|
|
644
|
+
so `ratio == bound / total` with no further adjustment.
|
|
645
|
+
|
|
646
|
+
**Language binding.** Python cross-module call binding via symbol/alias import
|
|
647
|
+
maps, plus method dispatch (`is_method` tagging and member flow-through).
|
|
648
|
+
TypeScript/JavaScript arrow-assigned definitions, arrow-valued class fields, and
|
|
649
|
+
`new X()` construction edges. Rust `use` and `mod` resolution against indexed
|
|
650
|
+
Cargo manifests, attributed per crate.
|
|
651
|
+
|
|
652
|
+
**A daemon that supervises only repos open in a coding agent**, with an
|
|
653
|
+
activation registry, a cross-process build lock with TTL staleness, `build
|
|
654
|
+
--detach`, and rebuilds queued when the engine identity changes rather than only
|
|
655
|
+
at restart.
|
|
656
|
+
|
|
657
|
+
**An append-only incident ledger** with event-sourced triage
|
|
658
|
+
(`incidents list/ack/resolve`), fed by build cycles, observer cycles, extraction
|
|
659
|
+
errors, artifact and graph-load failures, and inconclusive queries; surfaced in
|
|
660
|
+
`doctor` and `daemon-health`.
|
|
661
|
+
|
|
662
|
+
**Agent integration.** `graphite init` writes per-agent instruction files and
|
|
663
|
+
git hooks (`--no-hooks` opts out; `--adopt` brings legacy unversioned docs under
|
|
664
|
+
management by appending, never overwriting). A `graphite-first` PreToolUse hook
|
|
665
|
+
in remind or strict mode, where strict denial is gated on proven resolution
|
|
666
|
+
health. An MCP server, and a shared agent channel with a broker, identity
|
|
667
|
+
derivation, create-only rounds and an append-only status log.
|
|
668
|
+
|
|
669
|
+
**CLI.** `query` (with `callers`, `calls`, `reaches`, `path`, `depends-on`,
|
|
670
|
+
`imported-by`, `community-of`, `stats`), `search`, `capabilities`, `context`,
|
|
671
|
+
`impact`, `review-changes`, `doctor`, `incidents`, `channel`, `savings`, and
|
|
672
|
+
`--version`.
|
|
673
|
+
|
|
674
|
+
`graphite --version` reports the engine fingerprint — a digest over the engine's
|
|
675
|
+
own source files — alongside the cache and schema versions. It is byte-exact, so
|
|
676
|
+
the implication runs one way: two installs agreeing on the fingerprint are
|
|
677
|
+
running identical code, but two that differ are not necessarily running
|
|
678
|
+
different code. Line endings are bytes, and `.gitattributes` normalizes to LF on
|
|
679
|
+
commit, so a working tree holding CRLF fingerprints differently from a fresh
|
|
680
|
+
checkout of the same commit. Equality proves sameness; inequality does not prove
|
|
681
|
+
difference.
|
|
682
|
+
|
|
683
|
+
### Changed
|
|
684
|
+
|
|
685
|
+
- The reported version now comes from `graphite.__version__` in the source tree
|
|
686
|
+
rather than from `importlib.metadata`. Under an editable install the metadata
|
|
687
|
+
is written once, at install time, and never moves again — so a release bump
|
|
688
|
+
reached nobody until every consumer reinstalled. `pyproject.toml` reads the
|
|
689
|
+
version from the source (`[tool.hatch.version]`) rather than declaring it.
|
|
690
|
+
- `graphite --version` names a stale install when the source version and the
|
|
691
|
+
installed distribution metadata disagree, instead of silently preferring one.
|
|
692
|
+
That disagreement is routine for an editable install between reinstalls, but
|
|
693
|
+
it is also what a shadowing `graphite` on `sys.path` looks like.
|
|
694
|
+
- `requires-python = ">=3.11"` is now backed by measurement rather than
|
|
695
|
+
inherited: 3.11, 3.12, 3.13 and 3.14 each run the full suite clean.
|
|
696
|
+
- Extraction cache partitions on engine identity as well as cache version, so an
|
|
697
|
+
extraction change invalidates its own cached extraction and `cache_version` is
|
|
698
|
+
back to being a coarse manual override. Unreachable partitions are reclaimed
|
|
699
|
+
on build.
|
|
700
|
+
|
|
701
|
+
### Fixed
|
|
702
|
+
|
|
703
|
+
**Module shadowing (security).** `python -m graphite` puts the current directory
|
|
704
|
+
at `sys.path[0]`, so a `graphite.py` — or a `graphite/` directory — at a repo
|
|
705
|
+
root beats the installed package, and a module-shaped shadow *runs* before it
|
|
706
|
+
errors. Every launch graphite generates or performs now passes `-P`, across six
|
|
707
|
+
surfaces: agent hooks, git-hook trampolines, graphite's own repo, `.mcp.json`,
|
|
708
|
+
`.vscode/tasks.json` (which fires on folder open, with no invocation), and
|
|
709
|
+
graphite's own source, where seven launches carried `-B` — a flag that
|
|
710
|
+
suppresses bytecode and does nothing to `sys.path`. A bare `graphite …` console
|
|
711
|
+
script is shadowable identically and cannot express the fix. `-P` rather than
|
|
712
|
+
`-I`, because `-I` implies `-E` and would strip the `GRAPHITE_*` config the CLI
|
|
713
|
+
reads. `doctor` reports foreign hooks in a shadowable form, and never rewrites
|
|
714
|
+
what it did not author.
|
|
715
|
+
|
|
716
|
+
**Channel registry gate.** Emptying, deleting or renaming the committed agent
|
|
717
|
+
registry disarmed the commit-message audit gate as thoroughly as removing it;
|
|
718
|
+
authorisation is now derived from committed state, so a commit can no longer
|
|
719
|
+
register itself, and a corrupt committed registry wedges rather than opening.
|
|
720
|
+
|
|
721
|
+
**Subprocess decode.** `text=True` without `encoding=` decodes with the locale
|
|
722
|
+
codec, and the failure lands on subprocess's reader thread: `stdout` comes back
|
|
723
|
+
`None` while `returncode` stays 0, so the crash surfaces frames from its cause.
|
|
724
|
+
Observed live. Fixed across seven modules and guarded mechanically via `ast`.
|
|
725
|
+
|
|
726
|
+
**Graph-first bypass.** The PreToolUse matcher named tools rather than
|
|
727
|
+
behaviour, so searches issued through the Bash and PowerShell tools never
|
|
728
|
+
reached the hook. Shell commands are now parsed and routed through the same
|
|
729
|
+
denial path. Four subsequent parser defects fixed: redirection operands,
|
|
730
|
+
separated flag values and out-of-repo targets were falsely denied, and a
|
|
731
|
+
case-folded `-E`/`-e` collision failed open.
|
|
732
|
+
|
|
733
|
+
**Git enumeration hardening.** A trusted-path runner, isolated environment,
|
|
734
|
+
containment checks against symlink and absolute-path escapes, and fail-closed
|
|
735
|
+
behaviour — no filesystem fallback when a repository cannot be enumerated
|
|
736
|
+
safely.
|
|
737
|
+
|
|
738
|
+
**Portability.** Zero-argument `super()` inside a `@dataclass(slots=True)`
|
|
739
|
+
raises on Python 3.11 and 3.12 because the decorator rebuilds the class; the
|
|
740
|
+
explicit two-argument form is now an AST invariant, since the interpreter the
|
|
741
|
+
gate runs on cannot catch a reintroduction. `build_graph` no longer drops an
|
|
742
|
+
edge that shares a node pair but differs by relation. Redirected CLI output is
|
|
743
|
+
forced to UTF-8. An MCP probe no longer closes the child's stdin with a reply in
|
|
744
|
+
flight: the server read that EOF as end-of-session and tore down mid-reply, so
|
|
745
|
+
`initialize` was answered — it is handled inline — while `tools/list`, which is
|
|
746
|
+
dispatched to a task racing the same teardown, was dropped.
|
|
747
|
+
|
|
748
|
+
### Known limitations
|
|
749
|
+
|
|
750
|
+
- **Dynamic dispatch, decorator rebinding and `getattr` calls stay unbound.**
|
|
751
|
+
They are counted honestly in the resolution ratio rather than hidden.
|
|
752
|
+
- **Go and Rust imports ratios are honestly lower** than Python's and
|
|
753
|
+
TypeScript's: neither emits `EXTERNAL_IMPORT`, so external imports are not
|
|
754
|
+
excluded from their denominators. Cross-schema ratio comparison is invalid —
|
|
755
|
+
branch on `schema`.
|
|
756
|
+
- **Linux and macOS are unverified** (#45, #46). A POSIX routing defect is fixed
|
|
757
|
+
— `_canonical_executable` rejected symlinks, and every POSIX interpreter is
|
|
758
|
+
one — but the suite has not been run green on either. CI cannot currently
|
|
759
|
+
confirm it.
|
|
760
|
+
- **The Windows Store Python distribution is not supported.** Its app-execution
|
|
761
|
+
alias injects `PYTHONUSERBASE`, redirects AppData and changes process
|
|
762
|
+
identity, which breaks environment-sanitization and process-cleanup
|
|
763
|
+
behaviour. Use a regular CPython install.
|
|
764
|
+
- **One flaky test remains** (#37).
|
|
765
|
+
- Aggregate `resolution_health.healthy` can report true while the language your
|
|
766
|
+
question actually used is degraded. Gate on `answer.grade`, not the aggregate.
|
|
767
|
+
|
|
768
|
+
## [0.1.0] — 2026-07-10
|
|
769
|
+
|
|
770
|
+
Initial import as a standalone repository. Never tagged or released.
|