graphite-code 1.3.0__tar.gz → 1.4.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.3.0 → graphite_code-1.4.0}/CHANGELOG.md +120 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/PKG-INFO +2 -2
- {graphite_code-1.3.0 → graphite_code-1.4.0}/README.md +1 -1
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/__init__.py +1 -1
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/agent_hooks.py +61 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/cli.py +20 -3
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/doctor.py +2 -2
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/extract/ast.py +121 -29
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/extract/pyscope.py +57 -7
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/hookinstall.py +17 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/init.py +1 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/resolve.py +132 -1
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_agent_hooks.py +68 -6
- graphite_code-1.4.0/tests/test_ast_make_id_length.py +33 -0
- graphite_code-1.4.0/tests/test_ast_source_lines.py +79 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cache_file_set.py +6 -1
- graphite_code-1.4.0/tests/test_cli_build_missing_path.py +47 -0
- graphite_code-1.4.0/tests/test_cli_doctor_below_root.py +67 -0
- graphite_code-1.4.0/tests/test_cli_usage.py +36 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_doctor_hooks.py +57 -1
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_documentation.py +23 -0
- graphite_code-1.4.0/tests/test_file_node_id_collision.py +187 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_hookinstall.py +35 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_init_hooks.py +36 -0
- graphite_code-1.4.0/tests/test_python_builtin_named_symbols.py +72 -0
- graphite_code-1.4.0/tests/test_python_inherits_edge.py +97 -0
- graphite_code-1.4.0/tests/test_python_local_alias_call.py +157 -0
- graphite_code-1.4.0/tests/test_python_reexport_follow.py +144 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/.gitignore +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/ARCHITECTURE.md +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/CONTRIBUTING.md +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/LICENSE +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/RELEASING.md +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/pyproject.toml +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/__main__.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/_cleanup_worker.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/_win32_ctypes.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/activation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/agent_settings.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/analyze.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/answer_contract.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/bootstrap.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/buildlock.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/cache.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/channel.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/cluster.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/config.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/context.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/daemon.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/daemon_health.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/daemon_launch.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/debt.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/dependency_install.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/detach.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/doctor_probes.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/engine_identity.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/export/__init__.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/export/html.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/export/json.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/export/md.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/extract/__init__.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/freshness.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/git.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/graph.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/graph_io.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/health.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/hook_entry.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/hookshim.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/incident_ledger.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/ingest.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/io.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/launchd_agent.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/listing.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/llm.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/llm_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/mcp.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/mcp_server.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/natural_query.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/overlays.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/ownership.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/probe_process.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/probe_workspace.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/process_contracts.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/programs.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/provider_observer.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/py.typed +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/query.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/query_plan.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/replacement_audit.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/review.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/__init__.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/approval.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/classifier.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/claude_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/claude_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/cli_identity.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/codex_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/codex_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/context_builder.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/contracts.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/diff_policy.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/edit_apply.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/effort.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/lifecycle.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/lifecycle_operator.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/lifecycle_service.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/lifecycle_storage.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/ollama_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/ollama_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/openrouter_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/openrouter_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/policy.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/probe_runner.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/process_runner.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/profiles.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/prompt.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/registry.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/route_pool.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/route_pool_execution.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/schema_validation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/service.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/settings.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/shadow.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/storage.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/telemetry.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/worktree.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/zai_edit.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/zai_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/routing/zai_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/savings.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/systemd_unit.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/ts_bridge.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/ts_resolver.mjs +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/typescript_activation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/usage_ledger.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/validation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/watch.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/windows_job.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/windows_startup.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/src/graphite/windows_task.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/conftest.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/data/sqloracle/blindspots.py.txt +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/data/sqloracle/round69.py.txt +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/fake_clis/fake_cli.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/fixtures/provider_lifecycle_schema_v1.sql +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/fixtures/routing_schema_v2_ca77600.sql +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/fixtures/routing_schema_v3_94eb333.sql +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/fixtures/routing_schema_v4_lifecycle_migration.sql +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test__win32_ctypes.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_activation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_activation_backstop.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_agent_hook_activation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_agent_settings.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_answer_contract.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_arrow_and_constructor_binding.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_atomic_write.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_bootstrap.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_build_detach.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_buildlock.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cache_engine_identity.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cache_entry_pruning.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cache_file_identity.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cache_pruning.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_call_graph.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_case_ambiguous_answer.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_attribution.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_cli.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_core.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_kind.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_lock.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_log_integrity.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_mcp.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_register.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_report.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_channel_status.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_class_field_arrow_binding.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cli_below_repository_root.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cli_channel.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cli_channel_attribution.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cli_reference.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cli_version.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cluster.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_compare_dist.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_configuration_reference.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_context.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_context_builder.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cwd_relative_config.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_cycle_search.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon_activation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon_build_lock.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon_child_lock.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon_engine_staleness.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon_health.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon_health_activation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon_launch.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_daemon_service_cli.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_debt.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_dependency_install.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_destructured_binding.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_determinism.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_dispatch_evidence.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_doctor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_doctor_agent_hooks.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_doctor_managed_docs.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_doctor_probes.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_edit_apply.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_engine_identity.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_exit_codes_reference.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_external_calls.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_git_security.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_go_rust.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_graph_io.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_graph_provider_isolation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_hardening.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_health.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_hook_entry.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_hook_template.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_hookshim.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_html_security.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_incident_ledger.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_init.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_init_activation_doctrine.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_init_validation_exit.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_launchd_agent.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_lifecycle_operator.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_listing.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_listing_surfaces.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_llm.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_llm_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_mcp.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_mcp_server.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_mcp_server_channel_attribution.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_member_call_receiver.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_method_dispatch_scope.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_monorepo.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_mutation_tests.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_natural_query.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_no_path_is_graded.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_node_identity.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_ollama_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_overlays.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_path_leak_fixture.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_probe_diagnostics.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_probe_process.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_probe_workspace.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_process_contracts.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_programs.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_claude_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_codex_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_lifecycle.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_lifecycle_service.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_lifecycle_storage.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_observer.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_ollama_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_openrouter_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_provider_probe_runner.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_public_surface.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_published_schemas.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_pyscope.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_pyscopeoracle.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_python_import_bindings.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_python_resolver.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_python_scope_identity.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_query_plan.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_reliability.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_replacement_audit.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_repository_root_ownership.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_resolve.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_review.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_route_pool.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_route_pool_execution.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_approval.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_benchmark.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_classifier.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_claude_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_cli.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_cli_contracts.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_cli_recovery.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_codex_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_context.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_contracts.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_diff_policy.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_openrouter_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_policy.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_process_runner.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_profiles.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_registry.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_schema_validation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_security.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_service.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_shadow.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_storage.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_telemetry.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_worktree.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_zai_executor.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_routing_zai_probe.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_savings.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_search.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_self_super_dispatch.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_smoke.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_sqloracle.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_synthetic_repo.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_systemd_unit.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_ts_bridge.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_typescript_activation.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_typescript_resolver.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_usage_ledger.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_verify_artifact.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_verify_published_release.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_watch.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_windows_startup.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_windows_task.py +0 -0
- {graphite_code-1.3.0 → graphite_code-1.4.0}/tests/test_zai_edit.py +0 -0
|
@@ -15,6 +15,126 @@ machine-checkable identity; the version is for humans.
|
|
|
15
15
|
|
|
16
16
|
## [Unreleased]
|
|
17
17
|
|
|
18
|
+
## [1.4.0] — 2026-10-25
|
|
19
|
+
|
|
20
|
+
A minor release: eight fixes, to the Python graph, the build, the CLI and the
|
|
21
|
+
agent hooks, and one addition to the hook contract (#101).
|
|
22
|
+
|
|
23
|
+
The `hooks` check of `doctor --json` gains the field `chain_slots`, and
|
|
24
|
+
`graphite init` reports the same slots beside the hooks it installs; both are
|
|
25
|
+
additions under `docs/compatibility.md`. No field is removed or renamed,
|
|
26
|
+
`Requires-Dist` and the entry points are those of 1.3.0, and `graphite init`
|
|
27
|
+
writes what it wrote (`DOC_VERSION` 15): in a new repository, `init --all`
|
|
28
|
+
leaves 17 files, the three trampolines among them, and a `.git/config`, each
|
|
29
|
+
byte for byte as 1.3.0 leaves it.
|
|
30
|
+
|
|
31
|
+
The graph changes in this release. On this repository, measured against the
|
|
32
|
+
deployed 1.3.0 engine, with the same interpreter, from one tree:
|
|
33
|
+
|
|
34
|
+
- every definition and file node id survives (6,616 and 425), each naming
|
|
35
|
+
the same definition or file, and none is new. This tree has no pair of
|
|
36
|
+
paths that #82 tells apart;
|
|
37
|
+
- 24 placeholder nodes are gone and 4 are new, so 9,164 nodes become 9,144;
|
|
38
|
+
- 32 of the 120 `inherits` edges ended at a placeholder and now end at the
|
|
39
|
+
class the base names (#86);
|
|
40
|
+
- `calls` goes from 16,065 to 16,066. 21 call sites through a local alias
|
|
41
|
+
ended at a placeholder named for the local: 16 now end at a definition,
|
|
42
|
+
and 5 at a placeholder for a name the module only imports, which is where
|
|
43
|
+
a direct call of it already ended (#90; that gap is #107). One call site is
|
|
44
|
+
new (#85). `contains` (6,614) and `imports` (2,431) are unchanged, and
|
|
45
|
+
25,177 of the 25,230 edge triples are unchanged;
|
|
46
|
+
- the Python `calls` ratio goes from 0.976 to 0.978: 12,001 of 12,276 call
|
|
47
|
+
sites are bound, where 11,984 of 12,275 were;
|
|
48
|
+
- `analysis` lists 20 cycles before and after, and one entry is new, in
|
|
49
|
+
`git.py`: the new `inherits` edge closes a loop through a
|
|
50
|
+
`super().__init__` call that both engines bind to the wrong `__init__`
|
|
51
|
+
(#106). The entry it displaced is still a cycle in the graph. Entry
|
|
52
|
+
points, god nodes, orphans, surprising connections and top files are
|
|
53
|
+
identical, and so is the community count (177).
|
|
54
|
+
|
|
55
|
+
Two builds of this tree with either engine are byte-identical (`graph.json`,
|
|
56
|
+
`GRAPH_REPORT.md`, `graph.html`).
|
|
57
|
+
|
|
58
|
+
The entries below quote figures measured when each change was made, on
|
|
59
|
+
earlier commits of this tree, so they differ from the ones above.
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
**Where another tool's git hook goes is declared, not only written in
|
|
64
|
+
shell.** Each of graphite's trampolines (`post-commit`, `post-merge`,
|
|
65
|
+
`post-rewrite`) runs `<hook>.local` beside it first when that file exists. The
|
|
66
|
+
name was stated only inside the trampoline's text. When graphite was installed
|
|
67
|
+
first, nothing was in that slot, and a hook manager that would not chain to
|
|
68
|
+
graphite's trampoline left its own hook out entirely (channel rounds 322 to
|
|
69
|
+
324). The name is now part of the hook contract in `docs/compatibility.md`.
|
|
70
|
+
It is reported in two places:
|
|
71
|
+
|
|
72
|
+
- the `hooks` check of `doctor --json`, as `chain_slots`: for each trigger,
|
|
73
|
+
the `file` relative to the hooks directory and whether it is `present`;
|
|
74
|
+
- the hook line of `graphite init`, in either install order.
|
|
75
|
+
|
|
76
|
+
An empty slot is not a finding. The trampolines are byte for byte those of
|
|
77
|
+
1.3.0. This adds a field to `doctor --json`, so the next release is a minor.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- Python: a name a package re-exports is followed to the file that defines it
|
|
82
|
+
(#87). `from pkg import Base` used to bind to `pkg/__init__.py`, so a call of
|
|
83
|
+
a re-exported function ended at a placeholder, and a `self.m()` in a class
|
|
84
|
+
whose base was re-exported had no edge and was not counted, which let
|
|
85
|
+
`callers` answer `decision_grade` without that caller. Only a plain
|
|
86
|
+
top-level `from X import name` in an `__init__.py` is followed, for as many
|
|
87
|
+
steps as the source states; a name the package binds some other way, a cycle,
|
|
88
|
+
and a module other than `__init__.py` stay as they were. The Python cache key
|
|
89
|
+
gains a digest of the re-exports, so an unchanged importer is re-resolved
|
|
90
|
+
when an `__init__.py` changes.
|
|
91
|
+
- Python: an `inherits` edge ends at the class its base names (#86). Its far
|
|
92
|
+
end was always a node made from the base's bare name, of kind `unknown`, so
|
|
93
|
+
the graph held no link from a class to its base and every class of one name
|
|
94
|
+
shared one placeholder. A base that is a bare name the file binds by a
|
|
95
|
+
class statement or imports by name now ends at that class's node; a
|
|
96
|
+
builtin, a dotted name or a call keeps its placeholder.
|
|
97
|
+
- Python: a call through a local alias binds to what the alias names (#90).
|
|
98
|
+
`t = mod.target` followed by `t(x)` used to end at a placeholder named for
|
|
99
|
+
the local, so `callers target` left that function out and still answered
|
|
100
|
+
`decision_grade`. A name bound by one assignment of a bare name or of
|
|
101
|
+
`module.attr` is read as that name or attribute, as the call written
|
|
102
|
+
directly is, also through a chain of such assignments. A name bound a
|
|
103
|
+
second time in its scope, a `with ... as` name, a cycle, and a longer chain
|
|
104
|
+
such as `a.b.c` stay as they were.
|
|
105
|
+
- Build: two paths that differ only by `/`, `.` or `_` keep a file node each
|
|
106
|
+
(#82). `a/b.py` and `a_b.py` both made `a_b_py`, so one had no node and its
|
|
107
|
+
definitions hung off the other's. Only the paths of the tree that actually
|
|
108
|
+
collide are marked, each with a hash of its exact path; every other file
|
|
109
|
+
node id is what it was. A tree with such a pair re-extracts once, because
|
|
110
|
+
the marked ids are folded into the cache key.
|
|
111
|
+
- Python: a bare call of a name the file binds gets its edge also when the
|
|
112
|
+
name is a builtin of another language (#85). The list that drops calls of
|
|
113
|
+
builtins holds JavaScript, Go and Rust names beside Python's, so a class
|
|
114
|
+
`Error` or a function `make` had no callers in the graph and its call sites
|
|
115
|
+
were absent from the `calls` ratio. A listed name that nothing in the file
|
|
116
|
+
binds still gets no edge.
|
|
117
|
+
- CLI: `build` and `report` of a path that is not there say `path not found`
|
|
118
|
+
and exit 1, as `scan` does, and create nothing (#88). The build lock made
|
|
119
|
+
its cache directory first, which made the path, and a complete empty
|
|
120
|
+
`graph-out/` was written into it with exit 0. `build --detach` is refused
|
|
121
|
+
the same way. Below a repository root the message is still the one that
|
|
122
|
+
names the root.
|
|
123
|
+
- CLI: `doctor` typed below a repository's root reports on the repository
|
|
124
|
+
(#89). It took the folder for the repository, so in a subfolder of a
|
|
125
|
+
repository with a fresh graph it said `Graph artifact is missing` and
|
|
126
|
+
offered a build that is refused there, and its incident line read a ledger
|
|
127
|
+
nothing writes. It now follows the root, as the read commands do (#81); a
|
|
128
|
+
`.git` above that graphite does not take is not followed.
|
|
129
|
+
- Agent hooks: the strict hook reads files piped to `Select-String` as a
|
|
130
|
+
search of them (#84). `Get-ChildItem -Recurse src | Select-String name` was
|
|
131
|
+
let through as a filter, while `Select-String name -Path src` was refused.
|
|
132
|
+
A listing (`Get-ChildItem`, `gci`, `ls`, `dir`) now hands its paths to a
|
|
133
|
+
`Select-String` after it, also across `Where-Object`, `Select-Object` and
|
|
134
|
+
`Sort-Object`, and the search is judged as the path form is. `-Name`
|
|
135
|
+
listings, other commands' output and any other stage between them stay
|
|
136
|
+
filters.
|
|
137
|
+
|
|
18
138
|
## [1.3.0] — 2026-10-16
|
|
19
139
|
|
|
20
140
|
A minor release, with a security fix: from a folder below a repository's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: graphite-code
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.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.4.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.4.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.4.0"
|
|
@@ -968,6 +968,54 @@ def _forget_what_may_be_set_again(assigned: dict[str, str], argv: list[str], pow
|
|
|
968
968
|
assigned.pop(named.group().lower() if powershell else named.group(), None)
|
|
969
969
|
|
|
970
970
|
|
|
971
|
+
# PowerShell commands whose output is the files (or folders) of a path, so that a
|
|
972
|
+
# `Select-String` after a pipe searches what they hand it (#84).
|
|
973
|
+
_FILE_LISTINGS = frozenset({"get-childitem", "gci", "ls", "dir"})
|
|
974
|
+
# The searches that take files from the pipeline. `grep` does not: it reads the
|
|
975
|
+
# lines of what is piped, so `ls | grep name` filters a list of names.
|
|
976
|
+
_POWERSHELL_SEARCHES = frozenset({"select-string", "sls"})
|
|
977
|
+
# What may stand between such a listing and the search and still hand the search
|
|
978
|
+
# files: a stage that only keeps or orders them. Any other stage (`ForEach-Object`,
|
|
979
|
+
# `Out-String`, `Select-Object -ExpandProperty`) may make text, and ends the chain.
|
|
980
|
+
_FILE_KEEPING_STAGES = frozenset({
|
|
981
|
+
"where-object", "where", "?", "select-object", "select", "sort-object", "sort",
|
|
982
|
+
})
|
|
983
|
+
# Listing flags that take a word, so the word is not a path. `-Path` and
|
|
984
|
+
# `-LiteralPath` are the exceptions: their word IS one.
|
|
985
|
+
_LISTING_VALUE_FLAGS = frozenset({
|
|
986
|
+
"-filter", "-include", "-exclude", "-depth", "-attributes",
|
|
987
|
+
})
|
|
988
|
+
_LISTING_PATH_FLAGS = frozenset({"-path", "-literalpath"})
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def _listed_paths(name: str, rest: list[str]) -> list[str] | None:
|
|
992
|
+
"""The paths a file listing names, else None where `name` lists no files.
|
|
993
|
+
|
|
994
|
+
An empty list is a listing of the current directory. `-Name` makes the
|
|
995
|
+
listing text, and what is piped on is then a filter of it.
|
|
996
|
+
"""
|
|
997
|
+
if name not in _FILE_LISTINGS:
|
|
998
|
+
return None
|
|
999
|
+
paths: list[str] = []
|
|
1000
|
+
expecting = False
|
|
1001
|
+
skipping = False
|
|
1002
|
+
for token in rest:
|
|
1003
|
+
lowered = token.lower()
|
|
1004
|
+
if expecting or skipping:
|
|
1005
|
+
if expecting:
|
|
1006
|
+
paths.append(token)
|
|
1007
|
+
expecting = skipping = False
|
|
1008
|
+
elif lowered == "-name" or lowered == "-n":
|
|
1009
|
+
return None
|
|
1010
|
+
elif lowered in _LISTING_PATH_FLAGS:
|
|
1011
|
+
expecting = True
|
|
1012
|
+
elif lowered in _LISTING_VALUE_FLAGS:
|
|
1013
|
+
skipping = True
|
|
1014
|
+
elif not token.startswith("-"):
|
|
1015
|
+
paths.append(token)
|
|
1016
|
+
return paths
|
|
1017
|
+
|
|
1018
|
+
|
|
971
1019
|
def _bash_searches(
|
|
972
1020
|
command: str, root: Path, msys: bool = False, cwd: Path | None = None, powershell: bool = False
|
|
973
1021
|
) -> list[_Search]:
|
|
@@ -1024,9 +1072,11 @@ def _bash_searches(
|
|
|
1024
1072
|
base, placed = start, start_placed # `base` is where the shell is
|
|
1025
1073
|
moved = False # a `cd`, or a command the hook does not model, has run in this shell
|
|
1026
1074
|
pipeline_base, pipeline_placed = base, placed # where the pipeline being read started
|
|
1075
|
+
listed: list[str] | None = None # the paths of the file listing whose output the stage before gives
|
|
1027
1076
|
for head in _bash_command_heads(lines, powershell):
|
|
1028
1077
|
piped = head.before in ("|", "|&") # `||` is or-else, not a pipe
|
|
1029
1078
|
if not piped:
|
|
1079
|
+
listed = None
|
|
1030
1080
|
if head.before != "&&":
|
|
1031
1081
|
base, placed = (None, False) if moved else (start, start_placed)
|
|
1032
1082
|
pipeline_base, pipeline_placed = base, placed
|
|
@@ -1040,6 +1090,16 @@ def _bash_searches(
|
|
|
1040
1090
|
if len(head.argv) == 1 and _NESTED_RE.fullmatch(head.argv[0]):
|
|
1041
1091
|
continue # a subshell standing alone
|
|
1042
1092
|
if piped and _command_name(head.argv[0]) != "xargs":
|
|
1093
|
+
stage = _command_name(head.argv[0])
|
|
1094
|
+
if listed is not None and stage in _POWERSHELL_SEARCHES:
|
|
1095
|
+
# `Get-ChildItem -Recurse src | Select-String name` searches
|
|
1096
|
+
# the files it is handed (#84), as `xargs grep` does.
|
|
1097
|
+
found = _search_in(stage, [_expanded(t, assigned, powershell) for t in head.argv[1:]], here, msys)
|
|
1098
|
+
if found is not None:
|
|
1099
|
+
found = found._replace(paths=_located(listed, here, msys))
|
|
1100
|
+
searches.append(found if here_placed or cwd is None else _unplaced(found, cwd))
|
|
1101
|
+
if stage not in _FILE_KEEPING_STAGES:
|
|
1102
|
+
listed = None
|
|
1043
1103
|
continue # a filter: it reads what the command before it wrote
|
|
1044
1104
|
assignments = _assignments(head.argv, powershell)
|
|
1045
1105
|
if assignments is not None:
|
|
@@ -1063,6 +1123,7 @@ def _bash_searches(
|
|
|
1063
1123
|
continue
|
|
1064
1124
|
name = _command_name(argv[0])
|
|
1065
1125
|
rest = [_expanded(token, assigned, powershell) for token in argv[1:]]
|
|
1126
|
+
listed = _listed_paths(name, rest) if len(argv) == len(written) else None
|
|
1066
1127
|
# A `cd` behind a wrapper is not the shell's: `env cd docs` moves a
|
|
1067
1128
|
# program that then exits, and `! cd docs && ...` goes on only when
|
|
1068
1129
|
# the `cd` failed. It is read below, as a command that may move.
|
|
@@ -255,12 +255,17 @@ def _write_json(path: Path, data: dict[str, Any]) -> None:
|
|
|
255
255
|
atomic_write_json(path, data, indent=2)
|
|
256
256
|
|
|
257
257
|
|
|
258
|
-
def
|
|
259
|
-
|
|
260
|
-
root = Path(args.path).resolve()
|
|
258
|
+
def _require_existing(root: Path) -> None:
|
|
259
|
+
"""Exit 1 with `path not found` for a path that is not there."""
|
|
261
260
|
if not root.exists():
|
|
262
261
|
print(f"[graphite] path not found: {root}", file=sys.stderr)
|
|
263
262
|
raise SystemExit(1)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _scan(args: argparse.Namespace, cfg: Config) -> tuple[dict[str, Any], list[Any]]:
|
|
266
|
+
cfg = cfg.canonical_graph()
|
|
267
|
+
root = Path(args.path).resolve()
|
|
268
|
+
_require_existing(root)
|
|
264
269
|
top = _root_above(root)
|
|
265
270
|
if top is not None:
|
|
266
271
|
raise ValueError(_below_a_root(root, top))
|
|
@@ -727,6 +732,9 @@ def cmd_build(args: argparse.Namespace) -> int:
|
|
|
727
732
|
top = _root_above(asked)
|
|
728
733
|
if top is not None:
|
|
729
734
|
raise ValueError(_below_a_root(asked, top))
|
|
735
|
+
# A path that is not there is refused as `scan` refuses it (#88): the lock
|
|
736
|
+
# below would otherwise make it, and a detached child could not start in it.
|
|
737
|
+
_require_existing(asked)
|
|
730
738
|
if getattr(args, "detach", False):
|
|
731
739
|
root = Path(args.path).resolve()
|
|
732
740
|
# Deliberately omits --detach: the child must build, not re-spawn.
|
|
@@ -839,6 +847,10 @@ def cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
839
847
|
root = Path(args.path).resolve()
|
|
840
848
|
if not root.is_dir():
|
|
841
849
|
raise ValueError("doctor path must be an existing directory")
|
|
850
|
+
# The report is about the repository the folder is in (#89), as the read
|
|
851
|
+
# commands are: from a subfolder the graph is the root's, and so are the
|
|
852
|
+
# incidents. A root graphite does not take (`repository_root`) is not followed.
|
|
853
|
+
root = repository_root(root)
|
|
842
854
|
cfg = _project_scoped_config(args, root)
|
|
843
855
|
report = run_doctor(
|
|
844
856
|
root,
|
|
@@ -1204,6 +1216,11 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
1204
1216
|
extra += f", reason={hooks['reason']}"
|
|
1205
1217
|
if hooks.get("relocated"):
|
|
1206
1218
|
extra += f", relocated={', '.join(hooks['relocated'])}"
|
|
1219
|
+
if hooks.get("chain_slots"):
|
|
1220
|
+
# Said in either install order: with graphite first nothing is in
|
|
1221
|
+
# these slots yet, and this is where another tool's hook goes.
|
|
1222
|
+
files = ", ".join(slot["file"] for slot in hooks["chain_slots"].values())
|
|
1223
|
+
extra += f"; another tool's hook for the same event belongs in {files} there, run first"
|
|
1207
1224
|
print(f" - hooks: {hooks.get('action')} ({hooks.get('path')}{extra})")
|
|
1208
1225
|
allowlist = result["allowlist"]
|
|
1209
1226
|
if allowlist.get("changed"):
|
|
@@ -20,7 +20,7 @@ from .daemon import read_daemon_status
|
|
|
20
20
|
from .daemon_health import HealthOptions, evaluate_daemon_health
|
|
21
21
|
from .freshness import FreshnessLimitError, check_graph_freshness
|
|
22
22
|
from .git import GitError, GitRunner
|
|
23
|
-
from .hookinstall import DEFAULT_HOOKS_DIRNAME, hook_shim_present, hooks_dir
|
|
23
|
+
from .hookinstall import DEFAULT_HOOKS_DIRNAME, chain_slots, hook_shim_present, hooks_dir
|
|
24
24
|
from .init import gitignored_managed_paths, managed_doc_paths
|
|
25
25
|
from .hookshim import TRIGGERS
|
|
26
26
|
from .llm import canonical_provider_name
|
|
@@ -179,7 +179,7 @@ def check_hooks(root: Path) -> DoctorCheck:
|
|
|
179
179
|
installed = all(hook_shim_present(hdir / hook) for hook in TRIGGERS)
|
|
180
180
|
configured = _hooks_path_configured(root)
|
|
181
181
|
custom_hooks_dir = hdir != root / DEFAULT_HOOKS_DIRNAME
|
|
182
|
-
details = {"onboarded": True, "custom_hooks_dir": custom_hooks_dir, "hookspath_configured": configured, "trampolines_installed": installed}
|
|
182
|
+
details = {"onboarded": True, "custom_hooks_dir": custom_hooks_dir, "hookspath_configured": configured, "trampolines_installed": installed, "chain_slots": chain_slots(hdir)}
|
|
183
183
|
|
|
184
184
|
if configured and installed:
|
|
185
185
|
return DoctorCheck("hooks", "Hooks", "ready", "Git hooks are installed and enforced.", details)
|